返回

SwiftUI ForEach 联动 DetailView: 数据同步难题与解决方案

IOS

SwiftUI ForEach 中的数据与 DetailView 联动

当在 ForEach 循环中结合异步加载图片(例如 AsyncImage),并期望点击图片时跳转到 DetailView 展示对应的详细信息时,可能会遇到一些问题。尤其是数据顺序和点击事件对应的问题。下面将分析可能的原因并提供解决方案。

问题分析: NavigationLink 和数据匹配

常见的问题是,NavigationLink 的激活状态(isActive)或其 label 中的点击事件与 ForEach 循环中的数据项未能准确对应。在代码中,直接使用一个绑定的 @State 变量 isActive 作为 NavigationLink 的激活标志,导致所有链接同步激活或取消激活。此外,在 DetailImageHistory 中通过一个字符串参数(name),这可能不能确保每一个detailView与数据进行精确对应。这会导致显示的数据不正确。

解决方案:使用唯一标识和数据索引

以下是一些经过实践验证的方法,以确保点击 ForEach 循环中的每个项目都能正确导航到相应的 DetailView,同时保持正确的图片顺序和对应关系。

方案一:使用索引或者唯一ID

核心在于通过索引或每个数据项的唯一标识(假设存在)来创建对应的 NavigationLink。我们直接将数据传给 DetailView ,而不是仅仅使用 url string 。这将解决使用 @State isActive 导致所有 NavigationLink 联动的问题。

struct HistoryView: View {
    @EnvironmentObject var historyManager : HistoryManager
    
    var body: some View {
        NavigationView{
            let jsonDataList = historyManager.jsondata
            ScrollView{
                VStack{
                    if let response = jsonDataList{
                        ForEach(Array(response.dataJson.result.check_in.reversed().enumerated()), id: \.offset) { index, item in
                         
                             ZStack(alignment: .leading){
                                  if (historyManager.warna == true){
                                        Image("RoundedRectangle-red")
                                            .resizable()
                                            .frame(width: 370, height: 200)
                                    }else{
                                        Image("RoundedRectangle-green")
                                            .resizable()
                                            .frame(width: 370, height: 200)
                                    }
                                    
                                NavigationLink {
                                   DetailImageHistory(item: item)
                                } label: {
                                      let url = URL(string: item.links)
                                      AsyncImage(url: url){ image in
                                           image
                                               .resizable()
                                               .scaledToFill()
                                               .frame(width: 45, height: 45)
                                               .clipShape(Circle())
                                            
                                       } placeholder: {
                                            ProgressView()
                                                .progressViewStyle(.circular)
                                       }
                                    .frame(width: 45, height: 45)
                                    .clipShape(Circle())

                                 }
                                .offset(x: 300, y: -70)
                                VStack(alignment:.leading, spacing: 2){
                                  
                                      Text("Presensi Datang").bold().font(.system(size: 15))
                                        Text("\(item.createdAts)").font(.system(size: 15))
                                        if(historyManager.keteranganKehadiran == true){
                                          Text("Tepat Waktu").fontWeight(.bold).foregroundColor(.white)
                                          }else{
                                         Text("Terlambat").fontWeight(.bold).foregroundColor(.white).font(.system(size: 15))
                                          }
                                          Text("\(item.places)").font(.system(size: 15))
                                           Text("\(item.locations)").multilineTextAlignment(.leading).font(.system(size: 15))
                                           Text("Keterangan: ").bold().font(.system(size: 15))
                                           Text("\(item.tujuans)").bold().font(.system(size: 15))
                                          
                                   }.padding(.horizontal)
                              
                                }
                            }
                        }
                    }
                 
             }
           }
       }
   }

对应的DetailImageHistory视图修改如下:

import SwiftUI

struct DetailImageHistory: View {
    var item : ResultData.CheckIn
  
    var body: some View {
     let url = URL(string: item.links)
        AsyncImage(url: url){ image in
            image
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 300, height: 300)
            
        } placeholder: {
            Color.gray
            
        }
        .frame(width: 300, height: 300)
      
    }
}

解释:

  1. 使用 enumerated():ForEach 循环中使用了 Array(response.dataJson.result.check_in.reversed().enumerated()),它为每个元素生成了带索引的元组,可以通过 .offset 访问索引,并通过 .element访问数据项本身。
  2. 绑定唯一的 id.offset 用于 id ,保证每一个 NavigationLink 都有唯一标志符。
  3. 直接传递 item: 点击 NavigationLink 的时候, 将当前的数据 item 直接传递到 DetailView, 这样确保显示的数据是对应当前点击项的数据。

方案二: 使用自定义ID

如果数据源包含唯一的 ID ,可以直接使用它作为 ForEachNavigationLink 的标识。 如下代码假定您的 items 中存在一个名为 id 的唯一属性。

   ForEach(response.dataJson.result.check_in.reversed(), id:\.id) { items in

      NavigationLink {
         DetailImageHistory(item: items)
       } label:{
         // 原有的 `AsyncImage` 视图
       }
   }

DetailImageHistory 修改与方案一相同。

安全提示

  1. 确保唯一ID的唯一性: 使用的数据标识必须具有全局唯一性,防止跳转到错误的详情页。
  2. 处理异步加载: 当图片或其他数据异步加载时,应该考虑在视图加载完成后再启用 NavigationLink,避免用户过早点击导致数据加载错误。

通过这些方法,可以有效解决 SwiftUI ForEachNavigationLink 与数据同步的问题,使得每个条目都能准确跳转到相应的 DetailView,并呈现正确的详情内容。请务必根据自身数据结构的特点选择合适的解决方案。