UIViewControllerRepresentable封装UIScrollView在Sheet中触底安全区域时UI冻结问题
我之前也踩过几乎一模一样的坑!你用UIViewControllerRepresentable封装UIScrollView做的MyScrollView,平时用着都顺,一放到Sheet里只要内容碰到底部安全区就卡UI,大概率是UIKit和SwiftUI的安全区适配逻辑冲突,或者布局约束没处理好导致的布局死循环。
先再明确下你的问题场景:
自带的SwiftUI
ScrollView满足不了我的需求,所以基于UIScrollView用UIViewControllerRepresentable封装了自定义的MyScrollView,正常使用没毛病,但最近遇到个诡异问题:当MyScrollView在Sheet中使用,且它的内容触碰到底部安全区时,Sheet刚要显示UI就直接冻结了。
给你几个亲测有效的解决思路,你可以一个个试:
调整UIScrollView的安全区自适应行为
UIKit的UIScrollView默认会自动调整contentInset来适配安全区,但在SwiftUI的Sheet环境里,这个逻辑很容易和SwiftUI的安全区处理打架。你可以在创建UIScrollView的时候,手动修改contentInsetAdjustmentBehavior,把自动适配关掉:func makeUIViewController(context: Context) -> UIViewController { let vc = UIViewController() let scrollView = UIScrollView() // 关键:关闭自动安全区适配,自己控制内边距 scrollView.contentInsetAdjustmentBehavior = .never // 把你的内容视图添加到scrollView里 let contentView = UIView() scrollView.addSubview(contentView) contentView.translatesAutoresizingMaskIntoConstraints = false // 给scrollView和contentView加约束,注意关联到safeAreaLayoutGuide NSLayoutConstraint.activate([ scrollView.topAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.topAnchor), scrollView.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: vc.view.trailingAnchor), scrollView.bottomAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.bottomAnchor), contentView.topAnchor.constraint(equalTo: scrollView.topAnchor), contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), // 确保contentView宽度和scrollView一致,避免横向滚动异常 contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor) ]) vc.view.addSubview(scrollView) scrollView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ scrollView.edgesAnchor.constraint(equalTo: vc.view.edgesAnchor) ]) return vc }给内容视图底部加安全区内边距
如果你需要内容能延伸到安全区下方,别直接把内容视图的底部和scrollView底部约束死,而是通过内边距来处理,比如:// 给contentView的底部加上和安全区底部高度一致的内边距 contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -vc.view.safeAreaInsets.bottom).isActive = true这样既让内容能覆盖到安全区位置,又不会触发布局冲突。
换成UIViewRepresentable封装更轻量
其实如果只是封装UIScrollView,用UIViewRepresentable比UIViewControllerRepresentable更合适,少了一层ViewController的层级,能减少很多跨框架的布局冲突。你可以把封装改成基于UIViewRepresentable,结构更简单,排查问题也方便。检查是否有布局循环
要是上面的方法都没用,你可以检查下updateUIViewController方法里的逻辑,有没有在每次更新时都重新创建约束或者修改布局属性——这种操作很容易触发无限布局更新循环,最后导致UI卡死。
备注:内容来源于stack exchange,提问作者Andrei Herford

