Swift程式設計[第3單元] 網路程式基礎



JSON檔

老師請問一下我這個json檔為什麼執行起來會有這錯誤訊息,列表也沒出現



import SwiftUI
import AVKit
import Foundation


struct 鳥類: Codable, Identifiable {
let id: Int
let 車主姓名: String
let 手機: String
let 室內電話: String
let 車型: String
let 車號: String
let 線上查詢: String
let 出廠年月: String
let 排氣量: String
let 引擎號碼: String
}

func 傑森解碼器(_ 檔名: String) -> [鳥類]? {
if let 檔案 = Bundle.main.url(forResource: 檔名, withExtension: "json") {
do {
let 資料 = try Data(contentsOf: 檔案)
let 結果 = try JSONDecoder().decode([鳥類].self, from: 資料)
return 結果
} catch {
print("error: \(error)")
}
}
return nil
}

let 特有種清單 = 傑森解碼器("01") ?? []


struct 圖片與聲音: View {
let 鳥種: 鳥類
//var 播放器 = AVPlayer()
init(_ x: 鳥類) {
鳥種 = x
//guard let myURL = URL(string: 鳥種.錄音網址) else {
//print("錄音網址有誤:",鳥種.錄音網址)
//return
}
//播放器 = AVPlayer(url: myURL)
//}
var body: some View {
//AsyncImage(url:URL(string: 鳥種.圖片網址)) { 狀態 in
//if let 圖片 = 狀態.image {
VStack(alignment: .leading) {
//圖片
//.resizable()
//.scaledToFit()
Text(鳥種.車主姓名 + 鳥種.手機)
Text("攝影者:\(鳥種.車型)")
Text("錄音者:\(鳥種.車號)")
}
//} else if 狀態.error != nil {
//Image(systemName: "xmark.icloud.fill")
//.scaleEffect(3)
//.foregroundColor(.red)
//} else {
//ProgressView()
//}
}
//.onAppear { 播放器.play() }
//.onDisappear { 播放器.pause() }
//}
}

struct 台灣特有種鳥類: View {
@State var 特有種清單: [鳥類] = []
var body: some View {
NavigationView {
List(特有種清單) { 鳥種 in
NavigationLink(destination: 圖片與聲音(鳥種)) {
Label(鳥種.車主姓名 + 鳥種.手機, systemImage: "photo")
.font(.title2)
.lineLimit(1)
}
}
.navigationTitle("台灣特有種鳥類")
}
.onAppear {
//do {
//特有種清單 = try await 傑森解碼器(檔案)
特有種清單 = 傑森解碼器("01") ?? []

//} //catch {
//print("無法取得特有種清單")
}

}
}


雪白西丘斯
可能是 JSON 檔內容有問題,任何一點小錯(如"2006"後面缺少逗號)都會造成解碼錯誤。JSON檔只有最後一筆記錄,以及每筆紀錄的最後一欄,不加逗號,其他都要加。
CUNNING
老師我找到問題了,有幾列少了逗點,補上就可以用了


老師再請教一下,ipad聯絡人app這個標示功能是?我打te,下面那邊會變粗體字
雪白西丘斯
不錯,多觀察,多提問,對學習很有幫助。
這也是進階的字串處理功能,找到搜尋位置,前後加上兩個星號 "**",就可以顯示粗體,這是 "markdown" 的語法,以後有機會再詳細介紹。

import PlaygroundSupport
import SwiftUI

struct Test: View {
var body: some View {
Text("**te**[email protected]")
.font(.largeTitle)
}
}

PlaygroundPage.current.setLiveView(Test())


雪白西丘斯
是的,JSON要餵給URLSession或AsyncImage的網址不能直接打中文,必須是UTF8編碼過的,參考第3課 #10搜尋iTune
CUNNING
搞了一天還是搞不定,onedrive的連結比較複雜,還沒搞定


我小改了一下,做好一個資料查詢app

