앱이 복잡해질수록 로직을 분산해서 관리하면 좋음
struct FruitModel: Identifiable {
let id = UUID()
let name: String
let count: Int
}
struct ViewModelBootcamp: View {
@State var fruitArray: [FruitModel] = []
var body: some View {
NavigationView {
List {
ForEach(fruitArray) { fruit in
HStack {
Text("\(fruit.count)")
.foregroundColor(.red)
Text(fruit.name)
.font(.headline).bold()
}
}
.onDelete(perform: deleteFruit)
}
.listStyle(.grouped)
.navigationTitle("Fruit List")
.onAppear {
getFruits()
}
}
}
func getFruits() {
let fruit1 = FruitModel(name: "Orange", count: 1)
let fruit2 = FruitModel(name: "Banana", count: 2)
let fruit3 = FruitModel(name: "Watermelon", count: 88)
fruitArray.append(fruit1)
fruitArray.append(fruit2)
fruitArray.append(fruit3)
}
func deleteFruit(index: IndexSet) {
fruitArray.remove(atOffsets: index)
}
}
요렇게 작성했을 때 이 로직들을 분리해보자
먼저 getFruits와 deleteFruit은 모델(데이터)과 관련된 로직임
FruitViewModel이라는 클래스를 새로 만들어줌
근데 @State가 아니라 @Published를 붙여줬다
클래스에서는 @Published를 써야함
(hey something's changed. you might have to update something)
용도는 @State랑 비슷함!
기존에 있던 @State는 코멘트아웃해주고
fruitViewModel이라는 새로운 변수를 만들어줌
기존에 있던 로직들을 ViewModel로 옮기자
그럼 이렇게 깔끔하게 정리가 가능해졌다
그런데 여기서 문제가 발생함
view가 제대로 안나오고 있음
어떤 게 문제일까?
viewModel에 프로퍼티 래퍼인 @ObservedObject를 붙여서 데이터의 변경사항들이 있을 때 관찰되게 해줘야함
관찰이될라면 관찰이 가능해야겠죠
class viewModel에도 프로토콜 ObservableObject를 채택해줘야한다
그런데!!
@ObservedObject 같은 경우 포함된 뷰가 reload되게 되면 같이 refresh됨!!
이럴 때 해당 뷰모델은 유지되야할 필요가 있는데
@StateObject를 붙여서 persist되게 해준다
앱에서 처음 만들어지는 object다 -> @StateObject
넘겨 받아서 사용되는 object다 -> @ObservedObject
navigationBarItems 옛날 표현이긴 한데 그냥 사용했다
navigationLink를 넣고 싶었는데 link같은 경우 navigationView 안에 있어야해서
지금처럼 navigationItem으로 다른 뷰를 띄우고 싶을 때 어떻게 해줄 지 달리 방법이 안보임
HIG에서 저렇게 사용하지는 말라고 얘기하는 거 같기도 하고
이대로 뷰를 왔다갔다 할 경우에
.onAppear가 계속 실행되서 array가 계속 추가됨
ViewModel에서 init될 때 array가 추가되는 걸로 바꾸자
이제 새로 만든 뷰에서 전의 패런트 뷰의 데이터를 가지고 오고 싶다
어떻게할까?
@ObservedObject로 fruitViewModel을 가져와주면 됨
그리고 패런트뷰에서 fruitViewModel 그대로 넘겨주면 사용 가능!
ViewModel을 새로 만들어줬다
그리고 data를 가져오는 메소드를 작성했는데
append 메소드 중에 여러가지의 element를 가져올 수 있는 방법이 있다!
contentsOf 프로퍼티가 붙은 메소드를 사용해서 이렇게 시퀀스를 넘겨줄 수도 있음
디테일뷰를 만들어줬다
그리고 NavigationLink의 목적지가 될 뷰를 디테일뷰로 설정하고,
item들을 넘겨주면?!
선택된 리스트의 item이 다음뷰에서 전달되게 된다
뷰 하나 더 만들어보자
오케이!
디테일뷰에서 NavigationLink로 FinalView가 뜨게 해줬다.
위의 파이널뷰에서 처음 메인뷰에 있던 데이터를 가져오고 싶을 때 어떻게 해야할까?
뷰에 계속 중첩해서 binding을 하는 건 상당히 비효율적이지 않나?!
데이터를 쓰지 않는 뷰도 있으니!!
요렇게 계속 중첩해야되잖음
이럴 때 사용하는 게 environmentObject
메인뷰에서 상단에 .environmentObject를 붙이고 환경객체로 사용할 ViewModel을 넣어줌
지금 상단이라고 표현했는데 NavigationView 안에 포함되게 되는 모든 뷰들은
viewModel에 접근이 가능해진다
이제 두번째 뷰에선 @ObservedObject가 필요 없어졌다 (지워줌)
세번째 뷰에선 @ObservedObject로 선언했던 프로퍼티 래퍼를 바꿔주면 됨
@EnvironmentObject 등장!
그럼 끝임.
짱 편하쥬
.environmentObject에 담겼던 데이터를
하위뷰에서 @EnvironmentObject로 선언하는 것만으로도 그 데이터를 불러올 수 있어졌다!