Swift/SwiftUI

[SwiftUI] ViewModel 에 Command Pattern 사용해보기

insub4067 2023. 4. 25. 22:29
struct ContentView: View {
    
    @StateObject var viewModel = ContentViewModel()
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVStack {
                    ForEach(Array(viewModel.items.enumerated()), id: \.offset) { offset, item in
                        // MARK: - Cell
                        NavigationLink {
                            DetailItemView(item: item, offset: offset, delegate: viewModel)
                        } label: {
                            Cell(item: item, offset: 0)
                        }
                    }
                }
            }
        }
    }
    
    @ViewBuilder
    func Cell(item: Item, offset: Int) -> some View {
        HStack(spacing: 16) {
            Circle()
                .frame(width: 50, height: 50)
                .foregroundColor(item.color)
            Spacer()
            Text("\(String(item.id.prefix(8)))")
                .font(.body)
            Image(systemName: item.isLiked ? "heart.fill" : "heart")
                .resizable()
                .frame(width: 20, height: 20)
                .foregroundColor(item.isLiked ? .red : .black)
        }
        .padding(.horizontal, 16)
        .frame(maxWidth: .infinity)
        .frame(height: 80)
    }
}
class ContentViewModel: ObservableObject, ItemDelegate {
    
    @Published var items: [Item] = []
    
    init() { getItems() }

    func getItems() {
        for _ in 1...30 {
            DispatchQueue.main.async { [weak self] in
                self?.items.append(Item.init())
            }
        }
    }
    
    func didTapIsLike(offset: Int) {
        DispatchQueue.main.async { [weak self] in
            self?.items[offset].isLiked.toggle()
        }
    }
}
struct DetailItemView: View {
    
    @State var item: Item
    let offset: Int
    
    weak var delegate: ItemDelegate?
    
    init(item: Item, offset: Int, delegate: ItemDelegate? = nil) {
        self._item = State(wrappedValue: item)
        self.offset = offset
        self.delegate = delegate
    }
    
    var body: some View {
        VStack {
            Image(systemName: item.isLiked ? "heart.fill" : "heart")
                .resizable()
                .frame(width: 50, height: 50)
                .foregroundColor(item.isLiked ? .red : .black)
                .onTapGesture { didTapItem() }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(item.color.opacity(0.3))
    }
    
    func didTapItem() {
        item.isLiked.toggle()
        delegate?.didTapIsLike(offset: offset)
    }
}
protocol ItemDelegate: AnyObject {
    func didTapIsLike(offset: Int)
}

struct Item: Identifiable, Hashable {
    let id = UUID().uuidString
    let color = Color.random
    var isLiked = Bool.random()
}

extension Color {
    static var random: Color {
        return Color(
            red: .random(in: 0...1),
            green: .random(in: 0...1),
            blue: .random(in: 0...1)
        )
    }
}