SwiftUI添加API调用加载器后NavigationLink无法正常跳转问题
核心原因
视图结构突变打断导航状态
你的ActivityIndicatorModifier在isLoading切换时,直接替换了整个视图结构(从带GeometryReader的ZStack变成直接返回原内容),这种视图树的剧烈变化会导致SwiftUI重新计算布局,打断NavigationLink的isActive状态响应,延迟跳转触发。键盘收起与滚动冲突
点击按钮时调用UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder))收起键盘,会触发表单所在的ScrollView自动调整滚动位置(键盘消失后视图布局变化),这个滚动动画会和导航跳转的动画抢占主线程资源,导致导航被暂时阻塞,直到滚动完成后才触发。状态更新的优先级问题
在fetchDeliveryCharge中,showPreview = true和isLoading = false的更新虽然在主线程,但SwiftUI可能会优先处理加载器消失的布局变化,延迟处理导航状态的更新。
解决方案
1. 修改ActivityIndicatorModifier,避免视图结构突变
不要通过if-else切换整个视图层级,保持固定的ZStack结构,仅控制加载层的显示/隐藏:
struct ActivityIndicatorModifier: AnimatableModifier { var isLoading: Bool init(isLoading: Bool, color: Color = .primary, lineWidth: CGFloat = 3) { self.isLoading = isLoading } var animatableData: Bool { get { isLoading } set { isLoading = newValue } } func body(content: Content) -> some View { ZStack(alignment: .center) { content .disabled(isLoading) .blur(radius: isLoading ? 3 : 0) if isLoading { GeometryReader { geometry in VStack { Text("Loading...") ActivityIndicator(isAnimating: .constant(true), style: .large) } .frame(width: geometry.size.width / 2, height: geometry.size.height / 5) .background(Color.secondary.colorInvert()) .foregroundColor(Color.primary) .cornerRadius(20) .position(x: geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY) } } } } }
2. 优化键盘收起逻辑,用SwiftUI原生FocusState替代
在视图中添加@FocusState管理输入框焦点,避免全局调用resignFirstResponder触发不必要的滚动:
// 视图中添加 @FocusState private var isTextFieldFocused: Bool // 按钮点击时替换原收起键盘代码 Button("PROCEED") { isTextFieldFocused = false // 失去焦点自动收起键盘 validateFields() if !showErrorAlert { isLoading = true Task { await fetchDeliveryCharge() } } } // 给每个TextField添加focus绑定 TextField("Your input", text: $someText) .focused($isTextFieldFocused)
3. 调整状态更新顺序,确保导航优先触发
在fetchDeliveryCharge中,用withAnimation包裹状态更新,让SwiftUI同步处理导航和加载器状态:
func fetchDeliveryCharge() async { let params = ["userId": placeOrder.userId ?? "", "locationType":"AreaName", "locationName":placeOrder.areaName ?? "", "mLatitude":placeOrder.mLatitude ?? 0.0, "mLongitude":placeOrder.mLongitude ?? 0.0, ] as [String : Any] APIManager.shared.getDeliveryChargeDetails(dict: params) { response in if response.Results.status { DispatchQueue.main.async { withAnimation { showPreview = true isLoading = false } } } else { } } errorHandler: { error in DispatchQueue.main.async { isLoading = false } } }
4. 替换隐藏NavigationLink为更可靠的navigationDestination(iOS 16+)
如果你的项目支持iOS 16及以上,推荐使用.navigationDestination替代隐藏的NavigationLink,避免视图隐藏导致的状态失效:
// 移除原来的隐藏NavigationLink,在视图根节点添加 .navigationDestination(isPresented: $showPreview) { PreviewOrderView(placeOrderObj: placeOrder, deliveryCharge: deliveryCharge) }
内容的提问来源于stack exchange,提问作者RP89

