【问题标题】:SwiftUI view sizing is broken when added to UITableViewController添加到 UITableViewController 时,SwiftUI 视图大小被破坏
【发布时间】:2024-04-10 21:10:01
【问题描述】:

我需要在现有的UITableViewController 中显示一个 SwiftUI 视图,如tableHeaderView。但是,似乎 SwiftUI 视图的大小在添加到 UITableViewController 时被破坏了。

如果我只是使用 UIHostingController 将我的 SwiftUI 视图转换为 UIView 并将其设置为 tableHeaderView,则视图将显示在屏幕外:

func addHeaderView() {
    let view = VerticalTextStack()
    let hostingController = UIHostingController(rootView: view)
    tableView.tableHeaderView = hostingController.view
}

Incorrect layout

为了解决这个问题,我尝试了几种不同的方法来修复视图的高度。添加NSLayoutConstraint 没有做任何事情。手动设置tableHeaderView.frame.size 时,结果更好,因为至少现在视图显示在屏幕上,但多行Texts 变成单行并被截断。

正如您在此处看到的,第二个 Text 被截断:

这是一个展示问题的简化示例:

/// `UITableViewController` displaying a `UIView` as its `tableHeaderView`
class TableViewController: UITableViewController {
    let cellReuseIdentifier = "cell"
    let data = ["A", "B", "C", "D", "E"]
    let themeManager = AppThemeManager()

    // MARK: - UIViewController lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
        addHeaderView()
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        fixTableHeaderViewSize()
    }

    // MARK: - UITableViewDelegate
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        data.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
        cell.textLabel?.text = data[indexPath.row]
        return cell
    }

    // MARK: - SwiftUI view
    func fixTableHeaderViewSize() {
        guard let tableHeaderView = tableView?.tableHeaderView else { return }
        let expectedHeight = tableHeaderView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize).height
        let expectedSize = CGSize(width: tableHeaderView.frame.width, height: expectedHeight)

        tableHeaderView.frame.size = expectedSize
    }

    func addHeaderView() {
        let view = VerticalTextStack()
        let hostingController = UIHostingController(rootView: view)
        tableView.tableHeaderView = hostingController.view
    }
}

private struct VerticalTextStack: View {
    let data = ["First", "I am a very long text that only fits in multiple lines. I still continue.", "Third"]
    let themeManager = AppThemeManager()

    var body: some View {
        VStack(spacing: 10) {
            ForEach(data, id: \.self) { value in
                Text(value)
            }
        }
    }
}

我也尝试将 addHeaderView 移动到其他 UIViewController 函数,例如 viewWillLayoutSubviews,但这并没有改变任何东西。

lineLimit 设置为nilVerticalTextStack 内部Text 上的任何大数字并将.layoutPriority(.greatestFiniteMagnitude) 添加到Text 也不会使Text 成为多行。

【问题讨论】:

    标签: swift uitableview swiftui uikit


    【解决方案1】:

    这是一个可能的解决方案。

    用这个改变你的添加标题视图功能。

    func addHeaderView() {
        
        let view = VerticalTextStack()
        let hostingController = UIHostingController(rootView: view)
        
        let headerViewMain = UIView()
        headerViewMain.backgroundColor = .red
        headerViewMain.addSubview(hostingController.view)
        
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        
        let constraints = [
            hostingController.view.topAnchor.constraint(equalTo: headerViewMain.topAnchor),
            hostingController.view.leftAnchor.constraint(equalTo: headerViewMain.leftAnchor),
            headerViewMain.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor),
            headerViewMain.rightAnchor.constraint(equalTo: hostingController.view.rightAnchor)
        ]
        
        NSLayoutConstraint.activate(constraints)
        
        headerViewMain.frame.size.height = headerViewMain.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        headerViewMain.frame.size.width = headerViewMain.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).width
        self.tableHeaderView = headerViewMain
        
        self.layoutIfNeeded()
        self.setNeedsLayout()
        
        self.reloadData()
    }
    

    【讨论】:

    • 这段代码甚至无法编译,因为您似乎认为这是在UITableView 子类而不是UITableViewController 子类中。但是,即使我解决了这些问题,多行文本仍然是单行并被截断。你甚至测试过你提出的解决方案吗?顺便说一句,调用setNeedsLayout 之后 layoutIfNeeded 是一个可怕的想法,你为什么要在设置tableHeaderView 之后调用reloadData
    • 第一个,我忘了这是 UITableViewController 类。是的,我测试了代码,它会在多行中产生问题,我认为在 layoutIfNeeded 之后调用 setNeedsLayout 并不可怕。并且对于重新加载,不调用也没关系。我使用了另一个现有的演示,所以为什么要重新加载代码。
    • 和这一行解决的多行问题Text(value).frame(width: UIScreen.main.bounds.width).fixedSize().lineLimit(nil)
    • 好吧,如果您的代码仍然存在多行文本问题,那么这完全可以解决我的问题吗?多行文本问题是我唯一无法解决的问题,所以这并不比我的原始代码好。调用setNeedsLayout before layoutIfNeeded 可能有意义,但调用它之后根本没有意义。您首先告诉 UIKit 在必要时更新 UI,然后才告诉它有必要更新?
    • @DávidPásztor 检查我的第二条评论是否有多行文本问题。