前言
- 新建文件:command + n
- 代码格式化:control + shift + i
- 代码折叠:option + command + 向左
一般只要有括号的地方就能折叠 - 代码标记:// 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()
}
链接Link
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()
}
导航视图NavigationView和导航链接NavigationLink
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
- .accentColor / .tint:只对可交互控件生效(Link、Button、导航按钮),需要写在 NavigationStack 顶层才能作用导航栏;
- .foregroundColor:直接修改图片 / 文字填充色,无视容器层级,局部写在图标上一定生效;
- 不要在 .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 写法对比:
- id: .self
swift
// 元素是String/Int,值唯一时使用
ForEach(fruits, id: \.self) { fruit in Text(fruit) }
- 自定义模型 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()
}