如何使用SwiftUI视图代替表格视图单元格

时间:2019-08-22 20:15:15

标签: swift uikit swiftui

如何在UITableViewController中使用SwiftUI视图结构代替传统单元格和xib?

import UIKit
import SwiftUI

class MasterViewController: UITableViewController {

    var detailViewController: DetailViewController? = nil
    var objects = [Any]()

    // MARK: - View Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        navigationItem.title = "Table View"

        //...
    }



    // MARK: - Table View Methods

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return objects.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(MySwiftUIView())

        // ...
        return cell
    }
} ...

问题很明显,因为UIHostedController SwiftUI视图不是表单元格,但是我怎么能像使用它一样?

4 个答案:

答案 0 :(得分:1)

独自找到答案。答案很棘手,但是要带一个单元并放置一个托管控制器作为其内容视图。

<DataTable dataUrl={this.props.url}>
    {this.renderChildren()}
</DataTable>

}

答案 1 :(得分:1)

感谢您在此处回答自己的问题。您的解决方案帮助我制作了一个通用的HostingTableViewCell类。如果有人像我一样在Google上找到此问题,我会将其张贴在这里。

import SwiftUI

class HostingTableViewCell<Content: View>: UITableViewCell {

    private weak var controller: UIHostingController<Content>?

    func host(_ view: Content, parent: UIViewController) {
        if let controller = controller {
            controller.rootView = view
            controller.view.layoutIfNeeded()
        } else {
            let swiftUICellViewController = UIHostingController(rootView: view)
            controller = swiftUICellViewController
            swiftUICellViewController.view.backgroundColor = .clear

            layoutIfNeeded()

            parent.addChild(swiftUICellViewController)
            contentView.addSubview(swiftUICellViewController.view)
            swiftUICellViewController.view.translatesAutoresizingMaskIntoConstraints = false
            contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1.0, constant: 0.0))
            contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1.0, constant: 0.0))
            contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: 0.0))
            contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1.0, constant: 0.0))

            swiftUICellViewController.didMove(toParent: parent)
            swiftUICellViewController.view.layoutIfNeeded()
        }
    }
}

在您的UITableViewController中:

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(HostingTableViewCell<Text>.self, forCellReuseIdentifier: "textCell")
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "textCell") as! HostingTableViewCell<Text>
        cell.host(Text("Yay!"), parent: self)
        return cell
    }

如果人们似乎会使用它,则可以将其包装。

答案 2 :(得分:1)

我作为另一个回答者也找到了类似的解决方案,但是我意识到,如果您正确设置约束并调用layoutIfNeeded,则无需使用invalidateIntrinsicContentSize()强制进行布局传递。我写了关于这个深入的here的文章,但是对我有用的UITableViewCell子类是:

final class HostingCell<Content: View>: UITableViewCell {
    private let hostingController = UIHostingController<Content?>(rootView: nil)

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        hostingController.view.backgroundColor = .clear
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func set(rootView: Content, parentController: UIViewController) {
        self.hostingController.rootView = rootView
        self.hostingController.view.invalidateIntrinsicContentSize()

        let requiresControllerMove = hostingController.parent != parentController
        if requiresControllerMove {
            parentController.addChild(hostingController)
        }

        if !self.contentView.subviews.contains(hostingController.view) {
            self.contentView.addSubview(hostingController.view)
            hostingController.view.translatesAutoresizingMaskIntoConstraints = false
            hostingController.view.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor).isActive = true
            hostingController.view.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor).isActive = true
            hostingController.view.topAnchor.constraint(equalTo: self.contentView.topAnchor).isActive = true
            hostingController.view.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor).isActive = true
        }

        if requiresControllerMove {
            hostingController.didMove(toParent: parentController)
        }
    }
}

您应该能够像常规表格单元格一样对其进行注册,并在使该单元格脱离队列以使其正常工作后调用set(rootView:controller:)

答案 3 :(得分:0)

对答案的幻灯片进行了修改,以修复内存泄漏,因为它们只会将托管控制器添加为子级,而不会删除它。

final class HostingTableViewCell<Content: View>: UITableViewCell {
private let hostingController = UIHostingController<Content?>(rootView: nil)

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    hostingController.view.backgroundColor = .clear
}

private func removeHostingControllerFromParent() {
    hostingController.willMove(toParent: nil)
    hostingController.view.removeFromSuperview()
    hostingController.removeFromParent()
}

deinit {
    // remove parent
    removeHostingControllerFromParent()
}

@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

func set(rootView: Content, parentController: UIViewController) {
    hostingController.rootView = rootView
    hostingController.view.invalidateIntrinsicContentSize()

    let requiresControllerMove = hostingController.parent != parentController
    if requiresControllerMove {
        // remove old parent if exists
        removeHostingControllerFromParent()
        parentController.addChild(hostingController)
    }

    if !contentView.subviews.contains(hostingController.view) {
        contentView.addSubview(hostingController.view)
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        hostingController.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        hostingController.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        hostingController.view.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        hostingController.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }

    if requiresControllerMove {
        hostingController.didMove(toParent: parentController)
    }
}

}