[SwiftUI] ChartStocksClone: Ticker Symbol Sheet UI

Junyoung Park·2022년 12월 30일
0

SwiftUI

목록 보기
136/136
post-thumbnail

Build Swift Charts Stocks App Part 3 - Ticker Symbol Sheet UI - SwiftUI iOS 16 App

ChartStocksClone: Ticker Symbol Sheet UI

구현 목표

  • 차트 선택 시 시트 뷰 구현

구현 태스크

  • 시트 뷰 연결 로직 구현
  • 디테일 뷰 UI 구현
  • API를 통한 특정 심볼 데이터 패치 및 UI 바인딩

핵심 코드

struct StockTickerView: View {
    @StateObject var quoteViewModel: TickerQuoteViewModel
    @State private var selectedRange = ChartRange.oneDay
    @Environment(\.dismiss) private var dismiss
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            headerView.padding(.horizontal)
            Divider()
                .padding(.vertical, 8)
                .padding(.horizontal)
            scrollView
        }
        .padding(.top)
        .background(Color(uiColor: .systemBackground))
        .task {
            await quoteViewModel.fetchQuotes()
        }
    }
}
  • 특정 리스트 셀을 탭했을 때 모달 프레젠테이션로 프레젠트될 시트 뷰
  • TickerQuoteViewModel을 통해 UI로 표현할 데이터를 받아들임
  • task 메소드를 통해 뷰 모델이 가지고 있는 데이터 패칭
func fetchQuotes() async {
        phase = .fetching
        do {
            let response = try await stocksAPI.fetchQuotes(symbols: ticker.symbol)
            if let quote = response.first {
                phase = .success(quote)
            } else {
                phase = .empty
            }
        } catch {
            print(error.localizedDescription)
            phase = .failure(error)
        }
    }
  • async 함수이기 때문에 뷰 단의 task 내부에서 실행 가능
  • phase를 데이터 패치 중, 성공/실패, 빈 화면 유무 등 이넘으로 관리 중이며, 비동기적 흐름에 의해 변경되기 때문에 현재 상황을 UI로 곧바로 표현할 수 있음

struct MainListView: View {
    @EnvironmentObject private var appViewModel: AppViewModel
    @StateObject var quotesViewModel = QuotesViewModel()
    @StateObject var searchViewModel = SearchViewModel()
    var body: some View {
        tickerListView
            .listStyle(.plain)
            .overlay { overlayView }
            .toolbar {
                titleToolbar
                attributionToolbar
            }
...
            .sheet(item: $appViewModel.selectedTicker) {
                StockTickerView(quoteViewModel: .init(ticker: $0, stocksAPI: quotesViewModel.stocksAPI))
                    .presentationDetents([.height(560)])
            }
...
    }
}
  • 선택한 데이터를 리스트로 보여주는 메인 뷰
  • 시트 메소드를 통해 특정한 리스트를 클릭했을 때 전역으로 들고 있는 appViewModel 내부의 selectedTicker 퍼블리셔가 널 값이 아니라 값이 들어갈 때 자동으로 모달 프레젠트
  • presentationDetents를 통해 iOS 최신 버전부터 도입된 하프 모달을 통해 높이 조정
@MainActor
struct SearchView: View {
    @EnvironmentObject private var appViewModel: AppViewModel
    @StateObject var quotesViewModel = QuotesViewModel()
    @ObservedObject var searchViewModel: SearchViewModel
    var body: some View {
        List(searchViewModel.tickers) { ticker in
            TickerListRowView(data: .init(symbol: ticker.symbol, name: ticker.shortname, price: quotesViewModel.priceForTicker(ticker), type: .search(isSaved: appViewModel.isAddedToMyTickers(ticker: ticker), onButtonDidTap: {
                Task { @MainActor in
                    appViewModel.toggleTicker(ticker)
                }
            })))
            .contentShape(Rectangle())
            .onTapGesture {
                Task { @MainActor in
                    appViewModel.selectedTicker = ticker
                }
            }
        }
        .background(Color(uiColor: .systemBackground))
        .listStyle(.plain)
        .refreshable {
            await quotesViewModel.fetchQuotes(tickers: searchViewModel.tickers)
        }
        .task(id: searchViewModel.tickers, {
            await quotesViewModel.fetchQuotes(tickers: searchViewModel.tickers)
        })
        .overlay {
            listSearchOverlay
        }
    }
}
  • 서치 바의 텍스트 필드를 통해 검색할 경우 나타나는 결과 리스트 뷰
  • 해당 리스트 뷰를 클릭했을 때에도 디테일 뷰 모달 프레젠테이션이 가능하도록 탭 제스처 구현
  • 현재 SearchView는 메인 리스트 뷰에서 overlay를 통해 나타나고 있기 때문에 별도의 sheet 메소드를 사용할 필요가 없음
  • 현 시점의 하위 뷰, 즉 오버레이를 하고 있는 메인 리스트 뷰에서 appViewModel이 들고 있는 selectedTicker 퍼블리셔 값을 계속해서 관찰하고 있기 때문에 해당 이벤트가 발생할 경우 모달 뷰가 프레젠트되기 때문

구현 화면

이렇게 잘 구조화된 코드를 보고 있으면 대단하다는 생각부터 든다... 자연스럽게 더 잘 이해하고, "내가" 더 잘 할 수 있기를!

profile
JUST DO IT

0개의 댓글