swiftUI框架基础

前言

  1. 新建文件:command + n
  2. 代码格式化:control + shift + i
  3. 代码折叠:option + command + 向左
    一般只要有括号的地方就能折叠
  4. 代码标记:// MARK:或者 // MARK --
    点击Editor --> Minimap 后,有代码标记的部分能够快速到达

文本Text

swift 复制代码
import SwiftUI

struct textDemo: View {
    var body: some View {
        //        Text("Hello, World!")
        Text("如果大量可重新下载的资源放在默认备份目录,会消耗用户 5GB 免费 iCloud 空间,苹果审核直接驳回。解决方案")
        //            .font(.title) //标题
        //            .font(.largeTitle) //大标题
        //            .font(.title2) //二级标题
        //            .font(.title3) //三级标题

                       //字重
        //            .fontWeight(.heavy)

                       //字体样式
        //            .fontDesign(.rounded)
        //            .font(.system(size:40)) //手动设定字体大小后就不能动态调整了

        //            .bold() //加粗

        //            .italic() //斜体

        //            .underline() //下划线
        //            .underline(true, color: .red)//下划线的颜色
        //            .underline(pattern: .dash) //下划线的样式

        //            .strikethrough() //中间加删除线
        //            .strikethrough(true, color: .red)//删除线的颜色

        //            .foregroundStyle(.red)//前景字体颜色

        //            .background(.red)//背景颜色

                       //字体的大小写
        //            .textCase(.uppercase) //全大写
        //            .textCase(.lowercase)//全小写
        //            .textCase(.none)//无格式

                      //对齐格式
        //            .multilineTextAlignment(.leading)//左对齐
        //            .multilineTextAlignment(.trailing)//右对齐
        //            .multilineTextAlignment(.center)//中对齐

                       //限制行数,超出的字数用省略号表示
        //            .lineLimit(2)
                       //文本溢出,省略号加在什么地方
        //            .truncationMode(.middle) //省略号放中间
        //            .truncationMode(.head)//省略号放句首
        //            .truncationMode(.tail)//省略号放句尾

                       //行距
        //            .lineSpacing(30)

                        //字宽:标准,压缩,扩展
        //            .fontWidth(.expanded)
        //            .fontWidth(.compressed)

                        //字间距
        //            .kerning(10) //单词内字母间距
        //            .tracking(10)

                        // 句间偏移
                        //创建水平标签进行偏移
        //        HStack{
        //            Text("AA")
        //                .baselineOffset(20) //向上偏移
        //            Text("BB")
        //            Text("CC")
        //                .baselineOffset(-20)//向下偏移
        //        }

        //            .frame(width: 100,height: 200)//指定文本的宽度和高度
        //            .minimumScaleFactor(0.1)//指定缩小系数

                //全视图颜色
        //        Color.red
        //使用RGB自定义颜色,范围是0-1
        Color(red: 1, green: 0, blue: 0)
    }
}

#Preview {
    textDemo()
}

图像Image

swift 复制代码
import SwiftUI

struct image_Demo: View {
    var body: some View {
        ////Assets 图集名称,不带.jpg后缀
        //        Image("p1")
        //        Image(.p1)
        //            .resizable(resizingMode: .tile)//缩放属性-平铺,拉伸
        //            .antialiased(true)//是否抗锯齿
        //            .interpolation(.high)//插值
        //            .aspectRatio(contentMode: .fill)//像素比:填充,适合

        //系统图标
        Image(systemName: "badge.plus.radiowaves.forward")
            //渲染模式
            //原始图,模版,默认自动判断
            .renderingMode(.template)
            //SF 图标图像渲染模式
            //单色,分层,多色,调色盘
            .symbolRenderingMode(.palette)
            //开启图标自由缩放,否则 frame 无法改变图标大小
            .resizable(resizingMode: .stretch)
            //前景色
            //.foregroundStyle(.black)
            //调色盘
            .foregroundStyle(.green, .blue)
            .frame(width: 200, height: 200)
    }
}

#Preview {
    image_Demo()
}

形状Shape

swift 复制代码
import SwiftUI

struct shapeDemo: View {
    var body: some View {
        VStack{
            //纯色
            Color.red
            //矩形
            Rectangle()
                //填充
                .fill(.green)
                  //向外绘制外边框-设置线宽
//                .strokeBorder(.blue,lineWidth: 10)
                //向内填充形状路径,只描图形本身轮廓,不会向外拓展边框v
//                .stroke(.blue,lineWidth:10)
                //向外绘制外边框-(颜色,线条样式:线宽,线条端点样式,线条拐角样式,虚线数组)
//                .strokeBorder(.blue,style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round,dash: [5,20]))
            
            //圆形
            Circle()
                //必须先裁剪 trim,再填充 fill
                //上半圆起点类比钟表3点位置,从逆时针开始画圆,终点为9点位置
                //下半圆起点为9点位置,从逆时针开始画圆,终点为3点位置
//                .trim(from: 0.5, to: 0.9)
//                .fill(.orange)
                //尺寸
//                .size(CGSize(width: 50, height: 50))
                //缩放
//                .scale(1.1)
                //缩放比例,缩放锚点(锚点为固定支点拉伸)
//                .scale(1.1, anchor: .leading)
            
            //椭圆
            Ellipse()
                //旋转30度
//                .rotation(.degrees(30))
                //偏移
                .offset(CGSize(width: 10, height: 10))
            
            //圆角矩形
            RoundedRectangle(cornerSize: CGSize(width: 20, height: 30))
            
            //设置某个角为圆角的矩形
            UnevenRoundedRectangle(cornerRadii: RectangleCornerRadii(topLeading: 10))
        }
        .padding()
    }
}

#Preview {
    shapeDemo()
}

渐变Gradient

swift 复制代码
import SwiftUI

struct GradientDemo: View {
    var body: some View {
                //线性渐变
        //        LinearGradient(colors: [.red, .blue, .green], startPoint: UnitPoint(x: 0, y: 0), endPoint: UnitPoint(x: 1, y: 1))

                //从中心到四周的渐变
        //        RadialGradient(colors: [.red, .orange], center: UnitPoint(x: 0.5, y: 0.5), startRadius: 0, endRadius: 200)

                //椭圆渐变
        //        EllipticalGradient(colors: [Color.red, Color.blue])
        //        EllipticalGradient(colors: [.blue, .purple])

                //角度式渐变
        //        AngularGradient(colors: [.red, .brown, .blue], center: UnitPoint(x: 0.5, y: 0.5))

        //先创建一个矩形,再在矩形内部做填充渐变
        Rectangle()
            .fill(
                LinearGradient(
                    colors: [.red, .blue, .green],
                    startPoint: UnitPoint(x: 0, y: 0),
                    endPoint: UnitPoint(x: 1, y: 1)
                )
            )
    }
}

#Preview {
    GradientDemo()
}

三种布局堆栈HStack/VStack/ZStack

swift 复制代码
import SwiftUI

struct StackDemo: View {
    var body: some View {
                //水平堆栈
                //进行顶部对齐,底部对齐
                //进行间距设置
        //        HStack(alignment: .top, spacing: 20){
        //            Rectangle()
        //                .fill(.red)
        //                .frame(width: 200, height: 130)
        //            Rectangle()
        //                .fill(.green)
        //                .frame(width: 100, height: 190)
        //            Rectangle()
        //                .fill(.yellow)
        //                .frame(width: 150, height: 100)
        //        }

                //垂直堆栈
                //进行左对齐/右对齐
        //        VStack(alignment: .leading){
        //            Rectangle()
        //                .fill(.red)
        //                .frame(width: 200, height: 100)
        //            Rectangle()
        //                .fill(.green)
        //                .frame(width: 100, height: 100)
        //            Rectangle()
        //                .fill(.yellow)
        //                .frame(width: 150, height: 100)
        //        }

        //深度堆栈
        //可以设置对齐
        ZStack(alignment: .bottom) {
            Rectangle()
                .fill(.red)
                .frame(width: 200, height: 130)
            Rectangle()
                .fill(.green)
                .frame(width: 100, height: 190)
            Rectangle()
                .fill(.yellow)
                .frame(width: 150, height: 100)
        }
    }
}

#Preview {
    StackDemo()
}

组Group

组:与前面的三种布局堆栈类似。所以我们可以不把对象放入堆栈中,而是把它们组合在一起,不会形成新的堆叠。

swift 复制代码
import SwiftUI

struct GroupDemo: View {
    var body: some View {
        //如果要修饰其中两个文本,三种方法:
        //1.把修饰符分别放在对应文本下,这样有很多重复代码。
        //2.把那两个文本放进另一个堆栈,再用修饰符修饰这个堆栈。
        //3.使用Group,把那两个文本分为一组。这样只是把这些对象简单的字和在一起,不会形成新的堆叠。
        VStack(spacing: 30){
            Text("你好你好")
            Text("你好你好")
            
            Group{
                Text("你好你好")
                Text("你好你好")
            }
            .font(.largeTitle)
            .foregroundStyle(.yellow)
        }
        //把修饰符放在这个,时修饰整个垂直栈里的
        .font(.headline)
        .foregroundStyle(.blue)
    }
}

#Preview {
    GroupDemo()
}

图片效果

swift 复制代码
import SwiftUI

struct ImageEffect: View {
    var body: some View {
        Image("p1")
            //开启缩放模式
            .resizable()
            //像素比
            .aspectRatio(contentMode: .fill)
            //尺寸
            .frame(width: 300, height: 200)
            
        
            //模糊
//            .blur(radius: 10.0)
        
            //边框
            .border(.blue, width: 6)
        
            //亮度:0表示原图,大于0越来越亮,小于0变黑
//            .brightness(0)
        
            //裁剪:可以按图形裁剪
//            .clipShape(Circle())
            //当图片填充超过边框时,可以直接进行裁剪
//            .clipped()
        
            //颜色相乘
//            .colorMultiply(.red)
        
            //对比度:0表示没有对比度,为灰色
//            .contrast(10)
        
            //灰度处理
//            .grayscale(0.5)
        
            //色相旋转
//            .hueRotation(Angle(degrees: 90))
        
            //反相
//            .colorInvert()
        
            //遮罩
//            .mask({
                ////用图形遮罩
//                Circle()
                //用其他样式
//                Text("abx")
//                    .font(.system(size: 40))
//            })
        
            //不透明度:0表示完全不透明
//            .opacity(1)
        
            //饱和度:颜色的鲜艳程度
            //0表示完全不饱和,像灰色
//            .saturation(0)
        
            //阴影
            //radius阴影模糊半径,数值越大,阴影越模糊、扩散范围越大
//            .shadow(color: .red, radius: 10, x: 10, y: 10 )
           
    }
}

#Preview {
    ImageEffect()
}

.padding和.frame

内边距和框架:内边距会因动态特性自动更新。

swift 复制代码
import SwiftUI

struct PaddingAndFrameDemo: View {
    var body: some View {
        Text("Hello, World!")
            //内边距:默认模式是.all,20像素
//            .padding()

            //指定某个方向加内边距:上/下/左/右/垂直/水平
            //比如在水平方向添加内边距:会有左右留白的效果
//            .padding(.top, 20)

            //用数组加多个方向
//            .padding([.top, .bottom], 20)
//            .background(.red)
        
            //可重复叠加使用
//            .padding(.all, 20)
//            .background(.blue)
        
            //框架尺寸:默认情况是元素的最小尺寸
//            .background(.red)
            //自定义框架尺寸,和里面元素对齐方式(9种)
//            .frame(width: 200.0, height: 100.0, alignment: .bottomLeading)
//            .background(.red)
            
            //最小宽度,理想宽度,最大宽度(高度同理,这三个参数顺序不能调换)
//            .frame(minWidth: 10, idealWidth: 100, maxWidth: .infinity)
            
            //也可以重复叠加使用
//            .frame(width: 300, height: 200)
//            .background(.green)
    }
}

#Preview {
    PaddingAndFrameDemo()
}

.background

swift 复制代码
import SwiftUI

struct BackgroundDemo: View {
    var body: some View {
        Text("Hello World!")
            .padding(.all, 20)
            //使用一个颜色作为背景
//            .background(.red)
            
            //使用一个渐变色作为背景
//            .background(LinearGradient(colors: [.red, .yellow], startPoint:.topLeading, endPoint: .bottomTrailing))
        
            //使用一个形状作为背景
//            .background(.red, in: RoundedRectangle(cornerRadius: 25.0))
        
            //使用一个视图作为背景
//            .background(alignment: .center, content: {
//                Circle()
//                    .fill(.red)
//            })
        
        //使用一种材料作为背景
        ZStack{
            Image("p1")
            Text("niha")
                .font(.system(size: 80))
                .bold()
                //这里的材料有多种选择
                .background(.regularMaterial)
        }
    }
}

