SwiftUI中通过UIViewControllerRepresentable封装UIScrollView时,如何正确设置滚动条内边距?
嘿,这个问题我之前做UIKit和SwiftUI混编的时候也踩过坑!其实不是你的代码有bug,主要是SwiftUI和UIKit在安全区域适配的逻辑上有点“沟通不畅”~
你想啊,当你给MyScrollView加上.ignoreSafeArea(),SwiftUI这边是明确知道要把视图延伸到安全区域外面的,但你封装的UIScrollView是UIKit的控件,它根本感知不到SwiftUI的这个设置,还在按自己默认的安全区域规则计算滚动条的位置,这不就导致错位了嘛!
给你分享个亲测有效的解决办法,分几步来:
第一步:让UIScrollView放弃自动适配安全区域
在你创建UIScrollView的时候,把它的contentInsetAdjustmentBehavior设置为.never,这样它就不会自己瞎调整内边距了,咱们手动来控制它的行为。第二步:获取SwiftUI的安全区域状态
在你的MyScrollView结构体里,通过@Environment(\.safeAreaInsets)来拿到当前视图的安全区域内边距,这样就能同步SwiftUI这边的安全区域设置了。第三步:同步滚动条内边距
在updateUIViewController方法里,把拿到的SwiftUI安全区域内边距,直接赋值给UIScrollView的scrollIndicatorInsets,这样滚动条的位置就会和SwiftUI视图的边界对齐了。
给你贴个完整的代码示例,你可以对照着改自己的MyScrollView:
struct MyScrollView<Content: View>: UIViewControllerRepresentable { @ViewBuilder let content: Content // 从SwiftUI环境中获取安全区域内边距 @Environment(\.safeAreaInsets) private var safeAreaInsets func makeUIViewController(context: Context) -> ScrollHostingController<Content> { let hostingVC = ScrollHostingController(rootView: content) // 禁止UIScrollView自动调整内边距,接管控制权 hostingVC.scrollView.contentInsetAdjustmentBehavior = .never return hostingVC } func updateUIViewController(_ uiViewController: ScrollHostingController<Content>, context: Context) { // 同步SwiftUI的安全区域设置到滚动条内边距 uiViewController.scrollView.scrollIndicatorInsets = safeAreaInsets // 如果你的内容也需要适配安全区域,也可以把contentInset一起设置 // uiViewController.scrollView.contentInset = safeAreaInsets } } // 封装UIScrollView的ViewController class ScrollHostingController<Content: View>: UIViewController { let scrollView = UIScrollView() private let hostingController: UIHostingController<Content> init(rootView: Content) { self.hostingController = UIHostingController(rootView: rootView) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() setupLayout() } private func setupLayout() { // 添加ScrollView到视图 view.addSubview(scrollView) scrollView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ scrollView.topAnchor.constraint(equalTo: view.topAnchor), scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) // 添加SwiftUI内容的HostingController scrollView.addSubview(hostingController.view) hostingController.view.translatesAutoresizingMaskIntoConstraints = false hostingController.willMove(toParent: self) addChild(hostingController) hostingController.didMove(toParent: self) NSLayoutConstraint.activate([ hostingController.view.topAnchor.constraint(equalTo: scrollView.topAnchor), hostingController.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), hostingController.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), // 确保内容宽度和ScrollView一致,避免横向滚动 hostingController.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor) ]) } }
这样修改之后,不管你给MyScrollView加不加.ignoreSafeArea(),滚动条的位置都会和SwiftUI的视图边界完美对齐,再也不会错位啦!
备注:内容来源于stack exchange,提问作者Andrei Herford

