在 SwiftUI 中的 forEach 中获取索引和值

Posted

技术标签:

【中文标题】在 SwiftUI 中的 forEach 中获取索引和值【英文标题】:Getting both index and value within forEach in SwiftUI 【发布时间】:2020-07-19 21:41:04 【问题描述】:

我按照 SwiftUI 教程完成了它并且结果运行良好,但作为一个挑战,我想尝试将相同的代码转换为 MVVM 结构,但最终输出不一样,它必须与在我的 forEach 块中获取索引值和索引本身的复杂性。 这是非MVVM格式代码:

struct Language:Identifiable
  var id:Int
  var lang:String

struct ContentView_1:View
  @State private var languages : [Language] =
    [ Language(id: 1, lang: "English"), 
      Language(id: 2, lang:"Spanish"),
      Language(id: 3, lang:"German"),
      Language(id: 4, lang:"Japanese"),
      Language(id: 5, lang:"Chinese"),
      Language(id: 6, lang:"Korean"),
      Language(id: 7, lang:"Others")
    ]
  @State private var completed : [Language] = []
  @Namespace var nameSpace

  var body: some View
    VStack
      VStack
        if !self.languages.isEmpty
          ForEach(self.languages) language in
            Text(language.lang)
              .frame(width:100,height:100)
              .background(Color.yellow)
              .matchedGeometryEffect(id: language.id, in:self.nameSpace)
              .onTapGesture
                self.completed.append(language)
                self.languages.removeAll  (lang) -> Bool in
                  if lang.id == language.id
                    return true
                  else  return false 
                
              
          
        
      
      HStack
        Text("Completed Languages")
          .font(.title)
          .fontWeight(.bold)
      
      HStack
        ForEach(self.completed) language in
          Text(language.lang)
            .frame(width:100,height:100)
            .background(Color.green)
            .matchedGeometryEffect(id: language.id, in:self.nameSpace)
            .onTapGesture
              self.languages.append(language)
              self.completed.removeAll  (lang) -> Bool in
                if lang.id == language.id
                  return true
                else  return false 
              
            
        
      
    
    .animation(.easeOut)
  

如我所关注的教程中所述,上述工作正常,但这是我设法将其组合为 MVVM 结构

struct LanguageModel:Identifiable
  var id:Int
  var lang:String

class LanguageViewModel:ObservableObject
  @Published var languageModel:[ LanguageModel ] =
    [LanguageModel(id: 1, lang: "English"),
     LanguageModel(id: 2, lang:"Spanish"),
     LanguageModel(id: 3, lang:"German"),
     LanguageModel(id: 4, lang:"Japanese"),
     LanguageModel(id: 5, lang:"Chinese"),
     LanguageModel(id: 6, lang:"Korean"),
     LanguageModel(id: 7, lang:"Others")
    ]


struct ContentView_2:View
  @ObservedObject var langData = LanguageViewModel()
  @ObservedObject var langDataCompleted = LanguageViewModel()
  @Namespace var nameSpace
  var body: some View
    VStack
      VStack
        if !self.langData.languageModel.isEmpty
          ForEach(langData.languageModel.indices, id:\.self) language in
            Text(langData.languageModel[language].lang)
              .frame(width:100,height:100)
              .background(Color.yellow)
              .matchedGeometryEffect(id: language, in: self.nameSpace)
              .onTapGesture
                self.langDataCompleted.languageModel.append(langData.languageModel[language])
                self.langData.languageModel.removeAll  (lang) -> Bool in
                  if lang.id == self.langData.languageModel[language].id
                    return true
                  else  return false 
                
              
          
        
        HStack
          Text("Completed Languages")
            .font(.title)
            .fontWeight(.bold)
        
        HStack
          ForEach(self.langDataCompleted.languageModel.indices ,id:\.self) language2 in
            Text(self.langDataCompleted.languageModel[language2].lang)
              .frame(width:100,height:100)
              .background(Color.green)
              .matchedGeometryEffect(id: self.langDataCompleted.languageModel[language2].id, in: self.nameSpace)
              .onTapGesture
                self.langData.languageModel.append(langDataCompleted.languageModel[language2])
                self.langDataCompleted.languageModel.removeAll  (lang) -> Bool in
                  if lang.id == self.langDataCompleted.languageModel[language2].id
                    return true
                  else return false 
                
              
          
        
        .padding(.all)
      
    
    .animation(.easeOut)
    .onAppear
      self.langDataCompleted.languageModel = []
    
  

我知道有很多代码,但大部分只是设计元素和 SwiftUI 修饰符。 确切的问题包括语言在被点击后没有直接移动到已完成的部分,与非 MVVM 格式相比,动画完全关闭,并且语言之间出现了奇怪的差距。

【问题讨论】:

【参考方案1】:

在 ForEach 循环中使用 .indices 可能会破坏动画。请改用.enumerated。可以在这里找到一个很好的解释:How do you use .enumerated() with ForEach in SwiftUI?


注意:您不需要 两个 ObservableObjects。您可以拥有一个但有两个 @Published 属性:

class LanguageViewModel: ObservableObject
  @Published var languages: [LanguageModel] = [
     LanguageModel(id: 1, lang: "English"),
     LanguageModel(id: 2, lang:"Spanish"),
     LanguageModel(id: 3, lang:"German"),
     LanguageModel(id: 4, lang:"Japanese"),
     LanguageModel(id: 5, lang:"Chinese"),
     LanguageModel(id: 6, lang:"Korean"),
     LanguageModel(id: 7, lang:"Others")
   ]

   @Published var completedLanguages: [LanguageModel] = []

【讨论】:

不幸的是,它没有完成这项工作。该应用程序管理运行,但所有问题仍然存在。【参考方案2】:

经验法则:

索引在突变时失效。

当涉及到追加/删除时,您不应该使用索引。

而且不值得尝试。

不使用索引,看看效果如何。

【讨论】:

以上是关于在 SwiftUI 中的 forEach 中获取索引和值的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI - 在 ForEach 中获取 TextField 数组

SwiftUI ForEach 获取二维数组中的元素和索引

使用 SwiftUI ForEach 从 NSOrderedSet 获取字符串值

SwiftUI - 如果在 ForEach 循环内

如何在 SwiftUI 的 ForEach 循环中增加状态?

SwiftUI - 如何限制 ForEach 中显示的对象数量