SwiftUI:定义@Environment(\.presentationMode)时List项显示异常
问题1:Bug原因与代码疏漏分析
这是SwiftUI在iOS 16早期版本(16.1.2)的框架渲染Bug,你的代码本身没有逻辑疏漏。
触发Bug的核心是三个因素的组合:
- 使用旧版
NavigationView并设置.stack风格 - 视图持有
@Environment(\.presentationMode)环境变量 - List通过
ForEach动态生成内容
具体原因是SwiftUI布局系统在初始渲染时,presentationMode的监听逻辑干扰了List的content offset计算,导致列表内容被错误推到屏幕中间空白区域下方;滚动操作会触发布局系统重新计算,从而修正偏移位置。
这种Bug的设备/模拟器偶发性,源于SwiftUI的渲染调度在不同设备上存在细微时序差异,部分设备刚好避开了冲突点。
问题2:替代方案(保留List与返回功能)
方案1:使用官方推荐的新导航API(最优解)
从iOS 16开始,Apple推荐用NavigationStack替代旧版NavigationView,同时用@Environment(\.dismiss)替代presentationMode,从根源避开旧API的Bug:
import SwiftUI @main struct Test_2022_12_03_02App: App { var body: some Scene { WindowGroup { ListView() } } } struct ListView: View { var body: some View { // 替换NavigationView为NavigationStack NavigationStack { NavigationLink(destination: DetailView()) { Text("Hello") } } } } struct DetailView: View { // 用dismiss替代presentationMode @Environment(\.dismiss) private var dismiss var members = [1] var body: some View { List { ForEach(members, id:\.self) { member in Text("World") } } // 自定义返回按钮(按需添加) .toolbar { Button("返回") { dismiss() } } } }
方案2:旧API下的临时修复(兼容低版本)
如果必须保留NavigationView,可以通过强制List重新布局修正偏移:
struct DetailView: View { @Environment(\.presentationMode) var presentationMode @State private var listRefreshId = UUID() var members = [1] var body: some View { List { ForEach(members, id:\.self) { member in Text("World") } } .id(listRefreshId) .onAppear { // 延迟触发List重新渲染,修正布局 DispatchQueue.main.async { listRefreshId = UUID() } } } }
也可以通过强制滚动到顶部修正:
struct DetailView: View { @Environment(\.presentationMode) var presentationMode var members = [1] var body: some View { List { ForEach(members, id:\.self) { member in Text("World") } } .scrollTo(0, anchor: .top, animate: false) .onAppear { // 初始触发滚动到顶部 DispatchQueue.main.async { UIApplication.shared.sendAction(#selector(UIScrollView.setContentOffset(_:animated:)), to: nil, from: nil, for: nil) } } } }
内容的提问来源于stack exchange,提问作者Tom Petit
相关产品推荐
相关产品推荐