#Preview {
    BackgroundDemo()
}

.overlay

前景/覆盖层

swift 复制代码
//背景层(background)->中间层(我们自己写的)->覆盖层(overlazy)
struct overlayDemo: View {
    var body: some View {
        //        Rectangle()
        //            .fill(.orange)
        //            .frame(width: 300, height: 300)
                        //使用一个颜色作为覆盖
        //            .overlay(.blue)

                        //overlay也可以叠加使用
                        //使用渐变色作为覆盖
        //            .overlay(LinearGradient(colors: [.red, .blue], startPoint: .topLeading, endPoint: .bottomTrailing))

                        //使用材料作为覆盖
        //            .overlay(.regularMaterial)

                        //使用一个视图作为覆盖
        //            .overlay(content: {
        //                Text("hello")
        //            })
        //            .overlay(content: {
        //                Circle()
        //                    .frame(width: 20, height: 20)
        //            })

        //基于overlay的小demo
        VStack {
            Image(systemName: "heart")
                .font(.largeTitle)
                .padding()
                .foregroundStyle(.white)
                .background(content: {
                    Circle()
                        .fill(.pink)
                })
                .shadow(radius: 5)
                .overlay(alignment: .bottomTrailing) {
                    Circle()
                        .fill(.teal)
                        .frame(width: 20, height: 20)
                }
                .overlay(alignment: .bottomTrailing) {
                    Text("10")
                        .font(.caption)
                        .foregroundStyle(.white)
                        .frame(width: 20, height: 20)
                }
        }
    }
}

#Preview {
    overlayDemo()
}

间距Spacer

间距组件Spacer 的作用:自动占用容器内所有剩余空白空间,挤压它上方 / 下方的视图,类似于弹簧,自身不显示任何内容,只分配空白尺寸。

多个 Spacer 同时存在时,会均分全部剩余空间。

swift 复制代码
import SwiftUI

struct spacerDemo: View {
    var body: some View {
        VStack(alignment: .center){
            Spacer()
            
            Rectangle()
                .frame(width: 110, height: 100)
            //想把顶部和底部的空白距离,平分到矩形之间的间距上
            Spacer()
            
            Rectangle()
                .frame(width: 150, height: 100)
            Spacer()
            
            Rectangle()
                .frame(width: 100, height: 100)
            Spacer()
        }
        .frame(width: 200, height: 400, alignment: .center)
        .background(.red)
    }
}

#Preview {
    spacerDemo()
}

滚动视图ScrollView

swift 复制代码
import SwiftUI

struct scrollDemo: View {
    var body: some View {
        //垂直滚动,是否显示指示器(默认显示)
        ScrollView(.vertical, showsIndicators: false) {
            VStack {
                ForEach(1..<40) { index in
                    HStack {
                        Circle()
                            .fill(.red)
                            .frame(width: 20, height: 20)
                        Text("line\(index)")
                    }
                }

            }
        }
                //修改默认显示范围(有9种,默认是从显示居中)
                //这里设置为从底部开始显示
        //        .defaultScrollAnchor(.bottom)

                //是否显示滚动修改指示器(隐藏,可见,从不,自动),功能同上面的参数
        //        .scrollIndicators(.hidden)

                //键盘的显示(立刻,自动,交互式,从不)
                //滚动的时候如果有键盘,立即关闭
        //        .scrollDismissesKeyboard(.immediately)

                //背景内容的显示(隐藏,可见,自动)
        //        .scrollContentBackground(.hidden)

    }
}

#Preview {
    scrollDemo()
}

惰性堆栈LazyStack

swift 复制代码
import SwiftUI

struct Lazy: View {
    var body: some View {
        //惰性垂直堆栈:元素不是提前渲染好的,而是需要的时候渲染,可提高性能
        //惰性水平堆栈同理
        ScrollView {
            LazyVStack {
                ForEach(1..<200) { index in
                    Text("Hello, World!\(index)")
                        .padding()
                        .background(.red)
                }
            }
        }
    }
}

#Preview {
    Lazy()
}

分节Section

swift 复制代码
import SwiftUI

//分节
struct SectionDemo: View {
    var body: some View {
        		//内容-页脚
        //        Section {
        //            Text("nihaonihaonihaonihaonihaonihaonihaonihao,nihaonihaonihaonihaonihao,nihaonihaonihao,nihaonihaonihaonihaonihaonihao")
        //                .padding()
        //                .background(.teal)
        //                .foregroundStyle(.white)
        //                .clipShape(RoundedRectangle(cornerRadius: 25))
        //        } footer: {
        //            Text("诛仙")
        //                .font(.title)
        //        }

        //内容-页眉
        Section {
            Text(
                "你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,你好,"
            )
        } header: {
            Text("诛仙")
                .font(.largeTitle)
                .padding()
                .background(.teal)
                .foregroundStyle(.white)
                .clipShape(RoundedRectangle(cornerRadius: 25))
        }

    }
}

#Preview {
    SectionDemo()
}

内容-页脚效果图:

内容-页眉效果图:

惰性网格LazyGrid

惰性网格:有点类似于UIKit里的UICollectionView

swift 复制代码
import SwiftUI

//惰性网格LazyGrid有点类似于UIKit里的UICollectionView
struct LazyGridDemo: View {
    var body: some View {
        ScrollView {
            //垂直惰性网格(水平惰性网格用法同理)
            LazyVGrid(
                columns: //在数组里控制列数,并且控制每一个元素的对齐方式(默认居中)
                [
                    GridItem(.fixed(100), alignment: .leading),
                    GridItem(.fixed(100)),
                    GridItem(.fixed(100)),
                ],
                //控制列的对齐方式.默认居中
                alignment: .center,
                pinnedViews: [.sectionHeaders],
                content: {

                    Section {
                        ForEach(1..<150) { index in
                            Text("hello\(index)")
                                .padding()
                                .background(.red)
                        }
                    } header: {
                        Text("相册")
                            .font(.title)
                    }

                }
            )
        }
    }
}

#Preview {
    LazyGridDemo()
}

按钮Button

swift 复制代码
import SwiftUI

struct ButtonDemo: View {
    var body: some View {
        VStack(spacing: 30) {
            //方式1:
            Button("按钮A") {
                //右侧的预览画布不支持控制台输出
                print("你进行了点击")
            }
            
            //方式2:
            Button(
                action: {
                    //代码
                },
                label: {
                    Text("按钮B")
                        .padding()
                        .padding(.horizontal, 20)
                        .background(.teal)
                        .clipShape(RoundedRectangle(cornerRadius: 25))
                        .foregroundStyle(.white)
                        .shadow(radius: 10)
                }
            )
            
            //方式3:
            Button("你好", systemImage: "heart") {
                //代码
            }
        }
    }
}

#Preview {
    ButtonDemo()
}
swift 复制代码
import SwiftUI
internal import System

struct LinkDemo: View {
    var body: some View {
        Link("www.baidu.com", destination: URL(filePath: "https://www.baidu.com")!)
    }
}

#Preview {
    LinkDemo()
}

状态属性包装器@State+分离视图

1、给某个变量加上状态属性包装器,就是告诉视图监测这个变量,一旦这个变量变了,就有可能更新视图

2、进行视图的分离(一般容器内元素是静态的)

swift 复制代码
import SwiftUI

struct StateDemo: View {
    
    //让视图监听背景颜色
    @State var backgroundColor = Color.green
    //让视图监听标题
    @State var myTitle = "这是我的标题"
    //让视图监听计数
    @State var count:Int = 0
    
    var body: some View {
        ZStack{
            //背景
            backgroundColor
                .edgesIgnoringSafeArea(.all)//忽略安全区域
            
            //内容
            contentLayer
            
            //整个VStack里的内容前景色都是白色
            .foregroundColor(.white)
        }
        
    }
    //进行视图的分离
    var contentLayer: some View{
        VStack(spacing: 20){
            Text(myTitle)
                .font(.title)
            
            Text("count: \(count)")
                .font(.headline)
                .underline()
            
            HStack(spacing: 20){
                Button("按钮1") {
                    myTitle = "点击了按钮1"
                    backgroundColor = .red
                    count = count + 1
                }
                Button("按钮2") {
                    myTitle = "点击了按钮2"
                    backgroundColor = .purple
                    count = count - 1
                }
            }
        }
    }
}

#Preview {
    StateDemo()
}

提取子视图+初始化器

swift 复制代码
import SwiftUI

//在应用中有一个想要重复使用的视图,或许有不同的文本或颜色,那我们就可以提取子视图,使用初始化器更改文本和颜色
struct ExtracrSubviews: View {
    var body: some View {
        ZStack{
            //背景
            Color.blue
                .edgesIgnoringSafeArea(.all)
            
            //内容
            HStack {
                MyItem(title: "apple", count: 1, color: .red)
                MyItem(title: "banana", count: 2, color: .green)
                MyItem(title: "orange", count: 23, color: .yellow)
            }
        }
    }
    
}

//提取子视图并用构造器初始化
struct MyItem: View{
    
    let title: String
    let count: Int
    let color: Color
    
    var body: some View{
        VStack{
            Text("\(count)")
            Text(title)
        }
        .padding()
        .background(color)
        .cornerRadius(25)
    }
}

#Preview {
    ExtracrSubviews()
}

绑定属性包装器@Binding

子视图不知道背景颜色backgroundColor是什么,只在父视图中设置了这个变量,这里要使用Binding属性去绑定,变量名字可以随便取,一般与父视图相同,这样就可以把两个视图的变量联系起来了,子视图改变的还是父视图中的那个原变量。

swift 复制代码
import SwiftUI

struct BindingDemo: View {

    @State var backgroundColor = Color.red
    @State var title = "这个是原来的文本!!"

    var body: some View {
        ZStack {
            backgroundColor
                .edgesIgnoringSafeArea(.all)
            
            //内容
            //传绑定属性的变量时,用 $
            VStack {
                Text(title)
                    .foregroundColor(.white)
                    .font(.title)
                
                ButtonView(backgroundColor: $backgroundColor, title: $title)
                
            }

        }
    }
}

//分离子视图
struct ButtonView: View {
    
    //子视图不知道背景颜色backgroundColor是什么,只在父视图中设置了这个变量
    //这里要使用Binding属性去绑定,变量名字可以随便取,一般与父视图相同,这样就可以把两个视图的变量联系起来了,子视图改变的还是父视图中的那个原变量
    @Binding var backgroundColor:Color
    
    @State var buttonColor:Color = Color.blue
    
    @Binding var title: String
    
    var body: some View {
        Button {
            backgroundColor = .green
            buttonColor = Color.yellow
            title = "点击了按钮"
        } label: {
            Text("Button")
                .foregroundColor(.white)
                .padding()
                .padding(.horizontal, 20)
                .background(buttonColor)
                .cornerRadius(15)
        }
    }
}

#Preview {
    BindingDemo()
}

.animation

动画

toggle() 是 Swift 布尔值 Bool 的内置可变方法:

自动翻转布尔变量的值

  • true → 执行后变为 false
  • false → 执行后变为 true
swift 复制代码
import SwiftUI

struct AnimationDemo: View {

    //监测isAnimation变量,受这个变量影响的元素都会有动画效果
    @State var isAnimation: Bool = false

    var body: some View {
        VStack {

            Button("Button") {
                //添加动画
                withAnimation(
                    Animation
                        .default  //默认动画
                        .repeatCount(5, autoreverses: false)  //重复自动翻转,是否播放后回到初始状态
                ) {
                    isAnimation.toggle()
                }
            }
            Spacer()
            RoundedRectangle(cornerRadius: isAnimation ? 50 : 25)
                .fill(isAnimation ? .red : .green)
                .frame(
                    width: isAnimation ? 100 : 300,
                    height: isAnimation ? 100 : 300
                )
                //点旋转
                .rotationEffect(Angle(degrees: isAnimation ? 360 : 0))
                //偏移
                .offset(y: isAnimation ? 300 : 0)

            Spacer()
        }
    }
}

#Preview {
    AnimationDemo()
}

各种动画曲线:

swift 复制代码
import SwiftUI

struct AnimationEffectDemo: View {
    
    @State var isAnimation: Bool = false
    let time:Double = 10.0
    