它原本的用法是一個excel檔,一個人在電腦用vba匯入照片連到硬碟,另一個人在外場看維修單的話,要進來用電腦看,要打電話是看著螢幕一碼一碼按
要找資料是按Ctrl+f
也有用excel做一個fliter查詢功能,但是就是用不習慣

現在用成app整個都變方便了,查找資料更方便
手機也不用邊看邊按,直接按了就播號
維修照片直接連到onedrive
雪白西丘斯
太帥了!
雪白西丘斯 wrote:
#1 前言Apple(恕刪)
哇教學樓? 來簽個到
#37 第10課 逐步改善「我的App」

第1單元第8課曾提過「逐步改善」的觀念,軟體最大的優勢就是彈性,能夠逐步擴充App的內容、持續改善用戶體驗與服務流程,以因應新的需求。

持續改善的過程,其實也是持續學習的過程,沒有人第一次寫App就一炮而紅,總是從觀摩或模仿別人的App開始,甚至要從使用者回饋的問題中學習,最後才能寫出自己獨特的App。

在本單元最後一課,我們就來試試如何擴充並改善前一課所寫的App。

初步的想法,是利用 Swift Playgrounds 4.0 本身的新功能:一個App可以包含多個Swift程式,分散在多個檔案裡。本課再寫一個「台灣生物多樣性」的程式,然後將「特有種鳥類」與「生物多樣性」透過 SwiftUI 的 TabView 整合在一個App裡面,如下圖:


其次,在第1單元第8課也介紹過「物件導向」的程式設計方法,不過本單元到目前為止,大多將函式(func)與資料類型(struct)分開,觀念上屬於函式化程式設計(functional programming),兩個方法都能有效解決問題,但背後設計理念不同,物件導向更適合解決複雜問題,在本課我們就改用物件導向方法來試試看有何差異。

這次我們選用的內容是「政府開放資料平台(Open Data)」 -- 農委會特生中心(特有生物研究保育中心)的「台灣生物多樣性網絡(TBN)」,這裏搜集了台灣2萬多種動植物的物種分類與1千多萬筆百年來的觀測紀錄,堪稱是台灣目前最完整的生態資料庫,最棒的是提供Open API開放給大眾使用。

根據「台灣生物多樣性網絡」Open API說明文件,資料結構分為兩層,第一層有3個欄位:meta, links 與 data,第二層才是個別資料,我們需要的都在 data 裡面。對應的資料類型宣告如下:
struct 物種列表: Codable {
var meta: 總數
var links: 頁次
var data: [物種資料]
}
struct 總數: Codable {
var total: Int
}
struct 頁次: Codable {
var next: String
}
struct 物種資料: Codable, Hashable {
var taxonUUID: String //分類編號
var taxonName: String //分類名稱(中英文)
var scientificName: String //學名(拉丁文)
var vernacularName: String //本地名稱(中文)
var taxonRank: String //分類階層(中文)
var family: String //科名(中英文)
}

有了資料結構,就可以用「傑森解碼器」來取得資料,需要連結的網址格式範例為:
https://www.tbn.org.tw/api/v2/species?name=佛甲草&limit=30

基本上就是將「name=佛甲草」參數改為其他搜尋字串即可。

我們先仿照上一課範例3-9a寫一個文字模式程式,來驗證資料結構與傑森解碼器是否正確,但改用「物件導向」的方法,完整程式如下:
// 3-10a 台灣生物多樣性 Open Data (TBN) 
// Created by Heman, 2022/01/31
// TBN Open Data: https://www.tbn.org.tw/data/api
import Foundation

