如何在 SwiftUI 的列表项中调用完成处理程序?

Posted

技术标签:

【中文标题】如何在 SwiftUI 的列表项中调用完成处理程序?【英文标题】:How to call completion handler in list item in SwiftUI? 【发布时间】:2021-07-26 16:45:38 【问题描述】:

我有一个包含项目的列表,其中还有一个列表。这是列表项:

struct CohortItemView:View 
    @State var cohortsGetter: ProfilesInCohort
    let cohortItem:CohortArrayItem
    @ObservedObject var profilesGetter = CohortProcessor()
    
    init(item:CohortArrayItem) 
        self.cohortItem = item
        self.cohortsGetter = ProfilesInCohort.init(cohortWebId: cohortItem.cohort?.webID ?? "")
    
    
    var body: some View 
        VStack
            HStack
                Text(cohortItem.cohort?.name ?? "no data")
                    .padding(.leading,10)
                    .multilineTextAlignment(.leading)
                Spacer()
                
                ZStack
                    Text("Show all \(cohortItem.profilesNum!)")
                .padding(.trailing,10)
            
            
            
            var usersInCohort  array in
                ScrollView(.horizontal, showsIndicators: false) 
                    HStack
                        ForEach(1..<6, id: \.self) 
                            let user = (array![$0].profile)
                            UserCard(user: UserModel(name: user.firstName ?? "", position: user.lastName ?? "", image: "moon.stars.fill"))
                        
                    .padding(10)
                
            
            
        
    
    
    func usersInCohort(completion:@escaping ([ProfilesInCohortModel.ProfilesArrayItem]?)->Void) 
        let params = ["offset":6,"cohortWebID":cohortItem.cohort?.webID ?? ""] as [String : Any]
        
        var request = URLRequest(url: URL(string: "url")!)
        request.httpMethod = HTTPMethod.post.rawValue
        request.httpBody =  try? JSONSerialization.data(withJSONObject: params, options: [])
        request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
        
        
        AF.request(request).responseDecodable(of: ProfilesInCohortModel.self)  (response) in
            if response.response?.statusCode == 200
                completion(response.value?.profiles)
            
        
    

如您所见,我在列表项中有列表。我想在显示列表项后显示列表。这是这个子列表项:

struct UserCard: View 
    let user:UserModel
    init(user:UserModel) 
        self.user = user
    
    var body: some View 
        VStack
            ZStack(alignment: .topTrailing) 
                Image(systemName: user.image)
                    .resizable()
                    .frame(width: 64.0, height: 64.0)
                    .clipShape(Circle())
                    .shadow(radius: 10)
                Button(action: 
                    print("Edit button was tapped")
                ) 
                    Image(systemName: user.image)
                
            
            Text(user.name)
                .fontWeight(.regular)
                .foregroundColor(.black)
                .multilineTextAlignment(.center)
            Text(user.position)
                .foregroundColor(.gray)
                .multilineTextAlignment(.center)
        
        .background(Color.green)
    

但是在这个代码块中:

var usersInCohort  array in
                ScrollView(.horizontal, showsIndicators: false) 
                    HStack
                        ForEach(1..<6, id: \.self) 
                            let user = (array![$0].profile)
                            UserCard(user: UserModel(name: user.firstName ?? "", position: user.lastName ?? "", image: "moon.stars.fill"))
                        
                    .padding(10)
                
            

在这一行:

var usersInCohort  array in

我有这样的错误:

Closure containing a declaration cannot be used with result builder 'ViewBuilder'

我不明白为什么会这样。我也尝试使用观察到的对象,但它也没有帮助我。因此,我希望在父列表项中查看列表。也许我做错了什么?

更新

我的模型:

struct ProfilesInCohortModel:Decodable 
    var num,status:Int?
    var noMore,authRequired:Bool?
    var profiles:[ProfilesArrayItem]?
    
    enum CodingKeys:String,CodingKey 
        case num,status,authRequired,profiles
        case noMore = "flagNoMore"
    
    
    
    struct ProfilesArrayItem:Decodable
        let profile:ProfileItem
        let publiclyForwarded:Int
        let altCohort:CohortItem
        
        enum CodingKeys:String,CodingKey 
            case profile,altCohort
            case publiclyForwarded = "followedBySessionUser_publicly"
        
    
    
    struct ProfileItem:Decodable,Identifiable 
        let id = UUID()
        let inode,superSedByNode,status,createdUser,editedUser:Int?
        let webID,notes,web,wikipedia,suffix,bio,title:String?
        let name,akaName,firstName,middleName,lastName,fullName:String?
        let twitter,linkedin,instagram,github,email,phone:String?
        let createTime,lastEditTime:UInt64?
        let hasImage:Bool?
        
        enum CodingKeys:String,CodingKey 
            case inode,webID,name,akaName,firstName,middleName,lastName,fullName
            case notes,web,wikipedia,twitter,linkedin,instagram,github,phone
            case status,suffix,bio,title
            case editedUser = "user_lastedit"
            case createdUser = "user_created"
            case superSedByNode = "superseded_by_node"
            case email = "contact_email"
            case createTime = "time_created"
            case lastEditTime = "time_lastedit"
            case hasImage =  "has_image"
        
        
    