    var body: some View {
        VStack{
            Button("按钮", action: {
                isAnimation.toggle()
            })
            RoundedRectangle(cornerRadius: 20)
                .frame(width: isAnimation ? 350: 50, height: 100)
                // 默认
                .animation(.default, value: isAnimation)
            
            RoundedRectangle(cornerRadius: 20)
                .frame(width: isAnimation ? 350: 50, height: 100)
                //点状线形:从头到尾速度一致
                //持续时间决定了从开始到结束的整体时长
                .animation(.linear(duration: time), value: isAnimation)
            
            RoundedRectangle(cornerRadius: 20)
                .frame(width: isAnimation ? 350: 50, height: 100)
                //点状缓入:渐入动画,一开始慢,后面快
                .animation(.easeIn(duration: time), value: isAnimation)
            
            RoundedRectangle(cornerRadius: 20)
                .frame(width: isAnimation ? 350: 50, height: 100)
                //点状缓出:先快后慢
                .animation(.easeOut(duration: time), value: isAnimation)
            
            RoundedRectangle(cornerRadius: 20)
                .frame(width: isAnimation ? 350: 50, height: 100)
                //点状缓入缓出:慢,快,慢
                .animation(.easeInOut(duration: time), value: isAnimation)
            
            RoundedRectangle(cornerRadius: 20)
                .frame(width: isAnimation ? 350: 50, height: 100)
                //弹簧动画
                .animation(.spring(
                    response: 0.5, //响应时长
                    dampingFraction: 0.7, //阻尼,弹簧的反弹程度,0.6左右
                    blendDuration: 1.0 //混合时长
                ), value: isAnimation)
        }
    }
}

#Preview {
    AnimationEffectDemo()
}

.transition

过渡效果:当有一个对象不在屏幕上,你想把它动画到屏幕上,或是从屏幕上移出

swift 复制代码
import SwiftUI

//过渡效果:
//当有一个对象不在屏幕上,你想把它动画到屏幕上,或是从屏幕上移出
struct TransitionDemo: View {
    
    @State var showView:Bool = false
    
    var body: some View {
        GeometryReader { geo in // 替换UIScreen,安全获取高度
            ZStack(alignment: .bottom){
                VStack{
                    Button("按钮", action: {
                        showView.toggle()
                    })
                    
                    Spacer()
                }
                Group{
                    if showView {
                        RoundedRectangle(cornerRadius: 30)
                            .frame(height: geo.size.height * 0.5)
                        ////从某个方向移出
//                            .transition(.move(edge: .bottom))
                        //从左向右滑出
                                .transition(.slide)
                        //设置入场和出场的过渡方式
//                            .transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .bottom)))
                            
                    }
                }
                .animation(.spring, value: showView)
            }
        }
    }
}

#Preview {
    TransitionDemo()
}

工作表单sheet和fullScreenCover

表单:卡片式过渡效果,从底部弹出在当前屏幕显示,从而在顶部展示一个新屏幕

学习要点:

1、如何添加工作表单:

需要一个用于绑定的状态变量,要把它绑定到工作表上。

2、如果关闭工作表单:

需要添加环境呈现模式,调用该模式原始值的"取消"方法,变量的名称可任意更改。

@Environment(.presentationMode) 是 SwiftUI 环境变量,专门用于关闭 sheet /fullScreenCover /NavigationView弹出的第二页面,无需从父视图传递 @Binding 布尔值,子页面自身即可控制退出

3、两个主意事项:

① sheet和fullScreenCover在视图层级中只能出现一次,不能同时添加两个工作表弹窗

② sheet的content内容块中不要添加任何条件逻辑(if...)

③ ④⑤

swift 复制代码
import SwiftUI

struct SheetDemo: View {
    
    @State var showSheet: Bool = false
    var body: some View {
        ZStack{
            Color.green
                .edgesIgnoringSafeArea(.all)
            
            Button {
                showSheet.toggle()
            } label: {
                Text("按钮")
                    .font(.largeTitle)
                    .foregroundColor(.green)
                    .padding()
                    .background(Color.white.cornerRadius(25))
            }
            //$showSheet 把State转为Binding,供给sheet系统内部修改
            .sheet(isPresented: $showSheet) {
                SeconScreen()
            }
            
            //全屏工作表
//            .fullScreenCover(isPresented: $showSheet) {
//                SeconScreen()
//            }

        }
    }
}

//创建第二个视图,等工作表出现时使用
struct SeconScreen: View{
    
    //@Environment(\.presentationMode) 是 SwiftUI 环境变量,专门用于关闭 sheet /fullScreenCover 弹出的第二页面,无需从父视图传递 @Binding 布尔值,子页面自身即可控制退出
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View{
        ZStack(alignment: .topLeading){
            Color.red
                .edgesIgnoringSafeArea(.all)
            
            Button {
                presentationMode.wrappedValue.dismiss()
            } label: {
                Image(systemName: "xmark")
                    .font(.largeTitle)
                    .foregroundColor(.white)
                    .padding()

            }
        }
    }
}

#Preview {
    SheetDemo()
//    SeconScreen()
}

呈现第二屏幕的三种方式

三种方式实现弹出框:

1.使用sheet工作表

2.使用transition过渡

3.使用动画偏移

swift 复制代码
import SwiftUI

struct PresentScreenDemo: View {
    
    @State var showScreen:Bool = false
    
    var body: some View {
        ZStack{
            Color.red
                .edgesIgnoringSafeArea(.all)
            
            VStack{
                Button("按钮") {
                    showScreen.toggle()
                }
                .font(.largeTitle)
                .foregroundColor(.white)
                Spacer()
            }
            //方式1:工作表sheet
//            .sheet(isPresented: $showScreen) {
//                newScreen()
//            }
            
            //方式2:过渡transition
//            ZStack{
//                if showScreen{
//                    newScreen(showScreen: $showScreen)
//                        .padding(.top, 60)
//                        .transition(.move(edge: .bottom))
//                        .animation(.spring(response: 0.5), value: showScreen)
//                }
//            }
                //控制 ZStack / 重叠视图 的堆叠层级顺序,数字越大,视图越靠上层,会盖住数字更小的视图
//            .zIndex(2.0)
            
            //方式3:动画偏移animation offset
            newScreen(showScreen: $showScreen)
                .padding(.top, 60)
                .offset(y: showScreen ? 0 : UIScreen.main.bounds.height)
                .animation(.spring(), value: showScreen)
        }
    }
}

struct newScreen: View {
    
    @Environment(\.presentationMode) var presentationMode
    
    @Binding var showScreen:Bool
    
    var body: some View {
        ZStack(alignment: .topLeading){
            Color.green
                .edgesIgnoringSafeArea(.all)
            
            Button {
//                presentationMode.wrappedValue.dismiss()
                showScreen.toggle()
            } label: {
                Image(systemName: "xmark")
                    .padding()
                    .foregroundColor(.white)
                    .font(.largeTitle)
            }

        }
    }
}

#Preview {
    PresentScreenDemo()
//    newScreen()
}
swift 复制代码
import SwiftUI

struct NavigationViewDemo: View {
    var body: some View {
        NavigationStack {
            ScrollView {

                //导航栏链接
                NavigationLink(
                    "你好,世界",
                    destination: NewScreen()
                )

                Text( /*@START_MENU_TOKEN@*/
                    "Hello, World!" /*@END_MENU_TOKEN@*/
                )
                Text( /*@START_MENU_TOKEN@*/
                    "Hello, World!" /*@END_MENU_TOKEN@*/
                )
                Text("Hello, World!")
            }
            //导航栏标题
            .navigationTitle("导航栏标题")
            //导航栏标题显示模式:内联模式,大字号模式(默认),自动模式
            //            .navigationBarTitleDisplayMode(.automatic)
            //隐藏导航栏
            //            .navigationBarHidden(true)
            //在导航栏上添加图像(按钮,视图) - 在左,在右,或左右
            .navigationBarItems(
                leading: Image(systemName: "person.fill")
                    .foregroundColor(.red), //给图标上色
                trailing: NavigationLink(
                    destination: NewScreen(),
                    label: {
                        Image(systemName: "gear")
                            .foregroundColor(.green)
                    }
                )
            )

        }
    }
}

struct NewScreen: View {

    //若隐藏了导航栏,可以使用环境呈现模式定义"返回"键
    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        ZStack {
            Color.red
                .edgesIgnoringSafeArea(.all)

                //在新的视图中也可以操作导航栏
                .navigationTitle("这是个红屏")
                .navigationBarHidden(true)

            VStack(alignment: .leading) {
                Button("返回") {
                    presentationMode.wrappedValue.dismiss()
                }
                //                Spacer()

                NavigationLink("点击我,跳转第三个屏幕", destination: Text("这是第三个屏幕"))
            }
        }
    }
}

#Preview {
    NavigationViewDemo()
}

补充区分:accentColor /tint/foregroundColor

  1. .accentColor / .tint:只对可交互控件生效(Link、Button、导航按钮),需要写在 NavigationStack 顶层才能作用导航栏;
  2. .foregroundColor:直接修改图片 / 文字填充色,无视容器层级,局部写在图标上一定生效;
  3. 不要在 .navigationBarItems 闭包外部写着色修饰符,无法穿透到导航栏内部视图。

ForEach的使用

一,基础作用

ForEach 是循环生成视图的容器,接收一个数组数据源,自动遍历数组,为每一条数据生成一份子视图。

swift 复制代码
@State var fruits:[String] = ["西瓜","桃子","雪梨","香蕉","苹果"]
    
    var body: some View {
        List{
            ForEach(fruits, id: \.self) { fruit in
                Text(fruit)
            }
        }
    }

二,参数拆解

1.第一个参数:数据源 fruits

要求遵循 RandomAccessCollection 协议,常见:Array、Range、Set。

你的数据源:String 字符串数组,存放水果名称。

2.第二个参数 id: .self(标识规则,重中之重)

SwiftUI 需要唯一 ID 区分每一行视图,用来做刷新、删除、动画差分。

  • .self:KeyPath 语法,代表用元素自身作为唯一 ID
    适用场景:数组元素是不可变基础类型 (String、Int、Double),每个元素值不重复
    你的水果名称不重复,所以可以直接写 id: .self。

两种 ID 写法对比:

  1. id: .self
swift 复制代码
// 元素是String/Int,值唯一时使用
ForEach(fruits, id: \.self) { fruit in Text(fruit) }
  1. 自定义模型 id: .属性名(业务最常用)
    如果是自定义结构体,必须给结构体加 Identifiable 协议,或指定唯一标识字段:
swift 复制代码
// 模型
struct Fruit: Identifiable {
    let id = UUID() // 自带唯一id
    let name: String
}
// 数组
@State var fruitList: [Fruit] = [...]
// 循环,省略id参数(因为遵循Identifiable)
ForEach(fruitList) { item in Text(item.name) }

3.尾随闭包:生成单行视图

{ fruit in Text(fruit) }

  • fruit:遍历过程中取出的单条数据(数组里单个字符串)
  • 闭包内:写你需要的单行 UI(Text、HStack、图片按钮等任意 View)

三,四种常用写法

写法 1:遍历下标 indices(你当前写法,仅静态小数组临时用)

swift 复制代码
let arr = ["A","B","C"]
ForEach(arr.indices) { index in
    Text(arr[index])
}

arr.indices 是什么?

数组.indices 会生成该数组所有下标索引的区间

写法 2:id: .self 直接遍历值(简单值类型:String/Int)

swift 复制代码
let arr = ["最新的", "最受欢迎的", "最多点赞的"]
ForEach(arr, id: \.self) { text in
    Text(text)
}

原理:用元素自身作为唯一标识 id

缺点:数组存在重复值时 id 冲突,删除 / 编辑功能失效

写法 3:遍历遵循 Identifiable 的模型(项目标准推荐)

swift 复制代码
// 模型
struct Item: Identifiable {
    let id = UUID()
    let name: String
}
// 使用
@State var list: [Item] = [
    Item(name:"最新的"),
    Item(name:"最受欢迎的")
]
ForEach(list) { item in
    Text(item.name)
}

原理:模型自带唯一 id,自动识别,不用手动写 id:.self

写法 4:自定义路径 id: . 自定义属性(有唯一标识字段的模型)

如果模型自带唯一编号(比如数据库 id),不用 UUID:

swift 复制代码
struct Item {
    let itemID: Int
    let name: String
}
ForEach(list, id: \.itemID) { item in
    Text(item.name)
}

列表List

学习要点:

1.如何创建一个列表

2.如何定义列表的删除方法和移动方法

3.如何使用自定义导航栏的编辑按钮

4.设置列表样式

5.设置列表项背景颜色

swift 复制代码
import SwiftUI

struct ListDemo: View {

    @State var fruits: [String] = ["西瓜","桃子","雪梨","香蕉","苹果"]
    
    @State var vaggies: [String] = ["土豆","茄子","胡萝卜","马铃薯","白菜"]