struct 物種列表: Codable {
var meta: 總數
var links: 頁次
var data: [物種資料]
init() {
meta = 總數(total: 0)
links = 頁次(next: "")
data = []
}
mutating func 更新(_ 名稱: String) async {
// Sample URL: "https://www.tbn.org.tw/api/v2/species?name=佛甲草&limit=30"
if 名稱 == "" { return }
var 合成網址 = URLComponents()
合成網址.scheme = "https"
合成網址.host = "www.tbn.org.tw"
合成網址.path = "/api/v2/species"
合成網址.query = "name=\(名稱)&limit=30"
guard let myURL = 合成網址.url else { return }
do {
let (回傳資料, 回傳碼) = try await URLSession.shared.data(from: myURL)
self = try JSONDecoder().decode(物種列表.self, from: 回傳資料)
print(回傳碼)
} catch {
print("無法更新列表")
}
}
}
struct 總數: Codable {
var total: Int
}
struct 頁次: Codable {
var next: String
}
struct 物種資料: Codable, Hashable {
var taxonUUID: String //分類編號
var taxonName: String //分類名稱(中英文)
var scientificName: String //學名(拉丁文)
var vernacularName: String //本地名稱(中文)
var taxonRank: String //分類階層(中文)
var family: String //科名(中英文)
}

Task {
var 列表 = 物種列表()
await 列表.更新("佛甲草")
for 物種 in 列表.data {
print(物種.vernacularName, 物種.scientificName)
}
}

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

這次的「傑森解碼器」最大的不同,就是將函式寫在「struct 物種列表」宣告裡面,名稱改為「更新()」,呼叫的方式也有所不同,原來3-9a的用法:
//3-9a
let 特有種清單 = try await 傑森解碼器(網址)

現在的用法是:
//3-10a
await 列表.更新("佛甲草")

前者(3-9a)將函式(傑森解碼器)與資料(特有種清單)明顯區分開來,資料是函式(動作)回傳的結果。後者(3-10a)則將資料與函式視為一體,合稱為「物件(Object)」,資料是這個物件的屬性,函式是這個物件的動作(或稱方法),動作可能變更物件本身的屬性,這是一種將虛擬資料實體化,或反過來想,實體物件虛擬化的做法。

可別小看這樣小小的改變,這個改變在30-40年前掀起的軟體革命仍然延續至今。

這樣的「物件化」程式設計有什麼好處呢?事實上,資料與函式物件化之後,整個內涵完全不一樣,帶來許多好處,包括:

1. 函式可取相同名稱而不會衝突

例如上一課台灣特有種鳥類的傑森解碼器,可以同樣改名為物件的「更新()」函式,但一個是「特有種清單.更新()」,一個是「物種列表.更新()」,同樣的函式名稱包在不同物件裡面,就不會衝突。

否則,如果兩個函式都放在外面,取名為「傑森解碼器()」,語法上就會有重複宣告的衝突。若所有程式一個人從頭寫到尾,會知道改名稱,影響還不大,若是多人共同開發一個專案,就可能產生大麻煩。

對許多資料類型,都同樣有「增刪查改」(或稱"CRUD")的需要,也就是 Create (建立)、Read (讀取)、Update (更新)、Delete (刪除)等動作,用函式方法來設計會很麻煩,改用物件導向就容易多了。

2. 物件裡同一個函式(物件方法)允許重複宣告,產生多種用法

例如本課3-10a的更新(),傳入參數是搜尋的物種「名稱」,可再增加一個用法是以「網址」為參數,做到同樣功能,使用時就根據參數不同來區分。同一個函式多種用法,就可以應對各種情況,大大增加函式(動作)的彈性,例如前面提過 List 物件就有20多種使用方法。

範例程式為了簡化起見,我們先只用 async 來宣告「更新()」,不用 throws 傳遞錯誤,所以程式最後在 Task 裡面更新列表時,就不需要 do-try-catch:
Task {
var 列表 = 物種列表()
await 列表.更新("佛甲草")
for 物種 in 列表.data {
print(物種.vernacularName, 物種.scientificName)
}
}

使用物件方法之前,必須先產生一個物件實例。
   var 列表 = 物種列表()

「物種列表」是物件類型(type),在第一單元曾將類型比喻為物件的「模子」,類型名稱加上()就相當於用模子複製出一個物件實體或實例("instance"),而這個 () 複製過程,事實上是物件的初始化函式 init() 做的,所以在物件裡面必須有一個或多個初始化函式:
    init() {
meta = 總數(total: 0)
links = 頁次(next: "")
data = []
}

