Crypto App을 만들어보며 1

Dophi·2022년 12월 31일
0

코드 따라하기

목록 보기
1/5

소개글

Swiftful Thinking 이라는 유튜브 채널을 보며 Crypto 앱을 만들어봤습니다.
만들면서 감탄이 나왔거나 알아두면 좋겠다는 생각이 든 요소들을 공유하고자 합니다.
해당 영상은 아래 링크를 통해 볼 수 있으며, SwiftUI에 관심이 있다면 한번쯤 보시는 것을 추천드립니다!

영상 링크
코드

컴포넌트 분리

영상의 코딩 스타일을 보면서 컴포넌트 분리를 정말 깔끔하게 잘한다는 생각이 들었습니다.
크게 네가지 관점에서 감탄했는데,
첫번째는 MVVM 패턴을 잘 따랐다는 것,
두번째는 View 내에서 body에 들어가는 코드들을 최소화했다는 점,
세번째는 색상을 분리해서 사용이 편하도록 했다는 점,
네번째는 PreviewProvider Extension을 사용했다는 점입니다.

MVVM 패턴

MVVM은 데이터를 다루는 Model, 보여지는 화면을 다루는 View, Model과 통신하면서 가공된 데이터를 저장하고 Binding으로 View를 업데이트해주는 ViewModel로 이루어집니다.


// CoinModel.swift - 코인 데이터에 대한 Model
struct CoinModel: Identifiable, Codable {
    let id, symbol, name: String
}

// HomeViewModel.swift - HomeView에게 변화를 알리는 ViewModel
@Published var allCoins: [CoinModel] = []
$searchText
    ...
    .sink { [weak self] returnedCoins in
                self?.allCoins = returnedCoins }

// HomeView.swift - 홈화면을 보여주는 View
@StateObject var vm: HomeViewModel
ForEach(vm.allCoins) { coin in
    CoinRowView(coin: coin, showHoldingsColumn: false)
    ...
}

홈화면과 관련된 Model, View, ViewModel 코드를 일부만 가져왔습니다. 동작을 설명하자면,

  1. ViewModel에서 searchText값에 따라 allCoins 변수가 바뀌면
  2. Published 값이기 때문에 자동으로 View에게 알림이 가서
  3. View, 즉 화면이 업데이트되는 방식입니다.

이런 방식으로 Published와 Combine을 사용해서 ViewModel의 값이 변경되면 View도 자동으로 업데이트되도록 깔끔하게 구현을 해주셨습니다.

body 코드 최소화

이 화면을 나타내는 view의 코드에서 body를 보면 아래와 같습니다.

struct DetailView: View {
    var body: some View {
        ScrollView {
            VStack {
                ChartView(coin: vm.coin)
                    .padding(.vertical)
                VStack(spacing: 20) {
                    overviewTitle
                    Divider()
                    descriptionSection
                    overviewGrid
                    additionalTitle
                    Divider()
                    additionalGrid
                    websiteSection
                }
                .padding()
            }
        }
        .background(
            Color.theme.background
                .ignoresSafeArea()
        )
        .navigationTitle(vm.coin.name)
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                navigationBarTrailingItems
            }
        }
    }
}

화면은 상당히 복잡해보이는데 body는 매우 짧은 편입니다.
컴포넌트 분리를 정말 깔끔하게 잘하셨기 때문인데요, 예를 들어 overviewTitle 코드로 봐보면 아래와 같습니다.

extension DetailView {
    private var overviewTitle: some View {
        Text("Overview")
            .font(.title)
            .bold()
            .foregroundColor(.theme.accent)
            .frame(maxWidth: .infinity, alignment: .leading)
    }
}

이런식으로 아래쪽에 따로 extension을 만들고, 안쪽에 변수를 만들어서 빼주는 방식으로 복잡한 뷰들을 분리하셨습니다.

색상 분리

우선 Asset에 원하는 색상을 정의해놓습니다.

이때 라이트모드와 다크모드를 구분지어서 색상을 정의할 수 있습니다.

그리고 Color Extension에서 Asset에서 정의했던 색상을 가져옵니다.

extension Color {
    static let theme = ColorTheme()
}

struct ColorTheme {
    let accent = Color("AccentColor")
    let background = Color("BackgroundColor")
    let green = Color("GreenColor")
    let red = Color("RedColor")
    let secondaryText = Color("SecondaryTextColor")
}

이렇게 하면 원하는 색상을 쓰고 싶을 때 Color.theme.background 와 같은 코드만 써주면 됩니다!

여기서 더 나아가서 편리하다고 느꼈던 점은 색상 변경입니다.
만약 앱 전체의 색상들을 변경하고 싶다고 한다면 아래와 같이 extension의 코드만 살짝 고쳐주면 됩니다.

extension Color {
	// ColorTheme -> ColorTheme2로 변경
    static let theme = ColorTheme2()
}

struct ColorTheme {
    let accent = Color("AccentColor")
    let background = Color("BackgroundColor")
    let green = Color("GreenColor")
    let red = Color("RedColor")
    let secondaryText = Color("SecondaryTextColor")
}

// 새로운 색상 추가
struct ColorTheme2 {
    let accent = Color("AccentColor2")
    let background = Color("BackgroundColor2")
    let green = Color("GreenColor2")
    let red = Color("RedColor2")
    let secondaryText = Color("SecondaryTextColor2")
}

PreviewProvider Extension

SwiftUI로 개발을 하다보면 Preview에서 에러가 나는 경우가 많습니다.
여러 원인이 있겠지만, 클래스에 필요한 변수를 안넘겨줬을 때도 발생합니다.

struct CoinImageView_Previews: PreviewProvider {
    static var previews: some View {
        CoinImageView(coin: )
    }
}

예를 들어, 위와 같은 코드에서는 coin에 해당하는 변수를 넘겨줘야하는데, 아래와 같이 해결할 수 있습니다.

struct CoinImageView_Previews: PreviewProvider {
	let coin = CoinModel(
        id: "bitcoin",
        symbol: "btc",
        name: "Bitcoin",
        image: "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579",
        ...
        )

    static var previews: some View {
        CoinImageView(coin: coin)
    }
}

하지만 이렇게 하면 코드가 지저분해지기도 하고, 다른 뷰에서도 만약 coin이 필요하다면 또다시 정의를 해야합니다.

그래서 영상에서는 extension을 써서 편리하게 빼냈습니다.

// extension 정의
extension PreviewProvider {
    static var dev: DeveloperPreview {
        return DeveloperPreview.instance
    }
}

// 싱글톤 형식으로 클래스 정의
class DeveloperPreview {
    static let instance = DeveloperPreview()
    
    // 미리 Preview에서 쓰일 상수를 정의해둠
    let coin = CoinModel(
       id: "bitcoin",
       symbol: "btc",
       name: "Bitcoin",
       image: "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579",
       ...
       )
}

위에처럼 코드를 미리 정의해두면 아래와 같이 preview에서 편리하게 사용 가능합니다.

struct CoinImageView_Previews: PreviewProvider {
    static var previews: some View {
        CoinImageView(coin: dev.coin)
    }
}

컴포넌트 분리 외에도 배울만하고 생각됐던 요소는 애니메이션 효과입니다!
쓰다보니 글이 길어져서 이 내용은 다음 포스팅에 올리겠습니다.

profile
개발을 하며 경험한 것들을 이것저것 작성해보고 있습니다!

0개의 댓글