    var body: some View {
        NavigationStack {
            List {
                Section("水果")
                {
                    ForEach(fruits, id: \.self, content: { fruit in
                        Text(fruit)
                            .font(.title)
                            .foregroundColor(.green)
                            .background(.blue)
                    })
                    //删除列表元素
                    .onDelete(perform: delete)
                    //移动列表元素
                    .onMove(perform: move)
                    
                    //更改列表项背景
                    .listRowBackground(Color.brown)
                }
                
                Section("蔬菜") {
                    ForEach(vaggies, id: \.self, content: { vaggy in
                        Text(vaggy)
                    })
                }
                
            }
            //设置列表样式
//            .listStyle(.automatic)
            .navigationTitle("购物清单")
            //EditButton()内置编辑按钮
            .navigationBarItems(
                leading: EditButton(),
                trailing:AddButton
            )
        }
        //添加在这里时自定义导航栏里的元素变色
        .tint(.red)
    }
    
    var AddButton: some View{
        Button("Add", action: {
            add()
        })
    }
    
    func delete(indexSet: IndexSet) {
        fruits.remove(atOffsets: indexSet)
    }
    func move(indexSet:IndexSet, newOffset:Int){
        fruits.move(fromOffsets: indexSet, toOffset: newOffset)
    }
    func add(){
        fruits.append("椰子")
    }
}

#Preview {
    ListDemo()
}

警告弹窗Alert

警告弹窗从屏幕中间弹出,限制为两个按钮

swift 复制代码
import SwiftUI

struct AlertDemo: View {

    @State var backgroundColor: Color = Color.yellow
    @State var showAlert: Bool = false
    @State var alertType: MyAlert? = nil

    //枚举警告类型
    enum MyAlert {
        case error
        case success
    }

    var body: some View {
        ZStack {
            backgroundColor.edgesIgnoringSafeArea(.all)
            VStack {
                Button("按钮1") {
                    alertType = .error
                    showAlert.toggle()
                }
                .font(.title)
                .bold()
                .foregroundColor(.red)

                Button("按钮2") {
                    alertType = .success
                    showAlert.toggle()
                }
                .font(.title)
                .bold()
                .foregroundColor(.red)

                Spacer()
            }
            
            // 启用一个警告弹窗
            .alert(isPresented: $showAlert) {
                getAlert()
            }
        }
    }

    func getAlert() -> Alert {
                //普通弹窗
        //        return Alert(title: Text("这是一个弹窗"))

                //双键弹窗
        //        return Alert(title: Text("这是一个弹窗"), primaryButton: .destructive(Text("删除")), secondaryButton: .cancel())

        //根据警告类型触发相应弹窗
        switch alertType {
        case .error:
            return Alert(title: Text("这是一个错误!!"))
        case .success:
            return Alert(
                title: Text("这成功里!!"),
                message: nil,
                dismissButton: .default(
                    Text("OK"),
                    action: {
                        backgroundColor = .green
                    }
                )
            )
        case nil:
            return Alert(title: Text("文本错误!!"))
        default:
            return Alert(title: Text("error!"))
        }
    }
}

#Preview {
    AlertDemo()
}

操作表ActionSheet

操作表是从屏幕底部弹出,可自定义多个按钮。

但是这里有一个问题:

部分 Xcode /iOS 版本会持续渲染成居中气泡弹窗,无论是否加 NavigationStack、navigationTitle,并不会从底部弹出。

iOS13~14 用的是ActionSheet

swift 复制代码
import SwiftUI

//操作表
struct actionSheetDemo: View {

    @State var showActionSheet: Bool = false

    var body: some View {

        VStack {
//            Spacer()
            Button("点击弹出操作表") {
                showActionSheet.toggle()
            }
            .font(.title)
            
        }
        .actionSheet(isPresented: $showActionSheet) {
            getActionSheet()
        }
    }
    func getActionSheet() -> ActionSheet {
        //普通弹窗
        //        return ActionSheet(title: Text("这个是标题"))

        //多个按钮的弹窗
        let button1: ActionSheet.Button = .default(Text("默认按钮"))
        let button2: ActionSheet.Button = .destructive(Text("销毁按钮"))
        let button3: ActionSheet.Button = .cancel(Text("取消按钮"))

        return ActionSheet(
            title: Text("这是标题"),
            message: Text("这是信息"),
            buttons: [button1, button2, button3]
        )
    }
}

#Preview {
    actionSheetDemo()
}

Popover 气泡模式:不支持底部取消栏,忽略 .cancel 按钮。

iOS15+ .confirmationDialog

swift 复制代码
import SwiftUI

//操作表
struct actionSheetDemo: View {

    // 控制操作表显隐
    @State var showDialog = false

    var body: some View {
        NavigationStack {
            VStack(spacing: 40) {
                Button("打开底部操作菜单") {
                    showDialog.toggle()
                }
                .font(.largeTitle)
            }
            .navigationTitle("操作测试") // 新增导航标题,补齐页面结构
            .confirmationDialog(
                "请选择操作",
                isPresented: $showDialog,
                titleVisibility: .visible
            ) {
                Button("复制") {
                    print("点击复制")
                }
                Button("分享") {
                    print("点击分享")
                }
                // 危险操作:红色文字
                Button("删除", role: .destructive) {
                    print("执行删除")
                }
                // 取消按钮
                Button("取消", role: .cancel) {}
            } message: {
                Text("删除后数据无法恢复,请谨慎操作")
            }
        }
    }
}

#Preview {
    actionSheetDemo()
}

上下文菜单contextMenu

上下文菜单:用户点击一个对象,按住约半秒后,对象旁边会弹出一个小上下文菜单,里面会有一系列不同按钮。

swift 复制代码
import SwiftUI

struct ContexMenuDemo: View {
    @State var backgroundColor:Color = Color.red
    
    var body: some View {
        VStack(alignment: .leading, spacing: 10.0){
            Image(systemName: "house.fill")
                .font(.title)
            Text("学习swift")
                .font(.headline)
            Text("如何使用上下文菜单")
                .font(.subheadline)
        }
        .padding(50)
        .background(backgroundColor.cornerRadius(10.0))
        .foregroundColor(.white)
        //使用上下文菜单
        .contextMenu {
            Button {
                backgroundColor = Color.green
            } label: {
                Label("按钮1", systemImage: "flame.fill")
            }
            
            Button {
                backgroundColor = Color.blue
            } label: {
                Text("这是按钮2")
            }
            
            Button {
                backgroundColor = Color.yellow
            } label: {
                HStack{
                    Text("这是按钮3")
                    Image(systemName: "heart.fill")
                }
            }
        }  
    }
}

#Preview {
    ContexMenuDemo()
}

文本输入框TextField

文本输入框:用户可以直接输入文本,但是只能输入一行。

下面实现了一个文本编辑器,当输入的字符>=3个时,"保存"键才能点击使用,并且把输入的文本回显。

swift 复制代码
import SwiftUI

struct TextFieldDemo: View {
    
    @State var textField:String = ""
//    @State var backgroundColor:Color = Color.red
    @State var textArray:[String] = []
    
    var body: some View {
        NavigationStack{
            VStack{
            	//创建文本框
                TextField("请输入....", text: $textField)
                    .font(.title)
                    .padding()
                    .background(Color.gray.opacity(0.3).cornerRadius(10))
                    .foregroundColor(Color.red)
                    //设置文本框样式
    //            .textFieldStyle(.automatic)
                
                Button {
                    if checkText() {
                        saveText()
                    }
                    
                } label: {
                    Text("保存")
                        .font(.title)
                        .padding()
                        .frame(width: 600)
                        .background(checkText() ? Color.blue : Color.gray)
                        .cornerRadius(10)
                        .foregroundColor(Color.white)
                }
                //当文本符合要求时,按钮才能用
                .disabled(!checkText())
                
                ForEach(textArray, id: \.self) { data in
                    Text(data)
                        .padding()
                }
                
                Spacer()

            }
            .padding()
            .navigationTitle(Text("文本编辑器"))
        }
    }
    //检查文本:当文本字符大于三个时才保存
    func checkText() -> Bool{
        if textField.count >= 3{
            return true
        }
        return false
    }
    //保存文本
    func saveText(){
        textArray.append(textField)
        textField = ""
    }
}

#Preview {
    TextFieldDemo()
}

文本编辑器TextEditor

文本编辑器:用户直接输入文本,可以输入多行。

swift 复制代码
import SwiftUI

struct TextEditorDemo: View {
    
    @State var textEditorText: String = "请输入文字....."
    @State var saveText: String = ""
    
    var body: some View {
        NavigationStack{
            VStack{
                //创建文本编辑器
                TextEditor(text: $textEditorText)
                    .frame(height: 250)
                Button {
                    saveText = textEditorText
                } label: {
                    Text("保存")
                        .font(.title)
                        .foregroundColor(Color.white)
                        .padding()
                        .frame(maxWidth: .infinity)
                        .background(Color.blue)
                        .cornerRadius(15)
                }
                Text(saveText)
                    .padding()
                
                Spacer()
            }
            .padding()
            .background(Color.green)
            .navigationTitle("文本编辑器")
        }
    }
}

#Preview {
    TextEditorDemo()
}

切换开关Toggle

切换开关

下面实现:根据开关的状态,切换"在线"和"离线"

swift 复制代码
import SwiftUI

struct ToggleDemo: View {
    
    @State var isToggle:Bool  = false
    
    var body: some View {
        
        VStack {
            HStack{
                Text("状态:")
                Text(isToggle ? "Online" : "Offline")
            }
            .font(.title)
            
            //创建一个切换开关
            Toggle(isOn: $isToggle) {
                Text("标签")
            }
            //设置开关样式:开关式,按钮式,自动(注意:这个设置不能和下面的设置同时存在)
            //        .toggleStyle(.switch)
            //设置切换开关时的颜色
            .toggleStyle(SwitchToggleStyle(tint: Color.purple))
  //        .toggleStyle(ButtonToggleStyle())
            
            Spacer()
        }
        .padding(.horizontal, 110)

    }
}

#Preview {
    ToggleDemo()
}

选择器Picker

1.滚轮选择器

swift 复制代码
import SwiftUI

//选择器
struct PickerDemo: View {

    @State var selection: String = "1"

    var body: some View {
        VStack {
            HStack {
                Text("年龄:")
                Text(selection)
            }
            //创建一个选择器
            Picker(
                selection: $selection,
                content: {
                    ForEach(18..<100) { number in
                        Text("\(number)")
                            //设置里选择器样式之后才能设置文本样式
                            .font(.title)
                            .foregroundColor(.red)
                            .tag("\(number)")
                    }
                },
                label: {
                    Text("Picker")
                }
            )
            //设置选择器样式为:滚轮
            .pickerStyle(.wheel)
        }
    }
}

#Preview {
    PickerDemo()
}

2.菜单选择器

swift 复制代码
import SwiftUI

//选择器
struct PickerDemo: View {

    @State var selection: String = "18"
    let Options: [String] = ["最新的", "最受欢迎的", "最多点赞的"]

    var body: some View {
        Picker(selection: $selection,
               content: {
            ForEach(Options, id: \.self){ option in
                HStack {
                    Text(option)
                    Image(systemName: "heart.fill")
                }
                .tag(option)
            }
        }, label: {
            HStack {
                Text("Filter:")
                Text(selection)
            }
            
        })
        //设置为菜单样式
        .pickerStyle(.menu)
       
#Preview {
    PickerDemo()
}

3.分段选择器

swift 复制代码
import SwiftUI

//选择器
struct PickerDemo: View {

    @State var selection: String = "18"
    let Options: [String] = ["最新的", "最受欢迎的", "最多点赞的"]

    var body: some View {
        
        Picker(selection: $selection,
               content: {
            ForEach(Options.indices){ index in
                Text(Options[index])
                    .tag(Options[index])
            }
        },
               label: {
            Text("Filter:")
        })
        //设置样式为分段选择器
        .pickerStyle(.segmented)

#Preview {
    PickerDemo()
}

颜色选择器ColorPicker

swift 复制代码
import SwiftUI

struct ColorPickerDemo: View {
    
    @State var backgroundColor: Color = Color.red
    
    var body: some View {
        ZStack{
            backgroundColor
                .edgesIgnoringSafeArea(.all)
            
            //创建一个颜色选择器
            ColorPicker(selection: $backgroundColor,supportsOpacity: true) {
                Text("选择一种颜色")
            }
            .font(.title)
            .padding()
            .background(.green)
            .foregroundColor(.white)
            .cornerRadius(15)
        }
    }
}

#Preview {
    ColorPickerDemo()
}

日期选择器DatePicker

swift 复制代码
mport SwiftUI

struct DatePickerDemo: View {
    
