从网络请求更新 EnvironmentObject 后,NavigationLink 在嵌套的 ScrollView 中不起作用

Posted

技术标签:

【中文标题】从网络请求更新 EnvironmentObject 后,NavigationLink 在嵌套的 ScrollView 中不起作用【英文标题】:NavigationLink doesn't work in nested ScrollView after EnvironmentObject update from network request 【发布时间】:2021-11-26 06:09:30 【问题描述】:

我有两个用于 navigationView、Dashboard 和 Userscreen 的选项卡。

Userscreen用于通过网络请求获取用户信息,dashboard是根据用户信息通过网络请求显示信息,这里用户信息就是环境对象。

更新用户信息后,我可以查看仪表板属性,但单击导航链接不起作用。 经过一番测试,我发现 ScrollView -> ScrollView(.horizo​​ntal) -> NavigationLink 下的导航链接(根据用户信息显示)不起作用,但 VStack -> ScrollView( 下的导航链接(根据用户信息显示) .horizo​​ntal) -> NavigationLink 表现良好。

我想知道我是否在实现中遗漏了什么,或者我想问一下它是否是 XCode 的现有错误?

编辑:更新代码

再次编辑:请参考下面@workingdog的解决方案,整个问题只是由一个简单的愚蠢错误引起的,即没有声明正确的状态/环境对象。

import SwiftUI
import Kingfisher
import Alamofire

struct ExampleView: View 
@StateObject var userInfoManager = UserInfoManagerExample()

var body: some View 
    NavigationView 
        TabView ()
            DashboardScreenExample()
                .environmentObject(userInfoManager)
                .tabItem
                    Text("Dashboard")
                
            LoginScreenExample()
                .environmentObject(userInfoManager)
                .tabItem
                    Text("UpdateInfo")
                
        
        .navigationBarTitleDisplayMode(.inline)
    
    .navigationViewStyle(StackNavigationViewStyle())



struct DashboardScreenExample: View 

@EnvironmentObject var userInfoManager : UserInfoManagerExample
@State var please : Bool = false

init()
   Theme.navigationBarColors(background: UIColor(viewProperties.themered), titleColor: .white)


var viewProperties = ViewProperties()

var body: some View 
    if(userInfoManager.shouldShowDashboard)
        ScrollView//Switch this ScrollView to VStack make Navigation Link work again
            //I had other screens too, but screen below is the one causing problem
            DashboardContentExample()
        
    else
        ProgressView().onAppearuserInfoManager.shouldShowDashboard = true
    



struct DashboardContentExample: View 

@EnvironmentObject var userInfoManager : UserInfoManagerExample
@ObservedObject var dashboardList = DashboardConfigRequestExample()

func loadRequest()
    dashboardList.loadData(passedLanguage: userInfoManager.preferenceLangauge)


//View start from here
var body: some View 
    if(dashboardList.dashboardConfig.count == 0)
        ProgressView()
            .onAppear
                loadRequest()
            
    else
        ScrollView(.horizontal)
            LazyHStack
                //Here is the NavigationLink not working
                NavigationLink(destination: EmptyView())
                    Text("Hello")
                
            
        
    



struct LoginScreenExample: View 

let usernameLoginRequest = UsernameLoginRequestExample()
@EnvironmentObject var userInfoManager : UserInfoManagerExample
@State var loginClicked : Bool = false

var body: some View 
    VStack(alignment: .leading)
        if(userInfoManager.isLogin)
            Text("You have logged in.")
                .onAppear
                    loginClicked = false
                
        
        else
            ZStack
                VStack
                    Spacer()
                    //the username and password had to be entered
                    Button(action:clickLogin(forUsername: "iamusername", andPassword: "iampassword"))
                        Text("Sign In")
                    
                    Spacer()
                
                if(loginClicked)
                    ProgressView()
                
                
            
        
    


func clickLogin(forUsername: String, andPassword: String)
    loginClicked.toggle()
    usernameLoginRequest.loadData(forUsername: forUsername, andPassword: andPassword, andUserInfo: userInfoManager)



class UserInfoManagerExample : ObservableObject

@Published var isLogin: Bool = false
@Published var username: String = ""
@Published var password: String = ""
@Published var preferenceLangauge: String = "en"
@Published var shouldShowDashboard = true

init()


func updateCredential(forUsername: String, andPassword: String)
    username = forUsername
    password = andPassword



struct UsernameLoginRequestExample

func loadData(forUsername: String, andPassword: String, andUserInfo: UserInfoManagerExample)
    let request_url = "This is the request url"
    let request_parameters = getUsernameLoginParameter(withUsername: forUsername, andPassword: andPassword)
    
    AF
    .request(request_url, method: .post, parameters: request_parameters, encoding: JSONEncoding.default ).responseJSON
        responses
        in
        debugPrint(responses)
        
        switch responses.result
        case .success:
            do 
                andUserInfo.shouldShowDashboard = false
                andUserInfo.updateCredential(forUsername: forUsername, andPassword: andPassword)
                andUserInfo.isLogin = true
            
        case let .failure(error):
            print(error)
        
    


