如何使用 SwiftUI 视图代替 table 视图单元格
How to use a SwiftUI view in place of table view cell
如何使用 SwiftUI 视图结构代替 UITableViewController 中的传统单元格和 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 视图不是 table 单元格,但我怎么能像使用它一样使用它呢?
我自己找到了答案。答案是 hacky,但要获取一个单元并将托管控制器作为其内容视图。
func configureCellFromSwiftUIView(cell: UITableViewCell, rootView: AnyView){
let swiftUICellViewController = UIHostingController(rootView: rootView)
cell.layoutIfNeeded()
cell.selectionStyle = UITableViewCell.SelectionStyle.none
self.addChild(swiftUICellViewController)
cell.contentView.addSubview(swiftUICellViewController.view)
swiftUICellViewController.view.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1.0, constant: 0.0))
cell.contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1.0, constant: 0.0))
cell.contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: 0.0))
cell.contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1.0, constant: 0.0))
swiftUICellViewController.didMove(toParent: self)
swiftUICellViewController.view.layoutIfNeeded()
}
感谢您在这里回答您自己的问题。您的解决方案帮助我制作了一个通用的 HostingTableViewCell class。如果有人像我一样在 Google 上发现这个问题,我会 post 在这里。
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
}
如果人们似乎在使用它,可能会将它变成一个包。
对于这个问题,我找到了与另一个回答者类似的解决方案,但我意识到,如果您正确设置约束并调用 [=12=,则无需使用 layoutIfNeeded
强制布局传递].我写了这个深入 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)
}
}
}
您应该能够像常规 table 单元格一样注册它,并在单元格出队后调用 set(rootView:controller:)
以使其工作。
修复内存泄漏的答案的幻灯片修改,因为他们只将托管控制器添加为子控制器,但从不删除它。
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)
}
}
}
如何使用 SwiftUI 视图结构代替 UITableViewController 中的传统单元格和 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 视图不是 table 单元格,但我怎么能像使用它一样使用它呢?
我自己找到了答案。答案是 hacky,但要获取一个单元并将托管控制器作为其内容视图。
func configureCellFromSwiftUIView(cell: UITableViewCell, rootView: AnyView){
let swiftUICellViewController = UIHostingController(rootView: rootView)
cell.layoutIfNeeded()
cell.selectionStyle = UITableViewCell.SelectionStyle.none
self.addChild(swiftUICellViewController)
cell.contentView.addSubview(swiftUICellViewController.view)
swiftUICellViewController.view.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1.0, constant: 0.0))
cell.contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1.0, constant: 0.0))
cell.contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: 0.0))
cell.contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1.0, constant: 0.0))
swiftUICellViewController.didMove(toParent: self)
swiftUICellViewController.view.layoutIfNeeded()
}
感谢您在这里回答您自己的问题。您的解决方案帮助我制作了一个通用的 HostingTableViewCell class。如果有人像我一样在 Google 上发现这个问题,我会 post 在这里。
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
}
如果人们似乎在使用它,可能会将它变成一个包。
对于这个问题,我找到了与另一个回答者类似的解决方案,但我意识到,如果您正确设置约束并调用 [=12=,则无需使用 layoutIfNeeded
强制布局传递].我写了这个深入 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)
}
}
}
您应该能够像常规 table 单元格一样注册它,并在单元格出队后调用 set(rootView:controller:)
以使其工作。
修复内存泄漏的答案的幻灯片修改,因为他们只将托管控制器添加为子控制器,但从不删除它。
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)
}
}
}