iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0
Mobile Development

在 iOS 專案上加上 Unit testing - 因為 You need testing系列 第 17

D17 - 在 iOS 專案加上測試-You need testing {台股小工具 app-與 UI 進行組裝}

  • 分享至 

  • xImage
  •  

為了能讓 SwiftUI 的 View 可以 observer StockTradingRecordStore 的 property,要讓 StockTradingRecordStore conform Observable protocol。並把要被 observer 的 property,設定成 @Published。

//  StockTradingRecordStore.swift
/// conform ObservableObject
class StockTradingRecordStore: ObservableObject {
    
		/// 將 records 設定成 @Published
    @Published var records: [StockTradingRecord] = [] {

使用原生 UI 快整的刻畫一個列表

//  TradingRecordListView.swift
import SwiftUI

struct TradingRecordListView: View {
    
    @StateObject var store: StockTradingRecordStore = .init()
    
    var body: some View {
        
        VStack {
            
            Text("台股交易紀錄")
            List(store.records) { record in
                
                /// 將 record 呈現出來,未來可以寫一個 cell 型的 view 來裝載 record
                HStack {
                    
                    /// 股票名字和股票代號垂直排列,並對齊 leading
                    VStack(alignment: .leading) {
                        Text(record.stockName)
                        Text(record.stockID)
                    }
                    
                    Spacer()
                    
                    /// 交易金額
                    Text("\(record.tradingAmount)")
                        .padding(.trailing, 10)
                    
                    /// 買賣方向,未來可能會改寫這一段
                    if record.tradingSide == .buy {
                        Text("買進")
                    } else {
                        Text("賣出")
                    }
                }
            }
        }
    }
}

不過在刻畫時, preview 沒有設定好對應的 object,就不會有資料。

在 Preview 下方加上 mock store 的 code

//  TradingRecordListView.swift
struct TradingRecordListView_Previews: PreviewProvider {
    
    static var previews: some View {
        TradingRecordListView(store: store)
    }
    
    private static var store: StockTradingRecordStore = {
        let store = StockTradingRecordStore()
        store.add(getRecord())
        store.add(getRecord())
        return store
    }()
    
    private static func getRecord() -> StockTradingRecord {
        
        StockTradingRecord(stockID: "0050", stockName: "元大50", tradingSide: .buy, tradingShares: 1000, tradingAmount: 120000, tradingDateStr: "2023-09-07")
    }
}

這樣在右方,就可以看到對應的 UI,在 preview 的時候,我們加上了兩筆資料,所以 preview 有正確的顯示出來。

https://ithelp.ithome.com.tw/upload/images/20230928/20140622fUjVmdykAy.png

當你需要渲染一系列資料時,通常需要讓 Data Model conform Identifible,這邊將 Identifible 相關宣告加上去。

//  SettlementModel.swift
struct StockTradingRecord: Identifiable {
    
    let id: String = UUID().uuidString
    
    let stockID: String
    let stockName: String
    let tradingSide: TradingSide
    
    /// 成交股數
    let tradingShares: Int
    
    /// 成交金額
    let tradingAmount: Int
    
    /// 成交日期,格式為 yyyy-mm-dd
    let tradingDateStr: String
}

然後,我們真正的 run 這個 app,畫面會是一片空白,什麼都沒有。因為我們沒有將增加紀錄的功能作上去。所以我們補一個右上方的 Add 新增按鈕,並再給一個空值時的提示畫面。

https://ithelp.ithome.com.tw/upload/images/20230928/20140622c68ha60iGZ.png

Empty Record View - 空值的畫面

//  EmptyRecordView.swift
struct EmptyRecordView: View {
    var body: some View {
        VStack {
            Text("請按右上角按鈕\n新增股票交易紀錄")
                .multilineTextAlignment(.center)
                .font(.system(size: 30))
                .padding(.vertical)
            Image(systemName: "list.clipboard")
                .font(.system(size: 200))
            Spacer()
        }
    }
}

https://ithelp.ithome.com.tw/upload/images/20230928/20140622wUxFPCIMse.png

把這個 EmptyRecordView 組裝進紀錄 List,並加上「新增」的按鈕。這個新增的按鈕是用 sheet 的方式呈現,很像 UIKit 時代的 present UI 感。

//  TradingRecordListView.swift

@State private var showingAddRecordSheet = false

var recordList: some View {
        VStack {
            
            Text("台股交易紀錄")
            List(store.records) { record in
                
                /// 將 record 呈現出來,未來可以寫一個 cell 型的 view 來裝載 record
                HStack {
                    
                    /// 股票名字和股票代號垂直排列,並對齊 leading
                    VStack(alignment: .leading) {
                        Text(record.stockName)
                        Text(record.stockID)
                    }
                    
                    Spacer()
                    
                    /// 交易金額
                    Text("\(record.tradingAmount)")
                        .padding(.trailing, 10)
                    
                    /// 買賣方向,未來可能會改寫這一段
                    if record.tradingSide == .buy {
                        Text("買進")
                    } else {
                        Text("賣出")
                    }
                }
            }
        }
    }
    
    var topContainer: some View {
        HStack {
            Spacer()
            Button {
                print("新增按鈕被點擊")
								showingAddRecordSheet.toggle()
            } label: {
                Image(systemName: "plus.circle")
                    .font(.system(size: 37))
            }
            .padding()
            .sheet(isPresented: $showingAddRecordSheet) {
                StockTradingInputView()
            }
        }
    }
    
    var body: some View {
        
        VStack {
            topContainer
            if store.records.isEmpty {
                EmptyRecordView()
            } else {
                recordList
            }
        }
    }

https://ithelp.ithome.com.tw/upload/images/20230928/20140622GHY5607pSY.png

最後,再對 StockTradingInputView 進行修改,將 store 傳進去,並把 private 宣告改為 internal。即可。


上一篇
D16 - 在 iOS 專案加上測試-You need testing {台股小工具 app- test in Combine}
下一篇
D18 - 在 iOS 專案加上測試-You need testing {台股小工具 app-StockRecord InputView 和 RecordStore 的組裝}
系列文
在 iOS 專案上加上 Unit testing - 因為 You need testing32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言