「我手上只有 iPad 而已,我可以用它來開發 iOS App 嗎?」
這是入門開發者最常問的問題。我一般的回應都是:「不行,你需要一台 Mac,來運行 Xcode 開發 iOS 程式。」
「那 iPad 的 Swift Playgrounds 呢?我可以用這個 App 來學習 Swift 和 App 開發嗎?」
相信你已經知道 Apple 推出了一個叫做 Swift Playgrounds for iPad 的 App。這個 iPad app 的初版是在 2016 年 9 月時發佈的,目標是希望讓人人都能輕易學習程式語言的基礎,尤其是 Swift 語言。而且,感謝 Apple 推出了 Catalyst,Apple 在其 iPad App 建置了一個 Mac 版本的 Swift Playgrounds。現在,我們在 iPad 和 macOS 都可以執行 Swift playgrounds。
如果你的目的是建立一個真正的 App,然後上架到 App Store,我以前是不會建議用 Swift Playgrounds for iPad 為學習 iOS 編程的工具。不要誤會,Swift Playgrounds 對於初學者而言是一個很棒的教育工具,讓初學者了解 Swift 是甚麼,並學習一些基本的編程概念。然而,它不是一個真正的開發工具,目前大部分 Swift Playgrounds 課程都無法協助你學會開發一個真實的 App。
MacWorld 的作者 Jason Cross 寫了一篇很好的文章,分享他使用 Swift Playgrounds 的經驗,他提到這個 App 可以讓他更有效地學習編程概念,但是他也提到 Swift Playgrounds 有一個非常關鍵的問題。
Swift Playgrounds 教授了其概念,並使用實際 Swift 架構,但它始終不是真正的程式碼。它無法建構一個 App,只能讓你了解基礎知識,並解決迷題。畢竟,Swift 也無法建立一個如 collectGem() 的指令。
Swift Playgrounds 可以滿足你的好奇心,像是編程是甚麼、是怎麼運作的,但它無法讓你寫一個 App,就算是一個非常簡單的 App 都不能。你在這個平台所寫的程式碼都只能這兒用,它甚至無法在特定的謎題頁面外使用呢!
更重要的是,Mac 的 Xcode 和 iPad 的 Swift Playgrounds 有些差異。
如果,Apple 想啟發所有人 ── 不論是大人或小孩 ── 寫程式碼,而不是單單學習程式碼是甚麼,它就需要將 Swift Playgrounds 豐富多彩的解謎,與 Xcode 以開發者為中心的環境之間的差異彌合。
你在 Swift Playgrounds 學到的東西,未必直接適用於 Xcode。你還需要認識開發環境,學習如何使用 Interface Builder,並了解 iOS SDK 所提供的 API。你可以看到 Xcode 和 iPad App 有很大的差異。所以我推薦學生直接利用 Xcode 學習編程概念,即使你只是想試試 Swift,都試著使用 Xcode 的 Playgrounds 工具,它可以給你更實際的程式開發經驗。
但近期我們出版了關於 SwiftUI 的新書。我們又收到類似的問題:
「我可以用 iPad 來學 SwiftUI 嗎?」
自從 Swift Playgrounds v3.1 發佈後,Apple 增加了對 SwiftUI 的支援。雖然我仍建議使用 Mac 並在 Xcode 來學習編程,但同時我也很好奇,到底可不可以利用這個教育工具來學習 SwiftUI。為了找出這個答案,最好的方式就是親身體驗。我嘗試用 Swift Playgrounds for iPad 寫了一個簡單的 Swift UI Demo,再來看看是否能夠運作。
如果你有一台 iPad 的話,你可以依照下列步驟來建立一個 App,並留言分享你的經驗。
我們這篇教學會涵蓋以下內容:
- 如何在 Swift playgrounds 執行 SwiftUI App
- 利用 Swift Playgrounds for iPad 編寫一個 SwiftUI 範例 App
- 在 Swift Playgrounds for Mac 執行相同的 App
- 探討 Swift Playgrounds 在 iPad 和 Mac 上的限制
好的,讓我們開始吧。
範例 App
我們要建罝一個非常簡單的範例來呈現不同的 Blend Mode,我們將會把兩張圖片重疊在一起,再應用不同的 Blend mode 來看看效果。如果你曾經使用過 Photoshop 或其他圖片編緝 App,Blend mode 對你而言應該不陌生。只要將那兩個圖層重疊,就能輕易地創造出漂亮的相片效果。
Swift UI 為不同的 Blend mode 提供內建功能,像是 Hardlight、multiply、darken、和 saturation。這個範例 App 將會展示部分的功能。
利用 Swift Playgrounds 寫 Swift UI App
如果你已經在 iPad 安裝好了 Swift Playgrounds,就可以建立一個空白專案來開始;而如果你還沒有安裝 Swift Playgrounds for iPad,請先從此連結下載並安裝。
備註:雖然以下所有程式碼都是在 iPad 上撰寫的,但大部分都適用於 Mac 版本。
開啟了Swift Playgrounds 後,你會看到不同的課程與模板。讓我們從 Blank 模板開始吧,它的位置就在 Starting Points 部分下面。
當你安裝好模板之後,這個 App 就會創建一個空白專案,預設名為 “My Playground”(或類似的名字)。點選它以打開專案,你會看到一個空白的編輯器,點擊螢幕一下我們就可以開始編程了。讓我們輸入下列程式碼:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
Text("Hello SwiftUI!")
.font(.system(.largeTitle, design: .rounded))
.bold()
}
}
PlaygroundPage.current.setLiveView(ContentView())
感謝 Paul 分享的這個技巧,只要匯入 PlaygroundSupport
模組,並編寫最後一行的程式碼,我們就可以執行並預覽 SwiftUI。如此一來,我們就可以設置實時視圖,並在 playground 頁面執行程式碼同時,顯示可視化結果。
在 iPad 和 Mac 的 Swift Playgrounds 都內建程式碼完成功能 (code completion),當你輸入程式碼時,它會同時提供程式碼建議,協助你完成程式碼。在 iPad 上,程式碼建議會出現在螢幕鍵盤上方,點選你想要的建議,就能自動輸入到編輯器中,這個功能很棒,要完全使用螢幕鍵盤來輸入所有程式碼,比我想像中來得容易。
如果你使用 Mac 的 Swift Playgrounds,程式碼完成功能就運作得更好了。你可以直接按 ENTER/return 鍵接受程式碼建議。
寫好程式碼之後,你可以按下 Run My Code 按鈕來看看結果,Swift Playgrounds 就會執行程式碼,並在另一個版面顯示即時可視化結果。
我們目前輸入了的程式碼使用 SwiftUI 框架,並在畫面顯示一個文字標籤。SwiftUI 提供不同的修飾符 (modifier) 來客製 UI 元件,在程式碼中,我們使用了 .font
和 .bold
修飾符來變更字型。其實當你輸入 “.” 的時候,在實例化的 Text
view 中,Playgrounds 會自動在鍵盤上方提供所有可用的修飾符!例如,你可以加上 .foregroundColor
來變更字體顏色。
現在你可以在 Swift Playgrounds for iPad 上使用 SwiftUI。我不會在這裡解釋其他文字修飾符,如果你有興趣的話,可以看看這篇文章。
接下來,讓我們嘗試建構一些更複雜的東西,並看看 Swift Playgrounds 能否處理像是Image
、ZStack
、和手勢 (gesture) 等的其他 UI 元件吧!
建置一個範例 App
如上文所述,我們將會建立一個簡單的 App,來示範 blend modes 的效果。首先,我們需要準備兩張圖片,任何圖片都可以,不過我會建議你下載這兩張圖片,並儲存到圖庫內:
在 Swift Playgrounds for iPad 加入圖片
現在回到 Playgrounds 專案,並刪除所有有關 Text
的程式碼。然後,開始撰寫程式碼來重疊兩張圖片:
要利用 SwiftUI 來顯示圖片,我們可以使用 Image
視圖。要將圖片匯入到 Playgrounds 專案需要一點小技巧,我們不會輸入檔名來讀取照片,而是利用 uiimage
來做初始化。輸入上面的程式碼後,你會看到螢幕鍵盤上出現了圖片 icon,點擊 icon 並選擇 Photo Library -> Recently Added,選擇第一張圖片(有幾張桌子的圖片)。
然後,Swift Playgrounds 就會匯入一張圖片到專案內,你也會看到 UIImage
已經變成所選圖片的縮圖。接著,輸入以下程式碼,來加入一些修飾符,並將第二張圖片疊到第一張圖片上:
在 SwiftUI,你可以使用 ZStack
來將一張圖片疊到其他圖片上。我們使用了 resizable
和 scaleToFill
修飾符,來調整圖片尺寸來填滿預覽視窗。如果你現在執行程式碼,只會看到第二張圖片,因為第一張圖片被覆蓋了。
應用 Blend Modes
SwiftUI 提供了一個叫做 blendMode
的修飾符,可以控制這兩張圖片如何融合在一起。讓我們來做一個小測試,你就能快速了解這個功能。
插入 blendMode
修飾符到第二張圖片,然後點選 Run My Code 來看看效果吧:
Swift UI 提供了 21 種 blend modes,例如 color、colorBurn、和 difference。在上面的範例中,我們應用了 color blend mode,保留下層圖片亮度的同時,在上層圖片採用 hue 和 chroma 效果。
在 Swift Playgrounds 使用 Shared Code 和 Modules
現在你應該了解 blend mode 的功能了,讓我們來試試 Swift Playgrounds 的另一個功能,來讓範例 App 更互動吧。我們會讓使用者點選圖片,以轉換另一個 blend mode。
Swift Playgrounds 允許開發者建立可共享模組 (shareable module),讓程式碼可以與所有 Playgrounds 頁面共享。為了簡化工序,並示範這個程式碼共享功能,我已經寫好了部分的程式碼,你只需要複製並貼上到共享模組中,就可以直接使用了。
讓我們將程式碼放在模組內吧。在 Playgrounds 專案點選文件 icon,並選擇 UserModule -> SharedCode.swift。你可以自行建立客製的模組與檔案,在這個範例中,我們就選擇預設模組即可。
打開 SharedCode.swift
後,複製以下程式碼,並貼到檔案內:
import SwiftUI
public enum ImageEffect: CaseIterable {
case normal
case color
case colorBurn
case difference
case multiply
case luminosity
case saturation
public func next() -> Self {
let allCases = Self.allCases
let currentIndex = allCases.firstIndex(of: self)!
var nextIndex = currentIndex.advanced(by: 1)
if nextIndex == allCases.endIndex {
nextIndex = allCases.startIndex
}
print(nextIndex)
return allCases[nextIndex]
}
public func previous() -> Self {
let allCases = Self.allCases
let currentIndex = allCases.firstIndex(of: self)!
var prevIndex = currentIndex.advanced(by: -1)
if prevIndex < allCases.startIndex {
prevIndex = allCases.endIndex - 1
}
print(prevIndex)
return allCases[prevIndex]
}
}
extension ImageEffect: RawRepresentable {
public typealias RawValue = BlendMode
public init?(rawValue: Self.RawValue) {
switch rawValue {
case BlendMode.normal: self = .normal
case BlendMode.color: self = .color
case BlendMode.colorBurn: self = .colorBurn
case BlendMode.difference: self = .difference
case BlendMode.multiply: self = .multiply
case BlendMode.luminosity: self = .luminosity
case BlendMode.saturation: self = .saturation
default: return nil
}
}
public var rawValue: BlendMode {
switch self {
case .normal: return BlendMode.normal
case .color: return BlendMode.color
case .colorBurn: return BlendMode.colorBurn
case .difference: return BlendMode.difference
case .multiply: return BlendMode.multiply
case .luminosity: return BlendMode.luminosity
case .saturation: return BlendMode.saturation
}
}
public var text: String {
switch self {
case .normal: return "Normal"
case .color: return "Color"
case .colorBurn: return "Color Burn"
case .difference: return "Difference"
case .multiply: return "Multiply"
case .luminosity: return "Luminosity"
case .saturation: return "Saturation"
}
}
}
你可能無法完全了解所有程式碼,尤其是如果你才剛接觸 Swift。簡單來說,我們建立了一個名為 ImageEffect
的 Enum
,而上面這段程式碼定義了我想呈現的 Blend Modes 功能,並為他們命名,讓你能輕易地轉換不同的功能。
當你想在 Playgrounds 開啟另一個 Swift 檔案時,App 會為你開啟另一個頁面讓你編輯。再轉換回原本的檔案,就點選這個書本的 icon。
現在,我們會變更程式碼,來偵測使用者的點擊動作,並切換到不同的 blend mode 功能。首先,宣告一個狀態變數 (state variable),以維持當下的 blend mode 功能(如果你不知道要放到哪裡,可以看看下方的圖片):
@State private var currentBlendMode: ImageEffect = .normal
接著,改變 .blendMode(.color)
來啟動 currentBlendMode
變數:
.blendMode(currentBlendMode.rawValue)
我們將預設的 blend mode 設為 .normal
,代表上層圖片完全覆蓋下層圖片。所以當你執行這個程式碼時,你只會看到最上層的圖片,而沒有任何混合效果。
SwiftUI 簡化了讓偵測使用者點擊動作的步驟,為了可以判斷點擊動作,你可以添加 .onTapGesture
修飾符到第二張圖片,並將它放在 .blendMode
方法後面:
.onTapGesture {
self.currentBlendMode = self.currentBlendMode.next()
}
當偵測到點擊動作時,這組程式碼就會被觸發,然後我們就可以切換 blend mode。你可以在共享程式碼中,找到我們之前添加的 next()
函式。
再執行一次程式碼,你現在可以切換到下一個 Blend mode,並可以點選圖片來看看效果了!
現在這個 App 可以運作了,但是你可以讓它更加完善,就是在螢幕上顯示正在使用的 blend mode。在 ZStack
插入下列程式碼,以在圖片下方加入一個文字標籤:
VStack {
Spacer()
Text(currentBlendMode.text)
.font(.system(.title, design: .rounded))
.bold()
.foregroundColor(.white)
.padding()
.background(Color.yellow.opacity(0.5))
.cornerRadius(5.0)
}
.padding(.bottom, 100)
完成修正後,你應該可以看到圖片上有一個黃色標籤,顯示了現在的 blend mode。在上面的程式碼中,修飾符 padding
是用來增加下方邊界與標籤的間距。如果你不清楚要將程式碼放置在哪裡,請參考下方的圖片:
完成了!你已經在 iPad 完全地建立了一個 SwiftUI 範例,是不是比你想像的還簡單?
除錯及輸出數據到控制台
在 iPad 編程時,還有兩件事要注意:
- Swift Playgrounds 如何偵錯?
- 控制台訊息在哪兒?
當偵測到編譯錯誤時,Swift Playgrounds 會在該行程式碼標示紅點,點選紅點時,你會看到出錯的資訊;在某些狀況下,它還會提出可行的解決方法,就如 Xcode 一樣。若要一次顯示檔案內的所有問題,你可以點選右上角的紅點。
為了除錯,我們通常會使用 print()
函數來輸出資訊。在 Xcode 中,訊息會顯示於控制台,那 Swift Playgrounds for iPad 呢?Console又在哪裡?
Swift Playgrounds 是沒有控制台的,但你仍然可以使用一個內建的 viewer 來輸出資訊。如果你想要試試看,打開 SharedCode.swift 檔案,並執行程式碼。在next()
的方法內,有一個 print
述語 (statement)。當執行這個 App 時,你應該可以在 print
述語的最後部分看到一個計數器 (counter)。點選計數器,你可以看到輸出的數值。另一方面,你可以選擇 Add viewer,來放一個 viewer 在 print
述語下方。
因為 Swift Playgrounds 是為了教育用途而設計的,有時候你根本不會用到 print
函數來顯示一個變數的值。假如你不了解甚麼是 Self.allCases
,你只要設定一個計數器,並看看 allCases
裡面有甚麼就可以了!
Swift Playgrounds for iPad 和 Mac 的限制
我們剛剛嘗試過,完全使用 Swift Playgrounds 在 iPad 上建置範例 App。這是否代表在學習 SwiftUI 上,它可以完全取代 Xcode 呢?Swift Playgrounds 最主要的一個限制,就是不是所有 SwiftUI 元件都可以在 iPad 上運作。我並沒有測試所有內建元件,只是測試了幾個而已。舉例來說,不論是 iPad 或 Mac 的 Swift Playgrounds 都無法使用 .sheet
修飾符來呈現 modal view。同樣的程式碼放在 Xcode 也可以正常運作,但其用途與在 Swift Playgrounds 上是不一樣的。這會影響你的學習體驗,你根本不知道是你寫的程式碼有錯、還是只是因為在 Swift Playground 的限制。
另一個 Swift Playgrounds 的限制,就是它並沒有內建模擬器,所有可視化結果都只在長方型的畫布上顯示。但在 Xcode,你可以選擇用來渲染結果的模擬器類型。舉例來說,上方的程式碼在 Swift Playgrounds 中文渲染成 split view;若你是在 Xcode 執行,就可以如此渲染到一個 iPhone 模擬器上:
而且,不知道你有沒有注意到,在 navigation 視圖中有個監色的長方形,這是一個 Swift Playgrounds 沒有的好功能。除此之外,Xcode 還提供了即時預覽 (instant preview),而 Swift Playgrounds 上也沒有這個功能。
因此,有了 SwiftUI,Swift Playgrounds 和 Xcode 之間的學習差距的確有拉近了一點。你在 Playgrounds 上所學到的東西,全都可以轉移到 Xcode。利用我們之前開發的範例 App,你可以重用 Xcode 專案中的所有程式碼,並創建 iPhone App。
當然,你仍然需要讓自己熟悉 Xcode 的開發環境(像是要如何使用圖庫管理圖片),和學習如何預覽 UI 介面。但與之前沒有 SwiftUI 框架時做比較,改善可以說是十分成功,Xcode 與 Swift Playgrounds 的差距已經愈拉愈近了。
總結
我希望本篇文章可以為你帶來一些想法,像是如何在 iPad 上使用 Swift UI。所以如果再有人問我想利用 iPad 來學習 SwiftUI,我的建議是下載 Swift Playgrounds,並開始在 iPad 學習 SwiftUI 吧!當你熟悉了編程的基礎後,真的想投入到 iOS 編程的話,建議投資買一台 Mac,並繼續學習 Xcode。
如果你想了解更多關於 SwiftUI 的概念,可以參考下列的免費教學:
- Working with Text in SwiftUI
- SwiftUI 教學:如何建立星際大戰透視文字(Perspective Text)
- SwiftUI 小技巧:利用 Stack 簡單構建彈性的卡片視圖
- SwiftUI 教學:認識手勢 (Gestures) 和 @GestureState
如果你還想更深入了解 SwiftUI,可以看看我們的新書 《精通SwiftUI》。
最後,我希望能聽到你在學習 Swift Playgrounds for iPad 之路的體驗,歡迎留言與我分享。