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

SwiftUI同一结构体混用EnvironmentObject与Binding报错求助[新手]

问题描述

我已经搭好支持程序化添加/移除视图的NavigationStack,想在FindOrderView里通过Binding把变量ordernumber传给LoadingScreen视图,结果报错:Type 'LoadingScreen' does not conform to protocol 'Equatable'。我是SwiftUI纯新手,搜了好几个小时没解决,求帮忙。


相关代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 01:03:05