    @State var selectDate:Date = Date()
    
    var body: some View {
        //创建显示年月日时分的普通日期选择器
//        DatePicker("选择日期:", selection: $selectDate)
        //创建只显示年月/只显示时分的日期选择器
        DatePicker(selection: $selectDate, displayedComponents: [.hourAndMinute],
                   label: { Text("选择日期:")})
            //设置选择器的样式
            //1.紧凑样式(默认)
            .datePickerStyle(.compact)
            //2.图形样式
//            .datePickerStyle(.graphical)
            //3.滚轮样式
//            .datePickerStyle(.wheel)
            
    }
}

#Preview {
    DatePickerDemo()
}

步进器Stepper

swift 复制代码
import SwiftUI

struct StepperDemo: View {
    
    @State var stepValue: Int = 10
    @State var widthIncrement: CGFloat = 0
    
    var body: some View {
        VStack{
            //普通步进器,每次只能加1减1
            Stepper("Stepper: \(stepValue) ", value: $stepValue)
            
            RoundedRectangle(cornerRadius: 25.0)
                .frame(width: 100 + widthIncrement, height: 100)
            
            
            //自定义步进器
            Stepper("点击调整矩形大小") {
                //增加时
                adjustRec(step: 10)
            } onDecrement: {
                //减少时
                adjustRec(step: -10)
            }

        }
    }
    func adjustRec(step: CGFloat){
        //加上动画视觉更丝滑
        withAnimation(.easeInOut) {
            widthIncrement += step
        }
        
    }
}

#Preview {
    StepperDemo()
}

滑块Slider

swift 复制代码
import SwiftUI

struct SliderDemo: View {
    
    //初始值
    @State var sliderValue: Double = 1
    @State var color:Color = .red
    
    var body: some View {
        VStack() {
            HStack {
                Text("当前值为:")
                Text(String(format: "%0.2f", sliderValue))
//                Text("\(sliderValue)")
            }
            .foregroundColor(color)
            //创建普通的滑块
//            Slider(value: $sliderValue)
            //自定义滑块范围和步长
//            Slider(value: $sliderValue, in: 1...100,step: 0.5)
            Slider(
                value: $sliderValue,
                in: 1...10,
                step: 1.0,
                label: {
                    Text("标题")
                },
                minimumValueLabel: {
                    Text("1")
                }, maximumValueLabel: {
                    Text("10")
                },onEditingChanged: { _ in
                    color = Color.green
                })
                .tint(.red)
        }
    }
}

#Preview {
    SliderDemo()
}

底部标签栏TabView和分页标签PageTabView

1.底部标签栏

点击底部标签可以进入相应页面

swift 复制代码
import SwiftUI

struct TabViewDemo: View {
    
    //初始时选择的标签页(就是一开始在第几页)
    @State var selectTab: Int = 0
    
    var body: some View {
        //创建一个tab
        TabView(selection: $selectTab) {
            HomeView(selectTab: $selectTab) //把视图部分提取出来
                .tabItem {
                    Image(systemName: "house.fill")
                    Text("主页")
                }
                .tag(0)
            Text("网络")
                .tabItem {
                    Image(systemName: "globe")
                    Text("网络")
                }
                .tag(1)
            Text("联系人")
                .tabItem {
                    Image(systemName: "person.fill")
                    Text("联系人")
                }
                .tag(2)
        }
        .tint(.red)
    }
}

//分离子视图
struct HomeView: View {
    
    @Binding var selectTab: Int
    
    var body: some View {
        ZStack{
            Color.red.edgesIgnoringSafeArea(.all)
            VStack{
                
                Text("首页")
                    .font(.largeTitle)
                    .foregroundColor(.white)
                
                Button {
                    selectTab = 2
                } label: {
                    Text("点击跳转到联系人")
                        .font(.headline)
                        .padding()
                        .padding(.horizontal)
                        .background(.white)
                        .cornerRadius(10)
                }

            }
        }
    }
}
    
#Preview {
    TabViewDemo()
}

2.分页标签(分页轮播)

滑动视图可以切换图片

swift 复制代码
import SwiftUI

struct TabViewDemo: View {
    
    //初始时选择的标签页(就是一开始在第几页)
    @State var selectTab: Int = 0
    
    let icons: [String] = [
        "heart.fill", "globe","house.fill","person.fill"
    ]
    
    var body: some View {
        
        TabView{
            ForEach(icons,
                    id: \.self) { icon in
                Image(systemName: icon)
                    .resizable()
                    .scaledToFit()
                    .padding(30)
            }
        }
        .background(
            //背景加上渐变色
            RadialGradient(colors: [Color.red, Color.blue], center: .center, startRadius: 5, endRadius: 300)
        )
        .frame(height: 300)
        //设置样式为分页标签
        .tabViewStyle(.page)
    }
}

#Preview {
    TabViewDemo()
}

暗模式

应用中的暗模式:黑色背景,白色文字。

学习要点:哪些颜色能在明暗模式间自动切换,哪些不能,如何添加应用中的可适配颜色

swift 复制代码
import SwiftUI

struct DarkModeDemo: View {
    
    //获取当前的深浅模式
    @Environment(\.colorScheme) var colorScheme
    
    var body: some View {
        NavigationStack{
            ScrollView{
                VStack(spacing: 20) {
                    Text("这个颜色是主色")
                        .foregroundColor(.primary)//会自动适配
                    Text("这个颜色是次色")
                        .foregroundColor(.secondary)//会自动适配
                    Text("这个颜色是黑色")
                        .foregroundColor(.black)//不会自动适配
                    Text("这个颜色是白色")
                        .foregroundColor(.white)//不会自动适配
                    Text("这个颜色是红色")
                        .foregroundColor(.red)//会自动适配
                    Text("这个颜色是全局自适应的")
                        //这个颜色要在Assets文件中配置
                        .foregroundColor(Color("adaptiveColor"))
                    Text("这个颜色是局部自适应的")
                        .foregroundColor(colorScheme == .light ? .green : .blue)
                }
            }
            .navigationTitle("DarkModeDemo")
        }
       
    }
}

#Preview {
    DarkModeDemo()
}

浅色模式:

深色模式:

.onAppear和.onDisappear

学习要点:

1.如何使用这两个修饰符

2.在什么情况下使用

3.如何进行延时操作

swift 复制代码
import SwiftUI

//在视图出现在屏幕上或者消失时可以调用的,比如在数据显示在屏幕上之前,你不想从数据库中获取它
struct onAppearAndDisappearDemo: View {
    
    @State var myText:String = "起始文本"
    @State var count:Int = 0
    
    var body: some View {
        NavigationStack{
            ScrollView{
                Text(myText)
                LazyVStack{
                    ForEach(1..<50) { _ in
                        RoundedRectangle(cornerRadius: 25.0)
                            .frame(height: 200)
                            .padding()
                            //每个矩形都会有一个onAppear,这说明这个修饰符出现的位置很关键
                            .onAppear{
                                count += 1
                            }
                            
                    }
                }
            }
            //当这个滚动视图出现在屏幕时,改变文本
            .onAppear(perform: {
                //加上延时,使用调度队列
                DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: {
                    //5秒后显示
                    myText = "这是个新文本"
                })
                
                
            })
            //视图离开屏幕时,要是有东西在运行,主要用来清理相关事务(在这里看不出效果)
            .onDisappear(perform: {
                myText = "结束文本"
            })
            
            .navigationTitle("On Appear \(count)")
        }
        
    }
}

#Preview {
    onAppearAndDisappearDemo()
}

安全编码(if let和guard)

swift 复制代码
import SwiftUI

struct if_letAndguard: View {
    
    @State var myText: String? = nil
    @State var isLoading: Bool = false
    @State var currentUserID: String? = "123456"
    
    var body: some View {
        NavigationStack{
            VStack{
                Text("在这里进行安全编码")
                //如果myText的类型是可选String?,且为nil,这里就会报错!使用if let.
                //若myText不为空有实际值,就创建一个text的新变量,并赋予实际值,且运行括号内代码,若myText没有值,则不会创建text,永远不会执行
                if let text = myText{
                    Text(text)
                        .font(.title)
                }
                if isLoading{
                    ProgressView()
                }
                
                
                Spacer()
            }
            
            .navigationTitle("安全编码")
            .onAppear{
                LoadData2()
            }
        }
    }
    func LoadData(){
        
        //模拟只有当创建了账户,有实际ID时,才加载数据
        if let userID = currentUserID{
            isLoading = true
            DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
                myText = "这是新数据LoadData,用户ID是:\(userID)"
            })
            isLoading = false
        }else{
            myText = "错误!没有用户ID"
        }
    }
    
    func LoadData2(){
        //若currentUserID为空值,就无法创建userID变量,进入else语句,最终return结束程序
        guard let userID = currentUserID else{
            myText = "错误!没有用户ID"
            return
        }
        //若currentUserID不为空值,则运行下面的代码
        isLoading = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
            myText = "这是新数据LoadData2,用户ID是:\(userID)"
        })
        isLoading = false
    }
    
}

#Preview {
    if_letAndguard()
}

.onTapGesture

点击手势

点击手势和Button的区别:

1.点击是按钮会进入高亮状态,手势不会

2.手势添加更简洁,并且可以定义双击/三击

swift 复制代码
import SwiftUI

struct onTapGestureDemo: View {
    
    @State var isSelect:Bool = false
    
    
    var body: some View {
        VStack(spacing: 20){
            RoundedRectangle(cornerRadius: 25.0)
                .frame(height: 200)
                .foregroundColor(isSelect ? Color.red : Color.green)
            
            //按钮
            Button {
                isSelect.toggle()
            } label: {
                Text("按钮")
                    .font(.title)
                    .foregroundColor(.white)
                    .frame(height: 55)
                    .frame(maxWidth: .infinity)
                    .background(.blue)
                    .cornerRadius(25)
            }
            
            //点击手势
            Text("点击手势")
                .font(.title)
                .foregroundColor(.white)
                .frame(height: 55)
                .frame(maxWidth: .infinity)
                .background(.blue)
                .cornerRadius(25)
                //添加点击手势
//                .onTapGesture {
//                    isSelect.toggle()
//                }
                //双击
                .onTapGesture(count: 2) {
                    isSelect.toggle()
                }
            
            Spacer()

        }
        .padding(40)
        
    }
}

#Preview {
    onTapGestureDemo()
}

@StateObject和@ObservedObject

状态对象和可观察对象:本质上是属性包装器,可用于观察应用中的其他类,并使其实时更新我们的视图

学习要点:

1.在类的后面添加ObservableObject,告诉Xcode这个类具有可观察性

2.@Published:目前来说,@Published已发布属性包装器和@State作用类似,只是它用于类中,当水果数组改变时,它会通知水果视图模型变化,更新内容。

3.@ObservedObject:这里的@ObservedObject作用与@State作用类似,告诉视图观察fruitViewModel对象,一旦它发生改变就更新视图,只是它用于观察整个类,而@State用于观察变量。

但是@ObservedObject可观察对象有缺点:如果这个视图被重新创建,或者因为其他原因视图刷新(比如有动画)导致这个视图重新加载,那么这个观察对象fruitViewModel也会重新加载。而通常在应用中,当下载一个数据集时,其实不需要它重新加载,因为就算视图重新加载,数据集的底层数据没有改变,我们希望这些数据集不需要再重新刷新一遍了。所以我们使用另一个@StateObject状态对象,它可以避免数据集重新刷新的情况。
@StateObject:首次创建/初始化时使用

@ObservedObject:传给二级视图/子视图时使用

swift 复制代码
internal import Combine
import SwiftUI

struct FruitModel: Identifiable {
    //UUID()函数会生成随机ID
    let id: String = UUID().uuidString
    //水果名称
    let name: String
    //水果数量
    let count: Int
}

//在类的后面添加ObservableObject,告诉Xcode这个类具有可观察性
class FruitViewModel: ObservableObject {

    //目前来说,@Published已发布属性包装器和@State作用类似,只是它用于类中,
    //当水果数组改变时,它会通知水果视图模型变化,更新内容
    @Published var fruitAyyay: [FruitModel] = []
    //定义一个加载指示器变量
    @Published var isLoading: Bool = false
    
    init(){
        getFruits()
    }

