升级Xcode13/iOS15后SwiftUI NavigationBar标题下方出现异常空白问题
问题描述
Xcode 12开发的项目升级到Xcode 13后,仅在iOS 15设备上出现两个导航栏相关异常:
- 导航栏标题文本下方出现异常空白区域:

- 包含scrollview的页面中导航栏会和scrollview内容发生重叠:

相关代码
商品详情页
struct ProductDetailScreen: View { let product: Product @State var productVariation: ProductVariation? var body: some View { ZStack{ Color.themeBackground .ignoresSafeArea() VStack{ VStack{ AsyncImage( url: URL(string: product.image)!, placeholder: { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: Color.themeBackground)) }, image: { Image(uiImage: $0) .resizable() } ) .frame(height: 250, alignment: .center) .cornerRadius(10) FixedSpacer(height: 10) LazyVGrid(columns: [GridItem(.adaptive(minimum: 60))]){ ForEach(product.variations, id: \.self) { variation in VariationSizeButtonView(productVariation: $productVariation, variation: variation) } } Spacer() if productVariation != nil { NavigationButton(text: "Select Photos", destination: AnyView(SelectPhotosScreen(product: product, productVariation: self.productVariation!))) } } .frame( minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading ) Spacer() } .padding(.horizontal, 24) } .navigationBarTitle(product.name) .navigationBarTitleDisplayMode(.inline) } }
图片选择页
struct SelectPhotosScreen: View { @State private var id = 1 @State var product: Product @State var uploadingPhotos = false @State var productVariation: ProductVariation @StateObject var selectedImagesViewModel = SelectedImagesViewModel() @EnvironmentObject var cartViewModel: CartViewModel let imageUploaderService = ImageUploaderService() var body: some View { ZStack{ Color.themeBackground .ignoresSafeArea() VStack{ VStack { PhotoSelectionOptions(selectedImagesViewModel: selectedImagesViewModel) SelectedPhotoDisplayer(selectedImagesViewModel: selectedImagesViewModel) } Spacer() if uploadingPhotos && (selectedImagesViewModel.pendingImages() > 0) { ProgressView("Uploading....", value: Float(selectedImagesViewModel.uploadedImages()), total: Float(selectedImagesViewModel.images.count)) .padding(.horizontal, 24) .foregroundColor(.white) .frame(maxWidth: .infinity) } if !selectedImagesViewModel.images.isEmpty && !uploadingPhotos{ RoundedButton(text: "Upload Photos", action: { uploadingPhotos = true imageUploaderService.uploadImages(selectedImagesViewModel: selectedImagesViewModel) }) } if (selectedImagesViewModel.pendingImages() <= 0) && (selectedImagesViewModel.uploadedImages() >= 1) { NavigationButton(text: "Order Summary", destination: AnyView(OrderSummaryScreen( cartItem: CartItem( id: self.product.id, selectedImages: self.selectedImagesViewModel, product: self.product, productVariation: self.productVariation ) ))) } } .padding(.horizontal, 24) } .navigationBarTitle("Select Photos", displayMode: .inline) } }
首页入口
struct ContentView: View { let greet = Greeting().greeting() var body: some View { NavigationView{ HomeScreen() .navigationTitle("") .navigationBarHidden(true) } .navigationAppearance(backgroundColor: .themeBackground, foregroundColor: .white) } }
解决方案
问题原因
iOS 15对UINavigationBar的默认渲染逻辑做了调整,是问题的根源:
- 新增
UINavigationBarScrollEdgeAppearance属性,默认状态下页面未滚动时导航栏为透明样式,只有滚动到内容顶到导航栏底部才会切换为不透明样式,两种样式的高度计算逻辑不一致,就会出现空白或者内容重叠。 - 首页设置了导航栏隐藏,子页面显示导航栏时,iOS 15的SwiftUI布局没有自动重新计算顶部安全区域偏移,进一步加剧了布局异常。
- 部分版本默认导航栏样式采用分栏适配逻辑,iPhone端也会出现布局计算错误。
修复步骤
1. 全局适配导航栏新样式
修改你项目中navigationAppearance扩展的实现,同时配置导航栏所有状态下的外观,统一样式避免切换导致的偏移:
extension View { func navigationAppearance(backgroundColor: UIColor, foregroundColor: UIColor) -> some View { self.modifier(NavigationModifier(backgroundColor: backgroundColor, foregroundColor: foregroundColor)) } } struct NavigationModifier: ViewModifier { init(backgroundColor: UIColor, foregroundColor: UIColor) { let appearance = UINavigationBarAppearance() appearance.configureWithOpaqueBackground() appearance.backgroundColor = backgroundColor appearance.titleTextAttributes = [.foregroundColor: foregroundColor] // 关键:统一所有状态下的导航栏外观 UINavigationBar.appearance().standardAppearance = appearance UINavigationBar.appearance().scrollEdgeAppearance = appearance UINavigationBar.appearance().compactAppearance = appearance UINavigationBar.appearance().tintColor = foregroundColor } func body(content: Content) -> some View { content } }
2. 强制导航栏使用栈式样式
修改首页的NavigationView配置,添加.navigationViewStyle(.stack)避免分栏样式导致的布局异常:
struct ContentView: View { let greet = Greeting().greeting() var body: some View { NavigationView{ HomeScreen() .navigationTitle("") .navigationBarHidden(true) } .navigationViewStyle(.stack) // 新增该行 .navigationAppearance(backgroundColor: .themeBackground, foregroundColor: .white) } }
3. 子页面显式声明导航栏可见
在所有子页面(商品详情页、图片选择页)的导航栏配置末尾添加显式可见声明,让系统正确计算顶部安全区域:
// 商品详情页末尾添加 .navigationBarTitle(product.name) .navigationBarTitleDisplayMode(.inline) .toolbar(.visible, for: .navigationBar) // 新增 // 图片选择页末尾添加 .navigationBarTitle("Select Photos", displayMode: .inline) .toolbar(.visible, for: .navigationBar) // 新增
完成以上三步后即可修复iOS 15下的导航栏布局异常问题。
内容的提问来源于stack exchange,提问作者Asif Mushtaq
相关产品推荐
相关产品推荐

