【SwiftUI 2.0】它好用嗎?能吃嗎?
因為APPLE在WWDC19發表了SwiftUI這個語法簡潔的Framework,個人發想APPLE應該是在學Google的Flutter吧?想讓寫APP的門檻降低,不過秉持APPLE一貫的傳統,當然這個東西只能寫自家系統的東西,像macOS / watchOS / tvOS,不像Google想以Flutter一統江湖。
作業環境
項目 | 版本 |
---|---|
CPU | Apple M1 |
macOS | Big Sur 11.4 |
Xcode | 12.5 |
基本使用
建立專案
- 首先,因為SwiftUI支援iOS 13+ / macOS 10.15+以上,所以呢請使用Xcode 11以上來做測試。
- 這裡新建一個名叫『SwiftUI_First』的專案,Interface選擇『SwiftUI』, LifeCycle選擇『SwiftUI App』,當然如果要混搭風的話,LifeCycle可以選擇『UIKit App Delegate』。
- 在這裡跟以前最顯眼的不同,就是它有預覽的功能,而且要macOS 10.15以上才支援,其主要是因為它使用了PreviewProvider這個Protocol,當然預覽可以不只一支,不過要在模擬器有安裝的才可以 (風扇一直轉…XD)。
Xcode -> Editor -> Create Preview
簡單的例子
- 先來介紹一個簡單的例子,可以看到SwiftUI都使用struct,我想是因為速度的關係吧?
- 游標在哪,PreView就會對應到哪,其實還滿方便的,而且有寫到的特性,也可以直接做選取,所視即所得 - WYSIWYG
- 這些元件就自行參考一下:ZStack / RoundedRectangle / Text
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack(alignment: .center, content: { // ZStack => 有階層的StackView (z軸上的先後次序) / 內容物對齊置中
RoundedRectangle(cornerRadius: 25.0) // RoundedRectangle => 可以看成是長方形的UIView
.stroke(lineWidth: 3.0) // 畫外框線 (如果沒有的話,會變成實心的)
Text("Hello World !!!") // Text => 可以看成是UILabel
})
.padding(.horizontal) // .padding(.horizontal) => 置中對齊
.foregroundColor(.red) // .foregroundColor(.red) => 前景顏色
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().preferredColorScheme(.light).previewDevice("iPhone 11")
}
}
進階例子
要做一個長得像這樣的APP
符號表
let emojis = [
"🏳️", "🏴", "🏴☠️", "🏁", "🚩",
"🏳️🌈", "🏳️⚧️", "🇺🇳", "🇹🇹", "🇹🇷",
"🇹🇨", "🇹🇲", "🇧🇹", "🇨🇫", "🇨🇳",
"🇩🇰", "🇪🇨", "🇪🇷", "🇵🇬", "🇧🇷",
"🇧🇧", "🇵🇾", "🇧🇭", "🇧🇸", "🇵🇦",
]
新增一個CardView
- 新增一個中間有字的View
// MARK: - CardView
struct CardView: View {
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 20.0)
.fill()
.foregroundColor(.red)
Text("🏴")
.font(.largeTitle)
}
}
}
加上點擊功能
- 利用.onTapGesture()加上點擊的功能
- 不過要注意的是,因為CardView是struct,所以在要變更值的時候,要加上@State - Property Wrapper
- 而且@State有個神奇的副作用,只要它的內容改變,畫面也會立即更新,有點像Vue的雙向綁定。
// MARK: - CardView
struct CardView: View {
@State private var isFaceUp = true // 要加上@State(屬性包裝器)才可以改值 => struct => mutating
var body: some View {
let rectangle = RoundedRectangle(cornerRadius: 20.0)
let text = Text("🏴")
ZStack {
/// 正面 => 顯示文字 / 背面 => 沒有文字
if (!isFaceUp) {
rectangle.fill()
} else {
rectangle.fill().foregroundColor(.white)
rectangle.strokeBorder(lineWidth: 3.0, antialiased: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
text.font(.largeTitle)
}
}.onTapGesture { // 點擊後 => isFaceUp = false
isFaceUp.toggle()
}
}
}
讓Text能動態換文字
- 為了讓每張牌都不一樣,所以Text的文字要是可以更換的,所以加上『var content: String』。
import SwiftUI
struct ContentView: View {
let emojis = [
"🏳️", "🏴", "🏴☠️", "🏁", "🚩",
"🏳️🌈", "🏳️⚧️", "🇺🇳", "🇹🇹", "🇹🇷",
"🇹🇨", "🇹🇲", "🇧🇹", "🇨🇫", "🇨🇳",
"🇩🇰", "🇪🇨", "🇪🇷", "🇵🇬", "🇧🇷",
"🇧🇧", "🇵🇾", "🇧🇭", "🇧🇸", "🇵🇦",
]
var body: some View {
CardView(content: emojis[5])
}
}
// MARK: - CardView
struct CardView: View {
var content: String // 加上一個String變數,讓Text能動態換文字
@State private var isFaceUp = true // 要加上@State(屬性包裝器)才可以改值 => struct => mutating
var body: some View {
let rectangle = RoundedRectangle(cornerRadius: 20.0)
let text = Text(content)
ZStack {
/// 正面 => 顯示文字 / 背面 => 沒有文字
if (!isFaceUp) {
rectangle.fill()
} else {
rectangle.fill().foregroundColor(.white)
rectangle.strokeBorder(lineWidth: 3.0, antialiased: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
text.font(.largeTitle)
}
}.onTapGesture { // 點擊後 => isFaceUp = false
isFaceUp.toggle()
}
}
}
重複使用CardView
import SwiftUI
struct ContentView: View {
let emojis = [
"🏳️", "🏴", "🏴☠️", "🏁", "🚩",
"🏳️🌈", "🏳️⚧️", "🇺🇳", "🇹🇹", "🇹🇷",
"🇹🇨", "🇹🇲", "🇧🇹", "🇨🇫", "🇨🇳",
"🇩🇰", "🇪🇨", "🇪🇷", "🇵🇬", "🇧🇷",
"🇧🇧", "🇵🇾", "🇧🇭", "🇧🇸", "🇵🇦",
]
var body: some View {
VStack {
HStack {
CardView(content: emojis[1])
CardView(content: emojis[2])
CardView(content: emojis[3])
ForEach(emojis[4...6], id: \.self) { emoji in
CardView(content: emoji)
}
}
}.foregroundColor(.red)
}
}
把所有的CardView全部都排列上去
- 把所有的CardView全部都排列上去,利用LazyVGrid組合一下。
import SwiftUI
struct ContentView: View {
let emojis = [
"🏳️", "🏴", "🏴☠️", "🏁", "🚩",
"🏳️🌈", "🏳️⚧️", "🇺🇳", "🇹🇹", "🇹🇷",
"🇹🇨", "🇹🇲", "🇧🇹", "🇨🇫", "🇨🇳",
"🇩🇰", "🇪🇨", "🇪🇷", "🇵🇬", "🇧🇷",
"🇧🇧", "🇵🇾", "🇧🇭", "🇧🇸", "🇵🇦",
]
var body: some View {
VStack {
HStack {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) { // LazyVGrid => 一列 => 三個 (100是測出來的)
ForEach(emojis[0..<3], id: \.self) { emoji in
CardView(content: emoji).aspectRatio(2/3, contentMode: .fit) // 長相比例:2:3 => 按比例填滿
}
}
}.foregroundColor(.red)
}
}
}
超出範圍怎麼辦?
- 超出範圍怎麼辦?利用ScrollView來處理這個問題。
import SwiftUI
struct ContentView: View {
let emojis = [
"🏳️", "🏴", "🏴☠️", "🏁", "🚩",
"🏳️🌈", "🏳️⚧️", "🇺🇳", "🇹🇹", "🇹🇷",
"🇹🇨", "🇹🇲", "🇧🇹", "🇨🇫", "🇨🇳",
"🇩🇰", "🇪🇨", "🇪🇷", "🇵🇬", "🇧🇷",
"🇧🇧", "🇵🇾", "🇧🇭", "🇧🇸", "🇵🇦",
]
var body: some View {
VStack {
HStack {
ScrollView { // 超出範圍怎麼辦? => ScrollView
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) { // LazyVGrid => 一列 => 三個 (100是測出來的)
ForEach(emojis[0..<emojis.count], id: \.self) { emoji in
CardView(content: emoji).aspectRatio(2/3, contentMode: .fit) // 長相比例:2:3 => 按比例填滿
}
}
}.foregroundColor(.red)
}
}
}
}
加上新增 / 刪除的Button
- 最後再加上兩個功能Button,其中ICON是使用先Apple提供的APP - SF Symbols 2來處理的。
import SwiftUI
struct ContentView: View {
@State var emojiCount = 12 // 要加上@State(屬性包裝器)才可以改值 => struct => mutating
let emojis = [
"🏳️", "🏴", "🏴☠️", "🏁", "🚩",
"🏳️🌈", "🏳️⚧️", "🇺🇳", "🇹🇹", "🇹🇷",
"🇹🇨", "🇹🇲", "🇧🇹", "🇨🇫", "🇨🇳",
"🇩🇰", "🇪🇨", "🇪🇷", "🇵🇬", "🇧🇷",
"🇧🇧", "🇵🇾", "🇧🇭", "🇧🇸", "🇵🇦",
]
var body: some View {
VStack {
HStack {
ScrollView { // 超出範圍怎麼辦? => ScrollView
LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) { // LazyVGrid => 一列 => 四個 (80是測出來的)
ForEach(emojis[0..<emojiCount], id: \.self) { emoji in
CardView(content: emoji).aspectRatio(2/3, contentMode: .fit) // 長相比例:2:3 => 按比例填滿
}
}
}.foregroundColor(.red)
}
Spacer() // 中間的空白
HStack {
removeButton // 移除用的Button
Spacer()
addButton // 新增用的Button
}
.padding(.horizontal)
.font(.largeTitle)
}
}
/// 移除用的Button
var removeButton: some View {
/// 第一個的action:可以不寫 => label:
Button {
if (emojiCount > 1) { emojiCount -= 1 } // 最少有一個
} label: {
VStack {
Image(systemName: "minus.circle") // SF Symbols 2 => 上面圖示的名字
}
}
}
/// 新增用的Button
var addButton: some View {
Button(action: {
if (emojiCount < emojis.count) { emojiCount += 1 } // 最多就是emojis的量
}, label: {
VStack {
Image(systemName: "plus.circle") // SF Symbols 2 => 上面圖示的名字
}
})
}
}
範例程式碼下載
後記
- SwiftUI用起來的確是滿精簡的,不過在想法,給我的感覺上比較像CSS,也許有朝一日會用上吧?先有個基本的認識。