앞서 보았던 라디오 버튼 구현의 실질적인 동작을 이용하여 현재 보이는 차트를 바꿔보자
라디오 버튼을 눌러 상위 뷰의 차트를 변경시킬 때 하위 뷰에 정보가 반영되도록 하고 싶다.
3일정도 계속 막혔던 것 같다.
//
// 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의 규칙을 지키지 않거나 문제가 있는 코드일 경우 꼭 지적 부탁드립니다.