    //下面这些获取/删除水果的代码与视图并无直接关系,而是与数据数组有关,只关乎获取和存储数据
    //当在实际项目中。有更多更复杂的逻辑时,最好与视图结构进行分离,放入水果模型中
    func getFruits() {
        let fruit1 = FruitModel(name: "香蕉", count: 1)
        let fruit2 = FruitModel(name: "西瓜", count: 2)
        let fruit3 = FruitModel(name: "桃子", count: 3)

        isLoading = true
        DispatchQueue.main.asyncAfter(
            deadline: .now() + 3,
            execute: {
                self.fruitAyyay.append(fruit1)
                self.fruitAyyay.append(fruit2)
                self.fruitAyyay.append(fruit3)
                self.isLoading = false
            }
        )

    }
    func deleteFruit(index: IndexSet) {
        fruitAyyay.remove(atOffsets: index)
    }
}

struct StateObjectAndObservableObject: View {

    //@State var fruitAyyay: [FruitModel] = []

    //这里的@ObservedObject作用与@State作用类似,告诉视图观察fruitViewModel对象,一旦它发生改变就更新视图,只是它用于观察整个类,而@State用于观察变量。
    //但是@ObservedObject可观察对象有缺点:如果这个视图被重新创建,或者因为其他原因视图刷新(比如有动画)导致这个视图重新加载,那么这个观察对象fruitViewModel也会重新加载。而通常在应用中,当下载一个数据集时,其实不需要它重新加载,因为就算视图重新加载,数据集的底层数据没有改变,我们希望这些数据集不需要再重新刷新一遍了。所以我们使用另一个@StateObject状态对象,它可以避免数据集重新刷新的情况。
    //@StateObject:首次创建/初始化时使用
    //@ObservedObject:传给二级视图/子视图时使用
    @StateObject var fruitViewModel: FruitViewModel = FruitViewModel()

    var body: some View {
        NavigationStack {
            List {
                //如果此时正在加载,创建一个加载指示器视图
                if fruitViewModel.isLoading {
                    ProgressView()
                } else {
                    ForEach(fruitViewModel.fruitAyyay) { fruit in
                        HStack {
                            Text("\(fruit.count)")
                                .foregroundColor(Color.red)
                            Text(fruit.name)
                                .font(.headline)
                                .bold()
                        }
                    }
                    .onDelete(perform: fruitViewModel.deleteFruit)
                }
            }
            .listStyle(.grouped)
            .navigationTitle("水果")
            .navigationBarItems(
                trailing:
                    NavigationLink(
                        destination: {
                            RandomScreen(fruitViewModel: fruitViewModel)
                        },
                        label: {
                            Image(systemName: "arrow.right")
                                .font(.title)
                        }
                    )
            )
        }
    }

}

//创建第二个视图
struct RandomScreen: View {

    @Environment(\.presentationMode) var presentationMode
    
    //当我们想从第二个视图里访问水果列表时,可以在这里使用@ObservedObject添加一个可观察变量
    @ObservedObject var fruitViewModel: FruitViewModel

    var body: some View {
        ZStack {
            Color.green.ignoresSafeArea(.all)

//            Button {
//                presentationMode.wrappedValue.dismiss()
//            } label: {
//                Text("返回")
//                    .font(.title)
//                    .foregroundColor(.white)
//                    .fontWeight(.semibold)
//            }
            
            VStack{
                ForEach(fruitViewModel.fruitAyyay){ fruit in
                    Text(fruit.name)
                        .font(.headline)
                        .foregroundColor(.white)
                    
                }
            }

        }
    }
}

#Preview {
    StateObjectAndObservableObject()
    //    RandomScreen()
}

@EnvironmentObject

环境对象

学习要点:

如果想在第三视图里访问主视图的数据,有两种方法:

1.状态对象和可观察对象

但是会出现这样的情况,当需要在子视图中嵌套子视图时(有十多个),就要在每个View上创建可观察对象会很麻烦,会有很多重复代码,有些视图甚至没有用到这个可观察对象,就有点像多余代码。

所以我们可以把首次创建的可观察对象放入环境中,一旦它在环境中,那个层级结构里的所有视图都能访问到它,无需层层传递

2.环境对象

添加环境对象,把这个视图模型放入环境中。

在上面派生的所有子视图,即导航视图中的所有内容,不管是十个,一百个屏幕,都能访问这个视图模型。

swift 复制代码
internal import Combine
import SwiftUI

class EnvironmentViewModel: ObservableObject {
    @Published var dataArray: [String] = []

    init() {
        getData()
    }

    func getData() {
        dataArray.append(contentsOf: ["iphone", "ipad", "imac", "apple"])
    }
}

//如果想在第三视图里访问主视图的数据,有两种方法:
//1.状态对象和可观察对象
//但是会出现这样的情况,当需要在子视图中嵌套子视图时(有十多个),就要在每个View上创建可观察对象会很麻烦,会有很多重复代码,有些视图甚至没有用到这个可观察对象,就有点像多余代码。
//所以我们可以把首次创建的可观察对象放入环境中,一旦它在环境中,那个层级结构里的所有视图都能访问到它,无需层层传递
//2.环境对象
//添加环境对象,把这个视图模型放入环境中。
//在上面派生到所有子视图,即导航视图中的所有内容,不管是十个,一百个屏幕,都能访问这个视图模型

struct EnvironmentObjectDemo: View {

    //创建一个环境视图模型实例,并初始化
    @StateObject var viewModel: EnvironmentViewModel = EnvironmentViewModel()

    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.dataArray, id: \.self) { item in
                    
                    NavigationLink {
                        
                        //DetaiView(selectItem: item, viewModel: viewModel)
                        DetaiView(selectItem: item)
                    } label: {
                        Text(item)
                    }

                }

            }
            .navigationTitle("IOS设备")
        }
        .environmentObject(viewModel)
        //添加环境对象,把这个视图模型放入环境中。
        //在上面派生到所有子视图,即导航视图中的所有内容,不管是十个,一百个屏幕,都能访问这个视图模型
    }
}

struct DetaiView: View {

    let selectItem: String
    //创建被观察对象变量
    //@ObservedObject var viewModel: EnvironmentViewModel

    var body: some View {
        ZStack {
            //背景层
            Color.orange.edgesIgnoringSafeArea(.all)

            //前景层
            NavigationLink {
                //FinalView(viewModel: viewModel)
                //不需要传参了
                FinalView()
            } label: {
                Text(selectItem)
                    .font(.title)
                    .foregroundColor(.orange)
                    .padding()
                    .padding(.horizontal)
                    .background(.white)
                    .cornerRadius(25)
            }
        }
    }
}

struct FinalView: View {
    
    //@ObservedObject var viewModel: EnvironmentViewModel
    
    //直接在第三个视图里创建环境变量
    @EnvironmentObject var viewModel: EnvironmentViewModel
    
    var body: some View {
        ZStack {
            //背景层
            LinearGradient(
                colors: [Color.red, Color.green, Color.orange],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
            .ignoresSafeArea(.all)

            //前景层
            ScrollView {
                VStack(spacing: 20) {
                   //在这里就可以访问主视图的数据了
                    ForEach(viewModel.dataArray, id: \.self) { item in
                        Text(item)
                    }
                }
                .font(.largeTitle)
                .foregroundStyle(.white)

            }
        }
    }
}

#Preview {
    EnvironmentObjectDemo()
    //    DetaiView(selectItem: "apple")
    //    FinalView()
}

下面的内容是iO S16+苹果新更新的东西:

@AppStorage

应用存储属性包装器

学习要点:

1.两种对可选类型进行安全解包的方法

2.如何使用用户默认设置

3.如何使用应用存储

4.用户默认设置(UIKit) ---> 应用存储(SwiftUI)

类似于一个小型数据库,可以保存信息,如果用户关闭并重新打开你的应用,信息仍被保存。

5.应用存储的底层逻辑是用户默认设置,它的好处在于我们不在需要将变量存入用户默认设置里,也不需要从用户默认设置里获取变量了,直接更改那个变量就会保存。

swift 复制代码
import SwiftUI

struct AppStorageDemo: View {
    
    //下面是用户默认设置演示代码:
//    @State var currentUserName: String?
//    
//    var body: some View {
//        VStack{
//            //对于可选类型进行安全解包的两种方式:
//            Text(currentUserName ?? "在这里添加姓名:")
//            
//            if let name = currentUserName{
//                Text(name)
//            }
//            
//            Button("保存") {
//                let name: String = "张三"
//                currentUserName = name
//                //把名字保存到了用户默认设置里
//                UserDefaults.standard.set(name, forKey: "name")
//            }
//            
//        }
//        //当屏幕显示时,从用户默认中获取信息
//        .onAppear{
//            currentUserName = UserDefaults.standard.string(forKey: "name")
//        }
//    }
    
    //下面是应用存储演示代码:
    @AppStorage("name") var currentUserName: String?
    
    var body: some View {
        VStack{
            //对于可选类型进行安全解包的两种方式:
            Text(currentUserName ?? "在这里添加姓名:")
            
            if let name = currentUserName{
                Text(name)
            }
            
            Button("保存") {
                let name: String = "李四"
                currentUserName = name
            }
            
        }
    }
}

#Preview {
    AppStorageDemo()
}

异步图像AsynclImage

异步图像:只需一行代码就能从网络URL下载图片,并且可以对这张图片进行操作。

学习要点:

如何使用异步图像初始化并且对图片进行操作

swift 复制代码
import SwiftUI

struct AsyncImageDemo: View {
    
    let url = URL(string: "https://picsum.photos/200")
    
    var body: some View {
        //这种初始化方法获得的图片不能操作
        //AsyncImage(url: url)
        
        AsyncImage(url: url) { returnImage in
            //把返回的图像显示在屏幕上,并且可以进行操作
            returnImage
                .resizable()
                .scaledToFill()
                .frame(width: 100, height: 100)
        } placeholder: {
            //默认占位符是一开始的灰框,这里自定义成加载指示器
            ProgressView()
        }

    }
}

#Preview {
    AsyncImageDemo()
}

背景层系统材料

背景材料:基本上是能应用到系统上的背景,它们并非特定颜色,而是带有一定透明度的系统材料。

swift 复制代码
import SwiftUI

struct BackgroundMaterials: View {
    var body: some View {
        VStack{
            Spacer()
            
            VStack{
                Text("你好")
            }
            .frame(height: 350)
            .frame(maxWidth: .infinity)
            //在北京中使用系统材料:薄材料,厚材料,超薄材料,常规材料
            .background(.thinMaterial)
            .cornerRadius(30)
        }
        .edgesIgnoringSafeArea(.all)
        .background(Image("p1"))
    }
}

#Preview {
    BackgroundMaterials()
}

.textSelection

文本选择修饰符:添加文本选择并启用即可,这样用户就可以选中文本,进行复制,分享等操作。

swift 复制代码
import SwiftUI

struct TextSelectionDemo: View {
    var body: some View {
        Text("你好")
            //启用文本选择,默认禁用
            .textSelection(.enabled)
    }
}

#Preview {
    TextSelectionDemo()
}

.buttonStyle和.controlSize

苹果新提供的一些按钮样式,并且能够使用controlSize去自动控制大小。

swift 复制代码
import SwiftUI

struct buttonStyleAndcontrolSize: View {
    var body: some View {
        
        VStack{
            Button("按钮") {
                print("aaa")
            }
            .frame(height: 55)
            .frame(maxWidth: .infinity)
            //设置按钮大小,它是作用在按钮的标签上,而不是整个按钮组件
            .controlSize(.extraLarge)
            //设置按钮样式为 纯色(默认强调色是蓝色)
//            .buttonStyle(.plain)
            
            
            Button("按钮") {
                print("aaa")
            }
            .frame(height: 55)
            .frame(maxWidth: .infinity)
            .controlSize(.large)
            //设置按钮样式为 圆角灰底边框
//            .buttonStyle(.bordered)
            
            Button("按钮") {
                print("aaa")
            }
            .frame(height: 55)
            .frame(maxWidth: .infinity)
            //设置按钮大小
            .controlSize(.small)
            //设置按钮样式为 圆角蓝底边框
//            .buttonStyle(.borderedProminent)
            
            Button("按钮") {
                print("aaa")
            }
            .frame(height: 55)
            .frame(maxWidth: .infinity)
            .controlSize(.mini)
            //设置按钮样式为 无框
//            .buttonStyle(.borderless)
            
            
        }
        .padding()
    }
}

#Preview {
    buttonStyleAndcontrolSize()
}

按钮的样式:

按钮的大小:

.swipeActions

滑动操作修饰符:可以在列表行的手势中添加其他按钮。

swift 复制代码
import SwiftUI

struct ListSwipeActions: View {

    @State var fruits: [String] = ["西瓜", "桃子", "雪梨", "香蕉", "苹果"]

