在ExpoView中使用HostingController的SwiftUI View无法正确裁剪边界
ExpoView中SwiftUI View顶部被裁切的解决方法
问题描述
在Expo SDK 50、iOS 16环境下,将SwiftUI View通过UIHostingController嵌入ExpoView时,出现View顶部内容被裁切的情况(附图:SwiftUI View顶部被裁切),需要让内容贴合边界正常显示。
原SwiftUI View代码
struct MySwiftUIView: View { var name = "" var options: [String] = [] @State var isOpen = false @State var picker = 0 @State var slider = 50.0 @State var isScaled = false var body: some View { NavigationView { if #available(iOS 14.0, *) { ScrollView { VStack(spacing: 20) { Text("SwiftUI + Expo!") .font(.largeTitle) .scaleEffect(isScaled ? 1.5 : 1.0) .foregroundColor(isScaled ? .purple : .black) Button("Animations work too!") { withAnimation { isScaled.toggle() } } NavigationLink("To Details") { Text("Details") } Picker("Picker", selection: $picker, content: { ForEach(Array(options.enumerated()), id: \.1) { index, option in Text(option) .tag(index) } }).pickerStyle(.segmented) if #available(iOS 16.0, *) { Button("Toggle Sheet") { isOpen.toggle() }.sheet(isPresented: $isOpen) { Text("Sheet content") .presentationDetents([.medium, .large]) }.padding(.bottom, 100) } } }.navigationTitle(Text("Home")) } } } }
原ExpoView配置代码
class SwiftuiView: ExpoView { let hostingController = UIHostingController(rootView: MySwiftUIView()) required init(appContext: AppContext? = nil) { super.init(appContext: appContext) hostingController.view.translatesAutoresizingMaskIntoConstraints = false addSubview(hostingController.view) NSLayoutConstraint.activate([ hostingController.view.topAnchor.constraint(equalTo: self.topAnchor), hostingController.view.bottomAnchor.constraint(equalTo: self.bottomAnchor), hostingController.view.leftAnchor.constraint(equalTo: self.leftAnchor), hostingController.view.rightAnchor.constraint(equalTo: self.rightAnchor) ]) } }
解决方法
方案1:调整SwiftUI的安全区域适配
在SwiftUI的ScrollView层级添加.edgesIgnoringSafeArea(.top),让内容忽略顶部安全区域,直接贴合边界:
修改后的SwiftUI View代码
struct MySwiftUIView: View { var name = "" var options: [String] = [] @State var isOpen = false @State var picker = 0 @State var slider = 50.0 @State var isScaled = false var body: some View { NavigationView { if #available(iOS 14.0, *) { ScrollView { VStack(spacing: 20) { Text("SwiftUI + Expo!") .font(.largeTitle) .scaleEffect(isScaled ? 1.5 : 1.0) .foregroundColor(isScaled ? .purple : .black) Button("Animations work too!") { withAnimation { isScaled.toggle() } } NavigationLink("To Details") { Text("Details") } Picker("Picker", selection: $picker, content: { ForEach(Array(options.enumerated()), id: \.1) { index, option in Text(option) .tag(index) } }).pickerStyle(.segmented) if #available(iOS 16.0, *) { Button("Toggle Sheet") { isOpen.toggle() }.sheet(isPresented: $isOpen) { Text("Sheet content") .presentationDetents([.medium, .large]) }.padding(.bottom, 100) } } } .navigationTitle(Text("Home")) .edgesIgnoringSafeArea(.top) // 忽略顶部安全区域 } } } }
方案2:优化UIHostingController配置(可选)
在ExpoView的初始化代码中,禁用HostingController的自动滚动视图内边距调整,避免UIKit层面的额外内边距干扰:
修改后的ExpoView代码
class SwiftuiView: ExpoView { let hostingController = UIHostingController(rootView: MySwiftUIView()) required init(appContext: AppContext? = nil) { super.init(appContext: appContext) hostingController.view.translatesAutoresizingMaskIntoConstraints = false hostingController.automaticallyAdjustsScrollViewInsets = false // 禁用自动内边距调整 addSubview(hostingController.view) NSLayoutConstraint.activate([ hostingController.view.topAnchor.constraint(equalTo: self.topAnchor), hostingController.view.bottomAnchor.constraint(equalTo: self.bottomAnchor), hostingController.view.leftAnchor.constraint(equalTo: self.leftAnchor), hostingController.view.rightAnchor.constraint(equalTo: self.rightAnchor) ]) } }
原理说明
SwiftUI的NavigationView和ScrollView默认会为系统安全区域(如刘海、状态栏)预留内边距,当嵌入到ExpoView中时,Expo自身的布局可能已经处理了安全区域,导致双重内边距叠加,最终顶部内容被裁切。通过忽略SwiftUI的顶部安全区域适配,即可让内容贴合ExpoView的顶部边界正常显示。
内容的提问来源于stack exchange,提问作者Tezzus
相关产品推荐
相关产品推荐

