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

SwiftUI添加API调用加载器后NavigationLink无法正常跳转问题

问题原因与解决方案

核心原因

  1. 视图结构突变打断导航状态
    你的ActivityIndicatorModifier在isLoading切换时,直接替换了整个视图结构(从带GeometryReader的ZStack变成直接返回原内容),这种视图树的剧烈变化会导致SwiftUI重新计算布局,打断NavigationLink的isActive状态响应,延迟跳转触发。

  2. 键盘收起与滚动冲突
    点击按钮时调用UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder))收起键盘,会触发表单所在的ScrollView自动调整滚动位置(键盘消失后视图布局变化),这个滚动动画会和导航跳转的动画抢占主线程资源,导致导航被暂时阻塞,直到滚动完成后才触发。

  3. 状态更新的优先级问题
    在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 13:43:12