開始開發 iOS App。
首先準備一台 Mac,然後安裝 Xcode,新增專案,系統即刻生成基本的專案結構。coding 的起點在檔案 ContentView.swift:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
系統已經帶出最基本的 UI Layout VStack,就是會把裡面的元素從上往下排列。此基本專案依序顯示一個圖和一行字。
此圖的系統名為 "globe",那還有哪些系統內建的名稱可以用?原來 iOS 內建了一套極為豐富,超過 5000 個小圖示,稱為 SF Symbols 5,其中 SF 為 San Francisco 之意,而 Apple 系統所用的字體就叫 San Francisco,這一系列的圖示就是特別設計來完美搭配融合用的,真是貼心。為了開發過程可以方便查找圖示名稱,官方特別製作 App 給開發者用,可從上面官網連結下載安裝。
PS:後來發現,在 Xcode 的 library 瀏覽畫面中就可以查閱了,更方便。
此外,顯示自己的圖檔也是一定會用到的,使用方式必須先把圖形檔加入專案目錄中的 Assets.xcassets 中納管,然後在程式中直接用名稱引用,以下在 VStack 裡面加入一個 HStack,水平並排放三個圖,再放一個 Spacer(),把空間撐開,讓剛剛的可視元件頂到最上方。最後在底層的 VStack 加入背景圖,整個程式變成這樣:
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
HStack {
Image(systemName: "sun.max")
.imageScale(.large)
.foregroundStyle(.tint)
Image("logo")
Image(systemName: "sun.max")
.imageScale(.large)
.foregroundStyle(.tint)
}
Spacer()
}
.background {
Image("techNoteAvatar")
.resizable()
.scaledToFit()
.ignoresSafeArea()
}
.padding()
階段任務完成:
加碼演練一個功能,叫 iPhone 念出中文文字:
import AVFoundation
let synthesizer = AVSpeechSynthesizer()
func speak(text: String) {
let utterance = AVSpeechUtterance(string: text)
utterance.voice = AVSpeechSynthesisVoice(language: "zh-TW") // "en-GB")
synthesizer.speak(utterance)
}
在畫面任何一個地方加入按鈕,呼叫語音函數:
Button("中文語音") {
speak(text: "新人資訊,你好")
}
App 之所以好玩,在於手機的網路通訊能力,可以呼叫雲端做事,再加上因 GPS 而感知的地理位置,陀螺儀感知運動狀態,和透過藍芽連接周邊更多小裝置等等,搭配設計變化出許多有趣的應用。其中呼叫後台 api 算是最基本的,因此納入首要目標。
從我的後台系統隨意選一個 api,先用 Postman 測試,確認其可正常呼叫:
開始撰寫 swift 端的程式,首先建立幾個資料結構,並創建兩個與 UI 狀態連動的變數,採用 @State 前綴字,準備用來承接 api 的回傳資料:
@State private var results: [Ohlc] = []
@State private var result: String = ""
struct Profile: Codable {
let shortName: String
}
struct Ohlc: Codable, Identifiable {
let day: String
let Close: Double
var id: String {
day
}
}
struct StockInfo: Codable {
let info: Profile
let hist: [Ohlc]
}
回傳資料的結構必須一一對應上,任何一個名稱或型別錯誤,都將導致 decode json string 失敗。其中 Codable Protocol 很顯然必須加入的,另外 Identifiable 則是為了在 UI 元件中的 List 需要它。
接下來是呼叫後台的主程式,這一段就有點技術含量了。
func fetchApiData() {
guard let url = URL(string:"https://myxxserver/dash/stockInfo?symbol=2330.TW&days=5")
else {
return
}
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if let error = error {
print("Error: \(error)")
return
}
guard let data = data else {
return
}
do {
let stockInfo = try JSONDecoder().decode(StockInfo.self, from: data)
let info = stockInfo.info
let hist = stockInfo.hist
// begin to update UI in main thresd
DispatchQueue.main.async {
self.result = info.shortName
self.results = hist
}
} catch {
print("Error parsing JSON: \(error)")
}
}
task.resume()
}
以上代碼用到多次 guard let,確保程式執行穩健,若有問題可以提早跳出來,是很好的寫法。呼叫後台取資料是個容易有延遲的工作,因此必須用非同步的方式,開啟新的執行緒來執行,確認回傳結果正確後,再呼叫 DispatchQueue.main.async 排隊等候 UI 主執行緒任務空檔時接回資料。資料處理中的重點在於呼叫 JSONDecoder 將網路傳輸的字串解碼還原成物件。
UI 的佈局方面,先用一行顯示公司名稱,再跑一個迴圈,一天顯示一筆,顯示日期和股票收盤價格,放入主畫面的 VStack 中。
Text(result)
List(results) { ohlc in
let formattedPrice = String(format: "%.2f", ohlc.Close)
Text("\(ohlc.day): \(formattedPrice)")
}
階段任務完成:
Newman 2024/6/5
導覽頁:紐曼的技術筆記-索引
參考資料: