我正在为一家石油和天然气公司构建一个应用程序。该应用程序将在现场用于连接和管理将润滑剂注入泵的设备。该应用程序应按如下方式运行:用户从 DeviceListView 中选择要管理的设备,应用程序将导航到 HomeView,用户可以在其中以自己想要的方式设置系统。 我正在解决的问题是导航。 按原样,当用户点击该行的绿色部分(蓝色突出显示)时,我的 NavigationLink 将进行导航,但它不会与设备连接。这意味着,所有要在 HomeView 中显示的值都不会显示。 但是,当我点击文本(红色/橙色突出显示)时,应用程序将连接到设备并加载控制台中的特征,但它不会导航到 HomeView。 我以为这是一个 UI 问题。我尝试了 H-Stack、Z-stack、修改器等的一大堆组合,但结果仍然相同。 有没有办法将 NavigationLink 设置为同时执行连接和导航或使用相同的点击手势? 设备列表视图
import SwiftUI
import CoreData
import CoreBluetooth
import Foundation
//Defines the model for an item in the device list.
struct DeviceViewItem: Identifiable{
var id = UUID()
var uuid: String?
var sn: String?
var discovered: Bool
var name: String?
}
// Making a shared property to store savedArray
class DeviceDataManager: ObservableObject {
static let shared = DeviceDataManager() // Singleton instance
@Published var savedArray: [DeviceViewItem] = []
}
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
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
NavigationLink(destination: HomeView(), label:{
HStack {
Text(device.name ?? "Unknown Device")
.textStyle(TextStyles.display4)
}
.foregroundColor(device.discovered ? .black : .gray)
.onTapGesture {
signalConnect(device: device, manager: centralManager)
updateSaveDevice(deviceViewItem: device, context: viewContext)
}
}
)
.listRowBackground(Color("Green 1"))
}
// Swipe to delete functionality
.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() //button to begin scanning
}//Vstack
.padding([.leading, .trailing], 20)
}//Zstack
} //NavigationStack
}
// Functions to delete a saved device
// Save the context after deleting the device
}
//compares the serial number that we are trying to connect to with the serial number that we have stored
private func signalConnect(device: DeviceViewItem, manager: CentralManager) {
print("Tapped " + device.sn!)
manager.connectBySN(sn: device.sn ?? "Serial1234")
}
private func buildDeviceListView(savedDevice: FetchedResults<Device>, discoveredDevice: [CBPeripheral] ) -> Array<DeviceViewItem> {
var savedArray = Array<DeviceViewItem>()
var discoveredArray = Array<DeviceViewItem>()
// Start by adding all devices from CoreData with discovered = false
for device in savedDevice {
var item = DeviceViewItem(discovered: false)
item.sn = device.serialNumber
item.uuid = device.uuid
item.name = device.name
savedArray.append(item)
}
// Parse through every discovered device
for device in discoveredDevice {
var item = DeviceViewItem(discovered: false)
// var item = DeviceViewItem(discovered: true, isConnected: centralManager.isConnected(device.identifier.uuidString)) 🟡🟣
let sn = CentralManager.buildSn(uuid: device.identifier.uuidString, name: device.name)
// Find out if we've connected to this device before
// Check if device is present in array
if let matchIndex = savedArray.firstIndex(where: { $0.sn == sn }) {
savedArray[matchIndex].discovered = true
}
else {
// We've never saved or connected to this device before, therefore add it to the list.
item.sn = sn
item.uuid = device.identifier.uuidString
item.discovered = true
item.name = device.name
discoveredArray.append(item)
}
}
// Update the shared savedArray
DeviceDataManager.shared.savedArray = savedArray
return savedArray + discoveredArray //MAKE savedArray value public and accessible to all.
}
即使您可以将
.simultaneousGesture
与 NavigationLink
一起使用(我认为这不起作用),您也会遇到两个独立且同时操作的逻辑问题:导航和连接。
因此,这里的问题不仅仅是布局或 UI 问题,而是一个更根本的问题:导航应该依赖于连接状态,而不是并行发生。
解决方案是使用编程导航来控制导航的条件。您将如何导航(如果有的话)取决于您的具体用例。
这是一个使用编程导航的简化工作示例,其中单击设备会触发一个模拟函数,该函数返回连接成功布尔值(基于奇数/偶数设备 ID 号):
import SwiftUI
//Routes for programatic navigation
enum NavigateConnectRoute: Hashable {
case home, devices
}
//Observable singleton
@Observable
class DeviceDataManager {
var deviceID: Int?
static let shared = DeviceDataManager() // Singleton instance
private init() {}
}
//Main view
struct NavigateConnect: View {
//State values
@State private var path = NavigationPath()
@State private var showError: Bool = false
@State private var errorDeviceID: Int?
//Bindings
@Bindable var deviceManager: DeviceDataManager = .shared
//Body
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("Select a device:")
.fontWeight(.bold)
List {
ForEach(1...10, id: \.self) { id in
Button {
//Simulate connection to a device (odd numbers fail, even numbers succeed)
let connected = connect(deviceID: id, status: id % 2 == 0 ? true : false)
if connected {
//Display console message
print("Connection successful for device \(id)")
//Navigate to Home view programmatically
path.append(NavigateConnectRoute.home)
}
else {
//Show error
errorDeviceID = id
}
} label : {
HStack {
Text("Device \(id)")
.foregroundStyle(errorDeviceID == id ? .red : .primary)
Spacer()
Image(systemName: "chevron.right")
.foregroundStyle(.secondary)
}
}
.listRowBackground(Color.clear)
.tint(.black)
}
}
.scrollContentBackground(.hidden)
.background(Color.clear)
Spacer()
}
.onChange(of: errorDeviceID) {
if errorDeviceID != nil {
showError = true
}
}
.alert(
"Connection Error",
isPresented: $showError,
presenting: errorDeviceID
) { id in
Button("OK", role: .cancel) {
print("Showed error alert for device \(id)")
}
} message: { id in
Text("Unable to connect to device \(id)")
}
.padding(.top, 50)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.background{
Color.yellow
.ignoresSafeArea()
}
.navigationTitle("Connect")
.navigationDestination(for: NavigateConnectRoute.self) { route in
switch route {
case .home: NavigateConnectHome()
case .devices: NavigateConnect()
}
}
}
.tint(.black)
}
//Helper function to simulate connecting to a device and returning a connection status
private func connect(deviceID: Int, status: Bool) -> Bool {
//Display console message
print("Connection to device \(deviceID) was \(status ? "successful" : "unsuccessful")")
//Set singleton observer value with the device id
deviceManager.deviceID = status ? deviceID : nil
//Return simulated status
return status
}
}
//Device home view
struct NavigateConnectHome: View {
//Binding to singleton observable
@Bindable var deviceManager: DeviceDataManager = .shared
//Body
var body: some View {
VStack {
Text("This is home view for device \(deviceManager.deviceID ?? -1) ")
}
.navigationTitle("Home")
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.background{
Color.yellow
.ignoresSafeArea()
}
.onDisappear {
//Reset device id when leaving home view
deviceManager.deviceID = nil
}
}
}
//Preview
#Preview {
NavigateConnect()
}