    var body: some View {
        List {
            ForEach(fruits, id: \.self) {
                Text($0)
                    //自定义滑动按钮
                    .swipeActions(
                        edge: .trailing,
                        allowsFullSwipe: true
                    ) {
                        Button("保存") {
                            print("a")
                        }
                        .tint(.red)

                        Button("存档") {
                            print("a")
                        }
                        .tint(.green)

                        Button("保存") {
                            print("a")
                        }
                        .tint(.blue)
                    }
            }
        }
    }
}

#Preview {
    ListSwipeActions()
}

.badge()

添加徽章:当有个标签栏/列表时,想在上面放个小图标,显示该标签栏/列表上有多少通知。

学习要点:

如何在列表List和标签栏TabView中添加徽章。

swift 复制代码
import SwiftUI

struct badgeDemo: View {
    var body: some View {
        
        //在列表上添加:
        //它不是一个小图标,而是右侧的一些内容,文本颜色为次要色
        List{
            Text("hello")
                .badge(4)
            Text("hello")
                .badge("New Massage")
            Text("hello")
            Text("hello")
        }
        //在标签栏上添加:
        //显示在标签栏右上角的小图标
//        TabView{
//            Color.red
//                .tabItem {
//                    Image(systemName: "heart.fill")
//                    Text("hello")
//                }
//                .badge(3)
//            
//            Color.red
//                .tabItem {
//                    Image(systemName: "heart.fill")
//                    Text("hello")
//                }
//                .badge("new")
//        }
    }
}

#Preview {
    badgeDemo()
}

在列表上添加:

在标签栏上添加:

@FoucusState

焦点状态属性包装器:

比如在输入框TextField中,一般要点击输入框才会弹出键盘,光标聚焦到输入框。

通过焦点状态包装器,可以使选择文本字段来弹出键盘,还可以将光标从某个文本字段移到另一个文本字段。

swift 复制代码
import SwiftUI

struct FocusStateDemo: View {
    
    @State private var name:String = ""
    
    //定义Bool类型的焦点状态变量
    @FocusState private var nameInFocus: Bool
    
    var body: some View {
        VStack{
            TextField("请输入.....", text: $name)
                .focused($nameInFocus)
                .padding(.leading)
                .frame(height: 55)
                .frame(maxWidth: .infinity)
                .background(Color.gray.opacity(0.3))
                .cornerRadius(10)
            
            Button("点击切换焦点状态") {
                nameInFocus.toggle()
            }
        }
        .padding(40)
        //一进入该屏幕,键盘直接弹出
        .onAppear{
            self.nameInFocus = true
        }
    }
}

#Preview {
    FocusStateDemo()
}

.onSubmit和.submitLable

.onSubmit:当设备弹出键盘时,键盘右下角有个小返回按钮,在IOS15之前,用户只能点击它收起键盘,不能有其他额外操作。有了这个修饰符后,可以对它自定义操作了。

.submitLable:更改键盘上弹出的返回按钮标签

swift 复制代码
import SwiftUI

struct onSubmitAndsubmitLable: View {
    
    @State private var text:String = ""
    
    var body: some View {
        VStack{
            TextField("请输入....", text: $text)
                .submitLabel(.next)
                .onSubmit {
                    print("点击next后显示在控制台")
                }
            
            TextField("请输入....", text: $text)
                .submitLabel(.route)
                .onSubmit {
                    print("点击route后显示在控制台")
                }
            
            TextField("请输入....", text: $text)
                .submitLabel(.search)
                .onSubmit {
                    print("点击search后显示在控制台")
                }
        }
    }
}

#Preview {
    onSubmitAndsubmitLable()
}

导航栈NavigationStack

导航栈于导航视图的重要不同:

1.导航栈可以在二级屏幕上实现懒加载。

使用导航视图:在未进入屏幕之前,后台上已经把所有屏幕都渲染加载了,很耗资源。

使用导航栈:以一种懒加载的方式,只有在进入屏幕时才加载,需要哪个加载哪个。

2.可以堆叠使用导航目的地。

对多个不同类型设置不同的目的地。

swift 复制代码
import SwiftUI

struct NavigationStackDemo: View {
    
    let fruits = ["苹果","香蕉","西瓜"]
    
    var body: some View {
                    //使用导航视图:
        //        NavigationView {
        //            ScrollView {
        //                VStack(spacing: 40) {
        //                    ForEach(1..<10) { x in
        //                        NavigationLink(
        //                            "点击跳转\(x)",
        //                            destination: {
        //                                mySecondScreen(value: x)
        //                            }
        //                        )
        //                    }
        //                }
        //            }
        //
        //            .navigationTitle("导航标题")
        //        }

        //使用导航栈:
        NavigationStack {
            ScrollView {
                VStack(spacing: 40) {
                    ForEach(fruits, id: \.self) { fruit in
                        NavigationLink(value: fruit) {
                            Text(fruit)
                        }
                    }
                    
                    ForEach(1..<10) { x in
                        //注意:这里NavigationLink中value的类型要和.navigationDestination中for的类型一致
                        NavigationLink(value: x) {
                            Text("点击跳转\(x)")
                        }

                    }
                }
            }

            .navigationTitle("导航标题")
            .navigationDestination(
                for: Int.self
            ) { value in
                mySecondScreen(value: value)
            }
            
            .navigationDestination(
                for: String.self
            ) { value in
                Text("这是:\(value)")
            }
        }
    }
}

struct mySecondScreen: View {

    let value: Int

    init(value: Int) {
        self.value = value
        //使用导航视图:在未进入屏幕之前,后台上已经把所有屏幕都渲染加载了,很耗资源
        //使用导航栈:以一种懒加载的方式,只有在进入屏幕时才加载,需要哪个加载哪个
        print("初始化屏幕:\(value)")
    }

    var body: some View {
        Text("屏幕\(value)")
    }
}

#Preview {
    NavigationStackDemo()
}

工具栏Toolbar

工具栏:我们以前做导航栏时,可以把视图或者按钮放在导航栏顶部的左/右上角。现在可以使用工具栏,使用起来更简单,更有适应性。

swift 复制代码
import SwiftUI

struct ToolbarDemo: View {
    var body: some View {
        NavigationStack{
            ZStack{
                Color.white.ignoresSafeArea(.all)
                
                ScrollView{
                    ForEach(1..<50){_ in
                        RoundedRectangle(cornerRadius: 10)
                            .frame(width: 200, height: 200)
                            .foregroundStyle(.blue)
                    }
                }
            }
            .navigationTitle("工具栏")
            //使用工具栏修饰符
            .toolbar {
                //设置工具栏中的项目
                ToolbarItem(placement: .topBarLeading) {
                    Image(systemName: "heart.fill")
                }
                ToolbarItem(placement: .topBarTrailing) {
                    Image(systemName: "gear")
                }
                //设置在键盘上显示这个图标
                ToolbarItem(placement: .keyboard) {
                    Image(systemName: "gear")
                }
            }
            //隐藏某个位置的工具栏(自动,隐藏全部)
            //.toolbar(.hidden, for: .automatic)
            
            //设置导航栏背景(自动,背景隐藏变透明)
            .toolbarBackground(Color.white.opacity(0.95), for: .navigationBar)
            .toolbarBackground(.visible, for: .navigationBar)
            
            //设置工具栏配色方案
            //.toolbarColorScheme(.dark, for: .navigationBar)
            
            //设置工具栏标题菜单
            .navigationBarTitleDisplayMode(.inline)
            .toolbarTitleMenu {
                Button("按钮1") {
                    print("a")
                }
                Button("按钮2") {
                    print("a")
                }
            }
            
        }
    }
}

#Preview {
    ToolbarDemo()
}

可调整大小的表单

在iOS16之前做的sheet表单,它从底部弹出的高度大概接近安全区域,并且不能修改尺寸大小。但是iOS16之后,增加了可调整大小的修饰符。

swift 复制代码
import SwiftUI

struct ResizableSheetDemo: View {

    @State private var showSheet: Bool = false
    @State private var detent: PresentationDetent = .large

    var body: some View {
        Button("点击弹出表单") {
            showSheet.toggle()
        }
        .sheet(isPresented: $showSheet) {
            myNextView(detent: $detent)
                //设置弹窗尺寸大小:大尺寸(默认,全屏),中等尺寸(半屏)
                //.presentationDetents([.medium, .large])
                //也可以自定义设置弹窗尺寸(根据屏幕百分比,根据高度)
                //.presentationDetents([.fraction(0.75)])
                //.presentationDetents([.height(700)])
                //也可以自定义绑定选择大小
                .presentationDetents(
                    [.fraction(0.75), .height(200), .large, .medium],
                    selection: $detent
                )
                //设置是否隐藏展示指示符:可见,隐藏,自动
                .presentationDragIndicator(.hidden)
            //设置禁用交互式关闭(弹出后无法下拉关闭)
            //.interactiveDismissDisabled()
        }
    }
}

struct myNextView: View {

    @Binding var detent: PresentationDetent

    var body: some View {
        ZStack {
            Color.red.ignoresSafeArea(.all)

            VStack(spacing: 30) {
                //这里使用的尺寸都要在presentationDetents里设置好
                Button("大屏") {
                    detent = .large
                }
                Button("中等屏") {
                    detent = .medium
                }
                Button("屏幕的0.75") {
                    detent = .fraction(0.75)
                }
                Button("200px") {
                    detent = .height(200)
                }
            }

        }
    }
}

#Preview {
    ResizableSheetDemo()
}

安全区域插入SafeAreaInsets

安全区域插入:如果有一个视图,按钮,文本想特的放在安全区域,就可以使用这个修饰符。

swift 复制代码
import SwiftUI

struct SafeAreaInsetsDemo: View {
    var body: some View {
        NavigationStack{
            List{
                ForEach(1..<20) { _ in
                    Rectangle()
                        .frame(height: 300)
                }
            }
            .navigationTitle("安全区域插入")
            //在安全区域底部插入
            .safeAreaInset(edge: .bottom,alignment: .center,spacing: nil) {
                Text("hello")
                    .frame(maxWidth: .infinity)
                    .background(Color.yellow)
            }
        }
    }
}

#Preview {
    SafeAreaInsetsDemo()
}

.animation

使用原先的动画修饰符:这里的弹簧动画运用于整个ZStack里的这两个操作,但是我希望操作1用这个动画,但是操作2不用这种动画或是没有动画,我们不能分开指定。

使用新的动画修饰符:可以指定某个操作用某个动画效果!

swift 复制代码
import SwiftUI

struct AnimationUpdateDemo: View {
    
    @State private var animate1:Bool = false
    @State private var animate2:Bool = false
    
    var body: some View {
        ZStack {
            VStack(spacing: 40) {
                Button("按钮1") {
                    animate1.toggle()
                }
                Button("按钮2") {
                    animate2.toggle()
                }
                ZStack {
                    Rectangle()
                        .frame(width: 100, height: 100)
                        .frame(maxWidth: .infinity, alignment: animate1 ? .leading : .trailing)
                        .background(Color.green)
                        .frame(maxHeight: .infinity, alignment: animate2 ? .top : .bottom)
                        .background(Color.orange)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.red)
            }
        }
        //使用原先的动画修饰符:这里的弹簧动画运用于整个ZStack里的这两个操作,但是我希望操作1用这个动画,但是操作2不用这种动画或是没有动画,我们不能分开指定。
        //.animation(.spring())
        
        //使用新的动画修饰符:可以指定某个操作用某个动画效果!
        .animation(.spring(), value: animate1)
        .animation(.linear(duration: 5), value: animate2)
    }
}

#Preview {
    AnimationUpdateDemo()
}

菜单Menu

在前面学习的Picker选择器组件中有一个菜单样式,但是swiftUI中还有一个就叫菜单的组件,用起来也很简单,甚至比选择器还简单。

swift 复制代码
import SwiftUI

struct MenuDemo: View {
    var body: some View {
        //创建一个菜单
        Menu {
            Button("按钮1") {
                print("a")
            }
            Button("按钮2") {
                print("a")
            }
            Button("按钮3") {
                print("a")
            }
        } label: {
            Text("点击弹出菜单")
        }

    }
}

#Preview {
    MenuDemo()
}

弹出框Popover

我们前面学习了用三种方法制作自定义弹出框,这里的是原生swiftUI的弹出框版本。

学习要点:

用法与工作表sheet类似,但是要添加一个"呈现紧凑配置"修饰符。

swift 复制代码
import SwiftUI

struct popoverDemo: View {

    @State private var showPopover: Bool = false
    @State private var feedBackOptions:[String] = [
        "非常喜欢","一般","不喜欢"
    ]

