【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,也許有朝一日會用上吧?先有個基本的認識。