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

Xcode 14.0 beta 2中SwiftUI导航栏及按钮缺失问题咨询

SwiftUI Xcode14 导航栏与工具栏按钮消失问题

问题背景

我本打算就该问题向苹果提交反馈报告,但先在此发帖咨询,确认是否是我遗漏了相关配置,或是最新版SwiftUI有新的开发指引。
以下代码在Xcode 13中运行效果符合预期,但在Xcode 14 beta 2中,导航栏与“Cancel”按钮均消失。采用ProgressView实现内容延迟加载的方案,确认是否是该实现方式在新版本中不再兼容?

问题复现代码

import SwiftUI

struct ContentView: View {
    
    @State private var isFlowDetermined = false
    
    var body: some View {

        NavigationView {
            
            //NestedView()
            
            if self.isFlowDetermined {
                NestedView()
            } else {
                ProgressView()
                    .task {
                        await self.determineFlow()
                    }
            }
        }
    }
    
    private func determineFlow() async {
        
        self.isFlowDetermined = true
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct NestedView: View {
    
    var body: some View {
        
        ScrollView {
            Text("Where is the \"Cancel\" button?")
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.green)
    #if !os(macOS)
        .navigationBarTitleDisplayMode(.inline)
    #endif
        .toolbar {
    #if !os(macOS)
            ToolbarItem(placement: .navigationBarLeading) {
                Button("Cancel") {
                    print("got here")
                }
            }
    #endif
        }
    }
}

异常表现

运行效果如下图所示,导航栏区域既没有显示导航栏,也未展示“Cancel”按钮:
导航栏区域无“Cancel”按钮及导航栏

问题原因

这是Xcode14 搭载的iOS16 SDK中NavigationView的行为变更导致的:

  • 当NavigationView的直接子视图为条件渲染的不同类型视图时,系统会在初始加载时根据当前显示的子视图配置导航栏状态。
  • 上述示例代码初始状态显示的是ProgressView,该视图没有添加任何导航栏、工具栏相关修饰符,系统判定当前页面不需要显示导航栏,后续条件切换到NestedView时,没有触发导航栏状态的重新刷新,最终导致导航栏和按钮不显示。
  • Xcode13对应的iOS15及更早版本SDK没有该行为逻辑,所以运行正常。

解决方案

方案1(推荐):不切换NavigationView根视图,将加载状态作为覆盖层

保证NavigationView的直接子视图始终是带导航栏配置的NestedView,加载中的ProgressView作为覆盖层展示,从根源避免根视图类型切换导致的导航栏状态异常,修改后的ContentView代码如下:

struct ContentView: View {
    
    @State private var isFlowDetermined = false
    
    var body: some View {
        NavigationView {
            ZStack {
                // 始终加载带导航栏配置的业务视图
                NestedView()
                
                // 加载中状态作为覆盖层展示
                if !self.isFlowDetermined {
                    ProgressView()
                        .task {
                            await self.determineFlow()
                        }
                }
            }
        }
    }
    
    private func determineFlow() async {
        self.isFlowDetermined = true
    }
}

方案2:给所有条件分支的子视图添加一致的导航栏配置

如果必须保留根视图条件切换的写法,需要给初始显示的ProgressView也加上和NestedView一致的导航栏配置,保证初始加载时导航栏就被正确初始化:

NavigationView {
    if self.isFlowDetermined {
        NestedView()
    } else {
        ProgressView()
            .task {
                await self.determineFlow()
            }
            // 补上和NestedView一致的导航栏配置
            #if !os(macOS)
            .navigationBarTitleDisplayMode(.inline)
            #endif
            .toolbar {
                #if !os(macOS)
                ToolbarItem(placement: .navigationBarLeading) {
                    // 加载中隐藏按钮做占位
                    Button("Cancel") {}.hidden()
                }
                #endif
            }
    }
}

补充说明

iOS16开始系统已经将NavigationView标记为废弃,推荐使用NavigationStack实现导航逻辑,新组件对视图切换时的导航栏状态刷新逻辑更稳定。注意:NavigationStack仅支持iOS16及以上系统版本,如果项目需要兼容iOS15及更早版本,优先使用方案1解决问题。


内容的提问来源于stack exchange,提问作者Daniel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:18:26