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

SwiftUI环境对象访问异常问题:登录后解码数据无法在子视图中读取

Fix: Accessing Decoded Data in SwiftUI Child Views

Hey there! I see exactly what's going on here—you're dealing with a classic instance mismatch issue in SwiftUI. Let's break it down and get your data showing up properly.

The Core Problem

You've got two separate instances of AuthViewModel:

  1. One created in ContentView as @StateObject var test: AuthViewModel = AuthViewModel() (this is the one your SearchHistory view tries to access via @EnvironmentObject)
  2. Another created in SignIn view as @StateObject var vm: AuthViewModel = AuthViewModel() (this is the one that actually gets the decoded claimHistroy data after login)

Since these are completely separate objects, the data stored in the SignIn view's vm never makes it to the ContentView's test instance. That's why vm.claimHistroy is always nil in SearchHistory!

Step-by-Step Fix

1. Share a Single AuthViewModel Instance Across Your App

Instead of creating a new AuthViewModel in SignIn, use the one from the environment. Here's how to adjust your code:

Update the SignIn View

Remove the local @StateObject and use @EnvironmentObject instead:

struct SignIn: View {
    @EnvironmentObject var vm: AuthViewModel // Now uses the shared instance
    @EnvironmentObject var authentication: AuthenticationCheck
    @Binding var userID: String
    @Binding var passcode: String
    
    var body: some View {
        Button(action: {
            print("Button action")
            vm.signIn(username: userID, password: passcode)
        }) {
            Text("Sign In")
                .multilineTextAlignment(.center)
                .padding()
        }
        .frame(width: 150.0, height: 43.0)
    }
}

Inject the ViewModel at the App Root

For best practice, create your AuthViewModel at the app's entry point and inject it into the environment there, so every view can access it:

@main
struct YourAppName: App {
    @StateObject private var authVM = AuthViewModel()
    @StateObject private var authCheck = AuthenticationCheck()
    
    var body: some Scene {
        WindowGroup {
            // Toggle between SignIn and ContentView based on auth status
            if authCheck.isValidated {
                ContentView()
            } else {
                SignIn(userID: .constant(""), passcode: .constant(""))
            }
        }
        .environmentObject(authVM)
        .environmentObject(authCheck)
    }
}

2. Clean Up ContentView

You can remove the local @StateObject in ContentView since we're now injecting from the root:

struct ContentView: View {
    var body: some View {
        TabView {
            Home()
                .tabItem {
                    Image(systemName: "house.fill")
                    Text("Home")
                }
            Search()
                .font(.system(size: 30, weight: .bold, design: .rounded))
                .tabItem {
                    Image(systemName: "magnifyingglass")
                    Text("Search")
                }
            Text("Profile Page")
                .font(.system(size: 30, weight: .bold, design: .rounded))
                .tabItem {
                    Image(systemName: "person.fill")
                    Text("Profile")
                }
        }
    }
}

Bonus Code Optimizations

Since you're new to Swift, here are some tweaks to make your code more robust:

1. Simplify NetworkService's handleResponse

You're converting data to a JSON object and then decoding it—this is redundant. Clean it up:

private func handleResponse<T: Decodable>(result: Result<Data, Error>?, completion: (Result<T, Error>) -> Void) {
    guard let result = result else {
        completion(.failure(AppError.unknownError))
        return
    }
    switch result {
    case .success(let data):
        let decoder = JSONDecoder()
        do {
            let decodedData = try decoder.decode(T.self, from: data)
            print("Decoded response: \(decodedData)")
            completion(.success(decodedData))
        } catch {
            print("Decoding error: \(error)")
            completion(.failure(AppError.errorDecoding))
        }
    case .failure(let error):
        completion(.failure(error))
    }
}

2. Improve Optional Handling in SearchHistory

Instead of String(describing:), use a user-friendly default value:

Text(vm.claimHistroy?.result.member.yearToDateSearchSavingsAmount ?? "0.00")

Or conditionally show content:

if let savings = vm.claimHistroy?.result.member.yearToDateSearchSavingsAmount {
    Text(savings)
} else {
    Text("No data available")
}

3. Encapsulate AuthViewModel State

Add private(set) to your @Published properties to prevent external views from modifying state directly:

class AuthViewModel: ObservableObject {
    @Published private(set) var user: LoginResponseData.Root? = nil
    @Published private(set) var alert: CustomAlert? = nil
    @Published private(set) var claimHistroy: ClaimStruct? = nil
    // ... rest of your code
}

4. Merge AuthenticationCheck into AuthViewModel

You don't need a separate object—let AuthViewModel handle validation state:

class AuthViewModel: ObservableObject {
    @Published private(set) var isValidated = false
    // ... rest of your properties
    
    func signIn(username: String, password: String) {
        networkService.signIn(username: username, password: password) { (result) in
            switch result {
            case .success(let user):
                self.user = user
                self.isValidated = true
                self.profileSessionMember()
            case .failure(let error):
                print("Error: \(error.localizedDescription)")
                self.isValidated = false
                self.alert = CustomAlert(title: "Invalid Credentials", message: "Either username or password is incorrect. Please try again")
            }
        }
    }
    
    func logout() {
        self.user = nil
        self.isValidated = false
    }
    // ... rest of your code
}

Then you can remove the AuthenticationCheck class entirely and use authVM.isValidated in your root flow.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 05:02:30