SwiftUI基礎教程(1) 第一章:SwiftUI簡介 第二章:SwiftUI基礎元素實例

第一章: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)
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章