初始化函式最重要的任務,就是必須將物件的「所有屬性」(資料欄位)都給予初始值,這是物件導向非常重要的觀念,如果有一個資料欄位沒有初始化,就無法完成複製程序,生不出任何物件實例出來。

想想看,我們在第2單元學SwiftUI,所有範例程式幾乎都在做同一件事:初始化 View 物件的 body 屬性!

最後,我們將更新後的物種列表印出物種的中文名稱與學名,就可確定連接網路並解碼成功。程式執行結果如下圖:


註解
  1. 為什麼我們宣告 View 物件的時候,幾乎都沒有寫 init() 呢?因為任何物件宣告時,系統都會附上一個預設的 init(),若所有屬性(var/let)都有預設值,複製實例時就可直接用「物件類型()」不帶參數,若沒有預設值,就必須傳遞參數進去,例如:
         meta = 總數(total: 0)
    links = 頁次(next: "")
    這就是系統預設的 init() 負責處理的。
  2. 所以 View 物件的 var body { } ,其實就是在指定 body 的預設值(body = 匿名函式的回傳值)。
  3. 更新()會變更物件的屬性,所以必須宣告為 mutating,參考第1單元第9課
  4. 佛甲草在台灣野外分布相當廣,從海平面東北角海岸,到海拔3000公尺的高山,都有其分佈,但不容易找到,台灣共有20多種,下圖是筆者在雪山圈谷拍攝的台灣特有種--玉山佛甲草。
雪白西丘斯
TBN API 版本有所更新,新的網址格式為 "https://www.tbn.org.tw/api/v25/taxon?name=佛甲草&limit=30"
#38 神出鬼沒的 self

上一節的範例程式中,有一個關鍵語法沒有解釋,就是關於 "self",這是物件屬性更新的關鍵。

在原本的函式化程式設計中,傑森解碼器回傳的結果,會指定給「特有種清單」,非常直觀:
//3-9a
let 特有種清單 = try await 傑森解碼器(網址)

改用物件導向程式設計後,傑森解碼器已包在物件裡面,當物件實例「列表」使用「更新()」時,傑森解碼器的結果如何更新「列表」本身呢?
//3-10a
var 列表 = 物種列表()
await 列表.更新("佛甲草")

如果進一步查看函式「mutating func 更新()」的程式碼,會發現其中主要段落包含了兩個突然冒出的 "self":
do {
let (回傳資料, 回傳碼) = try await URLSession.shared.data(from: myURL)
self = try JSONDecoder().decode(物種列表.self, from: 回傳資料)
print(回傳碼)
} catch {
print("無法更新列表")
}

第3行JSONDecoder() 解碼後的結果,指定給 "self",這 "self" 並沒有宣告,到底是指誰呢?答案就是「列表」自己,列表是「物種列表」的物件實例,通常只有物件實例才能取用物件的屬性與方法(函式、動作),所以使用「await 列表.更新("佛甲草")」的結果,就是物件實例更新「自己(self)」的屬性。

如果用函式化的觀念來解釋,
self = try JSONDecode().decode(物種列表.self, from: 回傳資料) // 正確寫法
大致等同於:
列表 = try JSONDecode().decode(物種列表.self, from: 回傳資料) // 實際上不能這樣寫

因為「更新()」是宣告在物件類型裡面,相當於刻在模子上,不知道未來會複製出哪些物件實例,所以就用 "self" 來代表未來複製出的物件實例。

另一個 "self" 在 JSONDecoder().decode(物種列表.self, from: 回傳資料) 參數裡面,又是指什麼呢?「物種列表」是物件類型,相當於模子,所以「物種列表.self」指的就是模子本身,也就是物件類型,因為 decode() 第一個參數需要一個物件類型當作解碼的範本,解出來的就成為該物件的一個實例(instance)。

類似這樣,不請自來的"self" 會經常出現在物件導向的程式中。

