[SwiftUI] CryptoApp: PortfolioView

Junyoung Park·2022년 11월 3일
0

SwiftUI

목록 보기
83/136
post-thumbnail
post-custom-banner

Create a view to manager current user's portfolio | SwiftUI Crypto App #14

CryptoApp: PortfolioView

구현 목표

  • 포트폴리오 내용을 보여줄 뷰 UI 구현

구현 태스크

  • 홈 뷰 모델을 사용하는 포트폴리오 뷰 모달 구현
  • 디스미스 버튼
  • 저장 버튼
  • 코인 이름 텍스트 필드를 통한 검색 기능 구현
  • 선택에 대한 UI 인터렉션
  • 소유 개수 입력을 통한 총액 계산 기능 구현
  • 저장 버튼 UI 인터렉션

핵심 코드

ScrollView {
                VStack(alignment: .leading, spacing: 0) {
                    SearchBarView(searchText: $viewModel.searchText)
                    coinLogoList
                    if selectedCoin != nil {
                        portfolioInputSection
                    }
                }
            }
  • 스크롤 뷰를 통해 서치 바의 텍스트로 검색한 코인 셀 리스트를 UI 패치
private var coinLogoList: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            LazyHStack(spacing: 10) {
                ForEach(viewModel.allCoins) { coin in
                    CoinLogoView(coin: coin)
                        .frame(width: 75, height: 80)
                        .padding(4)
                        .onTapGesture {
                            withAnimation(.easeIn) {
                                if selectedCoin?.id == coin.id {
                                    selectedCoin = nil
                                } else {
                                    selectedCoin = coin
                                }
                            }
                        }
                        .background(
                            RoundedRectangle(cornerRadius: 10)
                                .stroke(selectedCoin?.id == coin.id ? Color.theme.green : Color.clear, lineWidth: 1)
                        )
                }
            }
            .frame(height: 120)
            .padding(.leading)
        }
    }
  • 각 셀의 탭 제스처를 통해 현재 selectedCoin 변수 선택 가능
  • 동일한 셀 선택 시 nil로, 특정 셀 선택 시 해당 코인을 선택 가능
private var portfolioInputSection: some View {
        VStack(spacing: 20) {
            HStack {
                Text("Current price of \(selectedCoin?.symbol.uppercased() ?? ""):")
                Spacer()
                Text(selectedCoin?.currentPrice.asCurrencyWith6Decimals() ?? "")
            }
            Divider()
            HStack {
                Text("Amount holding:")
                Spacer()
                TextField("Ex: 1.4", text: $quantityText)
                    .multilineTextAlignment(.trailing)
                    .keyboardType(.decimalPad)
            }
            Divider()
            HStack {
                Text("Current value:")
                Spacer()
                Text(getCurrentValue().asCurrencyWith2Decimals())
            }
        }
        .animation(.none)
        .padding()
        .font(.headline)
    }
  • 현재 선택한 코인이 있다면 UI를 통해 보여줄 뷰
  • 코인 개수를 담기 위한 텍스트 필드 구현
private func saveButtonPressed() {
        guard let coin = selectedCoin else { return }
        // save to portfolio
        
        // show checkmark
        withAnimation(.easeIn) {
            showCheckmark = true
            removeSelectedCoin()
        }
        
        // hide keyboard
        UIApplication.shared.endEditing()
        
        // hide checkmark
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            withAnimation(.easeIn) {
                showCheckmark = false
            }
        }
    }
  • 코인 개수 입력 뒤 저장 버튼을 누른 뒤 실행되는 부분
  • 체크마크를 1초 동안 띄운 뒤 현재 선택된 코인 정보 및 입력된 텍스트 초기화
  • 키보드 자동으로 디스미스 이후 체크마크 사라지는 효과

소스 코드

import SwiftUI

@main
struct CryptoAppBootCampApp: App {
    @StateObject private var viewModel = HomeViewModel()
    init() {
        UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor(Color.theme.accent)]
        UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor(Color.theme.accent)]
    }
    var body: some Scene {
        WindowGroup {
            NavigationView {
                HomeView()
                    .toolbar(.hidden)
            }
            .environmentObject(viewModel)
        }
    }
}
  • 네비게이션 상황의 타이틀 색깔 변경을 위한 UIKit 메소드 오버라이드
import SwiftUI

struct CoinLogoView: View {
    let coin: CoinModel
    var body: some View {
        VStack {
            CoinImageView(coin: coin)
                .frame(width: 50, height: 50)
            Text(coin.symbol.uppercased())
                .font(.headline)
                .foregroundColor(Color.theme.accent)
                .lineLimit(1)
                .minimumScaleFactor(0.5)
            Text(coin.name)
                .font(.caption)
                .foregroundColor(Color.theme.secondaryText)
                .lineLimit(2)
                .minimumScaleFactor(0.5)
                .multilineTextAlignment(.center)
        }
    }
}
  • 코인의 이미지 및 상세 정보를 표현하기 위한 셀 뷰
