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

SwiftUI中如何让NavigationLink同时实现设备连接与页面导航?

解决SwiftUI中NavigationLink点击区域行为不一致问题

问题场景

我正在为油气公司开发一款现场应用,用于管理泵润滑剂注入设备。用户从DeviceListView选择设备后需要导航到HomeView并完成设备连接,但当前出现以下问题:

  • 点击列表行的绿色背景区域:仅触发页面导航,不执行设备连接逻辑,导致HomeView无数据
  • 点击列表行的文本区域:执行设备连接并加载特征,但不触发页面导航

解决方案

问题根源是NavigationLink的默认点击行为与手动添加的onTapGesture存在事件冲突——文本区域的点击被onTapGesture拦截,而背景区域直接触发NavigationLink的默认导航。以下是两种可靠的解决方式:

方案一:通过isActive绑定统一控制

手动管理导航状态,先执行设备连接逻辑,再触发导航:

struct DeviceListView: View {
    // CoreData vars:
    @FetchRequest(entity: Device.entity(), sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)]) var coreDataDevices: FetchedResults<Device>
    @EnvironmentObject var centralManager: CentralManager
    @Environment(\.managedObjectContext) var viewContext
    
    // State for managing deletion confirmation alert
    @State private var showingDeleteAlert = false
    @State private var deviceToDelete: Device? = nil
    // 新增导航控制变量
    @State private var isNavigating = false
    @State private var selectedDevice: DeviceViewItem?

    var body: some View {
        NavigationStack {
            ZStack{
                Color("Green 1").ignoresSafeArea()
                VStack{
                    Image("HeliosLogoSmall")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 200, height: 100)
                        .padding(.top, 40)
                    Text("CONNECT TO A DEVICE")
                        .textStyle(TextStyles.headerBold)
                        .padding()
                    List {
                        let deviceViewList = buildDeviceListView(savedDevice: coreDataDevices, discoveredDevice: centralManager.discoveredPeripherals)
                        ForEach(deviceViewList, id: \.id) { device in
                            HStack {
                                Text(device.name ?? "Unknown Device")
                                    .textStyle(TextStyles.display4)
                            }
                            .foregroundColor(device.discovered ? .black : .gray)
                            .listRowBackground(Color("Green 1"))
                            .onTapGesture {
                                // 先执行设备连接逻辑
                                signalConnect(device: device, manager: centralManager)
                                updateSaveDevice(deviceViewItem: device, context: viewContext)
                                // 存储选中设备供HomeView使用
                                selectedDevice = device
                                // 触发导航
                                isNavigating = true
                            }
                        }
                        .onDelete(perform: showDeleteAlert)
                    }
                    .listStyle(.plain)
                    .font(Font.custom("InstrumentSans-Regular", size:22))
                    .foregroundColor(Color("Grey 5"))
                    .alert(isPresented: $showingDeleteAlert) {
                        Alert(
                            title: Text("Delete Device"),
                            message: Text("Are you sure you want to delete this device? This action cannot be undone."),
                            primaryButton: .destructive(Text("Delete")) {
                                if let device = deviceToDelete {
                                    deleteDevice(device)
                                }
                            },
                            secondaryButton: .cancel()
                        )
                    }
                    ScanButtonView()
                }
                .padding([.leading, .trailing], 20)
                // 后台隐藏的NavigationLink,通过isActive控制导航
                .background(
                    NavigationLink(
                        destination: HomeView(selectedDevice: selectedDevice),
                        isActive: $isNavigating,
                        label: { EmptyView() }
                    )
                )
            }
        }
    }
    
    // 原有函数保持不变
    private func showDeleteAlert(offsets: IndexSet) {
        if let index = offsets.first {
            deviceToDelete = coreDataDevices[index]
            showingDeleteAlert = true
        }
    }
    
    private func deleteDevice(_ device: Device) {
        viewContext.delete(device)
        do {
            try viewContext.save()
        } catch {
            print("删除设备失败: \(error)")
        }
    }
}

方案二:使用navigationDestination(推荐)

利用SwiftUI最新导航API,配合Button实现整行点击统一逻辑:

struct DeviceListView: View {
    // CoreData vars:
    @FetchRequest(entity: Device.entity(), sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)]) var coreDataDevices: FetchedResults<Device>
    @EnvironmentObject var centralManager: CentralManager
    @Environment(\.managedObjectContext) var viewContext
    
    // State for managing deletion confirmation alert
    @State private var showingDeleteAlert = false
    @State private var deviceToDelete: Device? = nil
    // 存储选中设备
    @State private var selectedDevice: DeviceViewItem?

    var body: some View {
        NavigationStack {
            ZStack{
                Color("Green 1").ignoresSafeArea()
                VStack{
                    Image("HeliosLogoSmall")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 200, height: 100)
                        .padding(.top, 40)
                    Text("CONNECT TO A DEVICE")
                        .textStyle(TextStyles.headerBold)
                        .padding()
                    List {
                        let deviceViewList = buildDeviceListView(savedDevice: coreDataDevices, discoveredDevice: centralManager.discoveredPeripherals)
                        ForEach(deviceViewList, id: \.id) { device in
                            // 用Button包裹整行,点击区域覆盖整行
                            Button(action: {
                                // 先执行设备连接逻辑
                                signalConnect(device: device, manager: centralManager)
                                updateSaveDevice(deviceViewItem: device, context: viewContext)
                                // 标记选中设备触发导航
                                selectedDevice = device
                            }, label: {
                                HStack {
                                    Text(device.name ?? "Unknown Device")
                                        .textStyle(TextStyles.display4)
                                }
                                .foregroundColor(device.discovered ? .black : .gray)
                            })
                            .listRowBackground(Color("Green 1"))
                        }
                        .onDelete(perform: showDeleteAlert)
                    }
                    .listStyle(.plain)
                    .font(Font.custom("InstrumentSans-Regular", size:22))
                    .foregroundColor(Color("Grey 5"))
                    .alert(isPresented: $showingDeleteAlert) {
                        Alert(
                            title: Text("Delete Device"),
                            message: Text("Are you sure you want to delete this device? This action cannot be undone."),
                            primaryButton: .destructive(Text("Delete")) {
                                if let device = deviceToDelete {
                                    deleteDevice(device)
                                }
                            },
                            secondaryButton: .cancel()
                        )
                    }
                    ScanButtonView()
                }
                .padding([.leading, .trailing], 20)
                // 定义导航目标,选中设备后自动跳转
                .navigationDestination(item: $selectedDevice) { device in
                    HomeView(selectedDevice: device)
                }
            }
        }
    }
    
    // 原有函数保持不变
    private func showDeleteAlert(offsets: IndexSet) {
        if let index = offsets.first {
            deviceToDelete = coreDataDevices[index]
            showingDeleteAlert = true
        }
    }
    
    private func deleteDevice(_ device: Device) {
        viewContext.delete(device)
        do {
            try viewContext.save()
        } catch {
            print("删除设备失败: \(error)")
        }
    }
}

关键说明

  1. 两种方案都确保整行任意区域点击会先执行设备连接逻辑,再触发导航,彻底解决点击行为不一致问题
  2. 新增的selectedDevice变量用于将设备数据传递给HomeView,确保页面能获取到连接后的设备信息
  3. 方案二更贴合SwiftUI现代导航架构,代码结构更清晰易维护

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 01:02:03