註解
  1. 從物件導向的理論來說,self 是每個物件預設存在的屬性,包括物件類型或物件實例,預設都會有一個屬性名稱 self,都是指向自己本身,在底層系統自動宣告(雖然我們看不到)。
  2. 大部分的物件屬性與方法,只有物件實例才能使用,術語稱為 instance property 以及 instance method;相對的,也有少數屬性與方法是給物件類型使用,相當於固定在模子上,產出物件實例時不會複製到實例上,在宣告時要加 "static",這種就稱為 type property 以及 type method,例如 Color.blue 或 URLSession.shared 就是 type property。
雪白西丘斯
可能是範例選得不好,想要將函式放進struct裡面,並取用struct的變數或常數(即屬性),就會用到self,多試試看就知道。這兩天再想個更好的範例。
CUNNING
是我悟性不夠好啦
#39 強大的 extension

接下來仿照範例3-9c,製作一個NavigationView來顯示「台灣生物多樣性」的搜尋列表與詳細資料:
NavigationView {
if 列表.data == [] && name != "佛甲草" {
Text("沒有資料🤷請重新搜尋")
.font(.largeTitle)
} else {
List(列表.data, id: \.self) { 生物 in
NavigationLink(destination: 生物.顯示詳細資料()) {
Label(生物.vernacularName + 生物.scientificName, systemImage: "leaf")
.font(.title2)
.lineLimit(1)
}
}
.navigationTitle("台灣生物多樣性")
}
}
.task { await 列表.更新(name) }
.navigationViewStyle(.stack)
.searchable(text: $name, placement: .navigationBarDrawer(displayMode: .always))
.onSubmit(of: .search) {
Task { await 列表.更新(name) }
}

在最後幾行,NavigationView 用了4個修飾語,分別說明如下表:
修飾語 用途
.task 取得一開始的列表內容
.navigationViewStyle 調整視圖顯示方式
.searchable 顯示搜尋列
.onSubmit 按ENTER後取得輸入的搜尋字串,並重新搜尋一次

在這裡的搜尋,並不像上一課是搜尋列表本身的內容,而是用API連網搜尋新的列表,所以用 .onSubmit 而不是 .onChange,這樣的互動反應比較流暢。

這些修飾語還有兩個新用法,提供不同的顯示效果,正好可以和第9課對比:
(1) .searchable 加了一個參數,placement: .navigationBarDrawer(displayMode: .always) 作用是將搜尋列固定顯示,而非預設的自動隱藏(列表向下滑才會出現)。
(2) .navigationViewStyle(.stack) 將導覽視圖改為單欄顯示,.stack相當於ZStack效果,第二欄詳細資料會(覆蓋)疊在第一欄列表上面。預設是雙欄,在 iPad 上一開始會只看到空白畫面,不太好。

另外,這段程式碼還有一個不同之處,是本節的重點:
NavigationLink(destination: 生物.顯示詳細資料())

這裡的「顯示詳細資料()」同樣改用物件導向的方法來寫,將函式包在「物種資料」類型裡面,由物件實例「生物」來使用,不過比較特別的是,函式並不寫在「struct 物種資料」宣告裡面,而是用 "extension" 來「擴充物件」:
extension 物種資料 {
func 顯示詳細資料() -> some View {
VStack(alignment: .leading) {
Image(systemName: "camera.viewfinder")
.resizable()
.scaledToFit()
.opacity(0.1)
.frame(width: 300)
.padding()
Text(self.vernacularName)
Text(self.scientificName)
Text("資料來源:台灣生物多樣性網絡(TBN) https://www.tbn.org.tw/")
Spacer()
}
}
}

用extension跟直接寫在struct宣告裡面其實是一樣的作用,那麼又何必多此一舉呢?看似畫蛇添足,其實大有深意,用extension至少有兩個好處:

1. 當多人開發專案時,不用修改隊友寫的物件類型(程式可能在另一個檔案裡),就能加入自己想要的物件屬性或方法。
2. 更重要的是,還可以擴充系統原有的任何物件(總不可能拿到系統的原始碼吧)!