Color.theme.background
                .ignoresSafeArea()
                .sheet(isPresented: $showPortfolioView) {
                    PortfolioView()
                        .environmentObject(viewModel)
                }
  • 홈 뷰에서 포트폴리오 뷰를 띄우기 위한 토글 버튼
  • 모달로 띄우기
import SwiftUI

struct PortfolioView: View {
    @Environment(\.dismiss) var dismiss
    @EnvironmentObject private var viewModel: HomeViewModel
    @State private var selectedCoin: CoinModel? = nil
    @State private var quantityText:String = ""
    @State private var showCheckmark: Bool = false
    var body: some View {
        NavigationView {
            ScrollView {
                VStack(alignment: .leading, spacing: 0) {
                    SearchBarView(searchText: $viewModel.searchText)
                    coinLogoList
                    if selectedCoin != nil {
                        portfolioInputSection
                    }
                }
            }
            .navigationTitle("Edit Portfolio")
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    xmarkButton
                }
                ToolbarItem(placement: .navigationBarTrailing) {
                    trailingNavBarButtons
                }
            }
        }
    }
}
  • 전체적인 포트폴리오 뷰
  • 코인 셀 리스트 → 셀 선택 시 인풋 섹션 뷰 → 섹션 뷰 내 인터렉션
extension PortfolioView {
    private var xmarkButton: some View {
        Button {
            dismiss()
        } label: {
            Image(systemName: "xmark")
                .font(.headline)
        }
    }
    
    private var coinLogoList: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            LazyHStack(spacing: 10) {
                ForEach(viewModel.allCoins) { coin in
                    CoinLogoView(coin: coin)
                        .frame(width: 75, height: 80)
                        .padding(4)
                        .onTapGesture {
                            withAnimation(.easeIn) {
                                if selectedCoin?.id == coin.id {
                                    selectedCoin = nil
                                } else {
                                    selectedCoin = coin
                                }
                            }
                        }
                        .background(
                            RoundedRectangle(cornerRadius: 10)
                                .stroke(selectedCoin?.id == coin.id ? Color.theme.green : Color.clear, lineWidth: 1)
                        )
                }
            }
            .frame(height: 120)
            .padding(.leading)
        }
    }
    
    private func getCurrentValue() -> Double {
        if
            let selectedCoin = selectedCoin,
            let quantity = Double(quantityText) {
            return quantity * selectedCoin.currentPrice
        }
        return 0
    }
    
    private var portfolioInputSection: some View {
        VStack(spacing: 20) {
            HStack {
                Text("Current price of \(selectedCoin?.symbol.uppercased() ?? ""):")
                Spacer()
                Text(selectedCoin?.currentPrice.asCurrencyWith6Decimals() ?? "")
            }
            Divider()
            HStack {
                Text("Amount holding:")
                Spacer()
                TextField("Ex: 1.4", text: $quantityText)
                    .multilineTextAlignment(.trailing)
                    .keyboardType(.decimalPad)
            }
            Divider()
            HStack {
                Text("Current value:")
                Spacer()
                Text(getCurrentValue().asCurrencyWith2Decimals())
            }
        }
        .animation(.none)
        .padding()
        .font(.headline)
    }
    
    private var trailingNavBarButtons: some View {
        HStack(spacing: 10) {
            Image(systemName: "checkmark")
                .opacity(showCheckmark ? 1.0 : 0.0)
            Button {
                saveButtonPressed()
            } label: {
                Text("Save".uppercased())
            }
            .opacity(selectedCoin != nil && selectedCoin?.currentHoldings != Double(quantityText) ? 1.0 : 0.0)
        }
        .font(.headline)
    }
    
    private func saveButtonPressed() {
        guard let coin = selectedCoin else { return }
        // save to portfolio
        
        // show checkmark
        withAnimation(.easeIn) {
            showCheckmark = true
            removeSelectedCoin()
        }
        
        // hide keyboard
        UIApplication.shared.endEditing()
        
        // hide checkmark
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            withAnimation(.easeIn) {
                showCheckmark = false
            }
        }
    }
    
    private func removeSelectedCoin() {
        selectedCoin = nil
        viewModel.searchText = ""
    }
}
  • 인풋 섹션 내 코인 개수 입력 → 타입 캐스팅 가능한 경우에만 반응
  • 저장 버튼 → 1초 동안의 체크 마크를 애니메이션으로 인터렉션 후 사라짐

구현 화면

SwiftUI의 애니메이션 효과는 매우 간단하고 손쉽다는 것을 다시 한 번 느끼고 있다!

profile
JUST DO IT
post-custom-banner

0개의 댓글