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)
        }
    }
}

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