You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级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的默认渲染逻辑做了调整,是问题的根源:

  1. 新增UINavigationBarScrollEdgeAppearance属性,默认状态下页面未滚动时导航栏为透明样式,只有滚动到内容顶到导航栏底部才会切换为不透明样式,两种样式的高度计算逻辑不一致,就会出现空白或者内容重叠。
  2. 首页设置了导航栏隐藏,子页面显示导航栏时,iOS 15的SwiftUI布局没有自动重新计算顶部安全区域偏移,进一步加剧了布局异常。
  3. 部分版本默认导航栏样式采用分栏适配逻辑,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 02:06:09