func getUsernameLoginParameter(withUsername : String, andPassword : String) -> [String : String]
    var parameters : [String : String] = [String : String]()
    parameters.updateValue(withUsername, forKey: "username")
    parameters.updateValue(andPassword, forKey: "password")
    
    var device_id : String
    
    if(UIDevice.current.identifierForVendor != nil)
        device_id = UIDevice.current.identifierForVendor!.uuidString
    else
        device_id = ""
    
    
    parameters.updateValue(device_id, forKey:"device_id")
    
    return parameters



class DashboardConfigRequestExample : ObservableObject
@Published var dashboardConfig = [DashboardConfigExample]()

func loadData(passedLanguage : String)
    let request_url = "This is request url"
          
    let request_parameters = getDashboardConfigParameter(passedLanguage: passedLanguage)
    
    AF
    .request(request_url, method:.post, parameters: request_parameters)
    .responseJSON
        
        responses in
        
        switch responses.result 
            case .success:
                guard
                    let data = responses.data
                else  return 
                do 
                    let configResponse = try JSONDecoder().decode(DashboardConfigDataExample.self, from: data)
                        self.dashboardConfig = configResponse.list ?? []
                 catch 
                    print(error)
                
            case let .failure(error):
                print(error)
        
    


func getDashboardConfigParameter(passedLanguage: String) -> [String:String]
    var parameters : [String:String] = [String:String]()
    parameters.updateValue(passedLanguage, forKey: "language")
    return parameters



struct DashboardConfigDataExample : Decodable
public var status: String?
public var list: [DashboardConfigExample]?


struct DashboardConfigExample: Encodable & Codable 
let background_image: String?
let background_color: String?

【问题讨论】:

您的代码在我的测试中似乎对我有用。单击NavigationLinks 可以工作。在 macos 12.1-beta 上,使用 xcode 13.2-beta,针对 ios 15 和 macCatalyst 12。 感谢您的回复,如果 @Published var 在没有网络请求的情况下更新,我的代码可以正常工作。代码中有两个cmet会从我的后端执行异步网络请求,依次更新环境对象,涉及NavigationLinks的屏幕需要再次更新,然后NavigationLinks在请求后将不起作用。但是用VStack 替换ScrollView 以某种方式使NavigationLinks 起作用。 因此,您向我们展示的代码并不是给您带来任何问题的代码。你能告诉我们可以复制你的问题的代码吗? 我改了代码,但是去掉了网络请求参数和url,谢谢。 【参考方案1】:

尝试替换

@ObservedObject var infoRequest = InfoRequest()

Dashboard

@StateObject var infoRequest = InfoRequest()

EDIT1:根据新代码。

尝试移动@ObservedObject var dashboardList = DashboardConfigRequestExample()DashboardContentExample 进入ExampleView@StateObject。类似于以下示例代码:

struct ExampleView: View 
    @StateObject var userInfoManager = UserInfoManagerExample()
    @StateObject var dashboardList = DashboardConfigRequestExample()  // <-- here
    
    var body: some View 
        NavigationView 
            TabView ()
                DashboardScreenExample().tabItem Text("Dashboard")
                LoginScreenExample().tabItem Text("UpdateInfo")
            .navigationBarTitleDisplayMode(.inline)
        
        .environmentObject(userInfoManager)  // <-- here
        .environmentObject(dashboardList)    // <-- here
        .navigationViewStyle(StackNavigationViewStyle())
    


struct DashboardContentExample: View 
    
    @EnvironmentObject var userInfoManager : UserInfoManagerExample
    @EnvironmentObject var dashboardList: DashboardConfigRequestExample  // <-- here
    
    func loadRequest()
        dashboardList.loadData(passedLanguage: userInfoManager.preferenceLangauge)
    
    
    //View start from here
    var body: some View 
        if(dashboardList.dashboardConfig.count == 0)
            ProgressView()
                .onAppear
                    loadRequest()
                
        else
            ScrollView(.horizontal)
                LazyHStack
                    //Here is the NavigationLink not working
                    NavigationLink(destination: Text("Hello destination"))  // <-- here for testing
                        Text("Hello")
                    
                
            
        
    

【讨论】:

我改了,导航链接好像还是不行。顺便说一句,我已经将代码编辑为真正引起我问题的代码,感谢您帮助@workingdog! 根据您的新代码更新了我的答案。 抱歉@workingdog 回复晚了……即使我根据更新后的答案添加了新的@stateobject@environmentobject,导航链接也无法正常工作,但无论如何感谢您提供帮助!但是navigation links有一个奇怪的行为是,它们在点击时没有响应,但在切换标签后,之前链接的屏幕会立即弹出。 原来是我把环境对象放到了错误的屏幕上,非常感谢!

以上是关于从网络请求更新 EnvironmentObject 后,NavigationLink 在嵌套的 ScrollView 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Swiftui @EnvironmentObject 在视图中更新

Swift UI - EnvironmentObject 更改不会更新 UI

如何使用@EnvironmentObject 在 SwiftUI 中自动填充和更新列表?

在 SwiftUI 中通过摇动手势(使用 UIKit)设置 EnvironmentObject 不会更新视图

无法使用@EnvironmentObject SwiftUI 更新先前视图上的文本

如何从类中为 EnvironmentObject 设置值?