    var body: some View {
        ZStack {
            Color.gray.ignoresSafeArea(.all)

            Button("提供反馈") {
                showPopover.toggle()
            }
            .font(.title)
            .padding(20)
            .background(.yellow)
            //使用原生弹出框
            //.popover(isPresented: $showPopover) {
            //     Text("你好")
            //          //使用呈现紧凑配置:可选 工作表,全屏工作表,弹出框,自动,无等
            //          .presentationCompactAdaptation(.popover)
            //            }

            .popover(
                isPresented: $showPopover,
                //附着锚点,以点状类型,从视图顶部弹出。
                //还有其他8个方位选项
                attachmentAnchor: .point(.top)
            ) {
                VStack(alignment: .leading, spacing: 12){
                    ForEach(feedBackOptions, id: \.self) { feedbackoption in
                        Button(feedbackoption) {
                            print("a")
                        }
                    }
                    .padding(20)
                }
                //使用呈现紧凑配置:可选 工作表,全屏工作表,弹出框,自动,无等
                .presentationCompactAdaptation(.popover)
            }
        }
    }
}

#Preview {
    popoverDemo()
}

任意布局AnyLayout

任意布局:能让我们根据设备方向大致调整布局。

首先我们要知道,iphone的尺寸不是紧凑就是常规。

在竖屏模式下,垂直尺寸类是常规,横屏模式下,垂直尺寸类是紧凑。

对于开发者来说,我们并不关注设备是否处于横/竖屏模式下,我们只关注环境变量告诉我们是什么,如果苹果说设备是紧凑型的,就知道空间会变小,然后就能调整布局了。

swift 复制代码
import SwiftUI

struct AnyLayoutDemo: View {
    
    @Environment(\.horizontalSizeClass) private var horizontalSizeClass
    @Environment(\.verticalSizeClass) private var verticalSizeClass
   
    var body: some View {
        VStack(spacing: 20){
            Text("horizontal: \(horizontalSizeClass.debugDescription)")
            Text("vertical: \(verticalSizeClass.debugDescription)")
            
            //使用any布局可以让下面的代码更简洁:
            let layout: AnyLayout = horizontalSizeClass == .compact ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
            
            layout{
                Text("张三")
                Text("李四")
                Text("王五")
            }
            
            
            //想根据设备的尺寸大小来制作不同的布局。
            //注意:我这里的模拟器是iPhone 17 Pro,不管横屏/竖屏,它的水平类都是 紧凑的
//            if horizontalSizeClass == .compact{
//                VStack{
//                    Text("张三")
//                    Text("李四")
//                    Text("王五")
//                }
//            }else{
//                HStack{
//                    Text("张三")
//                    Text("李四")
//                    Text("王五")
//                }
//            }
            
        }
    }
}

#Preview {
    AnyLayoutDemo()
}

适配视图ViewThatFits

适配视图:是一种创建适配屏幕视图的方法,会按你给的顺序评估子视图,然后会挑选,或是选择第一个适配你框架的。

一般用于标题或是不想改变文本大小的地方。

swift 复制代码
import SwiftUI

struct ViewThatFitsDemo: View {
    var body: some View {
        ZStack{
            Color.red.ignoresSafeArea(.all)
            
            //适配视图:会按你给的顺序评估子视图,然后会挑选,或是选择第一个适配你框架的
            ViewThatFits{
                //优先处理文本在第一行显示
                Text("这是我想给用户展示的文字,真的超级棒,超级厉害!!!!!")
                Text("这是我想给用户展示的文字,真的超级棒!")
                Text("这是我想给用户")
            }
            
        }
        .frame(height: 300)
        .padding(15)
        .font(.headline)
    }
}

#Preview {
    ViewThatFitsDemo()
}

导航分割视图NavigationSplitView

导航分割视图:能让你拥有多个屏幕,即同一时间屏幕上会显示两到三个。主要用于尺寸较大的设备,比如iPad

swift 复制代码
import SwiftUI

//可设置字符串初始值,并且可迭代
enum FoodStore: String, CaseIterable {
    case fruits, vagatables, meats
}

enum Fruits: String, CaseIterable {
    case apple, banana, orange
}

struct NavigationSplitViewDemo: View {

    //可设置分割视图的初始可见区域:自动,双列,仅详情页,全部
    @State private var visibility: NavigationSplitViewVisibility = .all
    @State private var selectedCategory: FoodStore? = nil
    @State private var selectedFruit: Fruits? = nil

    var body: some View {
        //创建三屏
        NavigationSplitView(columnVisibility: $visibility) {
            List {
                ForEach(FoodStore.allCases, id: \.rawValue) { category in
                    Button(category.rawValue.capitalized) {
                        selectedCategory = category
                    }
                }
            }
            .navigationTitle("食物类别")
        } content: {
            if let selectedCategory {
                Group {
                    switch selectedCategory {
                    case .fruits:
                        List {
                            ForEach(Fruits.allCases, id: \.rawValue) { fruit in
                                Button(fruit.rawValue.capitalized) {
                                    selectedFruit = fruit
                                }
                            }
                        }
                    case .vagatables:
                        EmptyView()
                    case .meats:
                        EmptyView()
                    }
                }
                .navigationTitle("Fruits")
            }else{
                Text("请选择类别")
            }
        } detail: {
            
            if let selectedFruit {
                Text("你选择了:\(selectedFruit.rawValue)")
                    .font(.largeTitle)
                    .navigationTitle(selectedFruit.rawValue.capitalized)
            }else{
                Text("请选择水果")
            }
        }
        //设置导航分割视图样式:自动,平衡,突出详情
        .navigationSplitViewStyle(.balanced)

        //创建两屏
        //        NavigationSplitView {
        //            Color.red
        //        } detail: {
        //            Color.blue
        //        }

    }
}

#Preview {
    NavigationSplitViewDemo()
}

网格Grid

网格:这是常规网格组件,并不是懒加载的

swift 复制代码
import SwiftUI

struct GridViewDemo: View {
    var body: some View {
        //设置单元格对齐,水平间距,垂直间距
        Grid(alignment: .leading, horizontalSpacing: 8, verticalSpacing: 8){
            ForEach(0..<4){ rowIndex in
                //设置每行的对齐方式
                GridRow(alignment: .bottom){
                    ForEach(0..<4){ columnIndex in
                        let cellNumbre = (rowIndex * 4) + (columnIndex + 1)
                        
                        //如果想要第7个单元格为空
                        if cellNumbre == 7{
                            //颜色清除视图的尺寸是无穷大的
                            //使用 无尺寸网格修饰符
                            Color.green
                                .gridCellUnsizedAxes([.horizontal, .vertical])
                        }else{
                            cell(int: cellNumbre)
                                //设置每个单元格占几列,默认1
                                .gridCellColumns(cellNumbre == 5 ? 2 : 1)
                                //设置每个单元格内的对齐
                                //.gridCellAnchor(.trailing)
                                //设置特定列的对齐方式
                                .gridColumnAlignment(cellNumbre == 1 ? .trailing : .center)
                        }
                    }
                }
            }
        }
    }
    private func cell(int: Int) -> some View{
        Text("\(int)")
            .font(.largeTitle)
            .padding()
            .frame(height: int == 10 ? 50 : nil)
            .background(Color.blue)
    }
}

#Preview {
    GridViewDemo()
}

内容不可见视图ContentUnavailableView

内容不可见视图:

它主要是给我们一个模版,用来向用户展示内容不可用。比如在应用中,你要加载数据的地方,结果数据没加载出来,或是用户在应用上搜索内容时,没有搜索结果,这就要用内容不可见视图。

swift 复制代码
import SwiftUI

struct ContentUnavailableViewDemo: View {
    var body: some View {
        //创建自定义内容不可见视图
        ContentUnavailableView("无网络连接",
                               systemImage: "wifi.slash",
                               description: Text("请连接到网络重试..."))
        
        //内容不可见视图的默认版本:无搜索结果的默认界面
        //ContentUnavailableView.search(text: "abc")
    }
}

#Preview {
    ContentUnavailableViewDemo()
}

自定义内容不可见视图:

无搜索结果的默认界面:

控制组ControlGroup

控制组:与菜单很相似,它能让我们为按钮自定义几种不同的布局。

学习要点:

1.简单使用控制组

2.Menu菜单中可以嵌套菜单

3.ControlGroup控制组中可以嵌套菜单

swift 复制代码
import SwiftUI

struct ControlGroupDemo: View {
    var body: some View {
        Menu() {
            //创建控制组
            ControlGroup {
                Button("按钮1") {
                    print("a")
                }
                Button("按钮2") {
                    print("a")
                }
                Button("按钮3") {
                    print("a")
                }
            } label: {
                Text("One")
            }

            
            Button("Two") {
                print("a")
            }
            Button("Three") {
                print("a")
            }
            
        } label: {
            Text("菜单")
        }

    }
}

#Preview {
    ControlGroupDemo()
}

@Observable 宏

可观察宏:

我们之前在定义可观察类时,该类需要遵循可观察对象ObservableObject协议,在使用时需 发布属性包装器@Published变量。

但是在iOS 17之后,有一种新的定义方法, 使用可观察宏@Observable去声明这个类,并且在使用时不需要包装器,它会默认所有内容都会发布。如果我们想让这个类中的内容不会向视图发布,我们要给这个属性添加观察忽略宏。

下面的代码是使用原来的:

swift 复制代码
import SwiftUI
internal import Combine

class ObservableViewModel: ObservableObject{
    @Published var title: String = "标题"
}

struct ObservableMacroDemo: View {
    
    @StateObject var ViewModel: ObservableViewModel = ObservableViewModel()
    
    var body: some View {
        VStack(spacing: 40) {
            Button(ViewModel.title) {
                ViewModel.title = "新标题"
            }
            
            SomeChildView(ViewModel: ViewModel)
            
            SomeThirdView()
        }
        .environmentObject(ViewModel)
        
    }
}

//创建子视图
struct SomeChildView: View {
    
    @ObservedObject var ViewModel: ObservableViewModel
    
    var body: some View {
        Button(ViewModel.title) {
            ViewModel.title = "第二视图新标题"
        }
    }
}

//创建第三视图
struct SomeThirdView:View {
    
    @EnvironmentObject var ViewModel:ObservableViewModel
    
    var body: some View {
        Button(ViewModel.title) {
            ViewModel.title = "第三视图新标题"
        }
    }
}

#Preview {
    ObservableMacroDemo()
}

更新后:

1.移除ObservableObject协议,在类前用@Observable宏修饰

2.移除可发布属性状态@Published

3.把状态对象@StateObject变为@State

4.把可观察对象@ObservedObject变成绑定对象@Binding

5.把.environmentObject变成.environment

6.在类的类型上也要变成环境environment自己的类型

swift 复制代码
import SwiftUI
internal import Combine

@Observable class ObservableViewModel{
    var title: String = "标题"
}

struct ObservableMacroDemo: View {
    
    @State var ViewModel: ObservableViewModel = ObservableViewModel()
    
    var body: some View {
        VStack(spacing: 40) {
            Button(ViewModel.title) {
                ViewModel.title = "新标题"
            }
            
            SomeChildView(ViewModel: $ViewModel)
            
            SomeThirdView()
        }
        .environment(ViewModel)
        
    }
}

//创建子视图
struct SomeChildView: View {
    
    @Binding var ViewModel: ObservableViewModel
    
    var body: some View {
        Button(ViewModel.title) {
            ViewModel.title = "第二视图新标题"
        }
    }
}

//创建第三视图
struct SomeThirdView:View {
    
    @Environment(ObservableViewModel.self) var ViewModel
    
    var body: some View {
        Button(ViewModel.title) {
            ViewModel.title = "第三视图新标题"
        }
    }
}

#Preview {
    ObservableMacroDemo()
}
相关推荐
pop_xiaoli1 小时前
【iOS】YYModel源码学习
学习·ios·objective-c·cocoa
末代iOS程序员华仔2 小时前
iOS 开发到上架 App Store 全流程详解
ios·objective-c·swift
2501_916007475 小时前
抓包工具对比 Charles、Fiddler、TraceEagle、Wireshark 与 Proxyman
前端·网络协议·ios·adb·https·fiddler·wireshark
LDyun_1 天前
2026暑期云手机选型指南:从稳定性、成本到风控六大核心评判维度
arm开发·ios·智能手机·安卓·玩游戏
库奇噜啦呼1 天前
【iOS】数据存储与持久化
ios
pop_xiaoli1 天前
【iOS】离屏渲染
macos·ios·objective-c·cocoa
_瑞2 天前
深入理解 iOS 渲染原理
前端·ios
Android出海2 天前
iOS 出海新门槛:未成年人合规来了
ios·app store·ios开发·苹果开发者·ios开发者·ios出海
2501_915106322 天前
iOS 软件测试工具性能监控、日志分析 KeyMob、Instruments等
android·ios·小程序·https·uni-app·iphone·webview