TIL

SwiftUI _ ViewModel (@Published, @ObservedObject, @StateObject)

h_luz 2024. 7. 22. 18:43

 

ViewModel을 사용하는 이유

 : 간단한 앱은 몰라도 복잡한 앱에서 데이터베이스와 관련해서 무언가를 변경하고 싶을 때

   데이터의 어떤 부분이 뷰의 어떤 부분인지 파악하고, 뷰를 살피는 시간을 절약할 수 있음

   -> 바로 ViewModel에서 데이터와 관련된 비즈니스 로직을 확인 및 변경 가능

 

@Published

: 클래스 내에 있다는 점을 제외하면 @State와 동일 (클래스 자체에 ViewModel을 알림)

 

@ObservedObject

: ViewModel에 @Published로 클래스에 변화를 알리는 것처럼 뷰에도 뷰모델로 인한 변화를 알려야 하는 데 그때 사용

* @ObservedObject를 사용하기 위해서는 ViewModel에 ObservableObject로  관찰 가능한 클래스라는 것을 표시해야함

 

 

- 데이터 Model 생성

struct FruitModel: Identifiable {
    let id: String = UUID().uuidString
    let name: String
    let count: Int
}

 

 

- ViewModel

//ObservableObject 는 @ObservedObject를 사용하기 위해서는 관찰 가능한 클래스라는 것을 표시해야함
class FruitViewModel: ObservableObject {
    
    // @Published는 클래스 내에 있다는 점을 제외하면 @State와 동일 (클래스 자체에 ViewModel을 알림)
    @Published var fruitArray: [FruitModel] = []
    
    func getFruits() {
        let fruit1 = FruitModel(name: "Orange", count: 6)
        let fruit2 = FruitModel(name: "Peach", count: 97)
        let fruit3 = FruitModel(name: "Watermelon", count: 82)
        
        fruitArray.append(fruit1)
        fruitArray.append(fruit2)
        fruitArray.append(fruit3)
    }
    
    func deleteFruit(index: IndexSet) {
        fruitArray.remove(atOffsets: index)
    }
}

현재 ViewModel에는 데이터를 삭제하는 함수와 데이터를 추가하는 함수가 담겨있음

 

 

- View

(ViewModel을 사용해서 UI와 관련된 내용만 남아있음)

struct ViewModelPractice: View {
    
    //@State var fruitArray: [FruitModel] = []
    //@ObservedObject는 ViewModel에 @Published로 클래스에 변화를 알리는 거 처럼 뷰에도 뷰모델로 인한 변화를 알려야하는 데 그 때 사용
    @ObservedObject  var fruitViewModel: FruitViewModel = FruitViewModel()
    
    var body: some View {
        NavigationView {
            List {
                ForEach(fruitViewModel.fruitArray) { fruit in
                    HStack {
                        Text("\(fruit.count)")
                            .foregroundStyle(.blue)
                        Text(fruit.name)
                            .font(.headline)
                            .bold()
                    }
                }
                .onDelete(perform: fruitViewModel.deleteFruit)
            }
            .navigationTitle("Fruit List")
            .onAppear {
                fruitViewModel.getFruits()
            }
        }
    }
}

 

 

 

isLoading 기능 추가

 

import SwiftUI

struct FruitModel: Identifiable {
    let id: String = UUID().uuidString
    let name: String
    let count: Int
}


class FruitViewModel: ObservableObject {
    @Published var fruitArray: [FruitModel] = []
    @Published var isLoading: Bool = false
    
    func getFruits() {
        let fruit1 = FruitModel(name: "Orange", count: 6)
        let fruit2 = FruitModel(name: "Peach", count: 97)
        let fruit3 = FruitModel(name: "Watermelon", count: 82)
         
        isLoading = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
            self.fruitArray.append(fruit1)
            self.fruitArray.append(fruit2)
            self.fruitArray.append(fruit3)
            self.isLoading = false
        }
    }
    
    func deleteFruit(index: IndexSet) {
        fruitArray.remove(atOffsets: index)
    }
}


struct ViewModelPractice: View {
    @ObservedObject  var fruitViewModel: FruitViewModel = FruitViewModel()
    
    var body: some View {
        NavigationView {
            List {
                if fruitViewModel.isLoading {
                    ProgressView()
                } else {
                    ForEach(fruitViewModel.fruitArray) { fruit in
                        HStack {
                            Text("\(fruit.count)")
                                .foregroundStyle(.blue)
                            Text(fruit.name)
                                .font(.headline)
                                .bold()
                        }
                    }
                    .onDelete(perform: fruitViewModel.deleteFruit)
                }
            }
            .navigationTitle("Fruit List")
            .onAppear {
                fruitViewModel.getFruits()
            }
        }
    }
}

 

@ObservedObject의 단점

: 이 뷰가 다시 생성되어 새로고쳐지면 앱에서 다른 일이 진행되고 있을 수 있음

이러한 문제를 해결하기 위해 @StateObject를 사용하면 됨

 

@StateObject

: 뷰가 다시 로드되더라도 데이터가 지속되길 원하는 상황에서 사용

 

 

 

 

'TIL' 카테고리의 다른 글

64일차 TIL _ LargeTitle / 페이지 뷰 컨트롤러로 화면 구성  (2) 2024.05.29
61일차 TIL _ 팀프로젝트 회고  (4) 2024.05.25
59일차 TIL  (5) 2024.05.22
58일차 TIL  (0) 2024.05.21
57일차 TIL  (0) 2024.05.20