【问题标题】:UITableViewHeaderFooterView with SwiftUI content getting automatic safe area inset带有 SwiftUI 内容的 UITableViewHeaderFooterView 自动插入安全区域
【发布时间】:2020-08-16 12:49:03
【问题描述】:

我有一个基本的UITableView 和一些单元格。我使用 SwiftUI View 作为单元格和节标题的内容。奇怪的是,只有在 iPhone XS Max 上出现在屏幕底部的部分标题似乎获得了 16 分的rawSafeAreaInset(检查了调试视图层次结构)。我的细胞按预期工作。

为了看看发生了什么,我向contentView 添加了一个虚拟的蓝色 SwiftUI 矩形,然后在顶部放置了一个红色的 UIView,两个视图都设置了相同的约束。 UITableView 已设置为对标题和单元格使用自动尺寸。

public class SectionHeader: UITableViewHeaderFooterView {
  public static let reusableIdentifier = "Section"

  private var innerHostedViewController: UIHostingController<AnyView>!

  public override init(reuseIdentifier: String?) {
    super.init(reuseIdentifier: reuseIdentifier)

    setupHeader()
  }

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

  private func setupHeader() {
    self.backgroundView = UIView()
    self.backgroundView?.backgroundColor = UIColor.green.withAlphaComponent(0.2)

    innerHostedViewController = UIHostingController(rootView: AnyView(Rectangle().fill(Color.blue).frame(height: 48)))
    innerHostedViewController.view.translatesAutoresizingMaskIntoConstraints = false
    innerHostedViewController.view.frame = self.contentView.bounds
    contentView.addSubview(innerHostedViewController.view)
    innerHostedViewController.view.backgroundColor = .clear

    let vv = UIView()
    vv.translatesAutoresizingMaskIntoConstraints = false
    vv.backgroundColor = .red
    contentView.addSubview(vv)

    NSLayoutConstraint.activate([
      vv.topAnchor.constraint(equalTo: self.contentView.topAnchor),
      vv.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor),
      vv.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
      vv.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),

      innerHostedViewController.view.topAnchor.constraint(equalTo: self.contentView.topAnchor),
      innerHostedViewController.view.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor),
      innerHostedViewController.view.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
      innerHostedViewController.view.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
    ])
  }
}

如下图所示,顶部两个标题的红色叠加层可见(带有空单元格用于演示),但屏幕上的最后一个标题的蓝色 SwiftUI 矩形向上移动!

这个 SwiftUI 视图似乎以某种方式获得了一些 safeAreaInset,而且似乎没有办法将其关闭。如果向上滚动,插图也不会消失。它永远留在那里。我尝试关闭 SwiftUI 视图的安全区域插图,但这也无济于事:

innerHostedViewController = UIHostingController(rootView: AnyView(Rectangle().fill(Color.blue).frame(height: 48).edgesIgnoringSafeArea(.all)))

我如何摆脱这个插图?正如我所提到的 - 它只发生在 UITableViewHeaderFooterViews 而不是 UITableViewCells。

调试视图层次结构揭示了一个基于安全区域插图的虚假底部填充修饰符:

【问题讨论】:

  • 我相信这是一个 SwiftUI 错误。我已经报告了它,并在 UIKit 中重新编写了布局,因为它现在似乎是不可避免的。
  • 令人沮丧的是,UIHostingView 上有一个 var disableSafeArea: Bool,但由于它是一个私有类,我们无法设置它:( 我假设这是他们在实现 List 等时使用的。

标签: swift uitableview swiftui uitableviewsectionheader


【解决方案1】:

在这个子类UIHostingController 中托管视图对我有用!

/// https://twitter.com/b3ll/status/1193747288302075906
class FixSafeAreaInsetsHostingViewController<Content: SwiftUI.View>: UIHostingController<Content> {
    func fixApplied() -> Self {
        self.fixSafeAreaInsets()
        return self
    }

    func fixSafeAreaInsets() {
        guard let _class = view?.classForCoder else {
            fatalError()
        }

        let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = { (sself: AnyObject!) -> UIEdgeInsets in
            return .zero
        }
        guard let method = class_getInstanceMethod(_class.self, #selector(getter: UIView.safeAreaInsets)) else { return }
        class_replaceMethod(_class, #selector(getter: UIView.safeAreaInsets), imp_implementationWithBlock(safeAreaInsets), method_getTypeEncoding(method))

        let safeAreaLayoutGuide: @convention(block) (AnyObject) -> UILayoutGuide? = { (sself : AnyObject!) -> UILayoutGuide? in return nil }

        guard let method2 = class_getInstanceMethod(_class.self, #selector(getter: UIView.safeAreaLayoutGuide)) else { return }
        class_replaceMethod(_class, #selector(getter: UIView.safeAreaLayoutGuide), imp_implementationWithBlock(safeAreaLayoutGuide), method_getTypeEncoding(method2))
    }

    override var prefersStatusBarHidden: Bool {
        return false
    }
}

【讨论】:

  • 谢谢!这看起来是唯一的解决方案,但非常hacky。我个人不喜欢调酒的方法。将其记录为 Apple 的错误(我已经有)也很好,因此投票越多,他们越早解决此问题。
  • 请注意,这将适用于所有 swiftUI 托管控制器。如果您只对 UIKit 应用程序中的组件使用 SwiftUI,那么这个技巧是否有效
【解决方案2】:

我正在为 UICollectionView 的类似问题而苦苦挣扎,并修复了以下具有 viewSafeAreaInsetsDidChange 覆盖方法的自定义 UIHostingController。

class TopInsetCounterHostingController<ContentView: SwiftUI.View>: UIHostingController<ContentView> {
    override func viewSafeAreaInsetsDidChange() {
        additionalSafeAreaInsets = .init(top: view.safeAreaInsets.bottom, left: 0, bottom: 0, right: 0)
    }
}

我发现内容视图向上移动的幅度与安全区域的底部插图一样多,因此尝试使用该值调整顶部插图。并最终得到了我预期的结果。顶部插图使视图移回预期位置。

很遗憾,由于我对此一无所知,因此无法清楚地解释它是如何工作的。这只是我尝试过的大量实验得出的结果。希望它对任何挣扎于它的人有用。

【讨论】:

    【解决方案3】:

    当我没有将 UIHostingController 添加到父视图控制器时,这个问题就消失了。我并不是说这是正确的解决方案,但它可能比 swizzling 解决方案更好。

    【讨论】:

      猜你喜欢
      • 2022-08-19
      • 2019-12-11
      • 2021-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-18
      • 2018-10-29
      • 1970-01-01
      相关资源
      最近更新 更多