【问题标题】:Add SwiftUI View to an UITableViewCell contentView将 SwiftUI 视图添加到 UITableViewCell contentView
【发布时间】:2019-12-28 20:36:17
【问题描述】:

我目前正在尝试在 UIViewControllerRepresentable 中实现 UITableViewController,其中单元格的内容再次是 SwiftUI 视图。我不能使用 SwiftUI 列表,因为我想稍后添加 UISearchController。
因为我希望能够将自定义 SwiftUI 视图作为每个单元格的内容,所以我不可能在单元格内没有 SwiftUI 视图的情况下做到这一点。
我当前不起作用的代码如下所示:

class SearchableListCell: UITableViewCell {
    let contentController: UIViewController

    init(withContent content: UIViewController, reuseIdentifier: String) {
        self.contentController = content

        super.init(style: .default, reuseIdentifier: reuseIdentifier)

        self.addSubview(self.contentController.view)
        // Tried also
        // self.contentView.addSubview(self.contentController.view)
    }

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

struct SearchableList: UIViewControllerRepresentable {
    let data: [String]

    var viewBuilder: (String) -> ContentView

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIViewController(context: Context) -> UITableViewController {
        return context.coordinator.tableViewController
    }

    func updateUIViewController(_ tableViewController: UITableViewController, context: Context) {
    }

    class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
        var parent: SearchableList

        let tableViewController = UITableViewController()

        init(_ searchableList: SearchableList) {
            self.parent = searchableList

            super.init()

            tableViewController.tableView.dataSource = self
            tableViewController.tableView.delegate = self
        }

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

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

        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let string = self.parent.data[indexPath.row]

            let view = parent.viewBuilder(string)

            let hostingController = UIHostingController(rootView: view)

            let cell = SearchableListCell(withContent: hostingController, reuseIdentifier: "cell")

            // Tried it with and without this line:
            tableViewController.addChild(hostingController)

            return cell
        }
    }
}

当我运行此程序时,例如使用此预览设置:

#if DEBUG
struct SearchableList_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            SearchableList(data: ["Berlin", "Dresden", "Leipzig", "Hamburg"]) { string in
                NavigationLink(destination: Text(string)) { Text(string) }
            }
            .navigationBarTitle("Cities")
        }
    }
}
#endif

我只看到一个带有 4 个明显空单元格的 TableView。不过,在视图层次结构调试器中,我可以看到,每个单元格确实都有带有 Text 的 NavigationLink 作为子视图,它只是不可见。因此我认为,这与将 UIHostingController 添加为 UITableViewController 的子级有关,但我只是不知道应该在哪里添加它。
目前有没有办法做到这一点?

【问题讨论】:

  • 这就是通常在 UIKit 中嵌入子视图控制器的方式吗?
  • @Fabian 是的,你可以看看here
  • 实用问题 - 您绝对是否需要将单元格作为 SwiftUI 视图?由于需要UINavigationController 子类,我在UITableViewRepresentable 中有一个正在工作的UITableView。如果通过在 SwiftUI 应用程序的 UIKit 表中使用 SwiftUI 获得的唯一好处是......向前兼容性?预览提供者?好吧,请记住,UIViewControllerRepresentable 被认为仅仅是一个 SwiftUI 视图。
  • @dfd 我只想使用 NavigationLink,我可以编辑问题以更好地展示它
  • 啊。我也需要导航!但我不需要搜索。我拥有/拥有的(可用示例代码)是/曾经是完整的UIKit 表格视图、单元格和所有内容,可以是子视图控制器或 SwiftUI 中的视图。但是我在让我的“编辑”按钮触发我的 SwiftUI 模型中的东西时遇到了问题,所以我开始使用List - 带有一个用于“添加”的附加按钮,因为我找不到一个好的在编辑模式下隐藏它的方法。您能否使用协调器将您的 UIKit 逻辑公开为委托?

