SwiftUI同一结构体混用EnvironmentObject与Binding报错求助[新手]
问题描述
我已经搭好支持程序化添加/移除视图的NavigationStack,想在FindOrderView里通过Binding把变量ordernumber传给LoadingScreen视图,结果报错:Type 'LoadingScreen' does not conform to protocol 'Equatable'。我是SwiftUI纯新手,搜了好几个小时没解决,求帮忙。
相关代码
NavigationRouter(控制视图层级的类)
import Foundation import SwiftUI enum Route: Hashable { case ContentView case View1 } final class NavigationRouter: ObservableObject { @Published var navigationPath = NavigationPath() func pushView(route: Route) { navigationPath.append(route) } func popToRootView() { navigationPath = .init() } func popToSpecificView(k: Int) { navigationPath.removeLast(k) } }
MainApp(应用入口,启动后直接跳转至MainScreenView)
import SwiftUI @main struct AvhamtningApp: App { @StateObject var router = NavigationRouter() var body: some Scene { WindowGroup { NavigationStack(path: $router.navigationPath) { MainScreenView() .navigationDestination(for: Route.self) { route in switch route { case .ContentView: MainScreenView() case .View1: FindOrderView() } } }.environmentObject(router) } } }
MainScreenView(含“Get Order”按钮,点击跳转至View1即FindOrderView)
import SwiftUI struct MainScreenView: View { @EnvironmentObject var router: NavigationRouter var body: some View { VStack() { Text("Testing") .font(.largeTitle) .fontWeight(.heavy) .foregroundColor(.yellow) .padding(.top, 50) Spacer() PrimaryButton(text: "Get Order") .onTapGesture { router.pushView(route: .View1) } Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(.white)).ignoresSafeArea(.all) } } struct MainScreenView_Previews: PreviewProvider { static var previews: some View { MainScreenView() } }
FindOrderView(含数字键盘,输入7位数字后应跳转至LoadingScreen并传递ordernumber的Binding)
import SwiftUI var list = [["1","2","3"],["4","5","6"],["7","8","9"],["","0","⌫"]] struct FindOrderView: View { @State private var ordernumber: String = "" @EnvironmentObject var router: NavigationRouter var body: some View { VStack(spacing: 25) { Text(ordernumber) .font(.largeTitle) .foregroundColor(.black) .onChange(of: ordernumber) { newValue in if ordernumber.count >= 7 { router.navigationPath.append(LoadingScreen(ordernumber: $ordernumber)) } } Spacer() ForEach(list.indices, id: \.self) { listindex in HStack{ ForEach(list[listindex], id: \.self) { variableindex in Text(variableindex) .foregroundColor(.blue) .font(.system(size: 60)) .onTapGesture(perform: { if variableindex == "⌫" && !ordernumber.isEmpty { ordernumber.removeLast() } else if ordernumber.count >= 7 {return} else if variableindex == "⌫" { () } else { ordernumber += variableindex }}) .frame(maxWidth: .infinity) } } } }.background(.white) } } struct FindOrderView_Previews: PreviewProvider { static var previews: some View { FindOrderView() } }
LoadingScreen(报错:“Type 'LoadingScreen' does not conform to protocol 'Equatable'”)
import SwiftUI struct LoadingScreen: Hashable, View { @EnvironmentObject var router: NavigationRouter @Binding var ordernumber: String var body: some View { ZStack{ Text("Loading...") .font(Font.custom("Baskerville-Bold", size: 50)) .foregroundColor(.orange) .padding(.bottom, 200) ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .orange)) .scaleEffect(2) }.frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(.white)).ignoresSafeArea(.all) } } struct LoadingScreen_Previews: PreviewProvider { static var previews: some View { LoadingScreen(ordernumber: .constant("constant")) } }
解决方案
错误原因
你直接把LoadingScreen视图实例塞进navigationPath,但NavigationStack的路径元素必须满足Hashable + Equatable协议。而@Binding类型无法被哈希或做相等比较,导致LoadingScreen无法自动合成这两个协议的实现,所以报错。
正确的做法是延续你之前的Route枚举路由模式,把参数通过枚举关联值传递,而不是直接传视图实例。
具体修改步骤
1. 更新Route枚举,添加LoadingScreen的路由case并关联订单号
enum Route: Hashable { case ContentView case View1 case LoadingScreen(ordernumber: String) // 添加带关联值的路由项 }
2. 修改FindOrderView的跳转逻辑,用路由枚举代替视图实例
把原来直接添加LoadingScreen的代码,改成添加对应的路由枚举值:
// 替换FindOrderView中onChange里的代码 .onChange(of: ordernumber) { newValue in if newValue.count >= 7 { router.navigationPath.append(Route.LoadingScreen(ordernumber: newValue)) } }
3. 在MainApp的navigationDestination中添加LoadingScreen的处理
在路由匹配的switch里新增LoadingScreen的分支,取出关联的订单号并传递给视图:
NavigationStack(path: $router.navigationPath) { MainScreenView() .navigationDestination(for: Route.self) { route in switch route { case .ContentView: MainScreenView() case .View1: FindOrderView() case .LoadingScreen(let ordernumber): // 取出关联的订单号 LoadingScreen(ordernumber: .constant(ordernumber)) // 如果后续需要修改订单号,建议把状态放到ObservableObject中管理,见下方补充 } } }
4. 调整LoadingScreen(可选,若需修改订单号)
如果LoadingScreen只需要展示订单号,直接用let常量代替@Binding即可:
struct LoadingScreen: View { @EnvironmentObject var router: NavigationRouter let ordernumber: String // 替换@Binding为常量 var body: some View { ZStack{ Text("Loading... 订单号:\(ordernumber)") .font(Font.custom("Baskerville-Bold", size: 50)) .foregroundColor(.orange) .padding(.bottom, 200) ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .orange)) .scaleEffect(2) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(.white)).ignoresSafeArea(.all) } } // 预览也要修改 struct LoadingScreen_Previews: PreviewProvider { static var previews: some View { LoadingScreen(ordernumber: "1234567") } }
补充:如果需要在LoadingScreen中修改订单号
如果后续需要在LoadingScreen里修改订单号,建议把订单号状态放到一个全局的ObservableObject类中,避免Binding失效:
// 新增状态管理类 class OrderManager: ObservableObject { @Published var ordernumber: String = "" } // 在App中注入这个状态 @main struct AvhamtningApp: App { @StateObject var router = NavigationRouter() @StateObject var orderManager = OrderManager() // 新增 var body: some Scene { WindowGroup { NavigationStack(path: $router.navigationPath) { MainScreenView() .navigationDestination(for: Route.self) { route in switch route { case .ContentView: MainScreenView() case .View1: FindOrderView() case .LoadingScreen: LoadingScreen() } } } .environmentObject(router) .environmentObject(orderManager) // 注入环境 } } } // 修改FindOrderView,使用环境中的状态 struct FindOrderView: View { @EnvironmentObject var router: NavigationRouter @EnvironmentObject var orderManager: OrderManager // 替换原来的@State var body: some View { VStack(spacing: 25) { Text(orderManager.ordernumber) .font(.largeTitle) .foregroundColor(.black) .onChange(of: orderManager.ordernumber) { newValue in if newValue.count >= 7 { router.navigationPath.append(Route.LoadingScreen) } } // 数字键盘部分把所有ordernumber换成orderManager.ordernumber Spacer() ForEach(list.indices, id: \.self) { listindex in HStack{ ForEach(list[listindex], id: \.self) { variableindex in Text(variableindex) .foregroundColor(.blue) .font(.system(size: 60)) .onTapGesture(perform: { if variableindex == "⌫" && !orderManager.ordernumber.isEmpty { orderManager.ordernumber.removeLast() } else if orderManager.ordernumber.count >= 7 {return} else if variableindex == "⌫" { () } else { orderManager.ordernumber += variableindex }}) .frame(maxWidth: .infinity) } } } }.background(.white) } } // 修改LoadingScreen,直接用环境中的状态 struct LoadingScreen: View { @EnvironmentObject var router: NavigationRouter @EnvironmentObject var orderManager: OrderManager var body: some View { ZStack{ Text("Loading... 订单号:\(orderManager.ordernumber)") .font(Font.custom("Baskerville-Bold", size: 50)) .foregroundColor(.orange) .padding(.bottom, 200) ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .orange)) .scaleEffect(2) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(.white)).ignoresSafeArea(.all) } }
内容的提问来源于stack exchange,提问作者Axxi
相关产品推荐
相关产品推荐