和:

struct UserModel 
    var name:String
    var position:String
    var image:String

【问题讨论】:

@matt,我该如何解决这个问题?也许我们有其他解决方案? 我尝试了 observable obj 但只为几个父列表项下载了数据,所以我认为我做错了 【参考方案1】:

首先,我会将您的异步代码移至视图模型。然后,使用onAppear 调用异步方法来检索队列。然后,您可以对存储在Published 变量中的结果使用ForEach

class CohortRetriever : ObservableObject 
    @Published var retrievedCohorts: [ProfilesInCohortModel.ProfilesArrayItem] = []
    
    func retrieve(cohortItem: CohortArrayItem) 
        self.usersInCohort(cohortItem: cohortItem)  result in
            self.retrievedCohorts = result
        
    
    
    private func usersInCohort(cohortItem: CohortArrayItem, completion:@escaping ([ProfilesInCohortModel.ProfilesArrayItem]?)->Void) 
        let params = ["offset":6,"cohortWebID":cohortItem.cohort?.webID ?? ""] as [String : Any]
        
        var request = URLRequest(url: URL(string: "url")!)
        request.httpMethod = HTTPMethod.post.rawValue
        request.httpBody =  try? JSONSerialization.data(withJSONObject: params, options: [])
        request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
        
        
        AF.request(request).responseDecodable(of: ProfilesInCohortModel.self)  (response) in
            if response.response?.statusCode == 200
                completion(response.value?.profiles)
            
        
    


struct CohortItemView:View 
    @State var cohortsGetter: ProfilesInCohort
    let cohortItem:CohortArrayItem
    @ObservedObject var profilesGetter = CohortProcessor()
    
    @StateObject var retriever = CohortRetriever()
    
    init(item:CohortArrayItem) 
        self.cohortItem = item
        self.cohortsGetter = ProfilesInCohort.init(cohortWebId: cohortItem.cohort?.webID ?? "")
    
    
    var body: some View 
        VStack
            HStack
                Text(cohortItem.cohort?.name ?? "no data")
                    .padding(.leading,10)
                    .multilineTextAlignment(.leading)
                Spacer()
                
                ZStack
                    Text("Show all \(cohortItem.profilesNum!)")
                .padding(.trailing,10)
            
            
            ScrollView(.horizontal, showsIndicators: false) 
                HStack
                    ForEach(retriever.retrievedCohorts)  user in //<-- Here
                        UserCard(user: UserModel(name: user.firstName ?? "", position: user.lastName ?? "", image: "moon.stars.fill"))
                    
                .padding(10)
            
        .onAppear  //<-- Here
            self.retriever.retrieve(cohortItem: cohortItem)
        
    

请注意,我没有您的所有代码,因此可能存在一些小问题,例如,如果您的模型不符合 Hashable,您可能需要将 id: 传递给 ForEach

此外,您可以将 retrieve 重构为 one 函数而无需回调 - 您可以看到我已经包装了 usersInCohort。但是,如果您有兴趣,那只是您可以做的繁琐工作。

最后,我不太确定CohortProcessor 在您的代码中做了什么——它被初始化然后从未使用过。也许该代码应该与我的想法结合起来。

【讨论】:

感谢您的回答,但在添加代码并更正模型导致的所有数据后,我收到了带有 foreach 代码块的此类错误Failed to produce diagnostic for expression; please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the project :( 就像我说的,我没有你所有的代码。我怀疑您需要为ForEach 添加id。你能用你的ProfilesInCohortModel.ProfilesArrayItemUserModel 代码更新你的问题,以便我看看它们是Hashable 还是Identifiable 等一下,我加他们 我刚刚添加了它们 尝试用ForEach(retriever.retrievedCohorts.map $0.profile , id: \.id) user in替换我的ForEach

以上是关于如何在 SwiftUI 的列表项中调用完成处理程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 中等待音频完成?

如何在 SwiftUI 中使用 .refreshable 调用 API 并刷新列表

SwiftUI:如何让列表视图中的图像与列表项的操作不同?

只有在 SwiftUI 和 Firebase 中完成循环中的异步调用时,如何才能返回函数?

如何在流口水规则引擎中处理列表项中对象的先前值?

如何从 CLGeocoder geocodeAddressString() 中的完成处理程序更新列表中的地址