0

I followed a SwiftUI tutorial and have completed it and results working well, but as a challenge I wanted to try and convert the same code into MVVM structure but the final outputs are just not the same, and it's got to do something with the complication of getting both the index value and index itself within my forEach blocks. This is the non-MVVM format code:

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)
  }}

The above works fine as outlined in the tutorial I was following, but this is what I managed to put together as MVVM structure

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 = []
    }
  }
}

I know there is a lot of code, but majority of it is just designing elements and SwiftUI modifiers. The exact issues include the languages not moving directly to the completed section after getting tapped on, the animations are completely off compared to the non-MVVM format, and there are odd gaps that show up in between the languages.

Anthney
  • 735
  • 1
  • 7
  • 12

2 Answers2

0

Using .indices in ForEach loops may break animations. Try .enumerated instead. A good explanation can be found here: How do you use .enumerated() with ForEach in SwiftUI?


Note: You don't need two ObservableObjects. You can have one but with two @Published properties:

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] = []
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • unfortunately that didn't do the job. The app manages runs, but all the issues are still there. – Anthney Jul 19 '20 at 23:53
0

Rule of thumb:

indices are invalidated on mutation.

You should not have used indices when append / remove are involved.

And it's not worth trying.

Do it without indices, and see how it goes.

Jim lai
  • 1,224
  • 8
  • 12