也就是說,用extension可以擴充任何現存(已宣告過)的物件,例如讓 Text() 增加跑馬燈的動作,或是讓 Image() 具備AI辨識能力...等等,相對於函式化程式設計來說,物件的extension是全新的玩法,顛覆性的遊戲規則,而且簡單好用還異常強大,稱得上是物件導向的精髓。

在上面這段 extension 裡面,我們增加一個物件方法,會讀取物件屬性,用VStack排列出詳細資料的視圖作為回傳值,回傳值類型宣告為 some View,跟視圖主體(View body)用法相同。這裏用 self.vernacularName 來讀取屬性,self 指物件實例 -- 也就是NavigationLink裡面的「生物」。

這樣就完成了「台灣生物多樣性」程式,下一節再與「3-9c 台灣特有種鳥類」合併到一個App裡面。完整程式列表如下:
// 3-10b 台灣生物多樣性 NavigationView & extension
// Last Revised by Heman, 2022/02/05
// TBN Open Data: https://www.tbn.org.tw/data/api
import SwiftUI

struct 物種列表: Codable {
var meta: 總數
var links: 頁次
var data: [物種資料]
init() {
meta = 總數(total: 0)
links = 頁次(next: "")
data = []
}
mutating func 更新(_ 名稱: String) async {
// Sample URL: "https://www.tbn.org.tw/api/v2/species?name=佛甲草&limit=30"
if 名稱 == "" { return }
var 合成網址 = URLComponents()
合成網址.scheme = "https"
合成網址.host = "www.tbn.org.tw"
合成網址.path = "/api/v2/species"
合成網址.query = "name=\(名稱)&limit=30"
guard let myURL = 合成網址.url else { return }
do {
let (回傳資料, 回傳碼) = try await URLSession.shared.data(from: myURL)
self = try JSONDecoder().decode(物種列表.self, from: 回傳資料)
print(回傳碼)
} catch {
print("無法更新列表")
}
}
}
struct 總數: Codable {
var total: Int
}
struct 頁次: Codable {
var next: String
}
struct 物種資料: Codable, Hashable {
var taxonUUID: String //分類編號
var taxonName: String //分類名稱(中英文)
var scientificName: String //學名(拉丁文)
var vernacularName: String //本地名稱(中文)
var taxonRank: String //分類階層(中文)
var family: String //科名(中英文)
}
extension 物種資料 {
func 顯示詳細資料() -> some View {
VStack(alignment: .leading) {
Image(systemName: "camera.viewfinder")
.resizable()
.scaledToFit()
.opacity(0.1)
.frame(width: 300)
.padding()
Text(self.vernacularName)
Text(self.scientificName)
Text("資料來源:台灣生物多樣性網絡(TBN) https://www.tbn.org.tw/")
Spacer()
}
}
}

struct 台灣生物多樣性: View {
@State var name = "佛甲草"
@State var 列表 = 物種列表()
var body: some View {
NavigationView {
if 列表.data == [] && name != "佛甲草" {
Text("沒有資料🤷請重新搜尋")
.font(.largeTitle)
} else {
List(列表.data, id: \.self) { 生物 in
NavigationLink(destination: 生物.顯示詳細資料()) {
Label(生物.vernacularName + 生物.scientificName, systemImage: "leaf")
.font(.title2)
.lineLimit(1)
}
}
.navigationTitle("台灣生物多樣性")
}
}
.task { await 列表.更新(name) }
.navigationViewStyle(.stack)
.searchable(text: $name, placement: .navigationBarDrawer(displayMode: .always))
.onSubmit(of: .search) {
Task { await 列表.更新(name) }
}
}
}

import PlaygroundSupport
PlaygroundPage.current.setLiveView(台灣生物多樣性())

為了方便操作Swift Playgrounds 4.0,原來電子書模式的預覽指令放在最後兩行,不需複製到「我的App」裡面。

程式執行的結果如下:


很可惜的是,台灣生物多樣性網絡的資料庫目前以觀測紀錄為主,沒有開放圖片的API,因此無物種圖片可抓,就暫時先以一個淡化的圖示來佔位子。
CUNNING
這個JSON打開一看會傻眼透過網站會比較清楚https://c.runoob.com/front-end/53/
CUNNING
另外請教一下這個JSON的DATA是陣列嗎?
#40 App 整合

