하위 View에 상위 ViewModel 바인딩하기

yongbeom kwak·2022년 7월 27일
0

Billboardoo

목록 보기
2/3

동기

앞서 보았던 라디오 버튼 구현의 실질적인 동작을 이용하여 현재 보이는 차트를 바꿔보자

목표

라디오 버튼을 눌러 상위 뷰의 차트를 변경시킬 때 하위 뷰에 정보가 반영되도록 하고 싶다.

고민한 점

3일정도 계속 막혔던 것 같다.

  • 상위 ViewModel의 @Publish 변수정보를 하위 View로 어떻게 넘겨줄까?
  • 해당 구현이 MVVM 구현을 깨는 것은 아닐까?

상위 뷰

//
//  HomeScreenView.swift
//  Billboardoo
//
//  Created by yongbeomkwak on 2022/07/21.
//

import SwiftUI
import Combine
import Alamofire

struct HomeScreenView: View {
    
    @StateObject var viewModel:HomeScreenViewModel //StateObject로 선언 View에 종속하지않기위해
    
    init(){
        _viewModel = StateObject.init(wrappedValue: HomeScreenViewModel())
     
    }
    
    
   
    
    var body: some View {
        ZStack(alignment: .leading)
        {
            ScrollView(.vertical, showsIndicators: false) {
                
                RadioButtonGroup { (prev:Int, now:Int) in
                    
                    if(prev != now) //이전 값과 다를 경우에만 fetch
                    {
                        // 하위 뷰인 라이도 버튼의 선택된 버튼 index의 따라 다른 차트를 가져옴
                        switch now{
                        case 0:
                            viewModel.fetchTop20(category: .total)
                        case 1:
                            viewModel.fetchTop20(category: .time)
                        case 2:
                            viewModel.fetchTop20(category: .daily)
                        case 3:
                            viewModel.fetchTop20(category: .weekly)
                        case 4:
                            viewModel.fetchTop20(category: .monthly)
                        default :
                            viewModel.fetchTop20(category: .total)
                        }
                        
                    }
                }
                FiveRowSongGridView(nowChart: $viewModel.nowChart) //nowChart 넘겨주기
                
                
            }
        }
    }
}



extension HomeScreenView{
    
    final class HomeScreenViewModel:ObservableObject{
        
        @Published var nowChart:[SimpleViwer] = [SimpleViwer]()
        var cancelBag = Set<AnyCancellable>()
      
        init()
        {
            fetchTop20(category: .total) //초기화  chart는 누적으로 지정
        }
        
        func fetchTop20(category:TopCategory)
        {
            Repository.shared.fetchTop20(category: category)
                .sink { completion in
                    
                    switch completion
                    {
                    case .failure(let err_):
                        
                        print("\(category) is Error")
                        
                    
                    case .finished:
                        print("\(category) is Finished ")
                    }
                } receiveValue: { [weak self] (datas:[SimpleViwer]) in
                    
                    guard let self = self else {return}
                    
                    self.nowChart = datas  //chart 갱신
                    
                }.store(in: &cancelBag)
        }
        
        
        
    }
}

내 생각에 여기서 중요한 점은
FiveRowSongGridView에 chart를 넘겨줄 때

FiveRowSongGridView(nowChart: $viewModel.nowChart) 

바인딩 형태로 넘겨주는 것이다.

하위 뷰

//
//  FiveRowSongGridMoreView.swift
//  Billboardoo
//
//  Created by yongbeomkwak on 2022/07/22.
//

import SwiftUI
import Kingfisher

struct FiveRowSongGridView: View {
    private let rows = [GridItem(.fixed(40), spacing: 20), //fixed 행 크기 ,spacing, 행간의 거리
                        GridItem(.fixed(40), spacing: 20),
                        GridItem(.fixed(40), spacing: 20),
                        GridItem(.fixed(40), spacing: 20),
                        GridItem(.fixed(40), spacing: 20)]
    
