Create a view to manager current user's portfolio | SwiftUI Crypto App #14
ScrollView {
VStack(alignment: .leading, spacing: 0) {
SearchBarView(searchText: $viewModel.searchText)
coinLogoList
if selectedCoin != nil {
portfolioInputSection
}
}
}
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)
}
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
}
}
}
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)
}
}
}
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 = ""
}
}
SwiftUI의 애니메이션 효과는 매우 간단하고 손쉽다는 것을 다시 한 번 느끼고 있다!