第一章:SwiftUI簡介
SwiftUI是蘋果與2019年推出的適用於iOS、watchOS、macOS等全平臺的聲明式UI編程框架。SwiftUI使用編碼的方式來構建UI,而不是像UIKit那樣可以基於storyBoard來構建。基於Xcode11之後的版本支持SwiftUI的編碼,可以幾乎實時預覽,也可以通過預覽界面以圖形化的形式對UI進行修改(Xcode會自動修改與之對應的代碼)。
SwiftUI是支持聲明式編程範式的,其與命令式編程範式有較大的差別。指令式編程範式是使用語句來改變程序狀態,就像自然語言中的命令式的語氣一樣。聲明式編程範式是通過表達運算邏輯,而不是控制流程來開發程序。
上面偏理論的介紹,下面以實際生活的例子來說明二者。假設,你到必勝客喫披薩。你會告訴服務員,你要鐵板的、厚邊的、10寸的等。這個過程就是聲明式的。後面的廚師在接收到這個訂單之後,告訴他的徒弟,說你要把烤爐調到300度、烤10分鐘、出爐之後撒上芝士.......。這就是命令式的。
上面兩者的區別請仔細拼讀,也許此時你還不甚理解,沒有關係,讓我們繼續深入學習SwiftUI,在學習的過程中,你的疑問會自然而然的得到解答。
使用SwiftUI編程時不在需要storyboard(在多人協作開發時,storyboard用的本來就不多)和自動佈局。雖然大家會使用Masory等三方庫來實現自動佈局,但其背後的AutoLayout其實是明顯命令式的。大家回想一下,AutoLayout要怎麼佈局,它需要知道其參照對象,然後明確告知其與參考對象的頭、尾、左、右等的關係。
使用UIKit時,VC是很重要的;但是在SwiftUI中VC不見了,取而代之的是Combine。
SwiftUI是適用於所有Apple的平臺,但它不是寫一次代碼就可以了,而是學習一次、到處使用。SwiftUI已經最大程度的支持各平臺移植,但是如果期望不做任何改動,那也是不現實的。另外,SwiftUI通過橋接的方式與UIKit、AppKit等代碼配合使用。
遺憾的是:SwiftUI要求的iOS13以上、MacOS 10.15、tvOS 13、watchOS 6。但是,這不是我們不學習、不採用SwiftUI的理由。
第二章:SwiftUI基礎元素實例
2.1 Text
在SwiftUI中Text元素與UIKit的Label類型,它可以展示一行或多行的文字且不可編輯。下面請新建一個最簡單的SwiftUI工程,並在其中輸入如下代碼。關於Xcode的預覽功能請通過百度搜索自行解決。
import SwiftUI
struct ContentView: View {
// some View 是不透明類型
var body: some View {
// 下面的鏈式表達式與Masory不同,它們叫做修飾符,修飾符的調用順序與效果有緊密關係
// 可以支持多行、單行
Text("Hello SwiftUI!Hello SwiftUI!Hello SwiftUI!Hello SwiftUI!Hello SwiftUI!Hello SwiftUI!Hello SwiftUI!")
.fontWeight(.bold) // 設置文字的粗體屬性
.font(.title) // 指定字體,.title使用動態字體,.system使用系統預定義字體,.custom使用自定義字體
.foregroundColor(.yellow) // 設置文字顏色
.multilineTextAlignment(.trailing) // 設置對齊方式
.lineLimit(100) // 設置允許的行數
.lineSpacing(10) // 設置行間距
.padding() // 設置View的四周留一些空間
.rotationEffect(.degrees(45), anchor: UnitPoint(x: 0, y: 0)) // 旋轉View
.shadow(color: .gray, radius: 2, x: 0, y: 15) // 添加陰影
}
}
// 用於預覽
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
請注意.font修飾符返回的是Text類型,而padding修飾符返回的是some View。
2.1 圖片
SwiftUI提供了一個Image的視圖,它可以用來展示圖片。
Image(systemName: "cloud.heavyrain") // CF symbol字體所提供的圖形
.font(.system(size: 200)) // 由於圖形使用字體來實現的,所以可以以文字的形式來處理
.foregroundColor(.red)
.shadow(color: .gray, radius: 10, x: 0, y: 10)
Image("bigpic") // 自己添加的圖形
.resizable() // 設置圖形可以縮放
.aspectRatio(contentMode: .fill) //填充形式的縮放
.frame(width:300) // 圖形的寬度
.clipShape(Ellipse()) // 使用橢圓對圖形進行裁剪
.opacity(0.9) // 設置不透明度
.overlay(
// 使用另外的圖形對原有圖形進行覆蓋
Image(systemName: "heart.fill")
.foregroundColor(.red)
.font(.system(size:100))
)
2.3 佈局視圖
在UIKit中有UIStackView,在SwiftUI中由於沒有自動佈局所以提供了與之類似的HStack、VStack、ZStack視圖用於佈局。通過從名字來看,可以看出來他們用於實現水平、垂直、垂直屏幕方向的佈局。
struct ContentView: View {
var body: some View {
VStack {
HeaderView()
HStack {
PriceView(title: "Basic", price: "$9", textColor: .white, bgColor: .purple)
ZStack {
PriceView(title: "Pro", price: "$19", textColor: .white, bgColor: .gray)
Text("Best for designer")
.font(.system(.caption, design: .rounded))
.fontWeight(.bold)
.foregroundColor(.white)
.padding(5)
.background(Color(red: 1, green: 183/255, blue: 37/255))
.offset(x: 0, y: 87)
}
}
.padding(.horizontal)
ZStack {
PriceView(icon: "wand.and.rays", title: "Team", price: "$299", textColor: .white, bgColor: .blue).padding()
Text("Perfect for teams with 20 members")
.font(.system(.caption, design: .rounded))
.fontWeight(.bold)
.foregroundColor(.white)
.padding(5)
.background(Color(red: 1, green: 183/255, blue: 37/255))
.offset(x: 0, y: 87)
}
Spacer()
}
.padding()
}
}
struct HeaderView: View {
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 2) {
Text("Choose").font(.largeTitle).fontWeight(.bold)
Text("Yout Plan").font(.largeTitle).fontWeight(.bold)
}
Spacer()
}
}
}
struct PriceView: View {
var icon: String?
var title: String
var price: String
var textColor: Color
var bgColor: Color
var body: some View {
VStack {
if let icon = icon { // 在SwiftUI中只能使用部分Swift的語法,例如for之類的不能使用
Image(systemName: icon)
.font(.largeTitle)
.foregroundColor(textColor)
}
Text(title)
.font(.system(.title, design: .rounded))
.fontWeight(.black)
.foregroundColor(textColor)
Text(price)
.font(.system(size:40, weight:.heavy, design: .rounded))
.foregroundColor(textColor)
Text("per month")
.font(.headline)
.foregroundColor(textColor)
}
.frame(minWidth: 0, maxWidth:.infinity, maxHeight: 100)
.padding(40)
.background(bgColor)
.cornerRadius(10)
}
}
2.5 滾動視圖
這是一個基礎的View,其效果如圖所示。
struct CardView: View {
var image: String
var category: String
var heading: String
var author: String
var body: some View {
if #available(iOS 15.0, *) {
VStack {
Image(image)
.resizable()
.aspectRatio(contentMode: .fit)
HStack {
VStack (alignment: .leading) {
Text(category)
.font(.headline)
.foregroundColor(.secondary) // iOS13引入的,可以支持淺色模式與深色模式的切換
Text(heading)
.font(.title)
.fontWeight(.black)
.foregroundColor(.primary)
.lineLimit(3)
Text(author.uppercased())
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
}.padding(.horizontal)
}
.cornerRadius(10)
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(Color(.sRGB, red: 100/255, green: 150/255, blue: 150/255), lineWidth: 1)
}.padding([.top, .horizontal])
} else {
// Fallback on earlier versions
}
}
}
以下視圖組合了Card視圖。
// 滾動視圖模式是垂直方向滾動的,這裏修改爲水平滾動
ScrollView(.horizontal, showsIndicators: false) {
HStack {
Group {
CardView(image: "1", category: "SwiftUI", heading: "Drawing a Border with Rounded Corners", author: "Simon NG")
CardView(image: "2", category: "macOS", heading: "Building a Simple Editing App", author: "Simon NG")
CardView(image: "3", category: "watchOS", heading: "This is a Test Thing with Me", author: "Simon NG")
CardView(image: "4", category: "iOS", heading: "This is most use full thing ", author: "Simon NG")
}
.frame(width: 300)
}
}
2.6 按鈕、標籤&漸變層
var body: some View {
// 這是一個最簡單的按鈕,它包含點擊事件以及外觀
// 可以看出其與UIKit中的butttong差異還是比較大的——關於外觀設置部分
Button(action:{
print("click me")
}) {
Text("Button")
}
}
// 以下代碼效果如圖:按鈕1
Button(action:{
print("click me")
}) {
Text("Button")
.padding()
.background(Color.purple)
.foregroundColor(.white)
.font(.title)
}
// 以下代碼實現了按鈕2的效果
Button(action:{
print("click me")
}) {
Text("Button")
.padding(.all, 30)
.foregroundColor(.purple)
.background(Color.white)
.font(.title)
.padding(.all, 10)
.background(Color.purple)
}
// 以下代碼使用另外一種方式實現了按鈕2的效果
Button(action:{
print("click me")
}) {
Text("Button")
.foregroundColor(.purple)
.font(.title)
.padding()
.border(Color.purple, width: 5)
}
// 以下代碼實現了按鈕3的效果
Button(action:{
print("click me")
}) {
Text("Button")
.foregroundColor(.white)
.font(.title)
.padding()
.background(Color.purple)
.padding(10)
.border(Color.purple, width: 5)
}
// 以下代碼實現了按鈕4的效果
Button(action:{
print("click me")
}) {
if #available(iOS 15.0, *) {
Text("Button")
.fontWeight(.bold)
.font(.title)
.padding()
.background(Color.purple)
.cornerRadius(40)
.foregroundColor(Color.white)
.padding(10)
.overlay {
RoundedRectangle(cornerRadius: 40)
.stroke(Color.purple, lineWidth: 5)
}
} else {
// Fallback on earlier versions
}
}
// 以下按鈕實現了帶圖標的效果,如下圖,按鈕4所示
Button(action:{
print("click me")
}) {
HStack {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.white)
Text("Delete")
.font(.largeTitle)
.foregroundColor(Color.white)
}
.padding()
.background(Color.red)
.cornerRadius(20)
}
// 以下代碼使用iOS14新增的Label組件實現了相同的功能
Button(action:{
print("click me")
}) {
Label (
title:{
Text("Delete")
.font(.largeTitle)
.foregroundColor(Color.white)
},
icon: {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.white)
}
)
.padding()
.background(Color.red)
.cornerRadius(20)
}
// 以下代碼實現了漸變背景的按鈕,如圖按鈕5所示
Button(action:{
print("click me")
}) {
Label (
title:{
Text("Delete")
.font(.largeTitle)
.foregroundColor(Color.white)
},
icon: {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.white)
}
)
.padding()
.background(LinearGradient(gradient:Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(20)
}
// 某些情況下,想要自定義符合某種要求的按鈕並廣泛複用,可以採用buttonStyle方式來定義
// 最終效果如按鈕6所示
struct SwiftUIButton: View {
var body: some View {
Button(action:{
print("click me")
}) {
HStack {
Image(systemName: "trash")
.font(.largeTitle)
Text("Delete")
.font(.largeTitle)
}
}
.buttonStyle(GradientBackgroundStyle())
}
}
struct GradientBackgroundStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.foregroundColor(.white)
.background(LinearGradient(gradient:Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(40)
.padding(.horizontal, 20)
}
}
struct SwiftUIButton_Previews: PreviewProvider {
static var previews: some View {
SwiftUIButton()
}
}
2.7 狀態綁定
在編寫UI界面相關的額代碼時,經常需要根據用戶的操作、系統的狀態等改變界面。採用UIKit編碼時,針對此種情況,一般是需要編寫一套創建UI的代碼,當用戶操作發生或內部狀態發生變化後,再執行更新UI的代碼。由於創建於更新UI的代碼不是相同的,很可能會導致多種UI的錯誤。在SwiftUI中,通過@State@Binding這兩個關鍵字(其實他們是屬性包裝器)來實現UI的創建於更新過程的一致。以下將通過實例來演示如何使用。
struct SwiftUIState: View {
// 以State標識的屬性在body範圍內的修改,會自動觸發UI刷新
private @State var isPlaying = false
var body: some View {
Button (action:{
print("click me")
isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "stop.circle.fill" : "play.circle.fill")
.font(.system(size: 50))
.foregroundColor(isPlaying ? .red : .green)
}
}
}
如果有三個按鈕,且他們都能展示當前用戶點擊次數,爲了實現這個功能需要用到@State與@Binding。
struct SwiftUIState: View {
@State var counter = 1
var body: some View {
VStack {
CounterButton(counter: $counter, color: .red) // 注意,這裏要加$
CounterButton(counter: $counter, color: .green)
CounterButton(counter: $counter, color: .blue)
}
}
}
struct SwiftUIState_Previews: PreviewProvider {
static var previews: some View {
SwiftUIState()
}
}
struct CounterButton: View {
@Binding var counter: Int
var color: Color
var body: some View {
Button (action:{
print("click me")
counter += 1
}) {
if #available(iOS 15.0, *) {
Circle()
.frame(width: 100, height: 100)
.foregroundColor(color)
.overlay {
Text("\(counter)")
.font(.system(size:100, weight: .bold, design: .rounded))
.foregroundColor(.white)
}
} else {
// Fallback on earlier versions
}
}
}
}
注意
在CounterButton中添加@Binding,代表其中的counter,是由外部來傳遞而來的。可以簡單理解爲傳遞了一個指針。
2.8 自繪
在SwiftUI中可以使用Path進行一些繪製,其與NSBezierPath類似。
// 效果如path1所示
struct SwiftUIPath: View {
var body: some View {
VStack {
Path { path in
path.move(to: CGPoint(x: 20, y: 20))
path.addLine(to: CGPoint(x: 300, y: 20))
path.addLine(to: CGPoint(x: 300, y: 100))
path.addLine(to: CGPoint(x: 20, y: 100))
}.fill(.red)
Path { path in
path.move(to: CGPoint(x: 20, y: 20))
path.addLine(to: CGPoint(x: 300, y: 20))
path.addLine(to: CGPoint(x: 300, y: 100))
path.addLine(to: CGPoint(x: 20, y: 100))
}.stroke(.blue, lineWidth: 3)
Path { path in
path.move(to: CGPoint(x: 20, y: 20))
path.addLine(to: CGPoint(x: 300, y: 20))
path.addLine(to: CGPoint(x: 300, y: 100))
path.addLine(to: CGPoint(x: 20, y: 100))
path.closeSubpath()
}.stroke(.purple, lineWidth: 3)
Path() { path in
path.move(to: CGPoint(x: 20, y: 60))
path.addLine(to: CGPoint(x: 40, y: 60))
path.addQuadCurve(to: CGPoint(x: 210, y: 60), control: CGPoint(x: 125, y: 0))
path.addLine(to:CGPoint(x:230, y:60))
path.addLine(to:CGPoint(x:230, y:100))
path.addLine(to:CGPoint(x:20, y:100))
}
.fill(.purple)
// fill 和 stroke不可同時使用,爲了實現既填充又描邊,可以採用ZStack
ZStack {
Path() { path in
path.move(to: CGPoint(x: 20, y: 60))
path.addLine(to: CGPoint(x: 40, y: 60))
path.addQuadCurve(to: CGPoint(x: 210, y: 60), control: CGPoint(x: 125, y: 0))
path.addLine(to:CGPoint(x:230, y:60))
path.addLine(to:CGPoint(x:230, y:100))
path.addLine(to:CGPoint(x:20, y:100))
}
.fill(.purple)
Path() { path in
path.move(to: CGPoint(x: 20, y: 60))
path.addLine(to: CGPoint(x: 40, y: 60))
path.addQuadCurve(to: CGPoint(x: 210, y: 60), control: CGPoint(x: 125, y: 0))
path.addLine(to:CGPoint(x:230, y:60))
path.addLine(to:CGPoint(x:230, y:100))
path.addLine(to:CGPoint(x:20, y:100))
path.closeSubpath()
}.stroke(.red, lineWidth: 3)
}
Path { path in
path.move(to: CGPoint(x: 100, y: 100))
path.addArc(center: .init(x: 100, y: 100)
, radius: 50
, startAngle: Angle(degrees: 0.0)
, endAngle: Angle(degrees: 90)
, clockwise: true)
}.fill(.red)
}
}
}
// 通過實現Shape協議來實現複用圖形,參加path2
struct SwiftUIPath: View {
var body: some View {
Button(action: {
}) {
Text("自定義Shape")
.font(.system(.title, design: .rounded))
.bold()
.foregroundColor(.white)
.frame(width: 250, height: 50)
.background(CustomShape().fill(Color.red))
}
}
}
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 0, y:9))
path.addQuadCurve(to: CGPoint(x: rect.size.width, y:0)
, control: CGPoint(x:rect.size.width/2, y:0 - rect.size.width * 0.1))
path.addRect(CGRect(x:0, y:0, width: rect.size.width, height: rect.size.height))
return path;
}
}
除此之外,SwiftUI還內置了Circle、Rectangle、RounderRectangle、Ellipse等圖形。
2.9 動畫與轉場
在SwiftUI中通過animation修飾器來實現隱式動畫,通過withAnimation塊中編寫動畫代碼來實現顯式動畫。
// 效果如animation所示
struct SwiftUIAnimation: View {
@State private var circleColorChange = false
@State private var heartColorChange = false
@State private var heartSizeChange = false
var body: some View {
VStack {
// 隱式動畫
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChange ? Color(.systemGray6) : .red)
.animation(.default) // 1
Image(systemName: "heart.fill")
.foregroundColor(heartColorChange ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChange ? 1.0 : 0.5)
.animation(.default) // 2
}
// .animation(.default) 在這裏寫也可以,如果在這裏寫的話,需要把1、2兩行刪除
.onTapGesture {
self.circleColorChange.toggle()
self.heartSizeChange.toggle()
self.heartColorChange.toggle()
}
// 顯示動畫
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChange ? Color(.systemGray6) : .red)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChange ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChange ? 1.0 : 0.5)
}
.onTapGesture {
// 動畫效果1
// withAnimation(.default) {
// self.circleColorChange.toggle()
// self.heartSizeChange.toggle()
// self.heartColorChange.toggle()
// }
// 動畫效果2
// withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
// self.circleColorChange.toggle()
// self.heartSizeChange.toggle()
// self.heartColorChange.toggle()
// }
// 動畫效果3,新型大小不做動畫
withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
self.circleColorChange.toggle()
self.heartColorChange.toggle()
}
self.heartSizeChange.toggle()
}
}
}
}
// 以下是一些更具實際意義的動畫實例,參見anamition2
struct SwiftUIAnimation2: View {
@State private var isLoading = false
@State private var progess: Double = 0.0
@State private var recordBegin = false
@State private var recording = false
var body: some View {
VStack {
Circle()
.trim(from: 0, to: 0.7)
.stroke(Color.green, lineWidth: 5)
.frame(width: 100, height: 100)
.rotationEffect(Angle(degrees: isLoading ? 360 : 0))
.animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
.onAppear {
isLoading = true
}
Spacer()
.frame(width: .infinity, height: 50)
ZStack {
RoundedRectangle(cornerRadius: 3)
.stroke(Color(.systemGray5), lineWidth: 3)
.frame(width: 250, height: 3)
RoundedRectangle(cornerRadius: 3)
.stroke(Color.green, lineWidth: 3)
.frame(width: 30, height: 3)
.offset(x: isLoading ? 110 : -110, y: 0)
.animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
}
Spacer()
.frame(width: .infinity, height: 50)
ZStack {
Text("\(Int(progess * 100))%")
.font(.system(.title, design: .rounded))
.bold()
Circle()
.stroke(Color(.systemGray5), lineWidth: 10)
.frame(width: 150, height: 150)
Circle()
.trim(from: 0, to: progess)
.stroke(Color.green, lineWidth: 10)
.frame(width: 150, height: 150)
.rotationEffect(Angle(degrees: -90))
}
.onAppear {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
self.progess += 0.05
if self.progess >= 1.0 {
timer.invalidate()
}
}
}
Spacer()
.frame(width: .infinity, height: 50)
HStack {
ForEach(0...5, id:\.self) { index in
Circle()
.frame(width: 10, height: 10)
.foregroundColor(.green)
.scaleEffect(self.isLoading ? 0 : 1)
.animation(.linear(duration: 0.6).repeatForever().delay(0.2 * Double(index)))
}
}
Spacer()
.frame(width: .infinity, height: 50)
ZStack {
if #available(iOS 15.0, *) {
RoundedRectangle(cornerRadius: recordBegin ? 30 : 5)
.frame(width: recordBegin ? 60 : 250, height: 60)
.foregroundColor(recordBegin ? .red : .green)
.overlay {
Image(systemName: "mic.fill")
.font(.system(.title))
.foregroundColor(.white)
.scaleEffect(recording ? 0.7 : 1)
}
RoundedRectangle(cornerRadius: recordBegin ? 35 : 10)
.trim(from: 0, to: recordBegin ? 0.0001 : 1)
.stroke(lineWidth: 5)
.frame(width: recordBegin ? 70 : 260, height: 70)
.foregroundColor(.green)
} else {
// Fallback on earlier versions
}
}
.onTapGesture {
withAnimation {
recordBegin.toggle()
}
withAnimation(.spring().repeatForever().delay(0.5)) {
recording.toggle()
}
}
}
}
}
// 實現了轉場效果
struct SwiftUITransition: View {
@State private var show = false
var body: some View {
VStack {
if #available(iOS 15.0, *) {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.green)
.overlay {
Text("Show details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
}.onTapGesture {
withAnimation {
show.toggle()
}
}
if show {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.purple)
.overlay {
Text("Well, here is the details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
}
// .transition(.scale(scale: 0, anchor: .bottom))
// .transition(.slide)
// .transition(AnyTransition.offset(x: -600, y: 0).combined(with: .slide).combined(with: .opacity))
// 進入與退出時使用不同的動畫
.transition(.asymmetric(insertion: .scale(scale: 0.1, anchor: .bottom), removal: .offset(x: -600, y: 0)))
}
} else {
// Fallback on earlier versions
}
}
}
}
2.10 列表
// 以下是簡單的列表,效果見 simpleTableView
struct SwiftUIList: View {
var body: some View {
VStack {
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
Text("Item 4")
}
List {
ForEach(1...4, id:\.self) { index in
Text("Item \(index)")
}
}
List(1...4, id: \.self) {
Text("Item \($0)") //$0是閉包的一種簡略寫法,其在這裏代表index
}
}
}
}
// 以下代碼展示了動態的列表,效果參見listView
struct SwiftUIList: View {
var tankName = ["M1A2", "T90", "T72", "99A", "LieKeLieEr", "BaoEr", "09", "10"]
var tankImage = ["1", "2", "3", "4", "5", "6", "7", "8"]
var tankInfo = [
ATankInfo(name: "M1A2", image: "1"),
ATankInfo(name: "T90", image: "2"),
ATankInfo(name: "T72", image: "3"),
ATankInfo(name: "99A", image: "4"),
ATankInfo(name: "LieKeLieEr", image: "5"),
ATankInfo(name: "BaoEr", image: "6"),
ATankInfo(name: "09", image: "7"),
ATankInfo(name: "10", image: "8"),
]
var tankInfo2 = [
ATankInfo2(name: "M1A2", image: "1"),
ATankInfo2(name: "T90", image: "2"),
ATankInfo2(name: "T72", image: "3"),
ATankInfo2(name: "99A", image: "4"),
ATankInfo2(name: "LieKeLieEr", image: "5"),
ATankInfo2(name: "BaoEr", image: "6"),
ATankInfo2(name: "09", image: "7"),
ATankInfo2(name: "10", image: "8"),
]
var tankInfo3 = [
ATankInfo3(name: "M1A2", image: "1"),
ATankInfo3(name: "T90", image: "2"),
ATankInfo3(name: "T72", image: "3"),
ATankInfo3(name: "99A", image: "4"),
ATankInfo3(name: "LieKeLieEr", image: "5"),
ATankInfo3(name: "BaoEr", image: "6"),
ATankInfo3(name: "09", image: "7"),
ATankInfo3(name: "10", image: "8"),
]
var body: some View {
VStack(alignment: .leading, spacing: 1) {
List(tankName.indices, id:\.self) { index in
HStack {
Image(tankImage[index])
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(tankName[index])
.font(.system(size: 15))
.foregroundColor(.green)
}
}
List(tankInfo, id: \.name) { aTankInfo in
HStack {
Image(aTankInfo.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
List(tankInfo2, id: \.id) { aTankInfo in
HStack {
Image(aTankInfo.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
List(tankInfo3) { aTankInfo in
HStack {
Image(aTankInfo.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
List(tankInfo3) { aTankInfo in
BasicRowView(aTankInfo: aTankInfo)
}
List(tankInfo3) { aTankInfo in
BigImageRow(aTankInfo: aTankInfo)
}
List(tankInfo3.indices) { index in
if (0...1).contains(index) {
BigImageRow(aTankInfo: tankInfo3[index])
} else {
BasicRowView(aTankInfo: tankInfo3[index])
}
}
}
}
}
struct ATankInfo {
var name: String
var image: String
}
struct ATankInfo2 {
let id = UUID()
var name: String
var image: String
}
struct ATankInfo3: Identifiable {
let id = UUID()
var name: String
var image: String
}
struct SwiftUIList_Previews: PreviewProvider {
static var previews: some View {
SwiftUIList()
}
}
struct BasicRowView: View {
var aTankInfo:ATankInfo3
var body: some View {
HStack {
Image(aTankInfo.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
}
struct BigImageRow : View {
var aTankInfo:ATankInfo3
var body: some View {
VStack {
Image(aTankInfo.image)
.resizable()
.frame(width: .infinity, height: 100)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
}