在前面兩課分別寫好「台灣特有種鳥類」與「台灣生物多樣性」之後,要整合成一個App出乎意外的簡單,只要在 MyApp 裡面(或單獨一個檔案)加入以下程式碼即可:
// 3-10c 台灣生物多樣性App (TabView)
// Last Revised by Heman, 2022/02/05
// For an App created by Swift Playgrounds 4.0 (iPad version)
import SwiftUI

struct 整合: View {
var body:some View {
TabView {
台灣特有種鳥類()
.tabItem {
Image(systemName: "photo")
Text("台灣特有種鳥類")
}
台灣生物多樣性()
.tabItem {
Image(systemName: "leaf")
Text("台灣生物多樣性")
}
}
}
}

兩者以 TabView 為排版容器(Container),以頁籤方式整合在一起(就像Excel下方用頁籤切換不同工作表),兩個視圖各自新增 .tabItem 修飾語,裡面放頁籤的圖示與文字標籤,點按就會切換頁籤,非常直觀。

最後記得將 MyApp 的「ContentView()」更改為「整合()」:
// MyApp
import SwiftUI

@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
整合()
}
}
}

整合成一個App之後,在 iPad 版的 Swift Playgrounds 4.0 執行的過程如下:


從App內容的角度來看,硬將「特有種鳥類」與「生物多樣性」放在一起,是有點勉強,還好本課是以程式設計教學為主,利用這些範例來解釋程式設計的語法與觀念,才是本課的主要目的。

從這個範例可以看出,Swift Playgrounds 4.0 的新功能令人激賞。過去Xcode 的複雜臃腫一直是Apple心頭難以解決的痛,而且越累積壓力越大,Swift Playgrounds 似乎有機會作為入門者或測試者的替代工具,緩解沈痾,雖然目前還不完全成熟,但未來想必會有很大成長空間。

筆者特別看好 Swift Playgrounds 4.0 導入 Swift Package (套件)的功能,這實在是開發 App 很關鍵的功能之一,目前已登錄的 Swift 開源套件就超過5,000套,若能善用這些套件,可輕易開發出成熟的App,節省大量時間。筆者測試了 Airbnb 的開源專案 "Lottie",這是用來設計動態圖片(animation)的工具,網路上已累積不少共享資源

測試導入套件的結果大致OK,雖然還不夠細緻,且較大的套件如 Google Firebase,一直無法導入成功,但至少是個不錯的開始,顯然下個版本會持續改善,就讓我們拭目以待。



可以想見,未來Apple原廠會繼續強化Swift Playgrounds,特別是開發App的功能上,筆者預期MacOS版 Swift Playgrounds也會很快支援App開發,如果再進一步整合 GitHub 版本管理的功能,那就更完美了。

註解
  1. 登錄在 swiftpackageregistry.com 的套件超過5,000套,看似很多,但比起 Python Package Index (PyPI) 超過30萬套件比起來,實在小巫見大巫,不過 Python 初期也是緩慢成長,最近10年才爆發,Swift 還年輕,潛力無窮。
  2. 用Swift Playgrounds 4.0 寫的 App 可以發布到 App Store 上,不過需先繳費加入App Store Connect帳號(年費$99美金),並且通過App審核。還好也可以利用 iCloud 分享功能,讓其他人使用,本節範例 App 共享網址為: https://www.icloud.com/iclouddrive/0f1dH9SZja80aF3SOiHEbpkpg,在 iPad上點開,應該會自動開啟Swift Playgrounds,當然也可以在Mac用Xcode打開。
雪白西丘斯
是的,網頁連接資料庫,用php最快最方便,學習資源非常多。
CUNNING
extension好特別,原來它是這用途


共享可以存一份回來
雪白西丘斯
不錯,學習就是需要分享與交流。
關閉廣告
文章分享
評分
評分
複製連結

今日熱門文章 網友點擊推薦!