    @Binding var nowChart:[SimpleViwer]
    
    
    
    
    var body: some View {
        
        ScrollView(.horizontal, showsIndicators: false) {
            VStack(alignment: .leading) {
                LazyHGrid(rows: rows,spacing: 30){ //GridItem 형태와, 요소간 옆 거리
                    fiveRowSongGridItemViews
                }
            }
        }.padding().onAppear{
            print(nowChart.count)
        }
        
    }
}


private extension FiveRowSongGridView {
    var fiveRowSongGridItemViews: some View {
        
    
        ForEach(nowChart.indices,id: \.self){ index in //여기서 id설정이 굉장히 중요하다. indices로 접근하기 때문에 속성의 id 말고 \.self를 사용함
            ZStack{
                HStack() {
                    AlbumImageView(url: nowChart[index].image)
                    RankView(now: index+1, last: nowChart[index].last)
                    
                    
                    //타이틀 , Artist 영역
                    VStack() {
                        Text("\(nowChart[index].title)").font(.system(size:13)).frame(width:150,alignment: .leading)
                        Text("\(nowChart[index].artist)").font(.system(size:11)).frame(width:150,alignment: .leading)
                    }
                    Button {
                        print(nowChart[index].url)
                    } label: {
                        Image(systemName: "play.fill").foregroundColor(Color("PrimaryColor"))
                    }
                    
                }
            }
        }
    }
}

struct FiveRowSongGridView_Previews: PreviewProvider {
    static var previews: some View {
        
        
        FiveRowSongGridView(nowChart:.constant([SimpleViwer]()))
    }
}

struct AlbumImageView: View {
    
    var url:String
    var body: some View {
        KFImage(URL(string: url)!)
            .cancelOnDisappear(true)
            .placeholder {
                Image("placeHolder")
                    .resizable()
                    .frame(width: 40, height: 40)
                    .transition(.opacity.combined(with: .scale))
            }
            .onSuccess { result in
                
            }
            .onFailure { err in
                print("Error: ,\(err)")
            }
            
            .resizable()
            .frame(width: 40, height: 40) //resize
    }
}
            

        //        AsyncImage(url: URL(string:url), transaction: .init(animation: .spring())) { phase in
        //
        //            switch phase{
        //            case .empty:
        //                Image("placeHolder")
        //                    .resizable()
        //                    .frame(width: 40, height: 40, alignment: .center)
        //                    .transition(.opacity.combined(with: .scale))
        //
        //
        //            case .success(let image):
        //                image
        //                    .resizable()
        //                    .frame(width: 40, height: 40, alignment: .center)
        //
        //            case .failure(let error):
        //                Image("placeHolder")
        //                    .resizable()
        //                    .frame(width: 40, height: 40, alignment: .center)
        //                    .transition(.opacity.combined(with: .scale))
        
        
        



struct RankView: View {
    
    var now_rank:Int
    var color:Color = .accentColor
    var change_rank:String = " "
    
    
    init(now:Int,last:Int)
    {
        self.now_rank = now
        if(now_rank>last) //랭크가 낮아지면
        {
            color = Color("RankDownColor")
            change_rank = "▼ \(self.now_rank - last)"
        }
        else if(now_rank==last)
        {
            color = Color("RankNotChangeColor")
            change_rank = "-"
        }
        else
        {
            color = Color("RankUpColor")
            change_rank = "▲ \(last - self.now_rank)"
        }
    }
    
    var body: some View {
        
        
        
        VStack(alignment: .center){
            
            
            Text("\(now_rank)").font(.system(size:16,weight: .bold))
            
            Text(change_rank).font(.system(size: 13, weight: .bold)).foregroundColor(color)
        }.frame(width: 40)
    }
}

하위 뷰의 중요한점은 @Binding형태로 차트를 받아오고
ForEach에서의 id설정이다.

결과

결론

동작은 성공적으로 이루어졌다.
아직 초보 단계라 각 어노테이션을 알맞게 사용했는지는 확신할 수 없다.

만약 해당 방법이 MVVM의 규칙을 지키지 않거나 문제가 있는 코드일 경우 꼭 지적 부탁드립니다.

profile
IOS개발 공부생

0개의 댓글