标签: swift uitableview swiftui uihostingcontroller


【解决方案1】:

为了解决单元格可见性问题,将 UIHostingController translatesAutoresizingMaskIntoConstraints 属性更改为 false
然后将其视图框架设置为等于单元格 contentView 边界,或者您可以使用 NSLayoutConstraint,
查看下方

class SearchableListCell: UITableViewCell {
    let contentController: UIViewController

    init(withContent content: UIViewController, reuseIdentifier: String) {
        self.contentController = content

        super.init(style: .default, reuseIdentifier: reuseIdentifier)


        contentController.view.translatesAutoresizingMaskIntoConstraints = false
        contentController.view.frame = self.contentView.bounds

        self.contentView.addSubview(self.contentController.view)

    }

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

【讨论】:

    【解决方案2】:

    我在尝试做同样的事情时发现了这一点,这对我有用。

    对我来说,这是子类化和隐藏导航栏所必需的

    import UIKit
    import SwiftUI
    /// SwiftUI UIHostingController adds a navbar for some reason so we must disable it
    class ControlledNavigationHostingController<Content>: UIHostingController<AnyView> where Content: View {
    
        public init(rootView: Content) {
            super.init(rootView: AnyView(rootView.navigationBarHidden(true)))
        }
    
        @objc dynamic required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            navigationController?.isNavigationBarHidden = true
        }
    }
    

    这是要使用的主要对象

    /// This UITableViewCell  wrapper allows you to have a SwiftUI View in your UITableView
    class HostingTableViewCell<Content: View>: UITableViewCell {
        /// This holds the SwiftUI View being displayed in this UITableViewCell wrapper
        private weak var swiftUIContainer: ControlledNavigationHostingController<Content>?
        /// Put the SwiftUI View into the contentView of the UITableViewCell, or recycle an exisiting instance and add the new SwiftUIView
        ///
        /// - Parameter view: The SwiftUI View to be used as a UITableViewCell
        /// - Parameter parent: The nearest UIViewController to be parent of the UIHostController displaying the SwiftUI View
        /// - Warning: May be unpredictable on the Simulator
        func host(_ view: Content, parent: UIViewController) {
            if let container = swiftUIContainer {
                // Recycle this view
                container.rootView = AnyView(view)
                container.view.layoutIfNeeded()
    
            } else {
                // Create a new UIHostController to display a SwiftUI View
                let swiftUICellViewController = ControlledNavigationHostingController(rootView: view)
                swiftUIContainer = swiftUICellViewController
    
                // Setup the View as the contentView of the UITableViewCell
                swiftUICellViewController.view.backgroundColor = .clear
                // Add the View to the hierarchy to be displayed
                parent.addChild(swiftUICellViewController)
                contentView.addSubview(swiftUICellViewController.view)
                swiftUICellViewController.view.translatesAutoresizingMaskIntoConstraints = false
                if let view = swiftUICellViewController.view {
                    contentView.addConstraint(NSLayoutConstraint(item: view, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1.0, constant: 0.0))
                    contentView.addConstraint(NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1.0, constant: 0.0))
                    contentView.addConstraint(NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1.0, constant: 0.0))
                    contentView.addConstraint(NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1.0, constant: 0.0))
                }
            
                swiftUICellViewController.didMove(toParent: parent)
                swiftUICellViewController.view.layoutIfNeeded()
            }
        }
    }
    

    然后像这样注册:

    tableView.register(HostingTableViewCell<YourSwiftUIView>.self, forCellReuseIdentifier: "WhateverIDYouWant")
    

    然后像这样使用:

    guard let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellID", for: indexPath) as? HostingTableViewCell<SomeSwiftUIView> else {
        print("Error: Could Not Dequeue HostingTableViewCell<SomeSwiftUIView>")
        return UITableViewCell()
    }
    cell.host(SomeSwiftUIView(), parent: self)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-08-10
      • 2013-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多