swiftUI
约 109166 字大约 364 分钟
2025-01-20
1-1 变量 打印 注释
import Foundation
var greeting = "Hello, playground"
var name = "aini"
print(greeting)
print(name)
var age = 23
print(age)
1-1-1 变量声明
1-1 var
(变量):
使用 var
声明的变量是可变的,也就是说,在初始化后,可以修改该变量的值。
例如:
var name = "Alice"
name = "Bob" // 可以修改 name 的值
1-2 let
(常量):
使用 let
声明的变量是不可变的,也就是常量。初始化后,该值不能再被修改。
例如:
let age = 25
// age = 30 // 错误!不能修改 let 声明的常量
1-1-2 打印输出
// 基本用法
print("你好,世界!")
// 调试代码
let age = 22
print("年龄是 \(age)")
// 显示信息给用户
print("请输入你的名字:")
// 输出多个值
let name = "aini"
print("名字:", name, "年龄:", age)
// 字符串插值
print("我的名字是 \(name),我今年 \(age) 岁。")
// 指定输出格式
print("苹果", "香蕉", "樱桃", separator: ", ", terminator: "。")
1-1-3 注释
// 单行注释示例
let name = "aini" // 这是对变量的注释
/*
多行注释示例
这里可以写一些详细的说明
适用于较复杂的代码逻辑解释
*/
let age = 22
/*
嵌套注释示例
这个多行注释里有嵌套的注释
/* 这是一个嵌套的注释 */
这个嵌套的注释可以在需要时使用
*/
print("Hello, \(name)!")
/// 这是一个函数,用于打印问候语
/// - Parameter name: 要问候的人的名字
func greet(name: String) {
print("Hello, \(name)!")
}
greet(name: "aini")
1-2 Bool String Int
下面是 Swift 中 Bool
、String
和 Int
三种常用数据类型的简单例子:
// 1. Bool 类型示例
let isStudent: Bool = true // 表示是否为学生
let hasGraduated: Bool = false // 表示是否毕业
// 2. String 类型示例
let name: String = "aini" // 表示名字
let greeting: String = "Hello, " + name + "!" // 字符串拼接
print(greeting) // 输出: Hello, aini!
// 3. Int 类型示例
let age: Int = 22 // 表示年龄
let yearOfBirth: Int = 2024 - age // 计算出生年份
print("出生年份是: \(yearOfBirth)") // 输出: 出生年份是: 2002
Bool
:Bool
类型表示布尔值,只有两个可能的值:true
和false
。它常用于条件判断。
String
:String
类型用于表示一串字符。可以通过加号+
来拼接字符串,也可以通过字符串插值\()
轻松插入变量的值。
Int
:Int
类型表示整数,可以用于存储年龄、年份等数值,也可以进行算术运算。
Int、
Double和
CGFloat区别
Int
:表示整数,没有小数部分。Double
:表示双精度浮点数,适合存储更高精度的数值,包括小数部分。CGFloat
:是Core Graphics
框架中的浮点数类型,在处理与图形相关的计算时常用,特别是在 iOS/macOS 的绘图操作中。根据平台的不同,它在 64 位系统上等同于Double
,在 32 位系统上等同于Float
。
以下是一个代码块示例,展示这三种类型的区别和用途:
import UIKit // UIKit 包含了 CGFloat
// Int 示例:只能表示整数
let age: Int = 22
let numberOfItems: Int = 10
// Double 示例:可以表示小数,适用于高精度计算
let price: Double = 19.99
let pi: Double = 3.14159265358979
// CGFloat 示例:常用于图形计算,例如在 iOS 中处理尺寸、坐标等
let screenWidth: CGFloat = 375.0
let screenHeight: CGFloat = 812.0
// 类型之间不能直接混用
// let total = age + price // 错误!不能将 Int 和 Double 直接相加
// 需要手动转换
let total = Double(age) + price // 正确,将 Int 转换为 Double 再进行运算
print("Total: \(total)")
// CGFloat 也不能与 Int 或 Double 直接运算,需要转换类型
let screenArea = screenWidth * screenHeight // 正确,都是 CGFloat 类型
// let incorrectArea = screenWidth * pi // 错误!不能将 CGFloat 和 Double 直接相乘
let correctArea = screenWidth * CGFloat(pi) // 正确,将 Double 转换为 CGFloat 后进行运算
print("Screen area: \(correctArea)")
Int
:用于整数,没有小数部分。Double
:用于高精度浮点数,适合处理带小数的数值。CGFloat
:在图形处理时使用的浮点数类型,通常用于坐标、尺寸等。
1-3 if 语句
1. 基本的 if
语句
if
语句用于判断一个条件是否为真,只有在条件为真时,才会执行代码块。
let age = 22
// 判断是否成年
if age >= 18 {
print("aini 是成年人,年龄是 \(age) 岁。")
}
if age >= 18
: 这行代码检查age
是否大于等于 18。- 如果条件成立(即
age
为 22,满足大于等于 18 的条件),会输出:aini 是成年人,年龄是 22 岁。
2. if-else
语句
当 if
的条件不满足时,else
语句会执行相应的代码。
let age = 22
// 判断是否成年
if age >= 18 {
print("aini 是成年人,年龄是 \(age) 岁。")
} else {
print("aini 还未成年。")
}
- 如果
age
是 22,则if
语句中的条件为真,输出aini 是成年人,年龄是 22 岁。
- 如果
age
是 16,则if
语句的条件不成立,执行else
语句,输出aini 还未成年。
3. 多个条件的 if-else if-else
语句
你可以通过 else if
来处理多个条件。每个条件依次检查,直到找到为真的条件。
let university = "东华大学"
// 判断所读的大学
if university == "东华大学" {
print("aini 就读于东华大学。")
} else if university == "复旦大学" {
print("aini 就读于复旦大学。")
} else {
print("aini 就读于其他学校。")
}
- 如果
university
是"东华大学"
,则输出aini 就读于东华大学。
- 如果
university
是"复旦大学"
,则输出aini 就读于复旦大学。
- 如果既不是
"东华大学"
也不是"复旦大学"
,则执行else
,输出aini 就读于其他学校。
4. 比较字符串的 if
语句
Swift 可以用 ==
比较字符串,判断两个字符串是否相等。
let city = "上海"
// 判断是否住在上海
if city == "上海" {
print("aini 住在上海。")
} else {
print("aini 不住在上海。")
}
- 如果
city
是"上海"
,则条件为真,输出aini 住在上海。
- 如果
city
是其他城市,如"北京"
,则条件不成立,输出aini 不住在上海。
5. 多个条件的 if
语句
可以使用逻辑运算符(如 &&
和 ||
)组合多个条件。
let age = 22
let city = "上海"
// 判断是否住在上海并且是否成年
if city == "上海" && age >= 18 {
print("aini 住在上海,并且是成年人。")
} else {
print("aini 不满足条件。")
}
- 使用
&&
连接两个条件:是否住在"上海"
,以及是否成年(age >= 18
)。 - 如果两个条件都为真,输出
aini 住在上海,并且是成年人。
- 如果其中任何一个条件为假,输出
aini 不满足条件。
6. 嵌套 if
语句
if
语句可以嵌套使用,形成多层条件判断。
let city = "上海"
let university = "东华大学"
// 判断是否住在上海,且是否就读于东华大学
if city == "上海" {
if university == "东华大学" {
print("aini 住在上海,且就读于东华大学。")
} else {
print("aini 住在上海,但不就读于东华大学。")
}
} else {
print("aini 不住在上海。")
}
- 首先判断是否住在
"上海"
,如果条件为真,再进一步判断是否就读于"东华大学"
。 - 如果两个条件都满足,输出
aini 住在上海,且就读于东华大学。
- 如果
university
不是"东华大学"
,但city
是"上海"
,则输出aini 住在上海,但不就读于东华大学。
- 如果
city
不是"上海"
,则输出aini 不住在上海。
7. 三元运算符
Swift 中的三元运算符 ? :
是简化 if-else
的一种方式,用于简单的条件判断。
let age = 22
// 使用三元运算符判断是否成年
let status = (age >= 18) ? "成年人" : "未成年"
print("aini 是 \(status)。")
age >= 18
是条件,如果条件为真,则返回"成年人"
,否则返回"未成年"
。- 输出
aini 是 成年人。
1-4 运算符
在编程中,operators(运算符) 是用于对变量或值进行操作的符号或关键字。运算符可以对值进行运算、比较、逻辑判断等操作。Swift 中提供了多种运算符,包括算术运算符、比较运算符、逻辑运算符等。
下面详细介绍 Swift 中常见的运算符,并通过简单的例子来演示。
1. 算术运算符
用于基本的数学运算,如加、减、乘、除。
运算符 | 功能 | 示例 |
---|---|---|
+ | 加法 | 2 + 3 |
- | 减法 | 5 - 2 |
* | 乘法 | 4 * 3 |
/ | 除法 | 10 / 2 |
% | 取余(取模) | 7 % 3 (结果为1) |
let a = 10
let b = 3
let sum = a + b // 加法,结果为 13
let difference = a - b // 减法,结果为 7
let product = a * b // 乘法,结果为 30
let quotient = a / b // 除法,结果为 3
let remainder = a % b // 取余,结果为 1
print("Sum: \(sum), Difference: \(difference), Product: \(product), Quotient: \(quotient), Remainder: \(remainder)")
2. 比较运算符
用于比较两个值,返回布尔值(true
或 false
)。
运算符 | 功能 | 示例 |
---|---|---|
== | 等于 | 5 == 5 (真) |
!= | 不等于 | 5 != 3 (真) |
> | 大于 | 5 > 3 (真) |
< | 小于 | 3 < 5 (真) |
>= | 大于等于 | 5 >= 5 (真) |
<= | 小于等于 | 3 <= 5 (真) |
let x = 5
let y = 10
print(x == y) // 输出 false
print(x != y) // 输出 true
print(x < y) // 输出 true
print(x >= 3) // 输出 true
3. 逻辑运算符
用于逻辑判断,主要用于布尔值操作。
运算符 | 功能 | 示例 |
---|---|---|
&& | 逻辑与(AND) | true && false |
` | ` | |
! | 逻辑非(NOT) | !true (false) |
let isAdult = true
let hasLicense = false
let canDrive = isAdult && hasLicense // 逻辑与,结果为 false
let canApplyForLicense = isAdult || hasLicense // 逻辑或,结果为 true
let notAdult = !isAdult // 逻辑非,结果为 false
print("Can drive: \(canDrive), Can apply for license: \(canApplyForLicense), Not adult: \(notAdult)")
4. 赋值运算符
用于将值赋给变量。
运算符 | 功能 | 示例 |
---|---|---|
= | 赋值 | a = 5 |
+= | 加后赋值 | a += 2 |
-= | 减后赋值 | a -= 1 |
*= | 乘后赋值 | a *= 3 |
/= | 除后赋值 | a /= 2 |
var value = 10
value += 5 // 等同于 value = value + 5,结果为 15
value -= 2 // 等同于 value = value - 2,结果为 13
print("Final value: \(value)")
5. 三元运算符
简化的 if-else
语句,格式为 condition ? value1 : value2
,如果条件为真,则返回 value1
,否则返回 value2
。
let age = 22
let canVote = (age >= 18) ? "可以投票" : "不能投票"
print(canVote) // 输出: 可以投票
6. 区间运算符
Swift 提供了两种区间运算符,用于表示一系列的值。
运算符 | 功能 | 示例 |
---|---|---|
... | 闭区间运算符 | 1...5 表示 1 到 5,包括 5 |
..< | 半开区间运算符 | 1..<5 表示 1 到 4,不包括 5 |
for number in 1...5 {
print(number) // 输出 1 到 5
}
for number in 1..<5 {
print(number) // 输出 1 到 4
}
7. 溢出运算符
当操作超过数据类型的最大或最小范围时使用。包括 &+
(溢出加法)、&-
(溢出减法)和 &*
(溢出乘法)。
let maxInt = Int8.max // Int8 最大值是 127
let overflowResult = maxInt &+ 1 // 溢出加法,结果是 -128
print(overflowResult) // 输出 -128
- 算术运算符:用于数学运算(加、减、乘、除)。
- 比较运算符:用于比较两个值(相等、大于、小于等)。
- 逻辑运算符:用于布尔逻辑判断(与、或、非)。
- 赋值运算符:用于将值赋给变量。
- 三元运算符:用于简化的条件判断。
- 区间运算符:用于表示值的范围。
- 溢出运算符:用于处理整数溢出。
Swift 的运算符非常灵活且功能强大,帮助开发者高效地处理数值、逻辑和条件操作。
1-5 函数
在 Swift 中,函数(functions) 是组织代码的基本方式之一。它们允许你将一段可复用的代码打包,并通过名称调用这些代码。函数可以有参数(用于传递输入)和返回值(用于返回结果)。Swift 中的函数功能非常强大,可以处理多种场景。
1. 定义函数
Swift 使用 func
关键字来定义函数。函数的基本结构如下:
func 函数名(参数名: 参数类型) -> 返回类型 {
// 函数体
}
func
:定义函数的关键字。函数名
:你为函数起的名称。参数名
:函数接收的输入变量。返回类型
:函数返回的值的类型。可以使用->
来定义返回类型。如果函数不返回值,返回类型可以省略或写作Void
。
2. 基本的函数例子
示例 1:无参数、无返回值的函数
func sayHello() {
print("你好,世界!")
}
sayHello() // 调用函数,输出:你好,世界!
示例 2:带参数的函数
func greet(name: String) {
print("你好,\(name)!")
}
greet(name: "Aini") // 调用函数,输出:你好,Aini!
- 这个函数
greet
接收一个String
类型的参数name
,并将其用于输出问候语。
示例 3:带参数和返回值的函数
func addNumbers(a: Int, b: Int) -> Int {
return a + b
}
let result = addNumbers(a: 5, b: 3)
print("结果是:\(result)") // 输出:结果是:8
- 这个函数
addNumbers
接收两个Int
类型的参数,并返回它们的和。返回类型是Int
。
3. 返回值的函数
示例:计算两个数的和
func sum(a: Int, b: Int) -> Int {
return a + b
}
let result = sum(a: 10, b: 20)
print(result) // 输出:30
return
关键字用于从函数中返回结果。
4. 多个参数的函数
函数可以接收多个参数,多个参数用逗号分隔。
示例:计算矩形的面积
func calculateArea(length: Double, width: Double) -> Double {
return length * width
}
let area = calculateArea(length: 5.0, width: 3.0)
print("面积是:\(area)") // 输出:面积是:15.0
5. 无返回值的函数
当函数没有返回值时,可以省略 ->
和返回类型,或者用 -> Void
显式表示函数不返回值。
func printMessage() {
print("这是一个没有返回值的函数")
}
printMessage() // 输出:这是一个没有返回值的函数
func sayHello() -> Void {
print("你好,世界!")
}
6. 带默认参数的函数
函数的参数可以设置默认值,当调用函数时如果不传递该参数,就会使用默认值。
func greet(name: String = "朋友") {
print("你好,\(name)!")
}
greet() // 输出:你好,朋友!
greet(name: "Aini") // 输出:你好,Aini!
- 如果不传递参数
name
,函数会使用默认值"朋友"
。
7. 参数标签
Swift 允许为函数的参数指定参数标签。参数标签在调用时使用,参数名在函数体内使用。参数标签可以让函数的调用更具可读性。
func greet(person name: String, from city: String) {
print("你好,\(name)!来自 \(city) 的问候。")
}
greet(person: "Aini", from: "上海") // 输出:你好,Aini!来自 上海 的问候。
- 在这个例子中,
person
和from
是参数标签,而name
和city
是实际的参数名称。
8. 可变参数
可变参数允许你向函数传递不确定数量的参数。通过在参数类型后面加上 ...
来定义可变参数。
func sum(numbers: Int...) -> Int {
var total = 0
for number in numbers {
total += number
}
return total
}
let result = sum(numbers: 1, 2, 3, 4, 5)
print("总和是:\(result)") // 输出:总和是:15
- 这个函数可以接收任意数量的整数参数,并返回它们的和。
9. 嵌套函数
Swift 支持在函数内部定义其他函数。
func outerFunction() {
func innerFunction() {
print("这是一个嵌套函数")
}
innerFunction() // 调用内部函数
}
outerFunction() // 输出:这是一个嵌套函数
innerFunction
是在outerFunction
内部定义的函数,只有在outerFunction
内部可以调用。
10. 返回函数的函数
函数可以返回另一个函数,这在 Swift 中称为高阶函数。
func makeIncrementer() -> (Int) -> Int {
func addOne(number: Int) -> Int {
return number + 1
}
return addOne
}
let increment = makeIncrementer()
print(increment(7)) // 输出:8
makeIncrementer
返回一个函数addOne
,它接收一个整数并返回该整数加 1。
11. 内联函数(Closure)
函数其实是一个特殊的闭包,闭包是一种可以捕获并存储其上下文中变量的代码块。Swift 中可以直接使用闭包表达式。
let addClosure = { (a: Int, b: Int) -> Int in
return a + b
}
let result = addClosure(3, 4)
print(result) // 输出:7
- 函数是 Swift 中组织和复用代码的基本方式。
- 函数可以有参数和返回值,也可以没有参数或返回值。
- Swift 支持函数的参数标签、默认参数、可变参数等高级功能。
- Swift 中的函数还可以嵌套、返回其他函数,甚至作为闭包来使用。
12. return 的作用
在 Swift 中,return
关键字用于返回函数的执行结果并终止函数的执行。如果函数需要返回一个值,return
用于指定这个值;如果函数没有返回值,return
也可以用于提前退出函数。
- 函数返回值:
return
用于将结果值返回给函数的调用者。 - 提前退出:即使函数没有返回值,你也可以用
return
来提前退出函数。
1. return
用于返回函数的结果
示例 1:带返回值的函数
如果函数的返回类型不为空,需要通过 return
来返回一个值。
func add(a: Int, b: Int) -> Int {
return a + b // 返回 a 和 b 的和
}
let result = add(a: 5, b: 3)
print("结果是:\(result)") // 输出:结果是:8
return a + b
表示返回两个数的和,返回类型是Int
。- 当调用
add(a: 5, b: 3)
时,返回值是8
。
示例 2:带返回值的字符串函数
func greet(name: String) -> String {
return "你好,\(name)!" // 返回一个问候语字符串
}
let message = greet(name: "Aini")
print(message) // 输出:你好,Aini!
return "你好,\(name)!"
返回了一个字符串,函数调用后将返回的值赋给message
。
2. 提前退出函数
即使函数没有返回值,你也可以通过 return
来提前终止函数的执行。在没有返回值的函数中,return
可以省略。
示例 3:无返回值函数中的提前退出
func checkAge(age: Int) {
if age < 18 {
print("未成年,无法访问。")
return // 提前退出函数
}
print("你是成年人,可以访问。")
}
checkAge(age: 16) // 输出:未成年,无法访问。
checkAge(age: 22) // 输出:你是成年人,可以访问。
- 当
age < 18
时,return
提前终止函数,跳过后续代码的执行。
3. 多次使用 return
在同一个函数中,可能会出现多个 return
语句,用于根据不同的条件返回不同的值。
示例 4:多条件返回
func checkGrade(score: Int) -> String {
if score >= 90 {
return "优秀"
} else if score >= 75 {
return "良好"
} else if score >= 60 {
return "及格"
} else {
return "不及格"
}
}
let grade = checkGrade(score: 85)
print("成绩:\(grade)") // 输出:成绩:良好
- 根据
score
的不同值,函数会返回不同的字符串。 - 函数一旦执行到某个
return
语句,便会立即退出并返回结果,后续代码不会执行。
4. return
可以省略的情况
如果函数返回类型是 Void
(无返回值),return
可以省略:
func sayHello() {
print("你好,世界!")
// 这里可以省略 return,因为没有需要返回的值
}
sayHello() // 输出:你好,世界!
5. 隐式返回(单行表达式函数)
Swift 允许你在某些情况下省略 return
关键字,通常是在只有一行表达式的函数或闭包中。
示例 5:隐式返回
func square(number: Int) -> Int {
number * number // 没有显式使用 return,但会隐式返回结果
}
let result = square(number: 4)
print(result) // 输出:16
在这个例子中,
square
函数只有一行代码,Swift 允许省略return
,但它会隐式地返回计算结果。return
关键字的作用是返回函数的结果给调用者,并结束函数的执行。如果函数有返回值,必须使用
return
返回与函数定义类型匹配的值。对于无返回值的函数,
return
可用于提前退出函数的执行。在一些情况下,Swift 允许省略
return
,特别是单行表达式的函数或闭包中。
1-6 guard 语句
在 Swift 中,guard
语句是一种条件检查语句,用于确保某些条件成立,否则提前退出当前代码块(例如函数、循环或其他作用域)。它通常用于提前退出代码块,如果条件不满足时立即终止执行,避免深度嵌套代码,从而提升代码的可读性和维护性。
1. guard
的特点:
- 必须在不满足条件时退出:当
guard
条件不成立时,必须退出当前代码块,通常使用return
、break
、continue
或throw
语句来终止执行。 - 提升代码可读性:相比
if
语句,guard
更加简洁,避免了代码深度嵌套。
2. guard
语句的语法:
guard 条件 else {
// 条件不满足时执行的代码块,通常是退出操作
return 或其他退出语句
}
3. 使用 guard
的场景:
- 输入校验:确保函数接收到合法参数。
- 提前退出:当条件不满足时,提前退出,避免后续代码执行。
- 可选值解包:用于安全解包
Optional
类型的值,确保后续代码能使用非可选类型。
4. guard
的用法示例:
1. 简单条件判断
guard
可以用来检查条件是否满足,如果不满足则提前退出。
func checkNumber(_ number: Int) {
guard number > 0 else {
print("数字必须大于 0")
return
}
print("数字 \(number) 是合法的")
}
checkNumber(10) // 输出:数字 10 是合法的
checkNumber(-5) // 输出:数字必须大于 0
- 在这个例子中,
guard
用来确保number
大于 0。如果条件不满足,函数提前退出。
2. 可选值的解包
guard
常用于解包可选类型(Optional
),并确保后续代码能安全地使用解包后的值。
func greet(person: String?) {
guard let name = person else {
print("名字不能为空")
return
}
print("你好,\(name)!")
}
greet(person: "Aini") // 输出:你好,Aini!
greet(person: nil) // 输出:名字不能为空
- 这里使用
guard let
解包可选值person
,只有当person
不为nil
时,才继续执行后续代码。如果person
是nil
,则提前退出。
3. 多个条件检查
guard
语句可以同时检查多个条件。
func validateCredentials(username: String?, password: String?) {
guard let user = username, !user.isEmpty,
let pass = password, pass.count >= 6 else {
print("用户名或密码无效")
return
}
print("用户名和密码有效")
}
validateCredentials(username: "Aini", password: "123456") // 输出:用户名和密码有效
validateCredentials(username: nil, password: "123") // 输出:用户名或密码无效
validateCredentials(username: "Aini", password: nil) // 输出:用户名或密码无效
- 在这个例子中,
guard
同时检查用户名和密码的多个条件。用户名不能为空,密码必须至少有 6 个字符。如果任何条件不满足,函数就会提前退出。
4. 循环中的 guard
在循环中,guard
可以用于过滤出不符合条件的元素,从而简化代码逻辑。
let numbers = [10, -5, 20, -3, 30]
for number in numbers {
guard number > 0 else {
continue // 跳过不符合条件的负数
}
print("正数:\(number)")
}
// 输出:
// 正数:10
// 正数:20
// 正数:30
guard
用于筛选出大于 0 的数字,不符合条件的负数通过continue
跳过,避免了嵌套的if
语句。
5. 在函数中的使用
guard
可以确保函数接收的参数是有效的。如果不符合条件,guard
会立即退出函数,确保后续的逻辑只会在合法的情况下执行。
func divide(_ numerator: Int?, by denominator: Int?) -> Int? {
guard let num = numerator, let den = denominator, den != 0 else {
print("无效的分子或分母")
return nil
}
return num / den
}
if let result = divide(10, by: 2) {
print("结果是 \(result)") // 输出:结果是 5
} else {
print("无法计算")
}
if let result = divide(10, by: 0) {
print("结果是 \(result)")
} else {
print("无法计算") // 输出:无法计算
}
guard
检查分子和分母是否存在,并确保分母不为 0。如果任何条件不满足,函数提前返回nil
,并跳过后续的计算。
5. 与 if
的对比
虽然 guard
和 if
都可以用来处理条件判断,但它们的使用场景不同:
if
:通常用于处理条件成立时的逻辑。guard
:用于处理条件不成立时的逻辑,常常用于提前退出函数、方法或循环。
if
示例:
func checkIfPositive(_ number: Int) {
if number > 0 {
print("数字是正数")
} else {
print("数字不是正数")
}
}
guard
示例:
func checkIfPositiveWithGuard(_ number: Int) {
guard number > 0 else {
print("数字不是正数")
return
}
print("数字是正数")
}
使用
guard
可以减少代码嵌套,使逻辑更加简洁和易读。在if
的例子中,代码块内有条件分支,而guard
会提前退出,使后续代码更简单。guard
用于确保某些条件成立,否则提前退出当前代码块。它在条件不满足时必须退出,通常与
return
、break
、continue
或throw
搭配使用。常用于输入校验、可选值解包、循环筛选等场景。
与
if
相比,guard
更适合处理提前退出的场景,能有效减少代码嵌套,提升代码的可读性。
1-7 Optionals 可选参数
1. 可选类型(Optional)
一个很简单的例子是,一个人的电话号码可能没有填写,这时我们可以使用 可选类型 来表示。
var phoneNumber: String? = nil // 初始状态下,电话号码没有值
// 之后可以赋值
phoneNumber = "123-456-7890"
// 输出
if let number = phoneNumber {
print("The phone number is \(number).")
} else {
print("No phone number provided.")
}
解释:
phoneNumber: String?
表示电话号码是可选的,可能是一个String
,也可能是nil
(无值)。- 使用
if let
语法来安全解包,如果phoneNumber
有值,就打印出来;如果是nil
,则输出 "No phone number provided."
2. 强制解包
假设我们有一个朋友的名字,你确定这个值不是 nil
,那么可以直接用 强制解包。
var friendName: String? = "Aini"
// 强制解包
print(friendName!) // 输出:Aini
解释:
friendName!
表示强制解包,将可选类型转换为普通类型。如果你确定friendName
不为nil
,可以用!
来获取值。- 注意:如果
friendName
是nil
,强制解包会导致程序崩溃。所以一般情况下我们更推荐用if let
语法来安全解包。
3. 可选链
假设你在存储一个人的家庭信息,其中可能没有填写地址。我们可以使用 可选链 来安全地访问地址的城市。
class Address {
var city: String
init(city: String) {
self.city = city
}
}
class Person {
var name: String
var address: Address? // 地址是可选的
init(name: String, address: Address?) {
self.name = name
self.address = address
}
}
let person1 = Person(name: "Aini", address: Address(city: "Shanghai"))
let person2 = Person(name: "John", address: nil)
// 使用可选链安全访问
if let city = person1.address?.city {
print("Person1 lives in \(city).")
} else {
print("Person1 has no address.")
}
if let city = person2.address?.city {
print("Person2 lives in \(city).")
} else {
print("Person2 has no address.")
}
解释:
address?.city
表示我们在安全地尝试访问address
的city
。如果address
是nil
,整个表达式都会返回nil
。- 通过可选链,程序不会崩溃,即使
address
没有值(为nil
)。
4. 可选参数
有时在定义函数时,某些参数是可选的,例如用户可以选择性地填写城市信息。
func displayPersonInfo(name: String, age: Int, city: String? = nil) {
print("Name: \(name)")
print("Age: \(age)")
// 如果提供了城市,打印出来
if let validCity = city {
print("City: \(validCity)")
} else {
print("City: Not provided")
}
}
// 调用函数时传递所有信息
displayPersonInfo(name: "Aini", age: 22, city: "Shanghai")
// 调用函数时不提供城市信息
displayPersonInfo(name: "Aini", age: 22)
解释:
city: String? = nil
表示city
是一个可选参数,默认值为nil
。- 如果用户不传递
city
,程序会输出 "City: Not provided"。
5. 使用默认值的可选参数
可选参数还可以带有默认值。例如一个问候函数,默认问候语是 "Hello",但用户可以提供自己的问候语。
func greetPerson(name: String, greeting: String = "Hello") {
print("\(greeting), \(name)!")
}
// 使用默认的问候语
greetPerson(name: "Aini") // 输出:Hello, Aini!
// 使用自定义的问候语
greetPerson(name: "Aini", greeting: "Hi") // 输出:Hi, Aini!
解释:
greeting: String = "Hello"
表示greeting
参数有一个默认值 "Hello"。- 如果调用时不提供
greeting
参数,它会使用默认值。
总结
通过这些简单的例子,你可以看出:
- 可选类型(
Optional
)用于表示一个变量可能有值,也可能没有值(nil
)。 - 强制解包(
!
)可以获取可选类型的值,但必须确保可选类型不为nil
,否则会崩溃。 - 可选绑定(
if let
)是一种安全的解包方式,确保只有在可选类型有值时才访问它。 - 可选链(
?.
)允许你在链式调用属性或方法时,如果遇到nil
会自动返回nil
,从而避免崩溃。 - 可选参数 允许函数参数可以为
nil
,使得函数调用更加灵活。
1-8 if let 和 guard let 区别
1. if let
和 guard let
的区别
相同点:
if let
和guard let
都是 Swift 中用于安全解包可选值(Optional
)的方式。它们可以确保在使用可选值时避免nil
引发的崩溃。- 它们都能够安全解包一个
Optional
类型,如果可选值有值,则解包并赋给一个临时常量。
不同点:
if let
是在解包成功后执行代码块,失败则跳过解包逻辑,不执行代码块。也就是说,解包失败时会执行else
语句块。guard let
是用于提前退出当前作用域的,它在解包失败时立即返回或退出当前的代码块,解包成功后继续执行后续的代码。
总结:
if let
更适合用在局部,在某个条件满足时才执行某段代码。guard let
更适合用在需要处理失败情况时,尤其是在函数或循环等需要提前退出的场景中。
2. if let
和 guard let
的使用场景举例
示例 1:使用 if let
// 一个简单的例子:处理用户可能没有填写的名字
var userName: String? = "Aini"
// 使用 if let 进行安全解包
if let validName = userName {
// 如果 userName 有值,则进入这个代码块
print("用户名是:\(validName)")
} else {
// 如果 userName 为 nil,则执行这个代码块
print("用户名为空")
}
解释:
if let
在userName
有值时解包,并将值赋给validName
。如果userName
为nil
,则执行else
语句,跳过解包。
示例 2:使用 guard let
func greetUser(userName: String?) {
// 使用 guard let 进行安全解包
guard let validName = userName else {
// 如果 userName 为 nil,则立即退出函数
print("用户名为空,无法继续")
return
}
// 如果 userName 有值,则继续执行下面的代码
print("欢迎,\(validName)!")
}
// 测试函数
greetUser(userName: nil) // 输出:用户名为空,无法继续
greetUser(userName: "Aini") // 输出:欢迎,Aini!
解释:
guard let
是用来保证一定有值才能继续执行后续代码。如果userName
为nil
,guard
会立即触发else
代码块,并退出函数。只有在解包成功时,才会继续执行函数的后续逻辑。
3. if let
和 guard let
的场景适用性
if let
的适用场景:
if let
常用于只需要解包后进行一小段操作的场景,并且当解包失败时,程序流程仍然可以继续。
func checkAge(age: Int?) {
// 使用 if let 检查年龄
if let validAge = age {
print("年龄是:\(validAge)")
} else {
print("未提供年龄")
}
// 其他逻辑可以继续执行
print("程序继续运行")
}
checkAge(age: 22) // 输出:年龄是:22
checkAge(age: nil) // 输出:未提供年龄
在这个例子中,即使 age
为 nil
,if let
解包失败后,程序仍然可以继续运行。
guard let
的适用场景:
guard let
常用于函数或方法中,遇到错误时需要提前退出。例如,函数需要某个参数值必须有效,若无效则不继续执行后续逻辑。
func processOrder(orderNumber: String?) {
// 使用 guard let 保证订单号存在
guard let validOrderNumber = orderNumber else {
print("订单号为空,无法处理订单")
return
}
// 订单号有效,可以继续处理订单
print("正在处理订单:\(validOrderNumber)")
}
processOrder(orderNumber: nil) // 输出:订单号为空,无法处理订单
processOrder(orderNumber: "123456") // 输出:正在处理订单:123456
在这里,如果 orderNumber
是 nil
,guard let
直接返回,不继续执行函数。否则,将继续执行订单处理逻辑。
if let
:用于局部的解包操作,适合处理可选值的场景,解包成功后执行特定代码。解包失败时,程序可以继续执行。guard let
:用于提前退出,适合需要满足某个条件才能继续运行的场景。如果条件不满足(解包失败),立即退出或返回,不再继续执行。如果你需要继续执行代码并且只处理某个可选值时用
if let
。如果你需要确保某个值存在且不满足时提前退出,用
guard let
。
1-9 元组
1. 基本的元组使用
场景: 假设你想存储一个人的姓名和年龄。这些信息很简单,并且你不想专门为它们创建一个类或结构体。那么可以使用元组来存储这些信息。
// 定义一个元组,存储人的姓名和年龄
let person = ("Aini", 22)
// 访问元组中的值
print("姓名是 \(person.0),年龄是 \(person.1) 岁。")
解释:
- 这个元组包含两个值:姓名
"Aini"
和年龄22
。 - 通过
person.0
来访问第一个值"Aini"
,通过person.1
来访问第二个值22
。
输出:
姓名是 Aini,年龄是 22 岁。
2. 给元组的元素命名
场景: 使用索引访问元组中的值虽然可以工作,但不够直观。我们可以给元组中的元素命名,使得代码更易读。
// 给元组中的值命名
let person = (name: "Aini", age: 22)
// 使用命名的方式来访问元组的值
print("姓名是 \(person.name),年龄是 \(person.age) 岁。")
解释:
- 在定义元组时,我们为每个元素取了名字
name
和age
,使得访问更加方便和直观。
输出:
姓名是 Aini,年龄是 22 岁。
3. 解包元组(Destructuring)
场景: 如果你想把元组里的每个值分别存储到单独的变量中,可以通过解包的方式来实现。
// 使用解包将元组中的值分别赋给不同的变量
let person = ("Aini", 22)
let (name, age) = person
// 使用解包后的变量
print("姓名是 \(name),年龄是 \(age) 岁。")
解释:
- 我们使用
let (name, age) = person
语法将元组中的两个值分别解包到name
和age
变量中,这样可以方便使用。
输出:
姓名是 Aini,年龄是 22 岁。
4. 函数返回多个值(使用元组)
场景: 假设你有一个函数,它需要返回多个值(比如学生的名字和成绩)。普通函数只能返回一个值,但是你可以用元组来让函数返回多个值。
// 定义一个函数,返回学生的名字和成绩
func getStudentInfo() -> (name: String, score: Int) {
return ("Aini", 95)
}
// 调用函数并接收返回的元组
let studentInfo = getStudentInfo()
// 使用返回的元组
print("学生的名字是 \(studentInfo.name),分数是 \(studentInfo.score) 分。")
解释:
getStudentInfo
函数返回了一个元组,包含学生的名字和成绩。- 我们可以通过
studentInfo.name
和studentInfo.score
来访问返回的多个值。
输出:
学生的名字是 Aini,分数是 95 分。
5. 元组用于交换两个变量的值
场景: 假设你想交换两个变量的值,元组可以让这个操作变得非常简单。
var a = 5
var b = 10
// 使用元组交换变量的值
(a, b) = (b, a)
print("a 的值是 \(a),b 的值是 \(b)。")
解释:
- 我们用
(a, b) = (b, a)
来同时交换a
和b
的值,元组让这个操作变得简洁高效。
输出:
a 的值是 10,b 的值是 5。
6. 忽略元组中的某些值
场景: 有时候你可能只关心元组中的部分值,其他值你可以忽略掉。这时可以使用 _
来忽略不需要的值。
// 定义一个元组,包含多个值
let person = (name: "Aini", age: 22, city: "Shanghai")
// 只解包你关心的值,忽略其他的值
let (name, _, city) = person
// 使用你关心的值
print("姓名是 \(name),居住在 \(city)。")
解释:
- 在解包时,
_
表示忽略元组中的某个值,这样你可以只使用需要的部分。
输出:
姓名是 Aini,居住在 Shanghai。
7. 元组与数组的区别
场景: 用简单的对比来展示元组和数组的不同。假设我们要存储三个数字,既可以用数组,也可以用元组。
// 使用数组
let numbersArray = [10, 20, 30]
// 使用元组
let numbersTuple = (10, 20, 30)
// 访问数组中的元素
print("数组中的第二个数字是 \(numbersArray[1])")
// 访问元组中的元素
print("元组中的第二个数字是 \(numbersTuple.1)")
解释:
- 数组 用于存储多个相同类型的值,可以通过索引访问,例如
numbersArray[1]
。 - 元组 可以存储多个不同类型的值,并且用
.1
、.2
等索引访问。
输出:
数组中的第二个数字是 20
元组中的第二个数字是 20
8. 使用元组来传递多种类型的数据
场景: 假设你要表示一个三维坐标点 (x, y, z)
,每个坐标轴都是不同类型的数据,元组就非常适合这种场景。
// 定义一个元组来表示一个三维坐标
let point3D = (x: 3.0, y: 4, z: "top")
// 访问元组中的值
print("X 轴坐标是 \(point3D.x),Y 轴坐标是 \(point3D.y),Z 轴表示的方向是 \(point3D.z)。")
解释:
- 这个元组包含
x
(浮点数)、y
(整数)、z
(字符串)三个不同类型的值,展示了元组可以存储不同类型数据的能力。
输出:
X 轴坐标是 3.0,Y 轴坐标是 4,Z 轴表示的方向是 top。
- 元组 是一种可以将多个不同类型的值组合在一起的数据结构,它允许你将多个相关的值整合在一起。
- 可以通过 索引 或 命名 来访问元组中的元素。
- 元组非常适合在不需要创建复杂数据结构时,临时存储和传递多个相关的值。
- 元组可以用来 解包,让你更方便地访问其中的每个值,甚至可以忽略不需要的部分。
在你这段代码中,错误的原因是你显式地定义了元组的类型为 (String, Int, Bool)
,当你显式定义类型时,元组不能带有命名的成员,只能通过索引来访问。
具体原因:
- 当你使用
(String, Int, Bool)
这种显式类型声明时,元组的元素没有命名,只能通过索引访问。 - 而
userData2.name
这种方式适用于未显式声明类型、并且使用了命名成员的元组。
因此,以下是两种正确的写法:
9. 如果你想使用元素名称来访问元组的成员,不要显式声明类型:
let userData2 = (name: "aini", age: 22, isStudent: true)
// 通过命名成员访问
print(userData2.name) // 输出:aini
print(userData2.age) // 输出:22
print(userData2.isStudent) // 输出:true
在这种情况下,元组的每个成员都有一个名字 (name
, age
, isStudent
),可以通过名称来访问。
10. 如果你要显式声明类型 (String, Int, Bool)
,只能通过索引访问:
let userData2: (String, Int, Bool) = ("aini", 22, true)
// 通过索引访问
print(userData2.0) // 输出:aini
print(userData2.1) // 输出:22
print(userData2.2) // 输出:true
在这种情况下,元组的成员没有名字,你只能通过索引来访问每个值。
- 命名成员:不要显式声明类型,直接定义并使用命名访问元组元素。
- 通过索引访问:如果显式声明类型
(String, Int, Bool)
,只能通过userData2.0
、userData2.1
等索引方式访问成员。
1-10 栈和队列
在 Swift 以及其他编程语言中,栈(Stack) 和 堆(Heap) 是两种内存管理机制,它们用于存储变量和对象。理解它们的工作原理对于优化性能和理解内存分配非常有帮助。在 Swift 中,栈和堆主要用于管理数据的存储和访问。接下来,我将详细介绍 栈和堆 的概念,并结合 Swift 进行讲解。
1. 什么是栈(Stack)
栈 是一种用于存储局部变量的内存区域,具有后进先出(LIFO, Last In First Out)的特点。栈的内存分配速度非常快,它通常用于存储小的、临时的、生命周期较短的值。
栈的特点:
- 内存分配非常快:栈是连续的内存块,分配和释放都由系统自动完成,速度很快。
- 后进先出:最新添加的变量最先被释放。
- 存储局部变量:函数内部的变量、基本数据类型(如
Int
,Float
,Bool
)通常存储在栈上。 - 自动管理内存:当函数执行结束时,栈上的内存自动被释放,程序员不需要手动管理。
示例:栈上存储数据
func calculateSum() {
let a = 10 // 局部变量 a,存储在栈中
let b = 20 // 局部变量 b,存储在栈中
let sum = a + b // sum 也是局部变量,存储在栈中
print("Sum is \(sum)")
}
calculateSum() // 当函数结束时,栈上的 a, b, sum 自动释放
解释:
- 变量
a
,b
,sum
是函数的局部变量,它们的生命周期很短,当函数calculateSum
结束时,它们自动从栈中释放。 - 栈的分配和释放非常高效,因为它是按照函数调用顺序自动管理的。
2. 什么是堆(Heap)
堆 是一种更灵活的内存区域,通常用于存储大型或动态分配的数据。与栈不同的是,堆上的数据不会自动释放,而是需要程序员或 Swift 的**自动引用计数(ARC)**来管理它。
堆的特点:
- 内存分配较慢:堆的内存分配和释放比栈要慢,因为它需要动态地管理内存。
- 存储复杂对象:引用类型的对象(如
class
实例)通常存储在堆上。 - 手动管理内存:堆上的内存需要通过 ARC 或手动管理来释放,内存不会在函数结束时自动释放。
- 灵活:堆上的数据可以在多个地方引用,数据的生命周期不受函数的限制。
示例:堆上存储数据
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
func createPerson() {
let person = Person(name: "Aini", age: 22) // Person 类的实例分配在堆中
print("Name: \(person.name), Age: \(person.age)")
}
createPerson() // Person 实例在函数结束后并不会立即释放,取决于引用计数
解释:
Person
是一个引用类型的类,类的实例person
被分配在堆中,即使函数createPerson
结束,堆中的数据不会立即被释放,而是依赖 Swift 的 ARC 机制来决定何时释放。- 堆上的数据可以在程序的多个地方被引用,当它不再被引用时,ARC 会自动释放它。
3. Swift 中栈和堆的使用
在 Swift 中,变量的存储位置(栈或堆)主要取决于它是值类型还是引用类型。
3.1 值类型(Value Types) - 栈上存储
Swift 中的值类型(如 struct
、enum
、基本数据类型
如 Int
, Bool
)一般存储在栈上。当你复制一个值类型时,它会生成一个完整的副本,并存储在栈中。
struct Point {
var x: Int
var y: Int
}
func createPoint() {
let point = Point(x: 10, y: 20) // Point 是值类型,存储在栈上
print("Point: (\(point.x), \(point.y))")
}
createPoint() // 函数结束时,point 自动从栈中释放
解释:
Point
是一个struct
,它是值类型。函数结束时,栈中的数据会自动释放。
3.2 引用类型(Reference Types) - 堆上存储
引用类型(如 class
)的实例存储在堆上,它们通过引用来访问。当多个变量指向同一个对象时,它们指向的是堆中的同一个内存区域。
class Car {
var brand: String
init(brand: String) {
self.brand = brand
}
}
func createCar() {
let car1 = Car(brand: "Tesla") // Car 是引用类型,存储在堆上
let car2 = car1 // car2 引用同一个 Car 实例
car2.brand = "BMW"
print("car1 brand: \(car1.brand)") // 输出:BMW
print("car2 brand: \(car2.brand)") // 输出:BMW
}
createCar()
解释:
Car
是引用类型,它的实例存储在堆上,car1
和car2
都引用了堆中的同一个Car
对象。改变car2
的brand
会影响car1
,因为它们指向的是同一个对象。
4. 栈与堆的比较
特性 | 栈(Stack) | 堆(Heap) |
---|---|---|
分配和释放速度 | 快,自动分配和释放 | 慢,动态分配,依赖 ARC 或手动释放 |
存储区域 | 小的局部变量、基本数据类型 | 类实例、引用类型等复杂对象 |
内存管理 | 自动管理,函数结束时自动释放 | 需要手动管理(通过 ARC) |
访问方式 | 按顺序(LIFO,后进先出) | 可以通过多个引用访问 |
是否拷贝 | 拷贝的是值 | 拷贝的是引用,多个引用指向同一对象 |
5. Swift 的 ARC 与堆内存管理
Swift 使用**自动引用计数(ARC)**来管理堆内存。每当你创建一个引用类型的实例时,ARC 会自动管理其内存的分配和释放。ARC 的工作方式如下:
- 当有变量引用对象时,ARC 会增加该对象的引用计数。
- 当没有任何变量引用该对象时,ARC 会自动释放该对象的内存。
示例:ARC 的工作原理
class Book {
var title: String
init(title: String) {
self.title = title
}
}
func createBook() {
var book1: Book? = Book(title: "Swift Programming")
var book2: Book? = book1 // book2 引用了同一个对象,引用计数增加
book1 = nil // book1 不再引用该对象,引用计数减少
print(book2?.title ?? "No Book") // 引用计数没有归零,book2 仍然可以访问
book2 = nil // book2 也不再引用该对象,引用计数归零,ARC 自动释放内存
}
createBook()
- 栈(Stack) 是用于存储临时、局部变量的内存区域,具有快速分配和释放的特点,适用于小的、简单的值类型数据。
- 堆(Heap) 是用于存储复杂对象(如类实例)的内存区域,存储在堆中的数据需要通过 ARC 或手动管理来释放内存。
- Swift 中的值类型(
struct
,enum
, 基本数据类型)通常存储在栈中,而引用类型(class
)的实例则存储在堆中。
Swift 的内存管理机制通过**自动引用计数(ARC)**来帮助程序员管理堆内存,使得在 Swift 中使用堆内存更加安全和高效
。如果你理解了栈和堆的区别及其工作原理,就能更好地编写高效且安全的 Swift 代码。
1-11 struct结构体
struct
(结构体) 是 Swift 中非常重要的一个概念,它是一种值类型,用来将多个相关联的数据打包在一起。结构体可以存储属性和方法,与类(class
)类似,但它们有一些关键的区别。下面我将详细讲解 struct
的概念,并通过一些简单易懂的例子帮助你理解它。
1. 什么是 struct
?
struct
是 Swift 中的一种自定义数据类型,用于将多个不同或相同类型的变量组合在一起。它可以包含属性(存储数据)和方法(提供功能)。与类不同,结构体是值类型,即它的实例在赋值或传递时会被复制。
2. 定义 struct
我们可以使用 struct
关键字来定义一个结构体。结构体内部可以包含:
- 属性:用来存储数据(也叫做成员变量)。
- 方法:用来定义功能(也叫做成员函数)。
示例 1:定义一个简单的结构体
struct Person {
var name: String
var age: Int
}
在这个例子中,我们定义了一个 Person
结构体,它有两个属性:name
(表示名字)和 age
(表示年龄)。你可以用这个结构体来存储多个人的信息。
3. 如何使用 struct
定义了 struct
之后,可以通过实例化结构体来创建结构体对象,并使用它的属性。
示例 2:使用结构体
// 定义 Person 结构体
struct Person {
var name: String
var age: Int
}
// 创建 Person 的实例
let person1 = Person(name: "Aini", age: 22)
let person2 = Person(name: "John", age: 30)
// 访问结构体的属性
print("姓名:\(person1.name),年龄:\(person1.age)") // 输出:姓名:Aini,年龄:22
print("姓名:\(person2.name),年龄:\(person2.age)") // 输出:姓名:John,年龄:30
解释:
- 通过
Person(name: "Aini", age: 22)
创建了一个Person
结构体实例person1
,并初始化了它的name
和age
属性。 - 我们可以通过
person1.name
和person1.age
来访问这些属性。
4. 结构体是值类型
在 Swift 中,结构体是值类型,这意味着当你将一个结构体实例赋值给另一个变量或传递给函数时,实际上是复制了一份数据,而不是引用同一个对象。每个结构体实例都是独立的,修改其中一个实例不会影响到另一个。
示例 3:结构体的值类型行为
struct Person {
var name: String
var age: Int
}
var person1 = Person(name: "Aini", age: 22)
var person2 = person1 // 复制 person1 的值给 person2
// 修改 person2 的属性
person2.name = "John"
// person1 和 person2 是独立的实例
print("person1 的名字是 \(person1.name)") // 输出:Aini
print("person2 的名字是 \(person2.name)") // 输出:John
解释:
person1
和person2
是两个独立的结构体实例。虽然person2
是通过复制person1
创建的,但它们是完全独立的。- 当你修改
person2
的name
属性时,person1
的值并不会受到影响。
5. 结构体中的方法
你可以在结构体中定义方法,用于操作或处理结构体中的数据。方法可以是实例方法,也可以是静态方法。
示例 4:在结构体中定义方法
struct Person {
var name: String
var age: Int
// 定义一个方法,打印问候语
func greet() {
print("你好,我叫 \(name),今年 \(age) 岁。")
}
}
let person = Person(name: "Aini", age: 22)
person.greet() // 输出:你好,我叫 Aini,今年 22 岁。
解释:
greet()
是结构体Person
中的一个方法,它可以访问name
和age
,并打印问候语。- 调用
person.greet()
方法时,结构体实例person
的name
和age
被用在了方法中。
6. 可变方法(Mutating Methods)
结构体中的方法默认不能修改属性,因为结构体是值类型。如果你想在方法中修改结构体的属性,需要使用 mutating
关键字。
示例 5:使用 mutating
修改属性
struct Person {
var name: String
var age: Int
// 定义一个 mutating 方法,用于修改名字
mutating func changeName(to newName: String) {
self.name = newName
}
}
var person = Person(name: "Aini", age: 22)
person.changeName(to: "John") // 修改名字
print("修改后的名字是 \(person.name)") // 输出:修改后的名字是 John
解释:
changeName
是一个mutating
方法,它允许修改结构体的name
属性。mutating
关键字告诉 Swift,方法会修改结构体实例自身的数据。
7. 结构体中的构造函数
结构体可以拥有自定义的构造函数(初始化方法),用来初始化结构体实例。Swift 为每个结构体提供了一个默认的构造函数,但你也可以根据需要自定义。
示例 6:自定义构造函数
struct Person {
var name: String
var age: Int
// 自定义构造函数
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let person = Person(name: "Aini", age: 22)
print("姓名:\(person.name),年龄:\(person.age)") // 输出:姓名:Aini,年龄:22
解释:
- 我们自定义了一个构造函数
init(name:age:)
,用于初始化结构体实例。 - 你可以根据需要定义多个构造函数,来处理不同的初始化需求。
8. 结构体和类的区别
虽然 struct
和 class
在很多方面相似,但它们有几个关键的区别:
特性 | 结构体 (struct ) | 类 (class ) |
---|---|---|
类型 | 值类型(Value Type) | 引用类型(Reference Type) |
内存存储 | 赋值时会复制 | 赋值时传递引用 |
继承 | 不支持继承 | 支持继承 |
ARC 内存管理 | 不受 ARC 控制 | 受 ARC 控制 |
构造函数 | 自动生成默认构造函数 | 需要自定义构造函数 |
- 值类型(结构体)在传递和赋值时会被复制,每个实例都是独立的。
- 引用类型(类)在传递时传递的是引用,多个实例可能指向同一个对象,修改其中一个会影响其他所有引用。
9. 实际应用场景
结构体 适用于表示简单的数据结构,例如几何图形、二维或三维坐标、商品信息、用户信息等。
类 适用于更复杂的对象,尤其是需要继承和共享状态的对象,例如用户账户、网络请求、UI组件等。
结构体 是 Swift 中的一种自定义数据类型,适用于存储相关的多组数据。结构体是值类型,赋值和传递时会复制,而不是传递引用。
结构体可以包含属性和方法,使用
mutating
关键字可以在方法中修改属性。struct
不支持继承,但使用场景非常广泛,尤其适合简单的数据模型。
1-12 struct里面 let 和 var 的区别
在 Swift 中,struct
内部使用 let
和 var
声明属性时,它们的行为有一些区别,尤其是在处理不可变和可变实例时。为了更好地理解这些区别,我们需要分开讨论:
1. let
和 var
的基本区别
let
:用于声明常量,一旦赋值后,属性的值不能再更改。var
:用于声明变量,赋值后可以更改属性的值。
这个区别不仅适用于结构体的属性声明,还适用于结构体实例本身的声明。我们需要分别讨论 结构体内部的属性声明 和 结构体实例的声明。
2. let
和 var
在 struct
内部的属性声明
示例:let
与 var
在结构体内部的区别
struct Person {
let name: String // 使用 let 声明的属性是常量,不能修改
var age: Int // 使用 var 声明的属性是变量,可以修改
}
在这个例子中:
name
是用let
声明的,所以在初始化Person
结构体时赋值后就不能修改。age
是用var
声明的,可以在结构体实例中自由修改。
示例 1:通过 var
实例修改属性
var person = Person(name: "Aini", age: 22)
// 允许修改 age,因为 age 是 var 声明的属性
person.age = 23
// 不允许修改 name,因为 name 是 let 声明的属性
// person.name = "John" // 错误:name 是常量,不能修改
print("姓名:\(person.name),年龄:\(person.age)") // 输出:姓名:Aini,年龄:23
解释:
person.age = 23
是允许的,因为age
是用var
声明的变量。person.name
无法修改,因为name
是用let
声明的常量。
3. let
和 var
在结构体实例声明时的区别
Swift 中的结构体是值类型,这意味着当你使用 let
或 var
声明结构体实例时,它会影响你是否可以修改结构体中的可变属性。
示例 2:通过 let
和 var
声明结构体实例
struct Person {
var name: String
var age: Int
}
var person1 = Person(name: "Aini", age: 22) // var 允许修改
let person2 = Person(name: "John", age: 30) // let 不允许修改
// 允许修改 person1 的属性,因为 person1 是 var
person1.age = 23
// 不允许修改 person2 的属性,因为 person2 是 let
// person2.age = 31 // 错误:不能修改 let 声明的结构体实例
print("person1 年龄:\(person1.age)") // 输出:23
print("person2 年龄:\(person2.age)") // 输出:30
解释:
var person1
允许修改Person
结构体的属性,因为person1
是用var
声明的。let person2
表示person2
是不可变的,因此不能修改它的任何属性,即使属性是用var
声明的。
小结:
- 使用
var
声明的结构体实例可以修改其中所有用var
声明的属性。 - 使用
let
声明的结构体实例,即使属性是var
,也不能修改这些属性。
4. mutating
关键字的作用
如果你在结构体中定义了方法,并且这个方法需要修改结构体中的属性,你必须在方法前面加上 mutating
关键字。
示例 3:使用 mutating
关键字
struct Person {
var name: String
var age: Int
// 定义一个 mutating 方法,用于修改 age
mutating func haveBirthday() {
age += 1
}
}
var person = Person(name: "Aini", age: 22)
person.haveBirthday() // 修改年龄
print("年龄:\(person.age)") // 输出:年龄:23
解释:
mutating
关键字允许haveBirthday
方法修改结构体的age
属性。- 只有使用
var
声明的结构体实例才能调用mutating
方法。用let
声明的结构体实例是不可变的,无法调用这些修改数据的方法。
示例 4:let
实例不能调用 mutating
方法
let person = Person(name: "Aini", age: 22)
// person.haveBirthday() // 错误:不能对 let 实例调用 mutating 方法
解释:
- 因为
person
是用let
声明的,不允许调用mutating
方法haveBirthday()
,因为该方法会试图修改结构体的状态。
5. 总结
let
和 var
在 struct
内部的区别:
let
:用于声明不可变的属性,一旦初始化后,不能修改其值。var
:用于声明可变的属性,可以在实例中自由修改。
let
和 var
在 struct
实例声明时的区别:
- 用
let
声明结构体实例时,不能修改实例中的任何属性,即使这些属性是用var
声明的。 - 用
var
声明结构体实例时,可以修改实例中的var
属性。
结构体中的 mutating
方法:
当结构体的方法需要修改结构体的属性时,必须在方法前加上
mutating
关键字。用
let
声明的结构体实例不能调用mutating
方法,因为let
表示这个结构体实例是不可变的。如果你想让结构体中的属性不可更改,使用
let
声明。如果你希望属性可以更改,使用
var
声明,并确保结构体实例也是通过var
声明的。结构体是值类型,所以结构体实例是独立的,不同实例之间不会互相影响。
1-13 mutating 的作用
在 Swift 中,mutating
关键字的作用是允许值类型(如 struct
和 enum
)中的方法修改该类型的实例的属性。默认情况下,结构体(struct
)和枚举(enum
)中的方法是不可修改其自身属性的,因为它们是值类型,当你尝试修改属性时,编译器会报错。
为了使方法能够修改实例的属性,Swift 要求你在方法前使用 mutating
关键字。这表明这个方法会修改结构体或枚举的实例数据。
1. 为什么需要 mutating
因为结构体和枚举是值类型,在默认情况下,所有的属性都是不可变的。如果你想在结构体的方法内部修改它的属性,就必须告诉编译器“我需要修改这个实例的数据”,这时候就需要使用 mutating
关键字。
2. mutating
的基本使用
示例 1:不使用 mutating
修改属性会报错
struct Person {
var name: String
var age: Int
// 尝试修改 age 属性
func haveBirthday() {
age += 1 // 错误:无法修改 age 属性,因为结构体方法默认是不可变的
}
}
var person = Person(name: "Aini", age: 22)
person.haveBirthday() // 这行代码会导致编译错误
错误原因:
- 在
haveBirthday()
方法中,我们试图修改age
属性,但 Swift 默认不允许值类型(如struct
)的方法修改它的属性,除非使用mutating
。
示例 2:使用 mutating
来修改属性
struct Person {
var name: String
var age: Int
// 使用 mutating 来修改 age 属性
mutating func haveBirthday() {
age += 1 // 现在可以修改 age 属性了
}
}
var person = Person(name: "Aini", age: 22)
person.haveBirthday() // 现在可以正常工作
print("年龄:\(person.age)") // 输出:年龄:23
解释:
mutating
关键字告诉编译器,这个方法会修改实例的属性。因此,haveBirthday()
方法能够成功修改age
属性。
3. mutating
可以修改属性,也可以改变实例本身
除了修改属性,mutating
方法还能改变结构体实例的整个值。通过 mutating
方法,你甚至可以让一个结构体实例变成另一个完全不同的实例。
示例 3:mutating
修改整个实例
struct Point {
var x: Int
var y: Int
// 使用 mutating 来修改整个 Point 实例
mutating func moveTo(newX: Int, newY: Int) {
self = Point(x: newX, y: newY) // 修改整个实例
}
}
var point = Point(x: 0, y: 0)
point.moveTo(newX: 10, newY: 20) // 调用 mutating 方法
print("新的坐标:(\(point.x), \(point.y))") // 输出:新的坐标:(10, 20)
解释:
moveTo()
方法使用self = Point(x: newX, y: newY)
语法,修改了整个Point
实例。通过mutating
方法,整个self
实例都可以被替换为另一个新值。
4. mutating
方法与 let
实例
如果你使用 let
来声明一个结构体实例,则即使该结构体的方法是 mutating
的,你也不能调用它们。这是因为 let
声明的实例是不可变的。
示例 4:let
实例无法调用 mutating
方法
struct Person {
var name: String
var age: Int
mutating func haveBirthday() {
age += 1
}
}
let person = Person(name: "Aini", age: 22)
// person.haveBirthday() // 错误:person 是 let,不能调用 mutating 方法
解释:
let
声明的实例是不可变的,因此你不能调用mutating
方法来修改它的属性。只有用var
声明的实例可以调用mutating
方法。
5. mutating
方法在 enum
中的使用
不仅仅是 struct
,在 Swift 中,enum
(枚举)也可以定义 mutating
方法。你可以通过 mutating
方法改变枚举实例的当前值。
示例 5:在 enum
中使用 mutating
enum LightSwitch {
case off
case on
// 定义一个 mutating 方法来切换开关状态
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var light = LightSwitch.off
light.toggle() // 改为 on
print(light) // 输出:on
light.toggle() // 再次切换,改为 off
print(light) // 输出:off
解释:
在这个例子中,
toggle
是一个mutating
方法,它可以修改枚举实例light
的值。每次调用时,开关在on
和off
之间切换。mutating
关键字:用于标记结构体或枚举中的方法,使得这些方法可以修改实例的属性,或者修改实例本身。值类型:Swift 中的
struct
和enum
是值类型,默认情况下,它们的方法是不可修改实例数据的。如果你想修改它们的属性或改变实例的状态,必须使用mutating
。不可变实例:如果用
let
声明了一个结构体或枚举的实例,那么即使这个结构体或枚举有mutating
方法,也不能调用,因为let
实例是不可变的。如果你想在结构体(
struct
)或枚举(enum
)中编写可以修改实例属性或改变实例自身的方法,需要使用mutating
关键字。mutating
只适用于值类型(如struct
和enum
),类(class
)因为是引用类型,默认可以修改实例属性,因此不需要mutating
。
1-14 Enums
1. 什么是枚举(enum
)?
枚举是用来表示一组有限的可能值的数据类型。比如,交通灯有三种状态:红灯、黄灯、绿灯。用枚举就可以很好地表达这种情况。
Swift 中的枚举不仅仅是简单的常量集合,它非常强大,甚至可以附加更多的信息和逻辑。
2. 如何定义和使用枚举
例子:交通灯的状态
enum TrafficLight {
case red // 红灯
case yellow // 黄灯
case green // 绿灯
}
这个 TrafficLight
枚举定义了三种交通灯的状态:红灯、黄灯、绿灯。每个状态用 case
定义。
使用枚举:
// 定义一个变量,表示当前的交通灯状态
var currentLight = TrafficLight.red
// 打印当前的交通灯状态
print(currentLight) // 输出:red
解释:
currentLight
是一个TrafficLight
类型的变量,它的值是.red
,表示当前交通灯是红灯。- 使用
currentLight = TrafficLight.red
赋值。
3. 枚举和 switch
语句
switch
语句非常适合用来处理枚举的不同值,因为它可以确保每个枚举的情况都被处理。
例子:根据交通灯状态执行不同的操作
// 定义交通灯的枚举
enum TrafficLight {
case red
case yellow
case green
}
// 当前交通灯状态
var currentLight = TrafficLight.green
// 使用 switch 处理不同的交通灯状态
switch currentLight {
case .red:
print("红灯,停止!")
case .yellow:
print("黄灯,准备。")
case .green:
print("绿灯,通行!")
}
解释:
switch
语句通过检查currentLight
的值来执行不同的操作。- 当
currentLight
为.red
时,会输出 "红灯,停止!",为.green
时会输出 "绿灯,通行!"。
4. 枚举的关联值(Associated Values)
有时,枚举的每个值可能需要附加额外的信息。比如,我们可以定义一个枚举,表示条形码,其中一种是数字条形码,另一种是二维码。每种条形码会带有不同的信息。
例子:带有关联值的枚举
enum Barcode {
case upc(Int, Int, Int, Int) // UPC 条形码,有四个整数
case qrCode(String) // QR 二维码,有一个字符串
}
// 使用带有关联值的枚举
var productBarcode = Barcode.upc(8, 85909, 51226, 3) // 数字条形码
print(productBarcode)
productBarcode = .qrCode("QR123456ABC") // 现在使用二维码
print(productBarcode)
解释:
Barcode
枚举定义了两种情况:upc
(四个整数条形码)和qrCode
(一个字符串二维码)。- 枚举的每个
case
可以附带不同类型的数据(即关联值)。 - 在
productBarcode = .qrCode("QR123456ABC")
这行代码中,我们将productBarcode
更改为一个带有二维码数据的值。
5. 原始值(Raw Values)
枚举中的每个 case
还可以有一个固定的值,称为原始值。比如,你可以给一周的每一天定义一个数字(星期一是 1
,星期二是 2
等等)。
例子:带有原始值的枚举
enum Weekday: Int {
case monday = 1
case tuesday
case wednesday
case thursday
case friday
case saturday
case sunday
}
// 通过原始值创建枚举实例
let today = Weekday(rawValue: 3)
print(today) // 输出:Optional(Weekday.wednesday)
解释:
Weekday
枚举表示一周的七天,并且每一天都关联了一个整数(Int
),这是它们的原始值。monday
的原始值是1
,接下来的tuesday
,wednesday
等原始值会依次递增。Weekday(rawValue: 3)
会创建一个代表星期三的枚举值。
6. 枚举中的方法
你可以在枚举中定义方法,使得每个枚举值不仅可以表示某个状态,还可以执行特定的操作。
例子:在枚举中定义方法
enum TrafficLight {
case red, yellow, green
// 定义方法,返回状态描述
func description() -> String {
switch self {
case .red:
return "红灯,停止!"
case .yellow:
return "黄灯,准备。"
case .green:
return "绿灯,通行!"
}
}
}
let currentLight = TrafficLight.green
print(currentLight.description()) // 输出:绿灯,通行!
解释:
TrafficLight
枚举中定义了一个方法description()
,用于根据不同的灯光状态返回对应的提示信息。- 当我们调用
currentLight.description()
时,它会根据当前的状态输出相应的信息。
7. 递归枚举(Recursive Enum)
递归枚举是指枚举的一个或多个 case
可以包含自身作为关联值。这种枚举用于表达像树结构或数学表达式之类的复杂数据。
例子:递归枚举表示数学表达式
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 表达式: (5 + 4) * 2
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, .number(2))
// 计算表达式的值
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case .number(let value):
return value
case .addition(let left, let right):
return evaluate(left) + evaluate(right)
case .multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
let result = evaluate(product)
print(result) // 输出:18
解释:
这个例子定义了一个递归枚举
ArithmeticExpression
,它可以表示数字、加法和乘法表达式。递归意味着
addition
和multiplication
可以包含其他ArithmeticExpression
值。最后,通过递归调用
evaluate
函数计算出表达式的结果。基本枚举:定义一组有限的可能值,适用于表示状态、类别等场景。例如:交通灯状态、指南针方向、星期几等。
关联值:枚举的每个
case
可以附加不同类型的值,使得枚举更灵活,适用于存储不同的相关数据。例如:条形码、二维码等。原始值:枚举的每个
case
可以关联一个固定的值(如Int
或String
),便于与其他系统或数据类型进行映射。例如:星期几和整数的映射。枚举方法:枚举不仅仅是数据,它们还可以包含方法,用于处理枚举中的值,增加功能性。
递归枚举:枚举可以包含自身作为关联值,适用于表达递归的数据结构,比如数学表达式或树形结构。
通过这些通俗易懂的例子,相信你已经对 Swift 中的枚举有了更深入的理解。枚举非常灵活,在代码中用来表示一组有限的、相关联的状态或值,是一个强大的工具。如果有更多问题或需要更详细的解释,欢迎继续提问!
1-15 Class
1. 什么是类(class
)?
在 Swift 中,类(class
) 是一种定义对象的模板,用来描述一类具有相同属性和方法的对象。你可以通过类来创建对象(类的实例),并为它们赋予特定的状态和行为。
类和结构体(struct
)的区别在于:
- 类是引用类型,当你将一个类的实例赋值给另一个变量时,两个变量引用的是同一个对象。
- 类支持继承,也就是说,一个类可以继承另一个类的属性和方法。
2. 如何定义类
我们可以使用 class
关键字来定义类。类通常包含属性(用来存储值)和方法(用来定义行为)。
例子 1:定义一个简单的类
class Person {
var name: String
var age: Int
// 定义一个初始化方法
init(name: String, age: Int) {
self.name = name
self.age = age
}
// 定义一个方法,用来打印信息
func introduce() {
print("你好,我叫 \(name),今年 \(age) 岁。")
}
}
解释:
Person
是一个类,它有两个属性:name
和age
,分别表示人的名字和年龄。init
是类的构造函数,用于在创建类的实例时初始化属性。introduce
是一个类的方法,用于打印人的名字和年龄。
3. 如何使用类
我们可以通过构造函数(init
)来创建类的实例,并调用它的方法。
例子 2:创建类的实例并调用方法
// 创建 Person 类的实例
let person1 = Person(name: "Aini", age: 22)
// 调用 introduce 方法
person1.introduce() // 输出:你好,我叫 Aini,今年 22 岁。
解释:
let person1 = Person(name: "Aini", age: 22)
创建了一个Person
类的实例,person1
是一个对象,它的name
是"Aini"
,age
是22
。person1.introduce()
调用了introduce()
方法,输出了这个人的自我介绍。
4. 类是引用类型
类是引用类型,这意味着当你将一个类的实例赋值给另一个变量时,两个变量引用的是同一个对象。修改其中一个变量的值会影响另一个。
例子 3:类的引用行为
// 创建类的实例
let person1 = Person(name: "Aini", age: 22)
let person2 = person1 // person2 引用的是 person1
// 修改 person2 的属性
person2.name = "John"
// 打印 person1 的属性
person1.introduce() // 输出:你好,我叫 John,今年 22 岁。
解释:
person2 = person1
表示person2
和person1
指向同一个Person
对象。- 当
person2
的name
被修改时,person1
也会受到影响,因为它们引用的是同一个对象。
5. 类的继承(Inheritance)
类支持继承,你可以定义一个类来继承另一个类的属性和方法。继承是 OOP(面向对象编程)的核心概念之一,它允许我们在新类中复用已有类的功能,同时扩展或修改这些功能。
例子 4:类的继承
// 定义一个基类
class Animal {
var name: String
init(name: String) {
self.name = name
}
func speak() {
print("\(name) 在发出声音。")
}
}
// 定义一个子类 Dog,继承自 Animal
class Dog: Animal {
// 重写父类的 speak 方法
override func speak() {
print("\(name) 叫:汪汪!")
}
}
// 使用继承创建 Dog 类的实例
let myDog = Dog(name: "旺财")
myDog.speak() // 输出:旺财 叫:汪汪!
解释:
Animal
是一个基类,表示所有动物都有名字并且可以发出声音。Dog
类继承了Animal
,并重写了speak()
方法,表示狗会发出 "汪汪" 的叫声。myDog
是Dog
类的实例,它调用了重写的speak()
方法。
6. 类中的 self
关键字
在类的方法中,self
用于引用当前对象的实例。我们使用 self
来区分类的属性和方法中的参数或局部变量。
例子 5:使用 self
关键字
class Car {
var brand: String
init(brand: String) {
// 使用 self 区分参数和属性
self.brand = brand
}
func printBrand() {
print("这是一辆 \(self.brand)")
}
}
// 创建 Car 类的实例
let myCar = Car(brand: "Tesla")
myCar.printBrand() // 输出:这是一辆 Tesla
解释:
- 在
init
方法中,self.brand
表示类的属性brand
,而brand
是构造函数的参数。使用self
可以清楚地区分二者。 - 在
printBrand()
方法中,self.brand
引用当前实例的brand
属性。
7. 类的 deinit 方法(析构函数)
Swift 中的类还有一个特殊的方法 deinit
,称为析构函数。当类的实例被释放时,系统会自动调用 deinit
方法,用来清理资源或执行一些任务。
例子 6:类的析构函数
class Person {
var name: String
init(name: String) {
self.name = name
print("\(name) 被初始化")
}
deinit {
print("\(name) 被销毁")
}
}
// 创建和销毁类的实例
var person1: Person? = Person(name: "Aini")
person1 = nil // 此时,person1 被销毁,deinit 被调用
解释:
init
是构造函数,在类实例被创建时调用。deinit
是析构函数,当类的实例不再使用时会被调用,用来清理资源。- 当我们将
person1
设置为nil
,它的deinit
方法被调用,表示实例被销毁。
8. 类的属性观察器
Swift 中的类属性可以有属性观察器,用于在属性值改变时执行一些代码。常见的属性观察器有 willSet
和 didSet
。
例子 7:属性观察器
class Person {
var age: Int = 0 {
willSet(newAge) {
print("年龄即将从 \(age) 改变为 \(newAge)")
}
didSet {
print("年龄已经从 \(oldValue) 改变为 \(age)")
}
}
}
// 创建 Person 实例并修改年龄
let person = Person()
person.age = 22 // 将触发 willSet 和 didSet
解释:
willSet
在属性的值即将改变时触发,newAge
是新值。didSet
在属性的值已经改变后触发,oldValue
是旧值。- 当
person.age = 22
被执行时,willSet
和didSet
都会被触发,并打印相关信息。
9. 类的 static
和 class
关键字
static
:用于定义类的静态方法或静态属性,它们属于类本身,而不是类的实例。class
:用于定义可以被子类重写的类方法。
例子 8:静态属性和方法
class MathUtility {
static let pi = 3.14159
static func square(_ number: Int) -> Int {
return number * number
}
}
// 访问静态属性和调用静态方法
print(MathUtility.pi) // 输出:3.14159
print(MathUtility.square(5)) // 输出:25
解释:
MathUtility.pi
是一个静态属性,用static
定义,它属于MathUtility
类,而不是某个实例。MathUtility.square()
是一个静态方法,可以直接通过类名调用。
. 总结
- 类(
class
) 是一种用来定义对象的模板,它可以包含属性和方法。 - 类是引用类型,当将类的实例赋值给另一个变量时,两个变量引用的是同一个对象。
- 类支持继承,子类可以继承父类的属性和方法,并可以重写父类的方法。
- 构造函数(
init
)用于初始化类的实例,析构函数(deinit
)用于在类的实例被销毁时执行一些清理任务。 - 你可以在类中使用属性观察器(
willSet
和didSet
)来监控属性值的变化。 static
关键字用于定义类的静态属性和方法,而class
关键字用于定义可以被子类重写的类方法。
1-16 private 和 public
在 Swift 中,访问控制(Access Control)用于限制代码中对类、结构体、枚举及其属性和方法的访问。最常见的访问控制有 private
和 public
,它们控制了你是否可以从不同的代码文件或模块中访问特定的属性、方法或类。
1. 什么是 private
?
private
修饰符用于将属性或方法限制在当前的类或结构体内部,这意味着:
- 只能在类、结构体或枚举的内部访问。
- 不能在外部访问这个属性或方法,甚至是子类也不能直接访问。
例子 1:private
修饰符的基本使用
class Person {
private var name: String
init(name: String) {
self.name = name
}
// 提供一个方法来访问 private 的属性
func getName() -> String {
return self.name
}
}
let person = Person(name: "Aini")
// person.name // 错误:不能直接访问 private 属性
print(person.getName()) // 输出:Aini
解释:
name
属性被private
修饰,这意味着它只能在Person
类内部访问,外部无法直接访问。- 我们通过类中的
getName()
方法间接访问了name
属性。 - 试图直接访问
person.name
会导致编译错误。
2. 什么是 public
?
public
修饰符用于将属性或方法的访问权限设置为模块内外都可以访问,这意味着:
- 任何模块(包括你自己的项目、第三方框架、或者库)中的代码都可以访问
public
属性或方法。 public
适合那些需要公开使用的属性或方法,比如公共 API。
例子 2:public
修饰符的基本使用
class Person {
public var name: String
init(name: String) {
self.name = name
}
public func getName() -> String {
return self.name
}
}
let person = Person(name: "Aini")
print(person.name) // 可以直接访问 public 属性
print(person.getName()) // 可以直接调用 public 方法
解释:
name
属性和getName()
方法被public
修饰,意味着它们可以在类外部访问。- 在这个例子中,我们可以直接访问
person.name
和调用person.getName()
,因为它们是public
。
3. private
和 public
的区别总结
修饰符 | 访问权限 | 适用场景 |
---|---|---|
private | 只能在类、结构体或枚举的内部访问 | 用于隐藏实现细节或保护敏感数据,防止外部访问 |
public | 任何模块或代码文件都可以访问 | 用于公开接口、API,供外部模块和代码使用 |
4. private
的使用场景
private
通常用于隐藏类或结构体内部的实现细节,不希望外部直接修改或访问它。通过这样做,你可以更好地控制类或结构体的行为,防止外部代码对其进行不合适的修改。
例子 3:使用 private
保护敏感数据
class BankAccount {
private var balance: Double = 0.0 // 余额是 private 的,不能直接访问
// 提供公开的方法来修改余额
public func deposit(amount: Double) {
balance += amount
}
public func withdraw(amount: Double) -> Bool {
if amount > balance {
print("余额不足")
return false
} else {
balance -= amount
return true
}
}
public func getBalance() -> Double {
return balance
}
}
let account = BankAccount()
account.deposit(amount: 100.0)
print(account.getBalance()) // 输出:100.0
let success = account.withdraw(amount: 50.0)
print("取款成功吗?\(success)") // 输出:取款成功吗?true
print(account.getBalance()) // 输出:50.0
解释:
balance
被标记为private
,因此它不能直接被外部访问或修改。- 提供了公开的
deposit()
和withdraw()
方法来控制如何修改balance
。这样可以确保只有通过这些方法才能改变余额,从而保护了数据的完整性。
5. public
的使用场景
public
适合用于那些你希望在其他模块中也可以使用的类、属性或方法,特别是公共 API 或者库中的代码。
例子 4:public
方法用于公开功能
class Car {
public var brand: String
public var speed: Double = 0.0
init(brand: String) {
self.brand = brand
}
public func accelerate(by amount: Double) {
speed += amount
print("\(brand) 加速到 \(speed) km/h")
}
public func stop() {
speed = 0
print("\(brand) 已停止")
}
}
let car = Car(brand: "Tesla")
car.accelerate(by: 20) // 输出:Tesla 加速到 20.0 km/h
car.stop() // 输出:Tesla 已停止
解释:
Car
类中的brand
属性、accelerate()
和stop()
方法都被标记为public
,因此可以在类外部访问和调用。- 使用
public
,你可以定义一个供外部使用的功能接口。
6. internal
和 fileprivate
在了解 private
和 public
之后,还有其他两个访问控制修饰符,它们是 internal
和 fileprivate
。虽然它们不在 private
和 public
的直接对比中,但为了更全面地理解 Swift 的访问控制,这里简单介绍一下它们:
internal
(默认):类、属性和方法在同一个模块(项目)内都可以访问。如果你不明确指定访问控制,Swift 默认就是internal
。fileprivate
:限制属性或方法只能在同一个源文件中访问。
例子 5:fileprivate
和 internal
class Vehicle {
fileprivate var model: String = "Default"
internal var speed: Double = 0.0
}
class Bike: Vehicle {
func printModel() {
print("型号是:\(model)") // 可以访问同文件中的 fileprivate 属性
}
}
let bike = Bike()
bike.printModel() // 输出:型号是:Default
bike.speed = 20.0 // internal 属性在同一模块内可以访问
解释:
fileprivate
修饰的model
属性只能在同一个文件中访问。即使是子类Bike
,也只能在同一个源文件中访问这个属性。internal
(默认)属性speed
可以在同一模块内的任意文件中访问。
修饰符 | 作用 | 适用场景 |
---|---|---|
private | 属性和方法只能在定义它的类、结构体或枚举内部访问,外部不可访问 | 用于隐藏类的实现细节,保护敏感数据 |
public | 属性和方法可以在模块外部访问,任何人都可以使用 | 用于定义公开接口或公共 API,供外部模块使用 |
internal | 属性和方法只能在同一模块内访问,这是默认的访问控制 | 适用于大多数场景,允许在模块内的所有文件中访问 |
fileprivate | 属性和方法只能在同一个源文件中访问 | 用于限制属性和方法在同一个文件中可见 |
1-17 Arrays 数组
在 Swift 中,数组(Array
)是非常常用的数据结构,它用于存储同一类型的多个值。数组的基本功能包括存储、访问、添加、删除和遍历元素。下面我会从最基础的用法开始,详细介绍数组的常见操作,并通过简单易懂的例子进行讲解。
1. 什么是数组?
数组是一个有序的数据集合,它可以存储同一类型的多个值。Swift 中的数组是有序的,这意味着数组中的每个元素都有特定的位置(索引),索引从 0 开始。
2. 创建数组
你可以用多种方式来创建数组。在 Swift 中,数组的类型写作 Array<Element>
,其中 Element
表示数组中存储的元素类型。
例子 1:创建一个简单的数组
// 创建一个整数数组
var numbers: [Int] = [1, 2, 3, 4, 5]
// 也可以省略类型,Swift 会自动推断
var fruits = ["Apple", "Banana", "Orange"]
解释:
numbers
是一个存储Int
类型的数组,包含 1 到 5 的整数。fruits
是一个字符串数组,存储了三个水果名称。var
表示数组是可变的,可以添加或删除元素。
空数组
你可以创建一个空数组,然后再向其中添加元素。
// 创建一个空的整数数组
var emptyArray: [Int] = []
// 或者使用简写
var anotherEmptyArray = [Int]()
解释:
emptyArray
是一个空数组,最开始没有任何元素。anotherEmptyArray
使用了简写形式来创建空数组。
3. 访问数组元素
数组中的元素可以通过索引来访问,索引从 0 开始。
例子 2:通过索引访问数组元素
let fruits = ["Apple", "Banana", "Orange"]
// 访问第一个元素
let firstFruit = fruits[0]
print(firstFruit) // 输出:Apple
// 访问第三个元素
let thirdFruit = fruits[2]
print(thirdFruit) // 输出:Orange
解释:
- 使用
fruits[0]
来访问数组的第一个元素,fruits[2]
来访问第三个元素。 - 注意:如果你尝试访问超出数组范围的索引,会引发运行时错误(
Array index out of range
)。
例子 : count first last 取值
let fruits = ["Apple", "Banana", "Orange", "Grape"]
// 1. count 属性:获取数组中的元素个数
print("数组中的元素个数是:\(fruits.count)")
// 输出:数组中的元素个数是:4
// 2. first 属性:获取数组中的第一个元素(如果数组非空)
if let firstFruit = fruits.first {
print("第一个水果是:\(firstFruit)")
} else {
print("数组是空的,没有第一个元素")
}
// 输出:第一个水果是:Apple
// 3. last 属性:获取数组中的最后一个元素(如果数组非空)
if let lastFruit = fruits.last {
print("最后一个水果是:\(lastFruit)")
} else {
print("数组是空的,没有最后一个元素")
}
// 输出:最后一个水果是:Grape
4. 修改数组元素
如果数组是用 var
定义的,你可以修改它的元素。
例子 3:修改数组中的元素
var fruits = ["Apple", "Banana", "Orange"]
// 修改第二个元素
fruits[1] = "Grape"
print(fruits) // 输出:["Apple", "Grape", "Orange"]
解释:
fruits[1] = "Grape"
修改了数组的第二个元素,把"Banana"
替换为"Grape"
。
5. 添加和删除数组元素
Swift 提供了多种方法来向数组中添加或删除元素。
例子 4:添加元素
var fruits = ["Apple", "Banana"]
// 在数组末尾添加一个元素
fruits.append("Orange")
print(fruits) // 输出:["Apple", "Banana", "Orange"]
// 插入元素到指定位置
fruits.insert("Mango", at: 1)
print(fruits) // 输出:["Apple", "Mango", "Banana", "Orange"]
解释:
fruits.append("Orange")
向数组末尾添加了"Orange"
。fruits.insert("Mango", at: 1)
在索引 1 处插入了"Mango"
,数组的其他元素向后移动。
例子 5:删除元素
var fruits = ["Apple", "Mango", "Banana", "Orange"]
// 删除指定索引的元素
fruits.remove(at: 2)
print(fruits) // 输出:["Apple", "Mango", "Orange"]
// 删除最后一个元素
fruits.removeLast()
print(fruits) // 输出:["Apple", "Mango"]
// 删除所有元素
fruits.removeAll()
print(fruits) // 输出:[]
解释:
fruits.remove(at: 2)
删除了数组中索引为 2 的元素"Banana"
。fruits.removeLast()
删除了数组的最后一个元素。fruits.removeAll()
清空了数组,删除了所有元素。
6. 数组的遍历
你可以使用 for-in
循环来遍历数组的所有元素。
例子 6:遍历数组
let fruits = ["Apple", "Banana", "Orange"]
for fruit in fruits {
print(fruit)
}
解释:
for fruit in fruits
循环遍历了fruits
数组的每个元素,并依次打印出来。
例子 7:使用索引遍历数组
你还可以同时遍历数组的元素及其索引。
let fruits = ["Apple", "Banana", "Orange"]
for (index, fruit) in fruits.enumerated() {
print("第 \(index + 1) 个水果是:\(fruit)")
}
解释:
fruits.enumerated()
返回一个元组,包含每个元素的索引和值。for (index, fruit)
遍历每个元素的同时,输出它们的索引和值。
7. 检查数组是否为空
你可以通过数组的 isEmpty
属性来检查数组是否为空。
例子 8:检查数组是否为空
let emptyArray: [Int] = []
if emptyArray.isEmpty {
print("数组是空的")
} else {
print("数组不是空的")
}
解释:
emptyArray.isEmpty
返回true
,因为数组中没有任何元素。
8. 获取数组的长度
你可以通过 count
属性来获取数组中元素的个数。
例子 9:获取数组长度
let fruits = ["Apple", "Banana", "Orange"]
print("水果的数量是:\(fruits.count)") // 输出:水果的数量是:3
解释:
fruits.count
返回数组中的元素个数。在这个例子中,数组有 3 个元素。
9. 数组的合并和连接
你可以使用 +
运算符或 append(contentsOf:)
方法来合并两个数组。
例子 10:合并数组
let array1 = [1, 2, 3]
let array2 = [4, 5, 6]
// 使用 + 运算符合并
let mergedArray = array1 + array2
print(mergedArray) // 输出:[1, 2, 3, 4, 5, 6]
// 使用 append(contentsOf:) 合并
var numbers = [1, 2, 3]
numbers.append(contentsOf: array2)
print(numbers) // 输出:[1, 2, 3, 4, 5, 6]
解释:
array1 + array2
使用+
运算符合并了两个数组。numbers.append(contentsOf: array2)
使用append(contentsOf:)
方法将array2
的内容添加到numbers
数组末尾。
10. 检查数组中是否包含某个元素
你可以使用 contains
方法来检查数组中是否包含某个元素。
例子 11:检查数组是否包含某个元素
let fruits = ["Apple", "Banana", "Orange"]
if fruits.contains("Banana") {
print("数组中包含 Banana")
} else {
print("数组中不包含 Banana")
}
解释:
fruits.contains("Banana")
检查数组中是否包含"Banana"
,如果存在,则返回true
。
11. 数组排序
Swift 提供了多种方法对数组进行排序,比如 sorted()
返回一个排序后的数组,而 sort()
直接对原数组进行排序。
例子 12:数组排序
var numbers = [5, 2, 8, 3, 9]
// 使用 sorted() 返回排序后的新数组
let sortedNumbers = numbers.sorted()
print(sortedNumbers) // 输出:[2, 3, 5, 8, 9]
// 使用 sort() 直接对原数组进行排序
numbers.sort()
print(numbers) // 输出:[2, 3, 5, 8, 9]
解释:
sorted()
返回一个新数组,原数组保持不变。sort()
直接对原数组进行排序,改变了数组的顺序。
- 创建数组:可以通过数组字面量(如
["Apple", "Banana"]
)或者空数组创建。 - 访问和修改元素:使用索引访问或修改元素,索引从
0
开始。 - 添加和删除元素:通过
append()
、insert()
添加元素,remove()
、removeLast()
删除元素。 - 遍历数组:通过
for-in
循环遍历数组,使用enumerated()
遍历时获取索引和值。 - 其他操作:数组提供了
isEmpty
、count
、contains()
等方法来检查状态和属性。
1-18 数组的indices相关属性
1. indices.contains
indices.contains(_:)
:检查数组是否包含某个有效的索引,返回true
或false
。这个方法通常用于防止数组越界访问。你可以传递一个索引作为参数,如果这个索引在数组的有效范围内,它会返回true
;否则返回false
。
使用场景:当你想要确保某个索引是合法的,并且不超出数组范围时,可以使用 indices.contains(_:)
来进行检查,防止程序崩溃。
2. 结合 first
、last
、indices.first
、indices.last
、indices.contains
的完整示例
let fruits = ["Apple", "Banana", "Orange", "Grape"]
// 1. first 属性:获取数组的第一个元素
if let firstElement = fruits.first {
print("第一个水果是:\(firstElement)") // 输出:第一个水果是:Apple
} else {
print("数组为空,没有第一个元素")
}
// 2. last 属性:获取数组的最后一个元素
if let lastElement = fruits.last {
print("最后一个水果是:\(lastElement)") // 输出:最后一个水果是:Grape
} else {
print("数组为空,没有最后一个元素")
}
// 3. indices.first 属性:获取数组的第一个有效索引
if let firstIndex = fruits.indices.first {
print("第一个水果的索引是:\(firstIndex)") // 输出:第一个水果的索引是:0
} else {
print("数组为空,没有索引")
}
// 4. indices.last 属性:获取数组的最后一个有效索引
if let lastIndex = fruits.indices.last {
print("最后一个水果的索引是:\(lastIndex)") // 输出:最后一个水果的索引是:3
} else {
print("数组为空,没有索引")
}
// 5. 使用 indices.first 和 indices.last 来获取元素
if let firstIndex = fruits.indices.first {
print("通过索引获取第一个水果:\(fruits[firstIndex])") // 输出:通过索引获取第一个水果:Apple
}
if let lastIndex = fruits.indices.last {
print("通过索引获取最后一个水果:\(fruits[lastIndex])") // 输出:通过索引获取最后一个水果:Grape
}
// 6. 使用 indices.contains 检查索引是否有效
let indexToCheck = 2
if fruits.indices.contains(indexToCheck) {
print("索引 \(indexToCheck) 是有效的,对应的水果是:\(fruits[indexToCheck])") // 输出:索引 2 是有效的,对应的水果是:Orange
} else {
print("索引 \(indexToCheck) 超出范围")
}
// 尝试使用超出范围的索引
let outOfBoundsIndex = 10
if fruits.indices.contains(outOfBoundsIndex) {
print("索引 \(outOfBoundsIndex) 是有效的")
} else {
print("索引 \(outOfBoundsIndex) 超出范围") // 输出:索引 10 超出范围
}
3. 详细解释:
first
和 last
first
:返回数组中的第一个元素。若数组为空,返回nil
。last
:返回数组中的最后一个元素。若数组为空,返回nil
。
使用场景:当你只关心数组的第一个或最后一个元素时,可以直接使用 first
和 last
。
indices.first
和 indices.last
indices.first
:返回数组中第一个有效的索引,通常是0
。若数组为空,返回nil
。indices.last
:返回数组中最后一个有效的索引(即count - 1
)。若数组为空,返回nil
。
使用场景:当你想获取数组的第一个或最后一个索引时,使用 indices.first
和 indices.last
。结合这些索引,你可以访问数组中的元素。它们返回的是索引,而不是数组元素。
indices.contains
indices.contains(_:)
:用于检查给定的索引是否在数组的有效范围内。
使用场景:当你需要访问数组元素时,尤其是当你无法确定索引是否有效时,使用 indices.contains(_:)
来进行索引有效性检查。这样可以避免数组越界访问导致的程序崩溃。
4. 对比总结:
属性/方法 | 返回值 | 作用 |
---|---|---|
first | 数组的第一个元素,可选类型 | 获取数组中的第一个元素,若数组为空则返回 nil |
last | 数组的最后一个元素,可选类型 | 获取数组中的最后一个元素,若数组为空则返回 nil |
indices.first | 数组的第一个有效索引,可选类型 | 获取数组中的第一个索引(一般为 0 ),若数组为空则返回 nil |
indices.last | 数组的最后一个有效索引,可选类型 | 获取数组中的最后一个索引(一般为 count - 1 ),若数组为空则返回 nil |
indices.contains(_:) | 布尔值,true 或 false | 检查给定索引是否在数组的有效范围内,避免索引越界访问 |
5. 应用场景
first
和last
:当你需要访问数组中的第一个或最后一个元素时,直接使用这两个属性。indices.first
和indices.last
:当你需要访问数组的边界索引时(如循环遍历数组或需要使用索引进行操作),使用这些索引值。indices.contains
:在访问数组元素之前,如果不确定索引是否有效,可以使用此方法进行检查,防止索引越界导致的错误。
1-19 set 集合
在 Swift 中,Set
是一种无序、唯一的数据集合类型。Set
集合和数组(Array
)的区别在于:Set
中的元素是无序的,并且每个元素只能出现一次(即集合中的元素是唯一的)。这使得 Set
适用于那些需要快速查找元素、确保数据唯一性的场景。
1. 创建 Set
例子 1:创建一个简单的 Set
// 使用字面量创建一个 Set
var fruits: Set<String> = ["Apple", "Banana", "Orange"]
// 也可以省略类型,Swift 会自动推断
var numbers: Set = [1, 2, 3, 4, 5]
解释:
fruits
是一个字符串类型的集合,包含"Apple"
、"Banana"
、"Orange"
。numbers
是一个整数类型的集合,包含1
到5
。Set
中的元素没有顺序且唯一,因此即使你添加多个相同的元素,它们也只会存在一次。
2. 常见的 Set
操作
Set
提供了一些常用的操作方法,比如添加、删除、检查是否包含某个元素等。
例子 2:添加和删除元素
var fruits: Set<String> = ["Apple", "Banana"]
// 添加元素
fruits.insert("Orange")
print(fruits) // 输出:["Banana", "Orange", "Apple"]
// 删除元素
fruits.remove("Banana")
print(fruits) // 输出:["Orange", "Apple"]
// 如果要删除的元素不存在,返回 nil
let removedFruit = fruits.remove("Grape")
print(removedFruit) // 输出:nil
解释:
insert
方法用于向Set
中添加新元素,且添加后不会保证元素的顺序。remove
方法删除指定的元素,如果元素不存在,返回nil
。
3. 检查 Set
中的元素
你可以使用 contains
方法来检查某个元素是否在集合中。
例子 3:检查集合中是否包含某个元素
let fruits: Set<String> = ["Apple", "Banana", "Orange"]
// 检查是否包含某个元素
if fruits.contains("Banana") {
print("集合中包含 Banana")
} else {
print("集合中不包含 Banana")
}
// 输出:集合中包含 Banana
解释:
contains
方法返回一个布尔值,用于判断集合中是否包含某个指定的元素。
4. Set
的遍历
虽然 Set
是无序的,但你依然可以使用 for-in
循环遍历集合中的所有元素。
例子 4:遍历集合
let fruits: Set<String> = ["Apple", "Banana", "Orange"]
// 使用 for-in 循环遍历 Set 中的元素
for fruit in fruits {
print(fruit)
}
// 输出的顺序是不固定的,可能是:
// Apple
// Orange
// Banana
解释:
Set
中的元素是无序的,因此遍历时的顺序无法预测。for-in
循环可以遍历Set
中的每一个元素。
5. Set
的基本属性
Set
提供了一些属性来获取集合的状态,比如检查集合是否为空、获取集合中的元素个数等。
例子 5:基本属性
let fruits: Set<String> = ["Apple", "Banana", "Orange"]
// count 属性:返回集合中元素的个数
print("集合中有 \(fruits.count) 个元素") // 输出:集合中有 3 个元素
// isEmpty 属性:检查集合是否为空
if fruits.isEmpty {
print("集合是空的")
} else {
print("集合不是空的")
}
// 输出:集合不是空的
解释:
count
返回集合中元素的个数。isEmpty
返回一个布尔值,表示集合是否为空。
6. Set
的集合操作
Set
提供了很多集合操作方法,比如求交集、并集、差集和对称差集。
例子 6:集合的交集、并集、差集和对称差集
let setA: Set = [1, 2, 3, 4, 5]
let setB: Set = [4, 5, 6, 7, 8]
// 交集:获取两个集合中共同的元素
let intersection = setA.intersection(setB)
print("交集:\(intersection)") // 输出:交集:[5, 4]
// 并集:获取两个集合中所有元素的集合
let union = setA.union(setB)
print("并集:\(union)") // 输出:并集:[5, 6, 4, 2, 3, 7, 1, 8]
// 差集:获取 setA 中有,但 setB 中没有的元素
let difference = setA.subtracting(setB)
print("差集:\(difference)") // 输出:差集:[2, 3, 1]
// 对称差集:获取两个集合中非共有的元素
let symmetricDifference = setA.symmetricDifference(setB)
print("对称差集:\(symmetricDifference)") // 输出:对称差集:[6, 7, 8, 1, 2, 3]
解释:
- 交集(
intersection
):返回两个集合的交集(即共同的元素)。 - 并集(
union
):返回两个集合的并集(即所有元素,去除重复项)。 - 差集(
subtracting
):返回setA
中有,但setB
中没有的元素。 - 对称差集(
symmetricDifference
):返回两个集合中各自独有的元素,即不重复的元素。
7. 集合的比较
你可以检查一个集合是否是另一个集合的子集、超集,或者两个集合是否相等。
例子 7:集合的比较
let setA: Set = [1, 2, 3, 4, 5]
let setB: Set = [1, 2, 3]
let setC: Set = [6, 7]
// 是否是子集
let isSubset = setB.isSubset(of: setA)
print("setB 是 setA 的子集吗?\(isSubset)") // 输出:setB 是 setA 的子集吗?true
// 是否是超集
let isSuperset = setA.isSuperset(of: setB)
print("setA 是 setB 的超集吗?\(isSuperset)") // 输出:setA 是 setB 的超集吗?true
// 是否不相交
let isDisjoint = setA.isDisjoint(with: setC)
print("setA 和 setC 互斥吗?\(isDisjoint)") // 输出:setA 和 setC 互斥吗?true
解释:
isSubset(of:)
:判断一个集合是否是另一个集合的子集(即集合 A 的所有元素都包含在集合 B 中)。isSuperset(of:)
:判断一个集合是否是另一个集合的超集(即集合 A 包含集合 B 的所有元素)。isDisjoint(with:)
:判断两个集合是否不相交(即没有共同的元素)。Set
是无序的:Set
中的元素是无序的,与数组不同,你不能通过索引访问Set
。Set
中元素唯一:Set
不允许重复的元素,添加重复的元素不会生效。常用操作:包括添加、删除、检查是否包含某元素,以及遍历集合中的所有元素。
集合运算:支持常见的集合操作,如交集、并集、差集和对称差集,适用于需要操作多个集合的场景。
集合比较:可以判断集合之间的关系,如是否为子集、超集或是否不相交。
1-20 Dictionaries 字典
在 Swift 中,字典(Dictionaries) 是一种存储多个键值对的数据结构。它类似于其他编程语言中的哈希表或映射(map)。字典使用唯一的键来访问和存储与之关联的值,每个键只能出现一次。字典中的键和值都可以是任意类型,但键必须是唯一的且是可哈希的(可以被哈希化)。
1. 什么是字典?
字典是由**键(key)和值(value)**组成的集合,每个键与唯一的值关联。字典中的键是唯一的,不能重复,但值可以重复。字典中的键和值都可以是任意类型,但键必须是哈希化的类型(例如 Int
、String
等)。
2. 如何创建字典?
字典的类型写作 Dictionary<KeyType, ValueType>
或者简写形式 [KeyType: ValueType]
,其中 KeyType
是键的类型,ValueType
是值的类型。
例子 1:创建一个简单的字典
// 使用字面量创建字典
var studentScores: [String: Int] = ["Alice": 85, "Bob": 92, "Charlie": 78]
// Swift 可以推断类型,类型声明可以省略
var countryCapitals = ["USA": "Washington, D.C.", "France": "Paris", "Japan": "Tokyo"]
解释:
studentScores
是一个字典,键是String
类型,值是Int
类型,表示学生的分数。countryCapitals
是另一个字典,键是国家名称,值是该国家的首都。
3. 访问字典中的值
你可以通过键来访问字典中的值,使用方括号 []
语法。
例子 2:访问字典中的值
let countryCapitals = ["USA": "Washington, D.C.", "France": "Paris", "Japan": "Tokyo"]
// 访问字典中 "France" 键对应的值
if let capital = countryCapitals["France"] {
print("法国的首都是:\(capital)")
} else {
print("找不到这个国家")
}
// 输出:法国的首都是:Paris
解释:
- 通过
countryCapitals["France"]
可以获取 "France" 对应的值 "Paris"。 - 因为字典访问返回的是可选值(可能
nil
),所以需要使用if let
语法来安全地解包。
4. 修改字典中的值
字典是可变的(如果用 var
声明),你可以修改它的键对应的值,或者添加新的键值对。
例子 3:修改和添加键值对
var studentScores = ["Alice": 85, "Bob": 92, "Charlie": 78]
// 修改现有的值
studentScores["Alice"] = 90
// 添加新的键值对
studentScores["David"] = 88
print(studentScores)
// 输出:["Bob": 92, "Charlie": 78, "David": 88, "Alice": 90]
解释:
- 使用
studentScores["Alice"] = 90
修改了Alice
的分数。 - 使用
studentScores["David"] = 88
添加了一个新的键值对"David": 88
。
5. 删除字典中的键值对
你可以使用 removeValue(forKey:)
方法从字典中删除一个键值对,或者直接将值设置为 nil
来删除。
例子 4:删除字典中的键值对
var studentScores = ["Alice": 85, "Bob": 92, "Charlie": 78]
// 使用 removeValue(forKey:) 删除键值对
studentScores.removeValue(forKey: "Charlie")
// 或者将值设置为 nil 来删除键值对
studentScores["Bob"] = nil
print(studentScores)
// 输出:["Alice": 85]
解释:
removeValue(forKey: "Charlie")
删除了"Charlie"
及其对应的分数。studentScores["Bob"] = nil
也删除了"Bob"
对应的键值对。
6. 遍历字典
字典是无序的,但你可以使用 for-in
循环遍历字典中的所有键值对。
例子 5:遍历字典
let countryCapitals = ["USA": "Washington, D.C.", "France": "Paris", "Japan": "Tokyo"]
// 使用 for-in 循环遍历字典
for (country, capital) in countryCapitals {
print("\(country) 的首都是 \(capital)")
}
// 输出:
// USA 的首都是 Washington, D.C.
// France 的首都是 Paris
// Japan 的首都是 Tokyo
解释:
- 使用
for (key, value) in dictionary
来遍历字典的键和值。 - 字典中的键值对是无序的,因此遍历的顺序并不确定。
7. 获取字典的键和值
你可以使用 keys
属性获取字典中所有的键,使用 values
属性获取字典中所有的值。
例子 6:获取字典的键和值
let countryCapitals = ["USA": "Washington, D.C.", "France": "Paris", "Japan": "Tokyo"]
// 获取所有的键
let countries = countryCapitals.keys
print("所有的国家:\(countries)") // 输出:所有的国家:["USA", "France", "Japan"]
// 获取所有的值
let capitals = countryCapitals.values
print("所有的首都:\(capitals)") // 输出:所有的首都:["Washington, D.C.", "Paris", "Tokyo"]
解释:
countryCapitals.keys
返回一个包含字典中所有键的集合(Keys
)。countryCapitals.values
返回一个包含字典中所有值的集合(Values
)。
8. 检查字典是否为空
你可以使用 isEmpty
属性来检查字典是否为空,也可以通过 count
属性来获取字典中的键值对数量。
例子 7:检查字典状态
let emptyDictionary: [String: Int] = [:]
let populatedDictionary = ["Alice": 85, "Bob": 92]
// isEmpty 属性:检查字典是否为空
print(emptyDictionary.isEmpty) // 输出:true
print(populatedDictionary.isEmpty) // 输出:false
// count 属性:获取字典中的键值对数量
print("字典中有 \(populatedDictionary.count) 个元素") // 输出:字典中有 2 个元素
解释:
isEmpty
返回true
表示字典为空,false
表示字典非空。count
返回字典中的键值对个数。
9. 合并字典
你可以使用 merge(_:uniquingKeysWith:)
方法将两个字典合并起来。如果两个字典中存在相同的键,你可以通过一个闭包来处理重复的键。
例子 8:合并字典
var dict1 = ["Alice": 85, "Bob": 92]
let dict2 = ["Charlie": 78, "Bob": 95]
// 合并 dict2 到 dict1,保留 dict2 中的值
dict1.merge(dict2) { (current, new) in new }
print(dict1)
// 输出:["Charlie": 78, "Alice": 85, "Bob": 95]
// 如果遇到重复键,保留 dict1 中的值
dict1.merge(dict2) { (current, new) in current }
print(dict1)
// 输出:["Charlie": 78, "Alice": 85, "Bob": 85]
解释:
merge(dict2)
将dict2
中的键值对合并到dict1
中。使用闭包来决定当键重复时如何处理值。
new
表示保留新值,current
表示保留现有值。字典的定义:字典使用键值对存储数据,每个键是唯一的,但值可以重复。
访问和修改:可以通过键来访问和修改值,使用
removeValue(forKey:)
或将值设为nil
来删除键值对。遍历:使用
for-in
循环遍历字典中的键值对。字典的键和值:可以通过
keys
获取所有键,通过values
获取所有值。合并字典:可以使用
merge
方法合并两个字典,处理重复键。
1-21 forEach 遍历
forEach
是 Swift 中一种方便的遍历集合(例如数组、字典、集合等)的方法。它是一个闭包方法,允许你对集合中的每个元素执行指定的操作。与传统的 for-in
循环相比,forEach
更简洁、现代,适合在需要对集合执行单一操作时使用。
1. 基本用法
forEach
是 Swift 标准库中集合类型(如数组、字典、集合)的一个方法,它接受一个闭包作为参数,并对集合中的每个元素执行该闭包的代码。
例子 1:使用 forEach
遍历数组
let fruits = ["Apple", "Banana", "Orange"]
// 使用 forEach 遍历数组
fruits.forEach { fruit in
print(fruit)
}
// 输出:
// Apple
// Banana
// Orange
解释:
fruits.forEach { fruit in ... }
遍历了fruits
数组中的每个元素,并在闭包中执行print(fruit)
操作。- 每次循环时,数组中的一个元素会传递给
fruit
。
例子 2:使用 forEach
遍历字典
let studentScores = ["Alice": 85, "Bob": 92, "Charlie": 78]
// 使用 forEach 遍历字典
studentScores.forEach { (name, score) in
print("\(name) 的分数是 \(score)")
}
// 输出:
// Alice 的分数是 85
// Bob 的分数是 92
// Charlie 的分数是 78
解释:
- 在字典的
forEach
中,闭包的参数是键值对(key, value)
,在这个例子中,name
是学生的名字,score
是分数。
2. forEach
与 for-in
的区别
虽然 forEach
和 for-in
循环都可以遍历集合中的元素,但它们有一些不同的特性:
特性 | forEach | for-in |
---|---|---|
使用方式 | 通过闭包函数对每个元素执行操作 | 使用显式的循环语法,对每个元素进行操作 |
使用 break 和 continue | forEach 不支持 break 和 continue | for-in 可以使用 break 提前退出循环,使用 continue 跳过某次迭代 |
简洁性 | 适合需要对每个元素执行简单操作时,代码更简洁 | 使用标准循环语法,适合更复杂的控制逻辑 |
3. forEach
的注意事项
1. 不能使用 break
或 continue
在 forEach
中,不能使用 break
或 continue
来控制循环的跳过或中断。如果你需要提前退出循环或跳过某些条件,for-in
循环更适合你。
例子 3:forEach
不支持 break
let numbers = [1, 2, 3, 4, 5]
numbers.forEach { number in
if number == 3 {
// break // 错误:不能在 forEach 中使用 break
}
print(number)
}
解释:
forEach
主要用于执行简单的操作,并且不支持break
或continue
。如果你需要这种控制逻辑,请使用for-in
。
2. 可以在 forEach
中修改外部变量
虽然 forEach
不直接修改集合,但你可以在闭包中修改外部变量。
例子 4:在 forEach
中修改外部变量
let numbers = [1, 2, 3, 4, 5]
var sum = 0
// 使用 forEach 计算数组中所有元素的和
numbers.forEach { number in
sum += number
}
print("数组元素的和是:\(sum)") // 输出:数组元素的和是:15
解释:
- 在这个例子中,
sum
是一个外部变量,虽然不能直接修改数组的元素,但可以通过forEach
对sum
进行累加操作。
4. 使用场景
场景 1:简单遍历集合
当你只是需要简单地遍历集合中的每个元素,并对每个元素执行相同的操作时,forEach
非常适合:
let names = ["Alice", "Bob", "Charlie"]
names.forEach { name in
print("Hello, \(name)!")
}
// 输出:
// Hello, Alice!
// Hello, Bob!
// Hello, Charlie!
场景 2:遍历字典进行操作
forEach
也适合在遍历字典时执行操作。比如,计算总分或者遍历键值对:
let studentScores = ["Alice": 85, "Bob": 92, "Charlie": 78]
var totalScore = 0
studentScores.forEach { (name, score) in
print("\(name) 的分数是 \(score)")
totalScore += score
}
print("总分数是 \(totalScore)") // 输出:总分数是 255
5. 结合 forEach
和 enumerated
当你需要访问元素的索引时,可以将 forEach
与 enumerated()
方法结合使用。
例子 5:使用 forEach
和 enumerated()
let fruits = ["Apple", "Banana", "Orange"]
// 使用 enumerated 和 forEach 遍历数组并访问索引
fruits.enumerated().forEach { (index, fruit) in
print("第 \(index + 1) 个水果是:\(fruit)")
}
// 输出:
// 第 1 个水果是:Apple
// 第 2 个水果是:Banana
// 第 3 个水果是:Orange
解释:
enumerated()
返回一个由元素及其索引组成的元组,forEach
可以通过这个元组访问每个元素的索引和值。forEach
是一种简洁的遍历集合的方法,它通过闭包对集合的每个元素执行操作。适用于不需要复杂控制逻辑的场景,例如跳过某次迭代或提前终止循环。
在
forEach
中,你无法使用break
或continue
,因此如果需要复杂的控制结构,for-in
更适合。forEach
可以用于数组、字典、集合等集合类型。当你只需要对集合执行简单的遍历和操作时,
forEach
让代码更加简洁易读。
1-22 for循环 while循环
在 Swift 中,for
循环和**while
循环**是两种非常常用的循环控制结构。它们用于重复执行一段代码,直到满足某个条件。虽然两者都能完成循环操作,但它们的使用场景和方式有所不同。
1. for
循环
for
循环 是一种固定次数的循环,通常用于已知需要循环多少次的情况。它通过一个计数器(如索引)来控制循环次数。
基本语法
for 变量 in 范围 {
// 循环体
}
变量
是一个在每次循环时会取到范围中每个元素的变量。范围
可以是数字范围、数组或其他可迭代的集合。
例子 1:for
循环遍历数字范围
// 遍历 1 到 5
for i in 1...5 {
print("当前数字是:\(i)")
}
// 输出:
// 当前数字是:1
// 当前数字是:2
// 当前数字是:3
// 当前数字是:4
// 当前数字是:5
解释:
1...5
表示闭区间范围,从 1 到 5,包括 5。for i in 1...5
将i
从 1 递增到 5,循环执行 5 次。
例子 2:for
循环遍历数组
let fruits = ["Apple", "Banana", "Orange"]
// 遍历数组
for fruit in fruits {
print(fruit)
}
// 输出:
// Apple
// Banana
// Orange
解释:
for fruit in fruits
遍历了fruits
数组,每次循环时,fruit
依次取数组中的每个值。
例子 3:使用 for
循环和 enumerated()
获取索引
let fruits = ["Apple", "Banana", "Orange"]
// 使用 enumerated 获取索引和值
for (index, fruit) in fruits.enumerated() {
print("第 \(index + 1) 个水果是:\(fruit)")
}
// 输出:
// 第 1 个水果是:Apple
// 第 2 个水果是:Banana
// 第 3 个水果是:Orange
解释:
enumerated()
方法返回一个包含索引和值的元组,使用(index, fruit)
来同时获取索引和数组的值。
2. while
循环
while
循环 是一种条件控制的循环,它会在条件为真时执行循环体,直到条件变为假时停止。它适用于那些无法确定确切的循环次数,但通过条件判断可以控制循环结束的情况。
基本语法
while 条件 {
// 循环体
}
条件
是一个布尔表达式,当其为true
时,执行循环体;为false
时,退出循环。
例子 4:while
循环示例
var i = 1
while i <= 5 {
print("当前数字是:\(i)")
i += 1 // 每次循环增加 1
}
// 输出:
// 当前数字是:1
// 当前数字是:2
// 当前数字是:3
// 当前数字是:4
// 当前数字是:5
解释:
- 变量
i
初始值为 1,循环在i <= 5
为真的情况下执行,当i
增加到 6 时,循环结束。 while
循环的优点是可以动态地根据条件执行不同次数的循环。
3. repeat-while
循环
repeat-while
循环 是 while
循环的一种变体。它会先执行一次循环体,然后检查条件是否为 true
,如果为 true
,继续执行下次循环。无论条件如何,repeat-while
总会执行至少一次。
基本语法
repeat {
// 循环体
} while 条件
例子 5:repeat-while
循环示例
var i = 1
repeat {
print("当前数字是:\(i)")
i += 1
} while i <= 5
// 输出:
// 当前数字是:1
// 当前数字是:2
// 当前数字是:3
// 当前数字是:4
// 当前数字是:5
解释:
repeat
先执行一次循环体,然后检查条件i <= 5
是否为真。如果条件为真,继续执行;如果为假,停止。- 与
while
循环不同的是,repeat-while
无论条件是否为真,都会执行至少一次循环。
4. for
循环与 while
循环的区别
for
循环:适用于已知循环次数的情况,比如遍历数组、固定范围等。它更加结构化,代码简洁明了。while
循环:适用于不确定循环次数的情况,通常用于条件控制下的循环,直到条件不再满足为止。repeat-while
循环:类似while
,但总会执行至少一次循环体,适合那些需要先执行循环然后再检查条件的场景。
5. 控制循环的跳转(break
和 continue
)
在 Swift 中,你可以使用 break
和 continue
来控制循环的执行。
1. break
:
终止整个循环,跳出当前循环体。
例子 6:使用 break
终止循环
for i in 1...5 {
if i == 3 {
print("遇到 3,退出循环")
break // 终止循环
}
print("当前数字是:\(i)")
}
// 输出:
// 当前数字是:1
// 当前数字是:2
// 遇到 3,退出循环
解释:
- 当
i == 3
时,break
语句会立即退出整个for
循环。
2. continue
:
跳过当前循环的剩余部分,直接进入下一次迭代。
例子 7:使用 continue
跳过某次循环
for i in 1...5 {
if i == 3 {
print("跳过 3")
continue // 跳过本次循环
}
print("当前数字是:\(i)")
}
// 输出:
// 当前数字是:1
// 当前数字是:2
// 跳过 3
// 当前数字是:4
// 当前数字是:5
解释:
- 当
i == 3
时,continue
跳过当前循环的剩余部分,直接进入下一次循环。
for
循环:
- 适合已知循环次数或遍历集合的情况。
- 基于范围或集合执行固定次数的循环。
while
循环:
- 适合不确定循环次数,通过条件控制循环结束的情况。
- 条件控制灵活,可以在任何条件下进行循环。
repeat-while
循环:
- 与
while
类似,但总会执行至少一次循环体。
break
和 continue
:
break
:用于提前终止整个循环。continue
:跳过当前循环的剩余部分,直接进入下一次迭代。
1-23 filter map sort
在 Swift 中,filter
、sort
和 map
是集合类型(如数组、集合、字典等)上的高阶函数,它们用于操作和处理集合中的元素。通过这些函数,你可以很方便地对集合中的数据进行筛选、排序、映射等操作。这些函数在编写简洁、高效代码时非常有用。
1. filter
:筛选集合中的元素
filter
是用于从集合中筛选出符合条件的元素的高阶函数。它接受一个闭包作为参数,并将闭包的返回值为 true
的元素组成一个新的集合。
基本语法:
let result = array.filter { 条件表达式 }
例子 1:筛选出大于 3 的数字
let numbers = [1, 2, 3, 4, 5, 6]
// 使用 filter 筛选大于 3 的数字
let filteredNumbers = numbers.filter { $0 > 3 }
print(filteredNumbers) // 输出:[4, 5, 6]
解释:
filter
方法对数组中的每个元素进行检查,$0
表示当前元素。- 闭包
{ $0 > 3 }
的意思是:筛选出所有大于 3 的元素。 - 最终返回
[4, 5, 6]
,因为这些元素符合条件。
例子 2:筛选出偶数
let numbers = [1, 2, 3, 4, 5, 6]
// 使用 filter 筛选偶数
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // 输出:[2, 4, 6]
解释:
{ $0 % 2 == 0 }
表示筛选出能被 2 整除的数,即偶数。
例子 3:筛选数组中的字符串
let fruits = ["Apple", "Banana", "Orange", "Grape"]
// 使用 filter 筛选出包含 'a' 字母的水果名
let filteredFruits = fruits.filter { $0.contains("a") }
print(filteredFruits) // 输出:["Banana", "Orange", "Grape"]
解释:
contains("a")
检查每个字符串是否包含字母'a'
,返回包含该字母的字符串。
2. sort
:对集合进行排序
sort
是用于对集合中的元素进行排序的高阶函数。它可以按升序或降序排序元素。sort
有两种常见的形式:
sort()
:对数组进行原地排序,直接修改原数组。sorted()
:返回一个新的已排序数组,不修改原数组。
基本语法:
let result = array.sorted { 条件表达式 }
例子 4:对数字数组进行升序排序
var numbers = [5, 3, 6, 2, 4]
// 使用 sorted 返回一个新的排序数组
let sortedNumbers = numbers.sorted()
print(sortedNumbers) // 输出:[2, 3, 4, 5, 6]
// 使用 sort 直接对原数组进行排序
numbers.sort()
print(numbers) // 输出:[2, 3, 4, 5, 6]
解释:
sorted()
返回一个新的排序数组,原数组不变。sort()
会修改原数组,直接对其排序。
例子 5:对字符串数组进行降序排序
let fruits = ["Apple", "Banana", "Orange", "Grape"]
// 使用 sorted 进行降序排序
let sortedFruits = fruits.sorted { $0 > $1 }
print(sortedFruits) // 输出:["Orange", "Grape", "Banana", "Apple"]
解释:
{ $0 > $1 }
表示按降序排序,$0
和$1
分别是两个待比较的元素。将较大的元素排在前面。
例子 6:按字符串的长度进行排序
let fruits = ["Apple", "Banana", "Orange", "Grape"]
// 按字符串长度升序排序
let sortedByLength = fruits.sorted { $0.count < $1.count }
print(sortedByLength) // 输出:["Apple", "Grape", "Banana", "Orange"]
解释:
{ $0.count < $1.count }
表示按字符串的长度进行排序。
3. map
:将集合中的元素映射为新的值
map
是用于对集合中的每个元素进行转换,并返回一个新的集合的高阶函数。它可以将数组中的每个元素根据给定的转换规则进行操作。
基本语法:
let result = array.map { 转换表达式 }
例子 7:将数组中的每个数乘以 2
let numbers = [1, 2, 3, 4, 5]
// 使用 map 将每个数字乘以 2
let doubledNumbers = numbers.map { $0 * 2 }
print(doubledNumbers) // 输出:[2, 4, 6, 8, 10]
解释:
map
对数组中的每个元素执行{ $0 * 2 }
,将每个数字乘以 2,并返回新的数组[2, 4, 6, 8, 10]
。
例子 8:将数字转换为字符串
let numbers = [1, 2, 3, 4, 5]
// 使用 map 将每个数字转换为字符串
let stringNumbers = numbers.map { "数字 \($0)" }
print(stringNumbers) // 输出:["数字 1", "数字 2", "数字 3", "数字 4", "数字 5"]
解释:
- 使用
map
将每个数字转换为对应的字符串,如"数字 1"
、"数字 2"
等。
例子 9:将数组中的字符串转换为大写
let fruits = ["Apple", "Banana", "Orange", "Grape"]
// 使用 map 将每个字符串转换为大写
let uppercasedFruits = fruits.map { $0.uppercased() }
print(uppercasedFruits) // 输出:["APPLE", "BANANA", "ORANGE", "GRAPE"]
解释:
map
将fruits
数组中的每个字符串转换为大写字母,并返回新的数组["APPLE", "BANANA", "ORANGE", "GRAPE"]
。
4. 综合应用:filter
、map
和 sorted
结合使用
你可以将 filter
、map
和 sorted
函数结合起来,构建强大的数据处理流水线。
例子 10:结合使用 filter
、map
和 sorted
let numbers = [1, 5, 3, 8, 2, 10, 7]
// 筛选出所有大于 3 的数字,乘以 2,然后按升序排序
let result = numbers.filter { $0 > 3 } // 过滤出大于 3 的数字
.map { $0 * 2 } // 将每个数字乘以 2
.sorted() // 按升序排序
print(result) // 输出:[6, 10, 14, 16, 20]
解释:
filter
筛选出大于 3 的数字[5, 8, 10, 7]
。map
将这些数字乘以 2,结果为[10, 16, 20, 14]
。sorted
最后对这些数字进行升序排序,得到[6, 10, 14, 16, 20]
。filter
:用于从集合中筛选出符合条件的元素,返回新的集合。- 常用于筛选特定条件的元素,如筛选出偶数、符合条件的字符串等。
sort
和sorted
:sort()
:对原集合进行原地排序。sorted()
:返回一个排序后的新集合,不修改原集合。- 常用于按大小、字母顺序、长度等进行排序。
map
:用于将集合中的元素映射为新的值,返回转换后的新集合。- 常用于对每个元素进行操作,如数值变换、类型转换
1-24 protocol 协议
在 Swift 中,protocol
(协议) 是一种用于定义蓝图的类型,它规定了某些特定功能应该如何实现,但不提供实现细节。协议可以定义属性、方法和其他要求,任何遵循协议的类型(类、结构体、枚举)都必须提供协议中规定的具体实现。
协议类似于其他编程语言中的接口(interface),是 Swift 中面向协议编程的基础。协议不仅可以在类中使用,还可以用于结构体和枚举,使得 Swift 的设计更加灵活。
1. 协议的基本用法
定义协议
你可以使用 protocol
关键字来定义一个协议,协议可以包含方法和属性的声明。
protocol SomeProtocol {
var name: String { get set } // 定义一个必须实现的属性
func greet() // 定义一个必须实现的方法
}
解释:
SomeProtocol
是一个协议,它要求遵循它的类型必须提供一个name
属性和一个greet()
方法。- 属性
name
规定它必须是可读写的(get set
)。 - 方法
greet()
没有方法体,只是声明了该方法必须存在,具体实现由遵循协议的类型来提供。
遵循协议
当一个类、结构体或枚举遵循某个协议时,它必须提供协议中规定的所有实现。
class Person: SomeProtocol {
var name: String
init(name: String) {
self.name = name
}
func greet() {
print("Hello, my name is \(name)")
}
}
解释:
Person
类遵循了SomeProtocol
协议,因此必须实现协议中的name
属性和greet()
方法。- 通过
class Person: SomeProtocol
声明Person
遵循SomeProtocol
协议。
创建对象并调用方法
let person = Person(name: "Alice")
person.greet() // 输出:Hello, my name is Alice
解释:
person
是Person
类的一个实例,调用greet()
方法时,输出问候语。
2. 协议中的属性要求
协议可以要求属性是可读的(get
),或者是可读写的(get set
)。遵循协议的类型可以选择如何存储这些属性——可以使用存储属性或计算属性来实现。
例子 1:只读属性和可读写属性
protocol Vehicle {
var numberOfWheels: Int { get } // 只读属性
var color: String { get set } // 可读写属性
}
struct Car: Vehicle {
var numberOfWheels: Int = 4
var color: String
}
解释:
numberOfWheels
是只读属性,因此只要求实现get
,表示可以读取它的值。color
是可读写属性,要求get
和set
,表示可以读取和更改它的值。- 在
Car
结构体中,我们通过存储属性来实现这些协议要求。
3. 协议中的方法要求
协议可以要求实现实例方法和类方法。方法在协议中仅有声明,没有实现细节,遵循协议的类型必须提供具体的实现。
例子 2:方法要求
protocol Movable {
func move(to point: String) // 实例方法
}
class Robot: Movable {
func move(to point: String) {
print("Moving to \(point)")
}
}
let robot = Robot()
robot.move(to: "Home") // 输出:Moving to Home
解释:
Movable
协议要求实现move(to:)
方法。Robot
类遵循了该协议,并提供了move(to:)
方法的具体实现。
4. 协议继承
协议可以继承其他协议,这样可以扩展一个协议的要求。一个协议可以继承多个协议。
例子 3:协议继承
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
// PersonProtocol 继承了 Named 和 Aged
protocol PersonProtocol: Named, Aged {
func introduce()
}
struct Person: PersonProtocol {
var name: String
var age: Int
func introduce() {
print("Hello, my name is \(name) and I am \(age) years old.")
}
}
let person = Person(name: "Alice", age: 25)
person.introduce() // 输出:Hello, my name is Alice and I am 25 years old.
解释:
PersonProtocol
继承了Named
和Aged
协议,因此它要求遵循者必须实现name
和age
属性,还必须实现introduce()
方法。Person
结构体遵循了PersonProtocol
,实现了所有的属性和方法。
5. 协议组合
Swift 允许通过 &
符号来组合多个协议,使得一个类型可以同时满足多个协议的要求。
例子 4:协议组合
protocol Drivable {
func drive()
}
protocol Flyable {
func fly()
}
// CarPlane 同时满足 Drivable 和 Flyable 协议
struct CarPlane: Drivable, Flyable {
func drive() {
print("Driving on the road")
}
func fly() {
print("Flying in the sky")
}
}
let vehicle: Drivable & Flyable = CarPlane()
vehicle.drive() // 输出:Driving on the road
vehicle.fly() // 输出:Flying in the sky
解释:
CarPlane
结构体遵循了Drivable
和Flyable
协议,因此它可以既开车又飞行。vehicle
变量的类型是Drivable & Flyable
,表示它必须同时符合这两个协议的要求。
6. 协议扩展
Swift 提供了强大的协议扩展功能,允许你为协议提供默认实现。这样,遵循协议的类型可以直接使用这些默认实现,而不需要重复编写代码。
例子 5:协议扩展
protocol Greetable {
func greet()
}
// 为 Greetable 协议提供默认实现
extension Greetable {
func greet() {
print("Hello!")
}
}
struct Person: Greetable {}
let person = Person()
person.greet() // 输出:Hello!
解释:
- 我们为
Greetable
协议提供了默认的greet()
方法实现。 Person
结构体遵循了Greetable
协议,但没有实现greet()
方法,因此自动使用了协议扩展中的默认实现。
7. 协议作为类型
协议不仅可以被类、结构体、枚举遵循,还可以用作类型。这意味着你可以将协议作为函数参数、返回值,甚至是集合中的元素类型。
例子 6:协议作为类型
protocol Describable {
func describe() -> String
}
struct Book: Describable {
func describe() -> String {
return "This is a book."
}
}
struct Car: Describable {
func describe() -> String {
return "This is a car."
}
}
let items: [Describable] = [Book(), Car()]
for item in items {
print(item.describe())
}
// 输出:
// This is a book.
// This is a car.
解释:
Book
和Car
都遵循了Describable
协议,并实现了describe()
方法。items
数组的类型是[Describable]
,因此它可以包含任何遵循Describable
协议的类型。通过协议作为类型,可以方便地将不同类型的对象放在一起处理。
协议(
protocol
) 是一种定义行为和属性的蓝图,规定了某些类型必须实现哪些功能。协议可以包含属性要求、方法要求,但不提供具体实现。
遵循协议的类型(如类、结构体、枚举)必须实现协议中的所有要求。
协议可以通过继承扩展功能,或者通过组合实现更复杂的行为。
通过协议扩展,你可以为协议提供默认的实现,让所有遵循协议的类型共享代码。
协议可以作为类型使用,允许你处理多种不同类型的对象,只要它们遵循相同的协议。
2.SwiftUI入门-上
Swift_UI_Learning(项目名称):
- 这是你的 SwiftUI 项目根目录,所有项目相关的文件和资源都位于此处。
Swift_UI_Learning 文件夹:
- 这个文件夹是项目的源代码文件夹,SwiftUI 项目的所有代码文件都放在这里。
Preview Content 文件夹:
- 这个文件夹通常用于存放 SwiftUI 预览内容。SwiftUI 提供实时预览功能,这里包含你在 SwiftUI 预览过程中使用的图片或其他数据。
Assets 文件夹:
- Assets 是用于存放项目中所需的图片、颜色、图标等资源的地方。这些资源可以通过 SwiftUI 的
Image
或其他 UI 组件来调用。
- Assets 是用于存放项目中所需的图片、颜色、图标等资源的地方。这些资源可以通过 SwiftUI 的
ContentView.swift:
ContentView
是 SwiftUI 项目中的主视图文件。通常,当你创建一个新的 SwiftUI 项目时,Xcode 会自动生成这个文件,它是应用的主用户界面。你会在这里编写 UI 代码,定义视图的布局、样式和交互。
Swift_UI_LearningApp.swift:
- 这是 SwiftUI 应用的入口文件。在 SwiftUI 中,应用的生命周期和界面是由这个文件管理的。通常在这个文件中你会看到
@main
属性,它标记了应用程序的主入口。这里你可以定义启动时加载的主视图(通常是ContentView
)。
- 这是 SwiftUI 应用的入口文件。在 SwiftUI 中,应用的生命周期和界面是由这个文件管理的。通常在这个文件中你会看到
这个目录结构就是典型的 SwiftUI 项目,Xcode 自动为你生成这些基础文件,目的是帮助你快速开始搭建用户界面。
1-1 Text()
我们新建一个新的swift 文件演示,右击项目文件夹文件
输入文件名称,点击确定即可
import SwiftUI
struct Text_learning: View {
var body: some View {
Text("Hello, World!")
Text("my name is aini")
Text("hello aini ")
Text("shanghai")
}
}
#Preview {
Text_learning()
}
import SwiftUI
struct TextFontExample: View { var body: some View { VStack(spacing: 20) { Text("Large Title this is the swift learning coure hahsh".capitalized) // 将文本的首字母大写 // .font(.largeTitle) // 已注释掉,这里可以通过自定义字体大小和样式来设置
//
// Text-learning.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/4.
//
import SwiftUI
struct TextFontExample: View {
var body: some View {
VStack(spacing: 20) {
Text("Large Title this is the swift learning coure hahsh".capitalized)
// .font(.largeTitle)
// .fontWeight(.semibold)
// .bold()
// .underline()
.underline(true,color:.red)
.italic()
.strikethrough(true,color:.blue)
.font(.system(size:48,weight: .semibold, design: .serif))
// .multilineTextAlignment(.center)
// .multilineTextAlignment(.leading)
// .baselineOffset(50.0)
.kerning(10)
.multilineTextAlignment(.trailing)
.foregroundColor(.red)
.frame(width: 200, height: 200, alignment: .center)
.minimumScaleFactor(0.1)
}
.padding()
}
}
#Preview {
TextFontExample()
}
1. 字体大小 (font
)
SwiftUI 提供了很多系统预设的字体大小,常用于快速设置文本的字体样式。常用的字体包括:
.largeTitle
.title
.title2
.title3
.headline
.subheadline
.body
.callout
.caption
.caption2
.footnote
示例代码:
import SwiftUI
struct TextFontExample: View {
var body: some View {
VStack(spacing: 20) {
Text("Large Title").font(.largeTitle)
Text("Title").font(.title)
Text("Title 2").font(.title2)
Text("Title 3").font(.title3)
Text("Headline").font(.headline)
Text("Subheadline").font(.subheadline)
Text("Body").font(.body)
Text("Callout").font(.callout)
Text("Caption").font(.caption)
Text("Caption 2").font(.caption2)
Text("Footnote").font(.footnote)
}
.padding()
}
}
#Preview {
TextFontExample()
}
2. 字体粗细 (fontWeight
)
SwiftUI 支持不同的字体权重(粗细),从极细到极粗。常用的权重包括:
.ultraLight
:极细.thin
:很细.light
:细.regular
:正常.medium
:中等.semibold
:半粗.bold
:粗.heavy
:很粗.black
:极粗
示例代码:
import SwiftUI
struct TextFontWeightExample: View {
var body: some View {
VStack(spacing: 20) {
Text("Ultra Light").fontWeight(.ultraLight)
Text("Thin").fontWeight(.thin)
Text("Light").fontWeight(.light)
Text("Regular").fontWeight(.regular)
Text("Medium").fontWeight(.medium)
Text("Semibold").fontWeight(.semibold)
Text("Bold").fontWeight(.bold)
Text("Heavy").fontWeight(.heavy)
Text("Black").fontWeight(.black)
}
.padding()
}
}
#Preview {
TextFontWeightExample()
}
3. 字体颜色 (foregroundColor
)
Text
可以通过 foregroundColor
来设置字体的颜色。你可以使用系统颜色,也可以自定义颜色。
示例代码:
import SwiftUI
struct TextColorExample: View {
var body: some View {
VStack(spacing: 20) {
Text("Red Text").foregroundColor(.red)
Text("Green Text").foregroundColor(.green)
Text("Blue Text").foregroundColor(.blue)
Text("Custom Color Text").foregroundColor(Color.purple)
}
.padding()
}
}
#Preview {
TextColorExample()
}
4. 文本对齐方式 (multilineTextAlignment
)
对于多行文本,你可以通过 multilineTextAlignment
来调整对齐方式。支持的值有:
.leading
:左对齐.center
:居中对齐.trailing
:右对齐
示例代码:
import SwiftUI
struct TextAlignmentExample: View {
var body: some View {
VStack(spacing: 20) {
Text("This is a multiline text that is left aligned.")
.multilineTextAlignment(.leading)
.frame(width: 200) // 控制文本宽度以显示多行效果
Text("This is a multiline text that is center aligned.")
.multilineTextAlignment(.center)
.frame(width: 200)
Text("This is a multiline text that is right aligned.")
.multilineTextAlignment(.trailing)
.frame(width: 200)
}
.padding()
}
}
#Preview {
TextAlignmentExample()
}
5. 行数限制 (lineLimit
)
lineLimit
用来限制文本显示的行数。如果内容超过设置的行数,剩余的文本会被截断显示省略号。
示例代码:
import SwiftUI
struct TextLineLimitExample: View {
var body: some View {
VStack(spacing: 20) {
Text("This is a very long text that will be truncated after one line.")
.lineLimit(1)
Text("This is a long text that will be displayed over two lines if needed.")
.lineLimit(2)
Text("This text will not be limited in number of lines and will expand as needed.")
.lineLimit(nil) // 不限制行数
}
.padding()
}
}
#Preview {
TextLineLimitExample()
}
6. 文本截断方式 (truncationMode
)
当文本超过行数限制时,你可以通过 truncationMode
来选择截断显示的位置。可选值有:
.head
:文本开头部分显示省略号.middle
:文本中间部分显示省略号.tail
:文本结尾部分显示省略号(默认)
示例代码:
import SwiftUI
struct TextTruncationExample: View {
var body: some View {
VStack(spacing: 20) {
Text("This text will be truncated at the beginning if too long.")
.truncationMode(.head)
.lineLimit(1)
Text("This text will be truncated in the middle if too long.")
.truncationMode(.middle)
.lineLimit(1)
Text("This text will be truncated at the end if too long.")
.truncationMode(.tail)
.lineLimit(1)
}
.padding()
}
}
#Preview {
TextTruncationExample()
}
7. 下划线、删除线和斜体
你可以为文本添加下划线、删除线或斜体效果:
underline()
:给文本加下划线strikethrough()
:给文本加删除线italic()
:斜体文本
示例代码:
import SwiftUI
struct TextDecorationsExample: View {
var body: some View {
VStack(spacing: 20) {
Text("Underlined Text").underline(true, color: .blue)
Text("Strikethrough Text").strikethrough(true, color: .red)
Text("Italic Text").italic()
}
.padding()
}
}
#Preview {
TextDecorationsExample()
}
8. 字母间距 (kerning
) 和行间距 (lineSpacing
)
kerning()
:设置字母间的距离。lineSpacing()
:设置行与行之间的距离。
示例代码:
import SwiftUI
struct TextSpacingExample: View {
var body: some View {
VStack(spacing: 20) {
Text("Kerning Example")
.kerning(5) // 字母间距增加 5
Text("This is a text with line spacing.")
.lineSpacing(10) // 行距增加 10
.frame(width: 200)
}
.padding()
}
}
#Preview {
TextSpacingExample()
}
9. 动态文本调整 (dynamicTypeSize
)
dynamicTypeSize
允许文本根据用户的系统字体设置动态调整大小,以提升可访问性。
示例代码:
import SwiftUI
struct TextDynamicTypeSizeExample: View {
var body: some View {
VStack(spacing: 20) {
Text("Text with Dynamic Type Size")
.font(.body)
.dynamicTypeSize(.medium) // 动态调整字体大小
Text("Text with Larger Dynamic Type Size")
.font(.body)
.dynamicTypeSize(.xxxLarge) // 更大的动态字体大小
}
.padding()
}
}
#Preview {
TextDynamicTypeSizeExample()
}
10. 背景和边框
尽管 Text
本身不能直接设置背景和边框,但可以通过 background()
和 border()
包裹。
示例代码:
import SwiftUI
struct TextBackgroundBorderExample: View {
var body: some View {
VStack(spacing: 20) {
Text("Text with Background")
.padding()
.background(Color.yellow) // 设置背景颜色
Text("Text with Border")
.padding()
.border(Color.gray, width: 2) // 设置边框
}
.padding()
}
}
#Preview {
TextBackgroundBorderExample()
}
1-2 shapes
1. Circle(圆形)
Circle
用来绘制一个完美的圆形,高度和宽度相等。如果 frame
中的宽度和高度不相等,它仍会保持圆形,并取较小的尺寸。
import SwiftUI
struct CircleExample: View {
var body: some View {
Circle()
.fill(Color.blue) // 填充圆形,颜色为蓝色
.frame(width: 100, height: 100) // 设置圆形的宽度和高度为100
.overlay(Circle().stroke(Color.red, lineWidth: 5)) // 给圆形添加红色描边
.shadow(radius: 10) // 添加阴影
}
}
#Preview {
CircleExample()
}
fill(Color.blue)
:填充圆形内部为蓝色。frame(width: 100, height: 100)
:设置圆形的宽高为100。即使高度和宽度不同,Circle
仍会保持为圆形。overlay(Circle().stroke(Color.red, lineWidth: 5))
:在圆形上添加红色的描边,线宽为5。shadow(radius: 10)
:为圆形添加半径为10的阴影。
1. .fill(Color.blue)
作用:使用指定的颜色填充形状内部区域。
在你的代码中,
fill(Color.blue)
会将圆形内部填充为蓝色。示例:
Circle() .fill(Color.blue) // 圆形内部填充为蓝色
2. .foregroundColor(Color.red)
作用:设置形状的前景色,和
fill()
类似。它用于填充形状的内部区域。在形状组件中,
foregroundColor
和fill()
的效果是一样的,都会填充形状的内部。示例:
Circle() .foregroundColor(Color.red) // 圆形内部填充为红色
3. .stroke()
作用:为形状添加描边(边框),默认颜色是黑色,默认宽度是1。
示例:
Circle() .stroke() // 给圆形添加一个默认的黑色描边,宽度为1
4. .stroke(Color.red, lineWidth: 30)
作用:为形状添加指定颜色和宽度的描边。
在这个例子中,
stroke(Color.red, lineWidth: 30)
会给圆形添加一个 30 个单位宽的红色描边。示例:
Circle() .stroke(Color.red, lineWidth: 30) // 红色边框,宽度为30
5. .stroke(Color.orange, style: StrokeStyle(...))
作用:为形状添加自定义样式的描边。通过
StrokeStyle
可以进一步定制描边的属性,比如虚线模式、线宽、线端样式等。StrokeStyle
中常见的参数:lineWidth
: 设置线条的宽度。lineCap
: 设置线条末端的样式,可以是.butt
(平端),.round
(圆端),.square
(方端)。dash
: 设置虚线模式,数组[10]
表示虚线每段为 10 单位长。
示例:
Circle() .stroke(Color.orange, style: StrokeStyle(lineWidth: 20, lineCap: .butt, dash: [10]))
- 这个代码会给圆形描边,颜色是橙色,线宽为20,线端是平端,虚线长度为10。
6. .trim(from: 0.2, to: 1.0)
作用:修剪形状,按百分比截取形状的一部分进行显示。参数
from
和to
表示截取的起始位置和结束位置,值的范围是 0 到 1,分别对应 0% 到 100%。trim(from: 0.2, to: 1.0)
会显示圆形的 80% 部分,从圆周的 20% 到 100%。示例:
Circle() .trim(from: 0.2, to: 1.0) .stroke(Color.purple, lineWidth: 50) // 紫色描边,线宽为50,圆形只显示 80%
7. .stroke(Color.purple, lineWidth: 50)
作用:为形状添加紫色描边,线宽为 50 个单位。因为
trim(from: 0.2, to: 1.0)
的作用,描边只会显示圆形的 80% 部分。示例:
Circle() .trim(from: 0.2, to: 1.0) // 修剪圆形,显示 80% 部分 .stroke(Color.purple, lineWidth: 50) // 给修剪后的形状描边,紫色,线宽 50
综合代码示例
import SwiftUI
struct Shapes: View {
var body: some View {
Circle()
.trim(from: 0.2, to: 1.0) // 修剪圆形,显示从 20% 到 100% 的部分
.stroke(Color.purple, lineWidth: 50) // 紫色描边,线宽为50
}
}
#Preview {
Shapes()
}
关键点总结:
fill()
和foregroundColor()
:都用于填充形状的内部区域,作用相同。fill
更直观,而foregroundColor
主要用于与Shape
一起的情况。stroke()
:用于为形状添加描边,支持自定义颜色、线宽和样式。trim(from:to:)
:用于截取形状的一部分进行显示,常用于需要展示部分圆弧或其他形状的情况下。StrokeStyle
:提供了自定义描边样式的灵活性,比如虚线、线条末端样式等。
通过这些属性,你可以非常灵活地定制 SwiftUI 中的形状,使其能够满足各种设计需求。
当然,我将为每一个形状组件(Circle
、Ellipse
、Capsule
、Rectangle
和 RoundedRectangle
)创建单独的代码块,并详细说明它们的作用及常用的修饰符。
2. Ellipse(椭圆形)
Ellipse
是用来绘制一个椭圆形的组件。如果 frame
的宽度和高度相等,它就是一个圆。如果宽高不同,它会根据指定的尺寸显示为椭圆形。
代码示例:
import SwiftUI
struct EllipseExample: View {
var body: some View {
Ellipse()
.fill(Color.green) // 填充椭圆,颜色为绿色
.frame(width: 150, height: 100) // 设置宽度150,高度100,椭圆形
.overlay(Ellipse().stroke(Color.black, lineWidth: 3)) // 添加黑色描边
.shadow(radius: 10) // 添加阴影
}
}
#Preview {
EllipseExample()
}
解释:
fill(Color.green)
:填充椭圆内部为绿色。frame(width: 150, height: 100)
:设置宽度为150,高度为100,形状为椭圆。overlay(Ellipse().stroke(Color.black, lineWidth: 3))
:在椭圆上添加黑色描边,线宽为3。shadow(radius: 10)
:为椭圆添加阴影。
3. Capsule(胶囊形)
Capsule
是一种具有圆形末端的矩形。宽度和高度相同的情况下,它看起来像一个圆形,而宽度大于高度时,它呈现出胶囊形状。
代码示例:
import SwiftUI
struct CapsuleExample: View {
var body: some View {
Capsule()
.fill(Color.purple) // 填充胶囊形,颜色为紫色
.frame(width: 200, height: 100) // 设置宽度200,高度100,胶囊形状
.overlay(Capsule().stroke(Color.white, lineWidth: 4)) // 添加白色描边
.shadow(radius: 10) // 添加阴影
}
}
#Preview {
CapsuleExample()
}
解释:
fill(Color.purple)
:填充胶囊形内部为紫色。frame(width: 200, height: 100)
:设置宽度为200,高度为100,形状为长椭圆的胶囊形。overlay(Capsule().stroke(Color.white, lineWidth: 4))
:为胶囊形添加白色描边,线宽为4。shadow(radius: 10)
:为胶囊形添加阴影。
4. Rectangle(矩形)
Rectangle
是一个标准的矩形形状。它不会有任何圆角,完全依赖于 frame
的宽度和高度来决定它的形状。
代码示例:
import SwiftUI
struct RectangleExample: View {
var body: some View {
Rectangle()
.fill(Color.orange) // 填充矩形,颜色为橙色
.frame(width: 150, height: 100) // 设置宽度150,高度100,矩形
.overlay(Rectangle().stroke(Color.black, lineWidth: 3)) // 添加黑色描边
.shadow(radius: 10) // 添加阴影
}
}
#Preview {
RectangleExample()
}
解释:
fill(Color.orange)
:填充矩形内部为橙色。frame(width: 150, height: 100)
:设置矩形的宽度为150,高度为100。overlay(Rectangle().stroke(Color.black, lineWidth: 3))
:为矩形添加黑色描边,线宽为3。shadow(radius: 10)
:为矩形添加阴影。
5. RoundedRectangle(圆角矩形)
RoundedRectangle
是一个带有圆角的矩形。你可以通过 cornerRadius
来设置圆角的大小,从而控制圆角的弧度。
代码示例:
import SwiftUI
struct RoundedRectangleExample: View {
var body: some View {
RoundedRectangle(cornerRadius: 25, style: .continuous) // 设置圆角半径为25,连续的圆角样式
.fill(Color.red) // 填充圆角矩形,颜色为红色
.frame(width: 150, height: 100) // 设置宽度150,高度100
.overlay(RoundedRectangle(cornerRadius: 25).stroke(Color.black, lineWidth: 4)) // 添加黑色描边
.shadow(radius: 10) // 添加阴影
}
}
#Preview {
RoundedRectangleExample()
}
解释:
RoundedRectangle(cornerRadius: 25, style: .continuous)
:创建一个圆角矩形,圆角半径为25,样式为连续圆角。fill(Color.red)
:填充圆角矩形内部为红色。frame(width: 150, height: 100)
:设置宽度为150,高度为100。overlay(RoundedRectangle(cornerRadius: 25).stroke(Color.black, lineWidth: 4))
:为圆角矩形添加黑色描边,线宽为4。shadow(radius: 10)
:为圆角矩形添加阴影。Circle
:一个完美的圆形,不论你设置的尺寸如何,它总是保持为圆形。Ellipse
:一个椭圆形状,它根据设置的宽高比例调整成不同的椭圆。Capsule
:一个矩形形状,但两端有圆角,宽高相同时,它看起来像一个圆形。Rectangle
:一个标准的矩形,没有圆角。RoundedRectangle
:一个带有圆角的矩形,通过设置cornerRadius
可以控制圆角的弧度。
这些形状可以通过组合、描边、填充、阴影等修饰符来创建丰富多样的视觉效果。在 SwiftUI 中,形状组件非常灵活,并且可以与其他布局和视图进行结合,构建出复杂的 UI 设计。
1-3 Color()
在 SwiftUI 中,Color
是用于表示颜色的结构体,常用于设置视图的背景、前景、文本颜色等。Color
提供了一系列内置的颜色选项,也支持通过不同的方式自定义颜色,如使用 UIColor
、NSColor
或者通过 RGB 和十六进制值来定义颜色。
1. 内置颜色
SwiftUI 提供了一组内置的基础颜色,类似于 UIColor
或 NSColor
。这些内置颜色可以直接通过 Color
构造器使用:
Color.red
Color.green
Color.blue
Color.black
Color.white
Color.gray
Color.pink
Color.purple
Color.orange
Color.yellow
这些颜色是常用的预定义颜色,适合在大多数 UI 设计中使用。
2. 系统颜色(动态颜色)
SwiftUI 也支持使用 iOS 和 macOS 系统中的动态颜色,尤其是在浅色模式和深色模式之间切换时,这些颜色会自动适应用户的界面外观(例如背景色、前景色等)。这些颜色通过 Color(UIColor)
或 Color(NSColor)
来使用。
iOS 上使用 UIColor
(适合深色和浅色模式切换):
Color(UIColor.systemBackground) // 系统背景颜色
Color(UIColor.label) // 系统标签颜色(文本颜色)
Color(UIColor.secondaryLabel) // 次要文本颜色
Color(UIColor.systemGray) // 系统灰色
这些颜色会根据设备的界面模式(深色/浅色)自动调整,因此可以确保应用程序的颜色在不同外观下保持一致的可读性。
macOS 上使用 NSColor
:
Color(NSColor.windowBackgroundColor) // 窗口背景颜色
Color(NSColor.textColor) // 文本颜色
3. 自定义颜色
除了系统提供的颜色,你也可以通过自定义颜色来丰富应用的配色方案。在 SwiftUI 中,使用自定义颜色有以下几种方式:
通过 RGB 创建自定义颜色
Color
提供了一个构造器,允许你通过 RGB 颜色模型来创建颜色。你可以指定红、绿、蓝和透明度值(范围从 0 到 1):
Color(red: 0.5, green: 0.8, blue: 0.3)
Color(red: 0.2, green: 0.7, blue: 0.5, opacity: 0.7) // 具有透明度的颜色
通过十六进制(Hex)创建自定义颜色
虽然 SwiftUI 没有直接支持十六进制颜色,但你可以通过扩展 Color
来实现。常见做法是创建一个接受十六进制字符串的构造器:
extension Color {
init(hex: String) {
let scanner = Scanner(string: hex)
var hexNumber: UInt64 = 0
if scanner.scanHexInt64(&hexNumber) {
let r = Double((hexNumber & 0xff0000) >> 16) / 255
let g = Double((hexNumber & 0x00ff00) >> 8) / 255
let b = Double(hexNumber & 0x0000ff) / 255
self.init(red: r, green: g, blue: b)
} else {
self.init(red: 0, green: 0, blue: 0)
}
}
}
let hexColor = Color(hex: "#34C759") // 自定义颜色
这种方法允许你使用更灵活的颜色定义方式,特别是在你有设计师提供的十六进制颜色时。
使用 UIColor
或 NSColor
自定义颜色
你还可以通过 UIColor
(iOS)或 NSColor
(macOS)自定义颜色,并将它们转换为 SwiftUI 中的 Color
:
Color(UIColor(red: 0.5, green: 0.3, blue: 0.8, alpha: 1.0))
这种方法很有用,如果你有现成的 UIColor
对象,或者你需要与 UIKit 或 AppKit 兼容。
4. 颜色资产(Color Assets)
在 Xcode 的资源库中,你可以创建颜色资产,并在不同的模式下定义颜色(例如浅色和深色模式下使用不同的颜色)。在 SwiftUI 中,你可以通过颜色资源的名称来引用这些颜色:
Color("CustomColor") // 使用颜色资产中的自定义颜色
这种方法特别适合管理大量颜色,并确保它们在不同的外观模式下的呈现效果。
5. 渐变颜色
SwiftUI 还支持渐变颜色,通过 LinearGradient
或 RadialGradient
可以创建渐变效果:
线性渐变:
LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .top, endPoint: .bottom)
径向渐变:
RadialGradient(gradient: Gradient(colors: [Color.purple, Color.orange]), center: .center, startRadius: 5, endRadius: 100)
渐变颜色通常用于背景或装饰性效果,能够提升界面的视觉吸引力。
SwiftUI 中的 Color
结构体提供了非常灵活的颜色定义方法,既包括常用的内置颜色,也可以通过 UIColor
、NSColor
或自定义的 RGB、十六进制来定义颜色。此外,使用颜色资产和动态颜色能够帮助开发者轻松管理不同外观模式下的颜色表现。结合 SwiftUI 的其他特性,如渐变颜色,开发者可以设计出丰富多彩的用户界面。
1-4 Gradients 渐变
1. 线性渐变(Linear Gradient)
线性渐变是最常用的一种渐变类型,它沿着一条直线从一个点过渡到另一个点。你可以指定渐变的起始点和终止点,以及过渡的颜色。
使用方式:
LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .top, endPoint: .bottom)
- Gradient: 包含一组用于过渡的颜色,可以使用
Gradient(colors:)
传入一个数组或Gradient(stops:)
指定颜色和位置。 - startPoint 和 endPoint: 指定渐变的起点和终点,支持的值有
.top
,.bottom
,.leading
,.trailing
,.center
, 以及自定义的点(如(x: 0.5, y: 0.5)
)。
示例代码:
struct ContentView: View {
var body: some View {
Rectangle()
.fill(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]),
startPoint: .leading, endPoint: .trailing))
.frame(width: 300, height: 200)
}
}
过渡点的渐变:
你还可以通过 Gradient(stops:)
来指定颜色的过渡位置:
LinearGradient(
gradient: Gradient(stops: [
Gradient.Stop(color: Color.red, location: 0.0),
Gradient.Stop(color: Color.green, location: 0.5),
Gradient.Stop(color: Color.blue, location: 1.0)
]),
startPoint: .top,
endPoint: .bottom
)
2. 径向渐变(Radial Gradient)
径向渐变从一个中心点向四周扩散,颜色渐变通常从中心颜色到外部颜色。径向渐变可以用来创建类似光晕或按钮背景的效果。
使用方式:
RadialGradient(gradient: Gradient(colors: [Color.yellow, Color.orange]), center: .center, startRadius: 5, endRadius: 200)
- center: 渐变的中心点(可以是
.center
或自定义的UnitPoint
)。 - startRadius 和 endRadius: 指定渐变的开始半径和结束半径,控制渐变的范围和强度。
示例代码:
struct ContentView: View {
var body: some View {
Circle()
.fill(RadialGradient(gradient: Gradient(colors: [Color.yellow, Color.orange]),
center: .center, startRadius: 20, endRadius: 100))
.frame(width: 200, height: 200)
}
}
在这个例子中,渐变从黄色(半径为 20 的位置)过渡到橙色(半径为 100 的位置)。
3. 角度渐变(Angular Gradient)
角度渐变是围绕一个中心点,以角度变化的方式渐变。常用于创建表盘或类似旋转的视觉效果。
使用方式:
AngularGradient(gradient: Gradient(colors: [Color.red, Color.yellow, Color.green]), center: .center)
- center: 渐变的中心点。
- startAngle 和 endAngle: 可选参数,定义渐变的开始角度和结束角度。可以通过
Angle(degrees:)
来指定。
示例代码:
struct ContentView: View {
var body: some View {
Circle()
.fill(AngularGradient(gradient: Gradient(colors: [Color.red, Color.yellow, Color.green]),
center: .center))
.frame(width: 200, height: 200)
}
}
这个渐变会绕着圆形从红色开始,依次过渡到黄色、绿色,形成环形渐变效果。
4. 颜色和位置的渐变控制
你可以通过 Gradient.Stop
控制颜色在渐变中的位置,使渐变效果更加精准:
示例代码:
LinearGradient(gradient: Gradient(stops: [
Gradient.Stop(color: .red, location: 0.0), // 红色开始于 0.0
Gradient.Stop(color: .green, location: 0.3), // 绿色出现在 30% 处
Gradient.Stop(color: .blue, location: 1.0) // 蓝色结束于 1.0
]), startPoint: .top, endPoint: .bottom)
在这个例子中,渐变颜色过渡的起点、终点和中间的过渡颜色可以通过 location
控制,定义更复杂的渐变模式。
5. 应用于不同视图
渐变不仅可以应用于 Rectangle
、Circle
等形状,还可以作为其他视图的背景,甚至可以作用于文本。以下是一些常见的用法示例:
渐变背景:
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!")
.font(.largeTitle)
.padding()
.background(
LinearGradient(gradient: Gradient(colors: [Color.purple, Color.blue]),
startPoint: .leading, endPoint: .trailing)
)
.cornerRadius(10)
}
}
渐变文本:
struct ContentView: View {
var body: some View {
Text("Gradient Text")
.font(.largeTitle)
.foregroundStyle(
LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]),
startPoint: .leading, endPoint: .trailing)
)
}
}
在 SwiftUI 中,通过 foregroundStyle
,渐变可以应用于文本,使得文字呈现出丰富的颜色过渡效果。
6. 自定义渐变角度和位置
你可以通过调整渐变的 startPoint
和 endPoint
来改变渐变的方向和范围。例如,创建从左下到右上的渐变:
LinearGradient(gradient: Gradient(colors: [Color.blue, Color.pink]),
startPoint: .bottomLeading, endPoint: .topTrailing)
7. 结合动画使用渐变
渐变与动画结合能够创造动态的视觉效果。在 SwiftUI 中,你可以使用渐变动画来创建令人惊艳的效果:
struct AnimatedGradient: View {
@State private var animate = false
var body: some View {
LinearGradient(gradient: Gradient(colors: [animate ? .red : .blue, animate ? .yellow : .green]),
startPoint: .topLeading, endPoint: .bottomTrailing)
.animation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true))
.onAppear {
self.animate.toggle()
}
.frame(width: 300, height: 300)
}
}
这里的例子中,渐变颜色在红色到蓝色之间不断来回过渡。
1-5 System Icons
https://developer.apple.com/sf-symbols/
这是苹果官方提供的图标,可以在项目中直接使用
import SwiftUI
struct systemIcons: View {
var body: some View {
// 使用 SF Symbols 中的 "heart.fill" 图标
Image(systemName:"heart.fill")
// 将图标调整为可调整大小的模式
.resizable()
// 使用 scaledToFill() 来确保图标填充整个视图
// 这会按比例填充,保持图标的宽高比,但可能裁剪部分内容
.scaledToFill()
// 设置图标的前景颜色为绿色
.foregroundColor(.green)
// 设置图标的宽度和高度为 200x200,并将其居中对齐
.frame(width: 200, height: 200, alignment: .center)
// 剪裁超出 frame 的图标部分,确保图标在设置的宽高内显示
.clipped()
}
}
#Preview {
systemIcons()
}
在 SwiftUI 中,系统图标是通过 SF Symbols
提供的,苹果为开发者准备了超过 4000 多个可缩放的矢量图标。SF Symbols
是一套统一的图标系统,可以方便地用于 iOS、macOS 和其他 Apple 平台的应用程序中。使用这些图标不仅可以确保在不同设备上的一致性,而且它们可以自动适应字体大小和动态变化。
1. 基础使用
使用系统图标非常简单,SwiftUI 提供了 Image
视图,它可以通过图标名称直接调用 SF Symbols 图标。
Image(systemName: "heart")
在这个例子中,"heart"
是 SF Symbols 图标的名称。这种方式可以用来显示不同的系统图标。
2. 常见 SF Symbols 图标
以下是一些常用的 SF Symbols 图标示例:
heart
:❤️(心形图标)star
:⭐️(星星图标)pencil
:✏️(铅笔图标)trash
:🗑️(垃圾桶图标)person
:👤(人形图标)plus
:➕(加号图标)magnifyingglass
:🔍(放大镜图标)
完整的 SF Symbols 图标库可以通过 SF Symbols 应用程序查看,SF Symbols 支持不同的权重、样式和状态(例如实心、空心、圆角等)。
3. 调整图标的大小
你可以通过修改 Image
视图的修饰符来自定义系统图标的大小。例如,使用 font
修饰符来设置图标的大小和样式:
Image(systemName: "star")
.font(.system(size: 50)) // 设置图标的大小为 50
还可以使用特定的字体权重来控制图标的粗细:
Image(systemName: "star.fill")
.font(.system(size: 40, weight: .bold)) // 粗体的图标
4. 设置图标颜色
系统图标默认是继承其父视图的前景色,但你可以通过 foregroundColor
修饰符来自定义图标的颜色:
Image(systemName: "star.fill")
.font(.system(size: 50))
.foregroundColor(.yellow) // 将图标设置为黄色
5. 不同样式的图标
SF Symbols 图标通常有多种样式,例如实心、空心、圆角等。例如,以下是不同的星星样式:
star
:普通星星star.fill
:实心星星star.circle
:带圆圈的星星star.circle.fill
:实心圆圈内的星星
你可以通过不同的图标名称来选择合适的样式:
HStack {
Image(systemName: "star")
Image(systemName: "star.fill")
Image(systemName: "star.circle")
Image(systemName: "star.circle.fill")
}
6. 响应式图标
SF Symbols 图标能够自动适应系统的字体动态变化(如动态文字尺寸设置),如果用户在设置中调整字体大小,图标也会相应调整。
Image(systemName: "heart.fill")
.imageScale(.large) // 自动根据系统设置调整图标大小
常见的 imageScale
选项有:
.small
.medium
.large
这些设置会根据上下文动态调整图标大小,确保在不同设备和系统设置下图标清晰可见。
7. 图标和文本的结合
你可以轻松将 SF Symbols 图标与文本结合使用,创建按钮或标签。SwiftUI 支持在 HStack
或 VStack
中混合图标和文本。
HStack {
Image(systemName: "pencil")
Text("Edit")
}
这种组合方式非常适合创建带有图标的按钮或其他 UI 组件。
8. 在 Button 中使用图标
在 SwiftUI 中,Button
可以使用 Image
作为按钮内容,从而轻松创建带图标的按钮:
Button(action: {
print("Button tapped")
}) {
Image(systemName: "trash")
.font(.system(size: 24))
.foregroundColor(.red)
}
在这个例子中,我们创建了一个带有垃圾桶图标的红色按钮。
9. 使用符号变体
在 iOS 15 和 macOS Monterey 后,SF Symbols 增加了符号变体(Symbol Variants)的概念,可以让开发者根据需要调整图标的外观,如填充、对齐等。你可以通过 symbolVariant
修饰符来使用:
Image(systemName: "person.fill")
.symbolVariant(.fill) // 使用填充样式
symbolVariant
支持多种变体:
.fill
:填充.circle
:圆圈.rectangle
:矩形
10. SF Symbols 3 新增的功能
SF Symbols 3 引入了新的功能和图标,进一步增强了定制能力,包括:
- 多色符号(Multicolor Symbols):支持多种颜色的符号,可以为图标不同部分指定不同颜色。
- 可变性(Variable Symbols):支持对图标某些部分进行动态调整。
例如,多色图标的使用:
Image(systemName: "pencil.circle.fill")
.symbolRenderingMode(.multicolor) // 开启多色符号
11. 定制多种图标样式
SF Symbols
提供了多种不同风格的图标,以下是一些常见的图标风格:
star
:标准图标star.fill
:填充图标star.slash
:图标带斜线star.circle
:带圆圈的图标
12. 动态渲染模式
你可以通过 symbolRenderingMode
控制符号的渲染模式。例如,你可以启用多色模式或者使用单色模式。
Image(systemName: "cloud.sun.rain.fill")
.symbolRenderingMode(.hierarchical) // 分层渲染模式
.foregroundColor(.blue)
渲染模式包括:
.monochrome
:单色渲染.hierarchical
:分层渲染,基于图标的层次结构调整颜色深浅.palette
:调色板模式,允许为图标的不同部分设置不同的颜色.multicolor
:多色渲染
13. 访问 SF Symbols 名称
SF Symbols 的图标名称是区分大小写的,因此你需要确保拼写和大小写正确。为了避免错误,你可以使用苹果提供的 SF Symbols
应用程序来搜索和浏览图标名称。可以从苹果开发者网站下载 SF Symbols 应用。
1-6 Image()
//
// Images.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/4.
//
import SwiftUI
struct Images: View {
var body: some View {
// 显示名为 "beaty" 的图像文件(需要在资源库中存在)
Image("beaty")
// 使图像可调整大小
.resizable()
// 使用 scaledToFill() 来按比例缩放图片并确保填充整个视图框架
// 这可能会导致部分图片被裁剪,以确保整个框架被填满
.scaledToFill()
// 设置图片的宽度为 300,高度为 200,并将其居中对齐
.frame(width: 300, height: 200, alignment: .center)
// 将图像裁剪为圆形
.clipShape(
Circle() // 你可以选择其他形状,如圆角矩形 RoundedRectangle
)
// 如果使用 .cornerRadius(100),则会在图像边框添加圆角
// 你可以取消注释并选择 .cornerRadius(100) 或其他值,来为图片加上圆角效果
}
}
#Preview {
Images()
}
在 SwiftUI 中,Image
是用于显示图像的基础视图组件。它可以用于展示应用资源中的图片、SF Symbols 系统图标,或者通过远程 URL 加载的图像。Image
视图可以通过一系列的修饰符来自定义和调整其外观,比如缩放、裁剪、边框、圆角等。
1. 加载图片
Image
可以从多个来源加载图像,包括:
1.1 从资源文件加载
Image("exampleImage")
这会加载项目资源库中的图片 "exampleImage"
。需要确保图片已经添加到 Xcode 项目中(通常是在 Assets 目录下)。
1.2 使用 SF Symbols
SF Symbols 是苹果提供的一套可缩放的矢量图标,可以通过 systemName
参数加载:
Image(systemName: "star.fill")
这里的 "star.fill"
是 SF Symbols 图标的名称。你可以在 SF Symbols 应用程序中找到图标名称并应用到 Image
中。
1.3 使用系统图片
你也可以使用系统图片文件,例如用于显示 iOS/macOS 的图片资源:
Image(uiImage: UIImage(named: "exampleImage")!)
2. 图片的调整和缩放
Image
视图提供了多种方式来调整图像的大小和缩放,以下是常用的几种方法:
2.1 设置图片大小
通过 frame
修饰符来设置图片的大小:
Image("exampleImage")
.frame(width: 100, height: 100)
这会将图片的显示区域设置为 100x100,但不会改变图片的缩放行为。
2.2 调整图片大小
resizable()
方法允许图像根据容器大小缩放:
Image("exampleImage")
.resizable()
.frame(width: 200, height: 150)
2.3 缩放内容模式
可以使用 scaledToFill()
和 scaledToFit()
修饰符来控制图像的缩放方式:
scaledToFill()
:将图像按比例缩放以填充整个框架,但可能会裁剪部分内容。
Image("exampleImage")
.resizable()
.scaledToFill()
.frame(width: 200, height: 200)
.clipped() // 如果图片超出框架,将其裁剪
scaledToFit()
:按比例缩放图像以适应框架大小,不会裁剪图片。
Image("exampleImage")
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
2.4 调整内容模式
你还可以使用 aspectRatio(contentMode:)
调整图像的内容模式:
.fill
:按比例填充并裁剪。.fit
:按比例适应框架,不裁剪。
Image("exampleImage")
.resizable()
.aspectRatio(contentMode: .fit)
3. 图片的裁剪与形状
SwiftUI 支持通过 clipShape
和 cornerRadius
来裁剪图片或为其添加圆角:
3.1 圆角图片
可以通过 cornerRadius
为图片设置圆角:
Image("exampleImage")
.resizable()
.frame(width: 150, height: 150)
.cornerRadius(20) // 圆角半径
3.2 使用 clipShape
裁剪图片
你可以使用 clipShape
将图片裁剪为指定形状:
Image("exampleImage")
.resizable()
.frame(width: 150, height: 150)
.clipShape(Circle()) // 裁剪为圆形
常见的形状包括:
Circle()
:圆形RoundedRectangle(cornerRadius: 25)
:圆角矩形Ellipse()
:椭圆形
4. 应用渐变、阴影等效果
你可以为图像添加渐变、阴影等效果:
4.1 应用阴影
Image("exampleImage")
.resizable()
.frame(width: 150, height: 150)
.shadow(radius: 10) // 应用阴影
4.2 应用遮罩
可以使用 mask
将图像限制在特定的区域内,例如渐变遮罩:
Image("exampleImage")
.resizable()
.frame(width: 150, height: 150)
.mask(LinearGradient(gradient: Gradient(colors: [.black, .clear]), startPoint: .top, endPoint: .bottom))
5. 设置图片的前景色
对于 SF Symbols
图标,你可以使用 foregroundColor
来设置图标颜色:
Image(systemName: "heart.fill")
.foregroundColor(.red)
这会将心形图标填充为红色。
6. 图片的合成和叠加
你可以将图片与其他视图或文字进行叠加,实现更加复杂的 UI 效果。
6.1 在图片上叠加文本
Image("exampleImage")
.resizable()
.frame(width: 300, height: 200)
.overlay(
Text("Hello, SwiftUI!")
.foregroundColor(.white)
.font(.largeTitle)
.padding(),
alignment: .bottom
)
6.2 叠加其他视图
你还可以在图片上叠加形状或其他视图:
Image("exampleImage")
.resizable()
.frame(width: 200, height: 200)
.overlay(
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 100, height: 100)
)
7. 图片的动态颜色适配
如果你使用 Image(systemName:)
加载 SF Symbols 图标,它可以自动适配浅色和深色模式。例如,你可以结合 foregroundColor
为图标设置不同的颜色。
8. 远程加载图片
虽然 SwiftUI 默认不支持从 URL 加载图片,但是你可以使用 AsyncImage
(从 iOS 15+ 开始支持)从远程 URL 异步加载图像:
AsyncImage(url: URL(string: "https://example.com/image.png")) { image in
image
.resizable()
.scaledToFit()
} placeholder: {
ProgressView() // 加载中的占位图
}
.frame(width: 300, height: 200)
9. 在 Button 中使用图片
Image
也可以与 Button
结合,创建带有图片的按钮:
Button(action: {
print("Button tapped")
}) {
Image(systemName: "trash")
.font(.system(size: 24))
.foregroundColor(.red)
}
10. 使用系统字体和图标调整
你可以使用 font
修饰符来改变 SF Symbols 的大小或样式:
Image(systemName: "star.fill")
.font(.system(size: 40, weight: .bold))
.foregroundColor(.yellow)
11. 图片的动画
你可以结合 SwiftUI 的动画系统为图像添加动态效果:
@State private var isRotated = false
Image(systemName: "arrow.right")
.rotationEffect(.degrees(isRotated ? 90 : 0))
.onTapGesture {
withAnimation {
isRotated.toggle()
}
}
1-7 frame()
import SwiftUI
// 定义一个名为 `Frames` 的视图结构体,遵循 `View` 协议
struct Frames: View {
var body: some View {
// 创建一个文本视图,初始显示“Hello, World!”
Text("Hello, World!")
// 给文本视图设置背景颜色为红色
.background(.red)
// 以下 frame 修饰符定义视图的尺寸和布局
// 1. 设置高度为 100,宽度未定,因此继承上一级的宽度
.frame(height: 100)
// 设置背景颜色为绿色。背景颜色作用在当前frame定义的区域
.background(.green)
// 2. 设置宽度为 150,继承上一级的高度 100
.frame(width: 150)
// 设置背景颜色为紫色,覆盖当前宽度和高度范围
.background(.purple)
// 3. 设置高度为 200,宽度继承上一级的 150
.frame(height: 200)
// 设置背景颜色为粉色,覆盖当前高度和宽度范围
.background(.pink)
// 4. 将宽度最大化为父视图的宽度,同时对齐方式为左对齐
.frame(maxWidth: .infinity, alignment: .leading)
// 背景颜色设置为黄色,覆盖整个最大宽度的区域
.background(.yellow)
// 5. 将高度最大化为父视图的高度,并设置对齐方式为顶部对齐
.frame(maxHeight: .infinity, alignment: .top)
// 背景颜色设置为青色,覆盖整个最大高度的区域
.background(.cyan)
}
}
// 预览部分,SwiftUI 的可视化预览代码,用于在设计器中快速查看效果
#Preview {
Frames()
}
- Text:使用
Text("Hello, World!")
创建一个文本视图,最初显示“Hello, World!”。 - background(.red):为文本设置背景颜色为红色。这个背景只会应用在文本的尺寸范围内。
- frame(height: 100):设置视图的高度为 100,宽度没有指定,因此继承父视图的宽度。
- background(.green):给上一步设定的 100 高度范围内设置背景颜色为绿色。
- frame(width: 150):将视图的宽度设为 150,此时高度仍为 100。
- background(.purple):在 150x100 的视图上添加紫色背景。
- frame(height: 200):将视图高度设置为 200,宽度依然是 150。
- background(.pink):为当前 150x200 的视图区域设置粉色背景。
- frame(maxWidth: .infinity, alignment: .leading):视图的宽度扩展到父视图的最大宽度,并左对齐。
- background(.yellow):为最大宽度的视图设置黄色背景。
- frame(maxHeight: .infinity, alignment: .top):将高度扩展到父视图的最大高度,并顶部对齐。
- background(.cyan):为最终扩展到父视图全高度的视图添加青色背景。
在 SwiftUI 中,frame
修饰符是一个非常强大的工具,用于控制视图的尺寸和位置。它允许你为视图指定宽度、高度、对齐方式等。我们可以通过 frame
来精确地调整视图在其父视图中的外观和布局。
1.frame
的基本用法
frame
有多个参数,常用的有:
- width: 视图的宽度。
- height: 视图的高度。
- alignment: 定义视图在可用空间内的对齐方式。
2.常见的 frame
语法形式
1. 指定固定的宽度和高度:
.frame(width: 100, height: 200)
这个例子将视图的宽度设定为 100,高度为 200。如果不指定宽度或高度,视图将默认继承父视图的宽度或高度。
2. 指定 maxWidth
和 maxHeight
:
.frame(maxWidth: .infinity, maxHeight: .infinity)
这一形式将视图的宽度和高度扩展到父视图的最大可用范围。常用于填充整个父视图的场景。
3. 带有对齐方式的 frame
:
.frame(width: 100, height: 100, alignment: .center)
你可以指定视图在其父视图内的对齐方式。默认情况下,视图会居中对齐(.center
),你还可以选择 .leading
(左对齐)、.trailing
(右对齐)、.top
(顶部对齐)、.bottom
(底部对齐)等。
4. 仅指定某一维度:
.frame(width: 100)
这种形式只指定了视图的宽度,视图的高度将由其内容或父视图决定。同理,你也可以仅设置高度。
3.frame
的层叠效果
你可以将多个 frame
修饰符连用。每个 frame
修饰符都会基于上一个 frame
的布局来进行调整。例如:
Text("Hello")
.frame(width: 100, height: 50)
.background(Color.red)
.frame(width: 150)
.background(Color.green)
在这个例子中:
- 第一个
frame
将文本的宽度设为 100,高度设为 50,并在其上应用了红色背景。 - 第二个
frame
仅改变宽度为 150,而保留了高度 50。此时,背景为绿色的框架覆盖了整个宽度为 150 的区域,而红色背景只在文本区域(100x50)范围内。
4. 对齐方式详解
alignment
参数控制视图在可用空间中的对齐方式。常用的对齐方式包括:
.center
:居中对齐,这是默认值。.leading
:左对齐,主要用于水平布局。.trailing
:右对齐。.top
:顶部对齐,主要用于垂直布局。.bottom
:底部对齐。
例如:
Text("Aligned Text")
.frame(width: 200, height: 100, alignment: .bottomTrailing)
.background(Color.gray)
这个例子中,文本在一个 200x100 的框中,右下对齐。
5.frame
和父视图的关系
- 无约束的父视图:当父视图没有明确的尺寸限制时(如
VStack
或HStack
),视图可以自由调整自己的尺寸,frame
的值会直接影响视图的尺寸。 - 有限制的父视图:如果父视图有明确的大小限制(如使用
GeometryReader
或ZStack
),frame
可以用于为子视图指定尺寸或位置。
6. 使用 frame
实现响应式布局
通过使用 frame
的 maxWidth
和 maxHeight
参数,并结合 .infinity
,你可以实现自适应的布局。例如,让视图自适应屏幕宽度:
Text("Full Width Text")
.frame(maxWidth: .infinity)
.background(Color.blue)
这种布局方式特别适用于在屏幕上填充整个宽度的需求。
frame
是控制 SwiftUI 中视图尺寸和对齐方式的关键工具。- 它允许灵活设置宽度、高度、最大宽度、高度,并可以叠加使用来实现复杂布局。
- 使用
frame
时要考虑父视图的布局方式和尺寸限制。
1-8 background() overlay()
import SwiftUI
// 定义一个名为 `backgrounds_overlay` 的视图结构体,遵循 `View` 协议
struct backgrounds_overlay: View {
var body: some View {
// 创建一个圆形视图,并填充粉红色
Circle()
.fill(Color.pink) // 用粉红色填充圆形
.frame(width: 100, height: 100, alignment: .center) // 设置圆形的宽度和高度为 100,并居中对齐
// 在圆形上叠加一个文本视图
.overlay(
Text("aini") // 叠加的文本内容为 "aini"
.font(.largeTitle) // 设置字体为大标题
.fontWeight(.semibold) // 设置字体为半粗体
.foregroundColor(.white) // 设置文本颜色为白色
)
// 在圆形后添加一个更大的绿色圆形作为背景
.background(
Circle() // 添加另一个圆形视图
.fill(.green) // 用绿色填充背景圆
.frame(width: 150, height: 150, alignment: .center) // 设置圆形的宽度和高度为 150,并居中对齐
)
}
}
// SwiftUI 预览部分,用于显示视图在设计器中的样子
#Preview {
backgrounds_overlay()
}
Circle(): 使用 SwiftUI 的
Circle()
生成一个圆形视图,圆形是标准的形状视图之一。.fill(Color.pink): 使用
.fill
修饰符为圆形填充颜色。在这个例子中,填充色为 粉红色。.frame(width: 100, height: 100, alignment: .center): 使用
frame
为圆形设置宽度和高度为 100。alignment: .center
表示该圆形在其父视图中居中对齐。.overlay:
overlay
用于在当前视图上叠加其他视图。在这个例子中,我们在粉红色的圆形上叠加了一个文本视图。Text("aini"): 显示一个文本 "aini",这是叠加在圆形中的文字内容。
.font(.largeTitle): 将文本的字体设置为大标题形式。
.fontWeight(.semibold): 设置字体的粗细为半粗体。
.foregroundColor(.white): 设置文本的颜色为白色,使其在粉红色圆形上更清晰可见。
.background:
background
修饰符用于在当前视图的后面(背景)添加内容。在这个例子中,背景是另一个圆形。Circle().fill(.green): 背景圆形用绿色填充,创建视觉上的层次感。
.frame(width: 150, height: 150, alignment: .center): 设置背景圆形的宽度和高度为 150,并确保其居中对齐。
background
和 overlay
是 SwiftUI 中两个非常有用的修饰符,用于在视图背后或前面叠加其他视图。通过它们,你可以轻松地创建复杂的视图效果,例如给按钮添加背景或在图片上叠加文字。它们提供了创建复杂UI设计的灵活性和便利性。
1. background 修饰符
background
用于将视图放置在当前视图的背后。你可以将任何视图(如颜色、形状、图片等)作为背景来修饰当前视图。
语法:
.view.background(背景视图)
示例 1: 给文本添加背景颜色
Text("Hello, World!")
.background(Color.yellow) // 给文本添加黄色背景
这个例子中,background
在文本的背后添加了一个黄色背景。
示例 2: 给按钮添加形状背景
Button(action: {
print("Button tapped")
}) {
Text("Click Me")
.padding()
}
.background(
RoundedRectangle(cornerRadius: 10) // 添加圆角矩形作为背景
.fill(Color.blue)
.frame(width: 150, height: 50)
)
在这个例子中,按钮背后有一个蓝色的圆角矩形,按钮上的文字被居中显示。
2. overlay 修饰符
overlay
修饰符用于在当前视图的顶部叠加另一个视图。与 background
相反,overlay
会在视图之上绘制叠加的内容。
语法:
.view.overlay(叠加视图)
示例 1: 给图片叠加文字
Image(systemName: "star.fill")
.resizable()
.frame(width: 100, height: 100)
.overlay(
Text("Favorite")
.foregroundColor(.white)
.font(.caption)
.padding(6)
.background(Color.black.opacity(0.7))
.cornerRadius(5),
alignment: .bottom // 文字在图片底部对齐
)
在这个例子中,我们在星星图片的底部叠加了一个小的 "Favorite" 标签,给它加了黑色的背景和圆角。
示例 2: 给圆形叠加图标
Circle()
.fill(Color.green)
.frame(width: 100, height: 100)
.overlay(
Image(systemName: "checkmark")
.font(.largeTitle)
.foregroundColor(.white)
)
这个例子展示了如何在绿色圆形的中心叠加一个白色的“对勾”图标。
3. background 和 overlay 结合使用
你可以同时使用 background
和 overlay
来创建更加复杂的视图结构。比如,一个视图既有背景又有叠加内容。
示例 1: 圆形背景和文本叠加
Text("SwiftUI")
.font(.title)
.padding()
.background(
Circle().fill(Color.blue) // 背景为蓝色圆形
)
.overlay(
Circle().stroke(Color.red, lineWidth: 4) // 圆形的红色边框叠加在文字外
)
在这个例子中,Text
视图的背景是一个蓝色圆形,叠加了一个红色的圆形边框。
示例 2: 叠加图片和背景颜色
Image(systemName: "house.fill")
.resizable()
.frame(width: 100, height: 100)
.background(Color.gray) // 灰色背景
.overlay(
Rectangle() // 叠加半透明矩形
.fill(Color.black.opacity(0.3))
)
这里,Image
视图有一个灰色背景,同时在其上叠加了一个带有 30% 透明度的黑色矩形。
4. background
和 overlay
的对齐
这两个修饰符都允许通过 alignment
参数来控制叠加或背景内容的对齐方式,默认情况下居中对齐。
示例 1: 使用 overlay
并改变对齐方式
Rectangle()
.fill(Color.blue)
.frame(width: 200, height: 200)
.overlay(
Text("Top Left")
.foregroundColor(.white)
.padding(),
alignment: .topLeading // 对齐到左上角
)
此例中,文本 "Top Left" 被叠加到蓝色矩形的左上角。
1-9 VStack HStack ZStack
1. VStack(垂直堆叠)
VStack
会将子视图垂直排列,就像从上到下依次排列的“堆叠”效果。你可以指定对齐方式以及视图之间的间距。
示例 1: 简单的 VStack
VStack {
Text("First Item")
Text("Second Item")
Text("Third Item")
}
在这个示例中,三个文本视图垂直堆叠,每个视图都默认居中对齐。
示例 2: 带有间距的 VStack
VStack(spacing: 20) {
Text("Swift")
Text("UI")
Text("Learning")
}
在这个例子中,每个文本视图之间的间距为 20 点。spacing
参数允许我们控制视图之间的间隔。
示例 3: 左对齐的 VStack
VStack(alignment: .leading) {
Text("Left Aligned 1")
Text("Left Aligned 2")
Text("Left Aligned 3")
}
这里通过 alignment: .leading
将视图左对齐。VStack
默认是居中对齐,但通过 alignment
参数,你可以轻松改变对齐方式,如左对齐或右对齐。
实践场景:
- 表单布局:在创建表单时,使用
VStack
可以轻松垂直排列表单字段。 - 内容区布局:例如展示用户信息的区域,一段文字上方是头像或标题。
2. HStack(水平堆叠)
HStack
则是将子视图水平排列,从左到右依次排列。它在水平布局中非常有用,适合创建横向排列的按钮、标签或其他组件。
示例 1: 简单的 HStack
HStack {
Text("A")
Text("B")
Text("C")
}
在这里,HStack
将三个文本视图水平排列,默认它们会垂直居中对齐。
示例 2: 带有间距的 HStack
HStack(spacing: 30) {
Text("Apple")
Text("Banana")
Text("Cherry")
}
在这个例子中,每个文本视图之间有 30 点的间距。spacing
参数控制水平方向上的间隔。
示例 3: 顶部对齐的 HStack
HStack(alignment: .top) {
Text("Tall Text")
.font(.largeTitle)
Text("Short Text")
.font(.body)
}
这里,我们使用 alignment: .top
来将视图的顶部对齐,即使它们的字体大小不同。
实践场景:
- 按钮栏:例如一组操作按钮水平排列,可以用
HStack
来轻松实现。 - 导航栏:创建水平的导航栏或菜单项时,
HStack
是非常合适的工具。
3. ZStack(层次堆叠)
ZStack
是一个非常强大的容器,它允许将多个视图在同一位置“叠加”,就像是在不同的层次上堆叠视图一样。可以理解为是在 Z 轴方向进行排列,Z 轴是指前后方向。
示例 1: 简单的 ZStack
ZStack {
Rectangle()
.fill(Color.blue)
.frame(width: 100, height: 100)
Text("Top Layer")
.foregroundColor(.white)
}
在这个例子中,蓝色的矩形在下方,文本 Top Layer
被叠加在矩形上面,形成前后层次的效果。
示例 2: 带对齐的 ZStack
ZStack(alignment: .topTrailing) {
Rectangle()
.fill(Color.green)
.frame(width: 200, height: 200)
Text("Overlay Text")
.padding()
.background(Color.black.opacity(0.5))
.foregroundColor(.white)
.cornerRadius(5)
}
这个例子展示了如何使用 alignment
参数来调整叠加视图的位置。文本 Overlay Text
被放置在绿色矩形的右上角,并且有黑色半透明背景。
示例 3: 多层叠加
ZStack {
Circle()
.fill(Color.yellow)
.frame(width: 200, height: 200)
Circle()
.fill(Color.orange)
.frame(width: 150, height: 150)
Text("Middle")
.foregroundColor(.white)
.font(.largeTitle)
}
在这里,我们使用 ZStack
叠加了三个视图:最底层是黄色的圆,中间是橙色的圆,最顶层是白色文本。这种结构可以轻松创建视觉上有层次感的布局。
实践场景:
- 图片和文字叠加:在展示图片时,常常需要叠加文字或按钮,
ZStack
是非常合适的选择。 - 自定义按钮:你可以通过
ZStack
叠加图标、背景颜色和文字来创建自定义按钮。
布局容器 | 描述 | 主要使用场景 |
---|---|---|
VStack | 垂直堆叠子视图,从上到下排列 | 创建表单、列布局、垂直列表 |
HStack | 水平堆叠子视图,从左到右排列 | 创建按钮栏、水平菜单、行布局 |
ZStack | 子视图在 Z 轴上堆叠,前后排列 | 叠加图片、背景和文字,创建层次化布局 |
实践例子(结合使用):
ZStack {
VStack {
Text("SwiftUI Layouts")
.font(.largeTitle)
.padding()
HStack {
Text("VStack Example")
.padding()
.background(Color.green)
.cornerRadius(10)
Text("HStack Example")
.padding()
.background(Color.blue)
.cornerRadius(10)
}
.padding()
}
Text("ZStack Overlay")
.font(.title)
.foregroundColor(.white)
.background(Color.black.opacity(0.5))
.cornerRadius(5)
.padding(.top, 300) // 将叠加的文本放置在ZStack的下部
}
这个例子展示了如何结合使用 VStack
、HStack
和 ZStack
来创建一个复杂的布局。你可以灵活地嵌套这些容器以满足不同的 UI 需求。
1-10 padding()
在 SwiftUI 中,padding
和 spacer
是两个非常有用的布局修饰符,分别用于控制视图之间的空白区域以及视图之间的间隔
1. padding
(内边距)
基本概念:
padding
用于在视图的边缘和其周围的内容之间增加空白区域,也就是内边距。它能够使视图与其边缘保持一定距离,以避免内容紧贴容器或其他视图。
用法:
默认的
padding
:- 调用
.padding()
时,SwiftUI 会在视图的四个方向上添加默认的内边距(通常为 16 个点的空间)。
Text("Hello, SwiftUI!") .padding() .background(Color.blue)
在这个例子中,文本的四周都添加了默认的空白,背景颜色不会紧贴文本。
- 调用
指定具体的
padding
大小:- 你可以通过给
padding()
传递一个数值来控制具体的内边距大小。
Text("Hello, SwiftUI!") .padding(30) .background(Color.blue)
这里我们为文本的四个方向添加了 30 点的空白。
- 你可以通过给
为特定的边添加
padding
:- 你可以选择只为某些特定的边添加内边距,比如左边、右边、顶部或底部。
Text("Hello, SwiftUI!") .padding(.leading, 20) // 只为左边添加 20 点的空白 .padding(.top, 10) // 只为顶部添加 10 点的空白 .background(Color.blue)
这个例子中,左边和顶部有不同大小的空白区域,而其他方向没有空白。
使用多个边组合:
- 你可以同时为多个边指定
padding
。
Text("SwiftUI") .padding([.leading, .trailing], 50) // 左右两边添加 50 点的空白 .background(Color.orange)
- 你可以同时为多个边指定
padding
的作用:
- 调整内容与边缘的距离:确保内容不会紧贴容器的边缘,提供更好的视觉布局。
- 创建视觉平衡:适用于任何布局中需要增加空白来保持内容之间的平衡。
- 常与
background
搭配:通常padding
与background
一起使用,保证背景与内容之间有适当的距离。
1-11 Spacer()
1. spacer
(间隔器)
基本概念:
spacer
是一个用于占据空白空间的视图,它会尽可能地扩展,以填充可用空间。在 HStack
或 VStack
中,spacer
通常用于在子视图之间均匀分布空白。spacer
没有任何视觉内容,它只是起到占位的作用。
用法:
简单的
spacer
示例:- 在
HStack
或VStack
中,spacer
可以自动扩展并填充可用的空白空间。
HStack { Text("Left") Spacer() Text("Right") } .padding()
在这个例子中,
Spacer()
将两个文本视图分开,并占据它们之间的所有可用水平空间。因此,Left
和Right
文本会分别靠左和靠右对齐。- 在
多个
spacer
的使用:- 你可以在一个布局中使用多个
spacer
来均匀分布空白。
HStack { Text("Item 1") Spacer() Text("Item 2") Spacer() Text("Item 3") } .padding()
这里
spacer
被插入到每个文本视图之间,它们会在水平方向上均匀分布空白,使每个文本视图等距排列。- 你可以在一个布局中使用多个
自定义
spacer
大小:- 你还可以为
spacer
指定固定的最小尺寸,以控制其占用的空间大小。
HStack { Text("Start") Spacer(minLength: 50) Text("Middle") Spacer(minLength: 100) Text("End") }
在这个例子中,第一个
spacer
至少占据 50 点的空间,第二个spacer
至少占据 100 点的空间。Spacer
的minLength
参数定义了它的最小长度。- 你还可以为
spacer
的作用:
- 灵活布局:
spacer
是一种非常灵活的布局工具,可以让你的视图在容器中自动扩展并填充空白。 - 均匀分布子视图:它常用于
HStack
或VStack
中,让子视图之间的空白均匀分布。 - 自适应布局:
spacer
可以根据可用空间自动调整大小,适合响应式布局。
2. padding
和 spacer
的组合使用
padding
和 spacer
常常一起使用,以帮助创建更复杂和灵活的布局。
例子 1: padding
和 spacer
结合使用
HStack {
Text("Left")
.padding(.leading, 20) // 为左侧文本添加内边距
Spacer()
Text("Right")
.padding(.trailing, 20) // 为右侧文本添加内边距
}
.background(Color.gray)
在这个例子中,Spacer()
占据了左右文本之间的空白空间,而 padding()
确保左右两侧的文本与视图的边缘保持适当的距离。
例子 2: 垂直和水平布局中的 padding
和 spacer
VStack {
Text("Top")
Spacer()
Text("Middle")
.padding(.horizontal, 50) // 为中间文本添加左右边距
Spacer()
Text("Bottom")
}
.background(Color.green)
在这个 VStack
中,Spacer()
占据了 Top
、Middle
和 Bottom
文本之间的空白区域。中间的文本还通过 padding
在水平(左右)方向上保持了一定的空白距离。
1-12 init() && enums
下面的代码等同于上面,其实两者同样的效果
可以自定义初始化变量
可以配合enum一起使用
例子 1:根据不同的交通工具类型设置背景颜色和图标
在这个例子中,我们使用 TransportMode
枚举来决定背景颜色、图标和标题。
import SwiftUI
struct TransportView: View {
let backgroundColor: Color
let icon: String
let title: String
init(mode: TransportMode) {
switch mode {
case .car:
self.title = "Car"
self.icon = "🚗"
self.backgroundColor = .blue
case .bike:
self.title = "Bike"
self.icon = "🚲"
self.backgroundColor = .green
case .bus:
self.title = "Bus"
self.icon = "🚌"
self.backgroundColor = .yellow
}
}
enum TransportMode {
case car
case bike
case bus
}
var body: some View {
VStack(spacing: 12) {
Text(icon)
.font(.largeTitle)
Text(title)
.font(.headline)
}
.frame(width: 150, height: 150)
.background(backgroundColor)
.cornerRadius(10)
}
}
#Preview {
HStack {
TransportView(mode: .car)
TransportView(mode: .bike)
TransportView(mode: .bus)
}
}
解释:
- 枚举的使用:定义了
TransportMode
枚举,包含car
、bike
、bus
三种交通方式。 - 初始化器中的
switch
:在初始化器中,通过switch
语句根据不同的交通工具设置背景颜色、图标和标题。 - 效果展示:在
#Preview
中,我们可以同时显示三种不同的交通工具,每个视图都有不同的颜色、图标和标题。
例子 2:根据用户角色设置视图的显示内容
在这个例子中,我们使用枚举 UserRole
来决定用户角色对应的 UI 样式。
import SwiftUI
struct UserRoleView: View {
let backgroundColor: Color
let roleDescription: String
init(role: UserRole) {
switch role {
case .admin:
self.roleDescription = "Administrator"
self.backgroundColor = .red
case .user:
self.roleDescription = "Regular User"
self.backgroundColor = .blue
case .guest:
self.roleDescription = "Guest User"
self.backgroundColor = .gray
}
}
enum UserRole {
case admin
case user
case guest
}
var body: some View {
VStack(spacing: 12) {
Text(roleDescription)
.font(.headline)
.foregroundColor(.white)
}
.frame(width: 200, height: 100)
.background(backgroundColor)
.cornerRadius(10)
}
}
#Preview {
VStack {
UserRoleView(role: .admin)
UserRoleView(role: .user)
UserRoleView(role: .guest)
}
}
解释:
- 枚举的使用:定义了
UserRole
枚举,包含admin
、user
和guest
三个角色。 - 初始化器中的
switch
:根据传入的role
参数,视图初始化器设置对应的背景颜色和角色描述。 - 效果展示:预览中,我们展示了三个不同角色的视图,分别是管理员、普通用户和访客。
例子 3:根据天气状况显示相应的视图
在这个例子中,使用 WeatherCondition
枚举来显示不同天气情况对应的图标和背景颜色。
import SwiftUI
struct WeatherView: View {
let backgroundColor: Color
let icon: String
let conditionText: String
init(condition: WeatherCondition) {
switch condition {
case .sunny:
self.conditionText = "Sunny"
self.icon = "☀️"
self.backgroundColor = .yellow
case .cloudy:
self.conditionText = "Cloudy"
self.icon = "☁️"
self.backgroundColor = .gray
case .rainy:
self.conditionText = "Rainy"
self.icon = "🌧"
self.backgroundColor = .blue
}
}
enum WeatherCondition {
case sunny
case cloudy
case rainy
}
var body: some View {
VStack(spacing: 12) {
Text(icon)
.font(.largeTitle)
Text(conditionText)
.font(.headline)
}
.frame(width: 150, height: 150)
.background(backgroundColor)
.cornerRadius(10)
}
}
#Preview {
HStack {
WeatherView(condition: .sunny)
WeatherView(condition: .cloudy)
WeatherView(condition: .rainy)
}
}
解释:
- 枚举的使用:定义了
WeatherCondition
枚举,包含三种天气情况:sunny
、cloudy
和rainy
。 - 初始化器的实现:通过
switch
语句,视图根据天气状况设置相应的背景颜色、天气图标和文本描述。 - 效果展示:预览中,展示了三种天气情况:晴天、阴天和雨天,每种情况的图标和背景色不同。
1-13 ForEach()
例子 1:简单的数字列表
import SwiftUI
struct NumberListView: View {
var body: some View {
VStack {
ForEach(1..<5) { number in
Text("Number \(number)")
}
}
}
}
#Preview {
NumberListView()
}
说明:
ForEach(1..<5)
表示循环遍历1
到4
(不包括5
)。- 每次循环会生成一个
Text
视图,显示 "Number" 和数字。
例子 2:根据字符串数组生成视图
import SwiftUI
struct StringListView: View {
let fruits = ["Apple", "Banana", "Orange", "Grape"]
var body: some View {
VStack {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
.font(.headline)
}
}
}
}
#Preview {
StringListView()
}
说明:
fruits
是一个字符串数组。ForEach(fruits, id: \.self)
遍历数组中的每个元素,并且使用id: \.self
来确保每个元素具有唯一性(self
表示每个字符串本身)。- 每个
fruit
都会生成一个Text
视图,显示水果名称。
例子 3:使用 enumerated()
同时获取索引和值
import SwiftUI
struct EnumeratedListView: View {
let animals = ["Dog", "Cat", "Bird", "Fish"]
var body: some View {
VStack {
ForEach(Array(animals.enumerated()), id: \.element) { index, animal in
Text("\(index + 1). \(animal)")
.font(.subheadline)
}
}
}
}
#Preview {
EnumeratedListView()
}
说明:
- 使用
enumerated()
来遍历数组并获得每个元素的索引和内容。 ForEach(Array(animals.enumerated()), id: \.element)
同时获取了索引和元素值,并确保元素唯一性通过id: \.element
。
例子 4:动态生成自定义视图
import SwiftUI
struct CustomItemView: View {
let title: String
let color: Color
var body: some View {
Text(title)
.padding()
.background(color)
.cornerRadius(10)
.foregroundColor(.white)
}
}
struct CustomViewList: View {
let items = [
("Item 1", Color.red),
("Item 2", Color.green),
("Item 3", Color.blue)
]
var body: some View {
VStack {
ForEach(items, id: \.0) { item in
CustomItemView(title: item.0, color: item.1)
}
}
}
}
#Preview {
CustomViewList()
}
说明:
CustomItemView
是一个自定义的视图,接收title
和color
作为参数。ForEach(items, id: \.0)
遍历元组数组,id: \.0
表示元组的第一个元素是唯一标识符。- 每次循环都会创建一个
CustomItemView
,并传入不同的标题和颜色。
例子 5:根据数据模型动态生成视图
import SwiftUI
struct Person: Identifiable {
let id = UUID()
let name: String
let age: Int
}
struct PersonListView: View {
let people = [
Person(name: "Alice", age: 24),
Person(name: "Bob", age: 30),
Person(name: "Charlie", age: 22)
]
var body: some View {
VStack {
ForEach(people) { person in
Text("\(person.name), Age: \(person.age)")
.padding()
}
}
}
}
#Preview {
PersonListView()
}
说明:
Person
是一个模型,它遵循了Identifiable
协议,因此每个Person
实例都有一个唯一的id
。ForEach(people)
会自动识别people
数组中的每个Person
的唯一标识符(id
),并生成对应的Text
视图。
例子 6:嵌套使用 ForEach
import SwiftUI
struct NestedForEachView: View {
let rows = ["Row 1", "Row 2", "Row 3"]
let columns = ["Col A", "Col B", "Col C"]
var body: some View {
VStack {
ForEach(rows, id: \.self) { row in
HStack {
ForEach(columns, id: \.self) { column in
Text("\(row) - \(column)")
.padding()
.border(Color.black, width: 1)
}
}
}
}
}
}
#Preview {
NestedForEachView()
}
说明:
- 通过嵌套
ForEach
,可以生成一个网格布局。 - 每行(
row
)中使用HStack
,再嵌套一个ForEach
来生成每列(column
)的视图。
1-14 ScrollView()
我们放了四个高度为300的矩形,但是没办法滚动
现在可以上下滚动了
ScrollView
是 SwiftUI 中的一个容器,用于显示可以滚动的内容。它允许我们在垂直或水平方向上滚动显示的内容,并且可以隐藏或显示滚动条。你可以根据不同的布局需求使用它,例如创建横向滚动视图、纵向滚动视图,甚至是嵌套滚动视图。
例子 1:简单的垂直滚动视图
这是一个最基本的 ScrollView
用法,内容会在垂直方向上滚动。
import SwiftUI
struct VerticalScrollViewExample: View {
var body: some View {
ScrollView {
VStack(spacing: 20) {
ForEach(0..<10) { index in
Rectangle()
.fill(Color.blue)
.frame(height: 150)
.overlay(
Text("Item \(index)")
.font(.headline)
.foregroundColor(.white)
)
}
}
.padding()
}
}
}
#Preview {
VerticalScrollViewExample()
}
说明:
ScrollView
的默认方向是垂直方向。- 使用
VStack
来垂直排列矩形块,每个矩形有不同的高度,超出屏幕的内容可以滚动显示。
例子 2:水平滚动视图
这段代码展示了一个水平滚动视图,矩形会在水平方向滚动。
import SwiftUI
struct HorizontalScrollViewExample: View {
var body: some View {
ScrollView(.horizontal) {
HStack {
ForEach(0..<20) { index in
Rectangle()
.fill(Color.red)
.frame(width: 150, height: 150)
.overlay(
Text("Box \(index)")
.font(.headline)
.foregroundColor(.white)
)
.padding(5)
}
}
}
}
}
#Preview {
HorizontalScrollViewExample()
}
说明:
ScrollView(.horizontal)
指定滚动方向为水平方向。- 使用
HStack
来水平排列矩形。 - 可以为每个矩形添加 padding,使得它们之间有一些间隔。
例子 3:隐藏滚动条
有时候你可能不希望滚动条显示出来,你可以通过设置 showsIndicators
为 false
来隐藏它。
import SwiftUI
struct HiddenIndicatorScrollViewExample: View {
var body: some View {
ScrollView(showsIndicators: false) {
VStack(spacing: 10) {
ForEach(0..<30) { index in
Circle()
.fill(Color.purple)
.frame(height: 100)
.overlay(
Text("Circle \(index)")
.font(.headline)
.foregroundColor(.white)
)
}
}
.padding()
}
}
}
#Preview {
HiddenIndicatorScrollViewExample()
}
说明:
ScrollView(showsIndicators: false)
会隐藏滚动条,即使内容超出显示范围。- 使用
VStack
来排列多个圆形的视图。
例子 4:嵌套滚动视图
你可以将垂直和水平滚动的 ScrollView
嵌套使用,以实现复杂的布局。
import SwiftUI
struct NestedScrollViewExample: View {
var body: some View {
ScrollView {
VStack {
ForEach(0..<5) { index in
ScrollView(.horizontal) {
HStack {
ForEach(0..<10) { subIndex in
RoundedRectangle(cornerRadius: 10)
.fill(Color.blue)
.frame(width: 120, height: 120)
.overlay(
Text("Item \(index)-\(subIndex)")
.font(.caption)
.foregroundColor(.white)
)
.padding(5)
}
}
}
.frame(height: 150)
}
}
.padding()
}
}
}
#Preview {
NestedScrollViewExample()
}
说明:
- 外部是一个垂直滚动的
ScrollView
,内部包含多个水平滚动的ScrollView
。 - 通过这种方式可以实现复杂的滚动效果,比如类似于表格的布局。
例子 5:ScrollView 与 LazyVStack 的结合
当有大量子视图时,使用 LazyVStack
会更有效率,因为它只会加载当前可见的子视图,减少内存占用。
import SwiftUI
struct LazyScrollViewExample: View {
var body: some View {
ScrollView {
LazyVStack(spacing: 20) {
ForEach(0..<100) { index in
RoundedRectangle(cornerRadius: 10)
.fill(Color.orange)
.frame(height: 100)
.overlay(
Text("Lazy Item \(index)")
.font(.title2)
.foregroundColor(.white)
)
}
}
.padding()
}
}
}
#Preview {
LazyScrollViewExample()
}
说明:
- 使用
LazyVStack
而不是VStack
来实现懒加载的效果,适合大量数据的展示。 - 与
ScrollView
配合使用,能够减少不必要的视图创建,提升性能。
例子 6:ScrollView 和 GeometryReader 结合
结合 GeometryReader
可以让你获取滚动视图的偏移量,实现一些动画或其他效果。
import SwiftUI
struct ScrollOffsetViewExample: View {
@State private var offsetY: CGFloat = 0
var body: some View {
ScrollView {
VStack {
GeometryReader { geometry in
Color.clear
.onAppear {
self.offsetY = geometry.frame(in: .global).minY
}
.onChange(of: geometry.frame(in: .global).minY) { newOffset in
self.offsetY = newOffset
}
}
.frame(height: 0) // Invisible GeometryReader
ForEach(0..<20) { index in
RoundedRectangle(cornerRadius: 10)
.fill(Color.pink)
.frame(height: 150)
.overlay(
Text("Item \(index)")
.font(.title)
.foregroundColor(.white)
)
.padding()
}
}
}
.overlay(
Text("Offset: \(Int(offsetY))")
.padding()
.background(Color.black.opacity(0.5))
.foregroundColor(.white)
.cornerRadius(10)
.padding(),
alignment: .top
)
}
}
#Preview {
ScrollOffsetViewExample()
}
说明:
- 使用
GeometryReader
可以监测滚动视图的偏移量。 offsetY
会随着用户的滚动而更新,可以用于创建滚动动画或其他交互效果。
1-15 LazyVGrid()
LazyVGrid
是 SwiftUI 中的一种布局组件,用于在垂直方向上以网格形式排列子视图。与传统的 VGrid
不同,LazyVGrid
会对子视图进行懒加载,只加载当前可见区域的视图,极大地提高了性能,尤其是在有大量视图时。
1. 基本结构
LazyVGrid
是垂直网格,它需要指定网格的列数或列布局。使用时,我们需要定义一个 GridItem
数组,指定每一列的宽度和布局方式,然后将这个数组传递给 LazyVGrid
。
2. 主要参数
- columns: 定义网格的列布局,使用
GridItem
数组来配置。 - spacing: 设置网格项之间的间距。
- pinnedViews: 可以固定("pinned")某些视图,如头部或尾部(
sectionHeaders
,sectionFooters
)。
3. 基本用法
例子 1:创建一个两列的 LazyVGrid
import SwiftUI
struct LazyVGridExample: View {
let data = Array(1...20)
// 定义两列的布局,.flexible 表示列的宽度是自适应的
let columns = [
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(data, id: \.self) { item in
Rectangle()
.fill(Color.blue)
.frame(height: 100)
.overlay(
Text("Item \(item)")
.foregroundColor(.white)
)
}
}
.padding()
}
}
}
#Preview {
LazyVGridExample()
}
说明:
GridItem(.flexible())
表示每一列的宽度是可变的,均匀分布。- 使用
ScrollView
包裹LazyVGrid
,因为当内容超出屏幕时,可以滚动。 ForEach
用来生成 20 个矩形,并且将它们填充到网格中。
4. 使用 GridItem
布局
GridItem
有三种布局方式:
- .fixed: 固定宽度的列。
- .flexible: 灵活宽度的列,宽度根据可用空间自适应。
- .adaptive: 自适应列,列的数量和宽度会根据可用空间自动调整。
例子 2:使用不同的 GridItem
布局
import SwiftUI
struct GridItemLayoutExample: View {
let data = Array(1...20)
// 定义不同类型的列布局
let columns = [
GridItem(.fixed(100)), // 固定宽度100
GridItem(.flexible()), // 灵活宽度
GridItem(.adaptive(minimum: 50)) // 自适应宽度,最小宽度50
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(data, id: \.self) { item in
Rectangle()
.fill(Color.purple)
.frame(height: 100)
.overlay(
Text("Item \(item)")
.foregroundColor(.white)
)
}
}
.padding()
}
}
}
#Preview {
GridItemLayoutExample()
}
说明:
.fixed(100)
: 第一列的宽度是固定的 100 点。.flexible()
: 第二列的宽度是灵活的,会根据剩余空间动态调整。.adaptive(minimum: 50)
: 第三列的宽度是自适应的,最小宽度为 50 点,根据屏幕空间可以放入多个这样的列。
5. LazyVGrid
的性能优势
LazyVGrid
只有在需要显示的时候才会加载视图,因此即使有大量的子视图,也不会一次性创建所有子视图。
例子 3:大量数据的 LazyVGrid
import SwiftUI
struct LargeDataGridExample: View {
let data = Array(1...1000)
// 两列布局
let columns = [
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 15) {
ForEach(data, id: \.self) { item in
RoundedRectangle(cornerRadius: 10)
.fill(Color.orange)
.frame(height: 100)
.overlay(
Text("Item \(item)")
.foregroundColor(.white)
)
}
}
.padding()
}
}
}
#Preview {
LargeDataGridExample()
}
说明:
- 即使有 1000 个元素,
LazyVGrid
只会加载当前屏幕上需要显示的视图,其余部分会在滚动到时再懒加载,性能更加高效。 - 适合用在长列表或大网格中。
6. 自定义列的宽度与间距
通过调整 GridItem
可以控制列的宽度和子视图之间的间距。
例子 4:自定义列宽度与间距
import SwiftUI
struct CustomGridSpacingExample: View {
let data = Array(1...15)
// 自定义每列的宽度和间距
let columns = [
GridItem(.fixed(80), spacing: 10), // 第一列固定宽度80
GridItem(.flexible(), spacing: 30), // 第二列灵活宽度,列间距30
GridItem(.adaptive(minimum: 70)) // 第三列自适应,最小宽度70
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(data, id: \.self) { item in
RoundedRectangle(cornerRadius: 10)
.fill(Color.green)
.frame(height: 80)
.overlay(
Text("Item \(item)")
.foregroundColor(.white)
)
}
}
.padding()
}
}
}
#Preview {
CustomGridSpacingExample()
}
说明:
- 第一列宽度固定为 80,列间距为 10 点。
- 第二列宽度灵活,列间距为 30 点。
- 第三列是自适应列,最小宽度为 70 点,可以根据可用空间动态调整列数。
7. 使用 LazyVGrid
配合 Section
你可以使用 LazyVGrid
来创建带有分区(Section
)的网格布局,类似于集合视图(UICollectionView)的分组效果。
例子 5:LazyVGrid
与分区 (Section)
import SwiftUI
struct SectionedGridExample: View {
let fruits = ["Apple", "Banana", "Orange", "Grape"]
let vegetables = ["Carrot", "Potato", "Pepper", "Broccoli"]
let columns = [
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
Section(header: Text("Fruits").font(.title)) {
ForEach(fruits, id: \.self) { fruit in
RoundedRectangle(cornerRadius: 10)
.fill(Color.red)
.frame(height: 100)
.overlay(
Text(fruit)
.foregroundColor(.white)
.font(.headline)
)
}
}
Section(header: Text("Vegetables").font(.title)) {
ForEach(vegetables, id: \.self) { vegetable in
RoundedRectangle(cornerRadius: 10)
.fill(Color.green)
.frame(height: 100)
.overlay(
Text(vegetable)
.foregroundColor(.white)
.font(.headline)
)
}
}
}
.padding()
}
}
}
#Preview {
SectionedGridExample()
}
说明:
- 使用
Section
将网格中的数据分组(这里有水果和蔬菜两组)。 - 每个
Section
有自己的头部视图(header
),例如 "Fruits" 和 "Vegetables"。 - 这类似于表格视图或集合视图中的分区效果。
1-16 SafeArea
edgesIgnoringSafeArea
和 ignoresSafeArea
是 SwiftUI 中用于处理安全区域的修饰符。它们的作用是让视图决定是否忽略屏幕的安全区域(例如状态栏、导航栏、底部工具栏等),从而影响视图的布局和显示。
1. edgesIgnoringSafeArea
介绍
edgesIgnoringSafeArea
是一个修饰符,允许你指定某些边缘应该忽略安全区域。这意味着视图将扩展到设备的边缘,覆盖状态栏或导航栏等。
用法
struct ContentView: View {
var body: some View {
Color.green
.edgesIgnoringSafeArea(.all) // 忽略所有安全区域
}
}
参数
- .top: 忽略顶部的安全区域(状态栏)。
- .bottom: 忽略底部的安全区域(如底部工具栏)。
- .leading: 忽略左侧的安全区域(如左侧边缘)。
- .trailing: 忽略右侧的安全区域(如右侧边缘)。
- .all: 忽略所有安全区域。
示例
以下示例演示如何使用 edgesIgnoringSafeArea
来让背景颜色覆盖整个屏幕,包括状态栏和底部工具栏:
import SwiftUI
struct ExampleView: View {
var body: some View {
Color.blue
.edgesIgnoringSafeArea(.top) // 忽略顶部安全区域
}
}
#Preview {
ExampleView()
}
2. ignoresSafeArea
介绍
ignoresSafeArea
是在 SwiftUI 2.0 中引入的,具有更现代的 API 设计。它可以更灵活地指定视图忽略安全区域的行为。
用法
struct ContentView: View {
var body: some View {
Color.red
.ignoresSafeArea() // 忽略所有安全区域
}
}
参数
ignoresSafeArea
允许你指定忽略安全区域的类型,例如忽略所有边缘或仅特定的边缘:
- .all: 忽略所有安全区域。
- .container: 忽略视图的容器安全区域。
- .keyboard: 忽略键盘的安全区域(适用于需要输入的视图)。
示例
以下示例演示如何使用 ignoresSafeArea
:
import SwiftUI
struct ExampleView: View {
var body: some View {
Color.orange
.ignoresSafeArea(.all) // 忽略所有安全区域
}
}
#Preview {
ExampleView()
}
对比
特性 | edgesIgnoringSafeArea | ignoresSafeArea |
---|---|---|
适用版本 | SwiftUI 1.0 | SwiftUI 2.0 |
使用方式 | 使用 .edgesIgnoringSafeArea() | 使用 .ignoresSafeArea() |
忽略边缘 | 可以单独指定边缘(如 .top , .bottom ) | 可以指定所有或特定的边缘,但语法更简单 |
设计风格 | 更传统 | 更现代化 |
使用场景
- 全屏背景: 当你希望某个视图(如背景色、图像)覆盖整个屏幕时,可以使用这两个修饰符。
- 布局管理: 在设计具有复杂布局的应用时,确保视图正确适应安全区域。
- 键盘处理: 在处理文本输入时,使用
ignoresSafeArea(.keyboard)
来确保键盘弹出时视图布局不会被影响。
1-17 Button()
//
// BUuttons.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/5.
//
import SwiftUI
// 定义一个视图结构体 BUuttons
struct BUuttons: View {
// 使用 @State 属性包装器来声明一个状态变量 title,初始值为 "This is my title"
@State var title: String = "This is my title"
var body: some View {
// 创建一个垂直堆叠的视图,视图之间的间距为 20
VStack(spacing: 20) {
// 显示当前的标题文本
Text(title)
// 创建一个按钮,标题为 "PRESS ME"
Button("PRESS ME") {
// 当按钮被点击时,更新 title 的值
self.title = "BUTTON WAS PRESSED"
}
// 设置按钮的强调颜色为红色
.accentColor(.red)
// 创建另一个按钮
Button {
// 当按钮被点击时,更新 title 的值
self.title = "BUTTON N2 WAS pressed"
} label: {
// 定义按钮的标签,这里是一个 Text 视图
Text("点击我")
.foregroundColor(.white) // 设置文本颜色为白色
.font(.title) // 设置字体为标题字体
.fontWeight(.bold) // 设置字体为粗体
.frame(width: 150, height: 60) // 设置按钮的宽度和高度
.background(
Color.green // 设置按钮背景颜色为绿色
)
.shadow(radius: 10) // 设置按钮的阴影半径为 10
.cornerRadius(15) // 设置按钮的圆角半径为 15
}
}
}
}
// 提供一个预览视图
#Preview {
BUuttons()
}
在 SwiftUI 中,Button
是一种非常重要的 UI 组件,用于处理用户的交互。用户点击按钮后,会触发某种动作或事件。以下是关于 Button
的详细说明,包括其基本用法、样式、交互效果等。
1. 基本结构
Button
的基本用法很简单,通常只需要提供一个标签和一个动作。标签可以是文本、图像或自定义视图。
示例 1:基本的按钮
import SwiftUI
struct BasicButtonExample: View {
var body: some View {
Button(action: {
print("Button was tapped!")
}) {
Text("Click Me")
.font(.title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
}
#Preview {
BasicButtonExample()
}
2. Button 的结构
Button
的构造函数有两个主要部分:
- action: 一个闭包(closure),当按钮被点击时会执行的代码。
- label: 你想在按钮上显示的内容,可以是文本、图像或其他 SwiftUI 视图。
3. Button 的样式
SwiftUI 提供了多种内置的按钮样式,开发者也可以自定义按钮的样式。下面是一些常见的样式:
示例 2:使用不同的按钮样式
import SwiftUI
struct ButtonStyleExample: View {
var body: some View {
VStack(spacing: 20) {
Button("Default Style") {
print("Default Button Tapped")
}
Button("Bordered Style") {
print("Bordered Button Tapped")
}
.buttonStyle(BorderedButtonStyle())
Button("Plain Style") {
print("Plain Button Tapped")
}
.buttonStyle(PlainButtonStyle())
}
.padding()
}
}
#Preview {
ButtonStyleExample()
}
常见按钮样式
- DefaultButtonStyle: 默认样式,通常包含圆角背景。
- BorderedButtonStyle: 具有边框的按钮样式。
- PlainButtonStyle: 纯文本样式,没有背景或边框。
4. 自定义按钮样式
你可以通过实现 ButtonStyle
协议来自定义按钮的样式。以下是一个简单的自定义按钮样式示例:
示例 3:自定义按钮样式
import SwiftUI
struct CustomButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding()
.background(configuration.isPressed ? Color.red : Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.animation(.spring(), value: configuration.isPressed)
}
}
struct CustomButtonExample: View {
var body: some View {
Button("Tap Me") {
print("Custom Button Tapped")
}
.buttonStyle(CustomButtonStyle())
}
}
#Preview {
CustomButtonExample()
}
5. Button 的交互效果
在按钮样式中,你可以使用 configuration
属性来检测按钮的状态,例如是否被按下,并根据状态调整样式或动画。
- configuration.isPressed: 用于检查按钮是否被按下,你可以在这个状态下改变按钮的外观。
6. 适应性按钮
SwiftUI 中的按钮会自动适应其内容,并根据平台和环境的不同(例如,暗色模式和亮色模式)进行调整。
示例 4:适应性按钮
import SwiftUI
struct AdaptiveButtonExample: View {
var body: some View {
Button(action: {
print("Adaptive Button Tapped!")
}) {
HStack {
Image(systemName: "hand.point.right.fill")
Text("Press Here")
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
}
#Preview {
AdaptiveButtonExample()
}
7. 按钮的可访问性
SwiftUI 中的按钮支持无障碍(accessibility),可以为按钮添加无障碍标签、提示和其他属性,以提高可访问性。
示例 5:无障碍按钮
import SwiftUI
struct AccessibleButtonExample: View {
var body: some View {
Button(action: {
print("Accessible Button Tapped!")
}) {
Text("Tap Me")
}
.accessibility(label: Text("Tap Me Button"))
.accessibility(hint: Text("Tap this button to perform an action"))
}
}
#Preview {
AccessibleButtonExample()
}
8. 禁用按钮
你可以通过 disabled(_:)
修饰符来禁用按钮,这样按钮将无法被点击。
示例 6:禁用按钮
import SwiftUI
struct DisabledButtonExample: View {
@State private var isButtonDisabled = true
var body: some View {
VStack {
Button("Click Me") {
print("Button Tapped!")
}
.disabled(isButtonDisabled) // 禁用按钮
Toggle("Enable Button", isOn: $isButtonDisabled) // 切换启用状态
.padding()
}
.padding()
}
}
#Preview {
DisabledButtonExample()
}
9. 在列表中使用按钮
按钮可以与列表(List
)结合使用,以创建具有交互性的列表项。
示例 7:列表中的按钮
import SwiftUI
struct ListButtonExample: View {
let items = ["Item 1", "Item 2", "Item 3"]
var body: some View {
List(items, id: \.self) { item in
Button(action: {
print("\(item) tapped!")
}) {
Text(item)
}
}
}
}
#Preview {
ListButtonExample()
}
10. 按钮的动画效果
你可以为按钮添加动画效果,使用户体验更流畅。
示例 8:按钮动画效果
import SwiftUI
struct AnimatedButtonExample: View {
@State private var scale: CGFloat = 1.0
var body: some View {
Button(action: {
withAnimation {
scale += 0.1 // 增加缩放比例
}
}) {
Text("Tap to Animate")
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)
.scaleEffect(scale) // 应用缩放效果
}
}
}
#Preview {
AnimatedButtonExample()
}
- 基本用法:
Button
是处理用户交互的重要组件。 - 样式和自定义:SwiftUI 提供了多种内置按钮样式,开发者也可以通过实现
ButtonStyle
协议来自定义按钮样式。 - 交互效果:按钮可以根据用户的交互状态(例如按下)动态改变外观。
- 可访问性:通过添加无障碍标签,可以提高按钮的可访问性。
- 禁用按钮:使用
disabled
修饰符可以轻松禁用按钮。 - 与列表结合:按钮可以嵌套在列表项中使用。
- 动画效果:按钮可以添加动画效果,以增强用户体验。
1-18 @State
@State
是 SwiftUI 中用于管理视图状态的属性包装器。它用于声明一个可以在视图中发生变化的状态变量。当这个状态变量的值发生改变时,SwiftUI 会自动重新渲染使用该状态变量的视图。
1. @State
的基本作用
- 状态管理:
@State
使得视图能够跟踪和管理内部状态。当状态变化时,视图会更新以反映新状态。 - 局部状态:
@State
主要用于视图内部的局部状态,适合用于简单的、与特定视图相关的状态管理。
2. 使用 @State
的基本语法
@State var variableName: DataType = initialValue
variableName
: 状态变量的名称。DataType
: 状态变量的数据类型。initialValue
: 状态变量的初始值。
3. 示例
示例 1:计数器
这个示例演示了如何使用 @State
来创建一个简单的计数器应用。
import SwiftUI
struct CounterView: View {
@State private var count = 0 // 声明一个状态变量 count,初始值为 0
var body: some View {
VStack {
Text("Current count: \(count)") // 显示当前计数
.font(.largeTitle)
Button("Increment") {
count += 1 // 点击按钮时增加计数
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.padding()
}
}
#Preview {
CounterView()
}
@State
声明的count
变量用于存储当前计数值。- 当用户点击 "Increment" 按钮时,
count
的值会增加,SwiftUI 会自动更新显示的文本。
示例 2:切换按钮状态
在这个示例中,使用 @State
来实现一个简单的开关按钮。
import SwiftUI
struct ToggleButtonView: View {
@State private var isOn = false // 状态变量 isOn,初始值为 false
var body: some View {
VStack {
Text(isOn ? "Switch is ON" : "Switch is OFF") // 根据状态显示不同文本
Button(action: {
isOn.toggle() // 切换状态
}) {
Text("Toggle Switch") // 按钮文本
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.padding()
}
}
#Preview {
ToggleButtonView()
}
isOn
是一个状态变量,用于存储开关的当前状态(开或关)。- 按钮点击时,调用
isOn.toggle()
方法切换状态,文本会根据当前状态更新。
示例 3:文本输入框
在这个示例中,使用 @State
来管理文本输入框中的文本。
import SwiftUI
struct TextInputView: View {
@State private var userInput = "" // 状态变量 userInput,初始值为空字符串
var body: some View {
VStack {
TextField("Enter something", text: $userInput) // 文本输入框,绑定到 userInput
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Text("You entered: \(userInput)") // 显示用户输入的文本
.font(.title)
}
.padding()
}
}
#Preview {
TextInputView()
}
userInput
是一个状态变量,用于存储文本输入框中的文本。- 文本输入框通过
$userInput
绑定到状态变量,任何输入都会自动更新userInput
的值,并重新渲染显示的文本。
- @State
的重要性**: 在 SwiftUI 中,
@State` 是管理视图状态的核心工具。它使得视图能够响应用户交互并动态更新。 - 局部状态: 适合用于视图内部的状态管理,保持简洁和易于理解。
- 自动更新: 任何对
@State
变量的修改都会自动触发视图更新,开发者不需要手动管理更新。
1-19 Extract Functions & Views
有时候主视图里面代码非常长,这个时候我们就可以把功能性的函数或者一些视图抽出来
import SwiftUI
// 定义一个名为 ExtractedFunctions 的视图结构体
struct ExtractedFunctions: View {
// 使用 @State 属性包装器声明一个状态变量 backgroundColor,初始值为粉色
@State var backgroundColor: Color = Color.pink
// 视图的主体
var body: some View {
ZStack {
// 设置背景颜色并忽略安全区域(填充整个屏幕)
backgroundColor.ignoresSafeArea(.all)
// 调用提取的内容层视图
contentLayer
}
}
// 定义一个计算属性,返回内容层的视图
var contentLayer: some View {
VStack {
// 显示标题文本
Text("title")
.font(.largeTitle) // 设置字体大小为大标题
// 创建一个按钮
Button {
buttonPressed() // 按钮点击时调用 buttonPressed 函数
} label: {
// 按钮的标签
Text("Press ME")
.font(.headline) // 设置字体为标题字体
.foregroundColor(.white) // 设置文本颜色为白色
.padding() // 添加内边距
.background(Color.black) // 设置背景颜色为黑色
.cornerRadius(10) // 设置圆角半径为 10
}
}
}
// 定义一个函数,当按钮被按下时调用
func buttonPressed() {
// 修改状态变量 backgroundColor 的值为黄色
self.backgroundColor = .yellow
}
}
// 提供一个预览视图
#Preview {
ExtractedFunctions() // 预览 ExtractedFunctions 视图
}
结构体和视图:
struct ExtractedFunctions: View
: 定义一个新的视图结构体ExtractedFunctions
,并遵循View
协议。
状态管理:
@State var backgroundColor: Color = Color.pink
: 使用@State
属性包装器声明一个状态变量backgroundColor
,初始值为粉色。这使得当backgroundColor
发生变化时,视图会自动更新。
布局:
ZStack
: 创建一个 Z 轴堆叠的视图,其中背景和内容将重叠。backgroundColor.ignoresSafeArea(.all)
: 设置背景颜色,并忽略安全区域,使其填充整个屏幕,包括状态栏和底部工具栏。
提取的内容层:
var contentLayer: some View
: 定义一个计算属性contentLayer
,返回内容部分的视图。VStack
: 在垂直方向上堆叠视图元素。
文本视图:
Text("title")
: 显示标题文本,字体设置为大标题。
按钮:
- 使用
Button
组件创建一个按钮,点击时会调用buttonPressed()
函数。 - 按钮的标签由一个
Text
视图构成,包含样式设置(颜色、字体、内边距、背景颜色和圆角)。
按钮的功能:
func buttonPressed()
: 当按钮被点击时调用的函数,更新backgroundColor
的值为黄色,从而触发视图的更新。
预览:
#Preview
: 提供了视图的预览,便于在 Xcode 的设计界面中实时查看效果。
1-20 Extract SubViews
import SwiftUI
// 定义一个名为 ExtractedFunctions 的视图结构体
struct ExtractedFunctions: View {
// 使用 @State 属性包装器声明一个状态变量 backgroundColor,初始值为粉色
@State var backgroundColor: Color = Color.pink
// 视图的主体
var body: some View {
ZStack {
// 设置背景颜色为橙色,并忽略安全区域(填充整个屏幕)
Color.orange.ignoresSafeArea(.all)
// 使用 HStack 水平排列 MyItem 视图
HStack {
// 创建三个 MyItem 视图,每个视图显示不同的水果和数量
MyItem(count: 10, fruit: "Orange")
MyItem(count: 20, fruit: "Apples")
MyItem(count: 30, fruit: "Banana")
}
}
}
}
// 提供一个预览视图
#Preview {
ExtractedFunctions() // 预览 ExtractedFunctions 视图
}
// 定义一个名为 MyItem 的视图结构体
struct MyItem: View {
let count: Int // 定义一个常量 count,用于表示数量
let fruit: String // 定义一个常量 fruit,用于表示水果名称
// 视图的主体
var body: some View {
VStack {
// 显示数量的文本
Text("\(count)") // 将 count 转换为字符串并显示
// 显示水果名称的文本
Text(fruit) // 显示传入的水果名称
}
.padding() // 添加内边距
.background(.red) // 设置背景颜色为红色
.cornerRadius(15) // 设置圆角半径为 15
}
}
结构体和视图:
struct ExtractedFunctions: View
: 定义一个新的视图结构体ExtractedFunctions
,并遵循View
协议。这个结构体包含了整个视图的内容。
状态管理:
@State var backgroundColor: Color = Color.pink
: 使用@State
属性包装器声明一个状态变量backgroundColor
,初始值为粉色。虽然在这个示例中没有使用该变量,但可以将其用于背景色的动态改变。
布局:
ZStack
: 创建一个 Z 轴堆叠的视图,其中背景和内容将重叠。背景色会填充整个屏幕。Color.orange.ignoresSafeArea(.all)
: 设置背景颜色为橙色,并忽略安全区域,使其填充整个屏幕,包括状态栏和底部工具栏。
水平排列视图:
HStack
: 在水平方向上堆叠MyItem
视图,允许在同一行中显示多个项。
调用子视图:
MyItem(count: 10, fruit: "Orange")
: 创建一个MyItem
视图,传递参数count
和fruit
。- 依此类推,创建两个其他
MyItem
视图,分别显示 "Apples" 和 "Banana"。
子视图结构:
struct MyItem: View
: 定义一个名为MyItem
的视图结构体,用于展示水果的信息。let count: Int
和let fruit: String
: 定义两个常量,分别用于存储数量和水果名称。
子视图布局:
VStack
: 在垂直方向上堆叠视图元素。Text("\(count)")
: 显示数量的文本,将count
转换为字符串。Text(fruit)
: 显示水果名称的文本,直接显示传入的fruit
参数。
样式设置:
.padding()
: 为VStack
添加内边距,使得内容与边框之间有一些空隙。.background(.red)
: 设置VStack
的背景颜色为红色。.cornerRadius(15)
: 设置VStack
的圆角半径为 15,使得角部呈现圆形效果。
预览:
#Preview
: 提供了视图的预览,便于在 Xcode 的设计界面中实时查看效果。
1-21 @Binding
@Binding
是 SwiftUI 中用于在父视图和子视图之间共享状态的属性包装器。它允许子视图访问和修改父视图中的状态变量,而不需要将状态复制到子视图中。这是构建复杂 UI 组件时非常有用的工具,因为它使得数据流和状态管理更加清晰。
1. @Binding
的基本作用
- 状态共享:
@Binding
允许子视图访问父视图的状态,使得子视图能够反映和修改父视图的状态。 - 避免数据复制: 使用
@Binding
可以避免将状态数据复制到每个子视图,确保所有视图始终同步。
2. 使用 @Binding
的基本语法
struct ChildView: View {
@Binding var variable: DataType // 声明一个绑定变量
...
}
3. 示例
示例 1:简单的开关
这个示例展示了如何使用 @Binding
来创建一个简单的开关。
import SwiftUI
struct ParentView: View {
@State private var isOn: Bool = false // 父视图的状态变量
var body: some View {
VStack {
ToggleView(isOn: $isOn) // 将 isOn 绑定到子视图
Text("Switch is \(isOn ? "ON" : "OFF")") // 显示开关状态
}
.padding()
}
}
struct ToggleView: View {
@Binding var isOn: Bool // 子视图的绑定变量
var body: some View {
Toggle("Toggle Me", isOn: $isOn) // 通过绑定变量连接开关
.padding()
}
}
#Preview {
ParentView() // 预览父视图
}
@State
在父视图中声明isOn
,用来管理开关的状态。ToggleView
中的@Binding
声明允许子视图修改父视图的状态。- 使用
$isOn
将绑定传递给ToggleView
。
示例 2:文本输入框
这个示例展示了如何使用 @Binding
来管理文本输入框中的文本。
import SwiftUI
struct ParentView: View {
@State private var userInput: String = "" // 父视图的状态变量
var body: some View {
VStack {
TextFieldView(userInput: $userInput) // 将 userInput 绑定到子视图
Text("You entered: \(userInput)") // 显示用户输入
}
.padding()
}
}
struct TextFieldView: View {
@Binding var userInput: String // 子视图的绑定变量
var body: some View {
TextField("Enter something", text: $userInput) // 绑定文本输入
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
}
#Preview {
ParentView() // 预览父视图
}
@State
在父视图中声明userInput
,用来存储文本输入。TextFieldView
中的@Binding
声明允许子视图访问和修改父视图的状态。- 文本框通过
$userInput
进行绑定,用户输入会实时更新父视图的状态。
示例 3:颜色选择器
这个示例展示了如何使用 @Binding
来创建一个颜色选择器。
import SwiftUI
struct ParentView: View {
@State private var selectedColor: Color = .blue // 父视图的状态变量
var body: some View {
VStack {
ColorPickerView(selectedColor: $selectedColor) // 将 selectedColor 绑定到子视图
Rectangle()
.fill(selectedColor) // 用选中的颜色填充矩形
.frame(width: 100, height: 100)
}
.padding()
}
}
struct ColorPickerView: View {
@Binding var selectedColor: Color // 子视图的绑定变量
var body: some View {
HStack {
Button("Red") {
selectedColor = .red // 修改选中的颜色
}
Button("Green") {
selectedColor = .green // 修改选中的颜色
}
Button("Blue") {
selectedColor = .blue // 修改选中的颜色
}
}
.padding()
}
}
#Preview {
ParentView() // 预览父视图
}
@State
在父视图中声明selectedColor
,用来存储当前选中的颜色。ColorPickerView
中的@Binding
声明允许子视图访问和修改父视图的状态。- 颜色按钮通过修改
selectedColor
来更新父视图的颜色。
1-22 if else 判断
import SwiftUI
// 定义一个名为 Conditionnal 的视图结构体
struct Conditionnal: View {
// 使用 @State 属性包装器声明一个状态变量 showCircle,初始值为 false
@State var showCircle: Bool = false
// 视图的主体
var body: some View {
VStack {
// 创建一个按钮,点击时切换 showCircle 的值
Button("Circle Button : \(showCircle.description)") {
showCircle.toggle() // 切换 showCircle 的值
}
// 条件语句:如果 showCircle 为 true,则显示一个圆形
if showCircle {
Circle()
.frame(width: 100, height: 100) // 设置圆形的大小
}
// 条件语句:如果 showCircle 为 false,则显示一个绿色的圆角矩形
if !showCircle {
RoundedRectangle(cornerRadius: 15.0) // 创建一个圆角矩形
.fill(.green) // 填充颜色为绿色
.frame(width: 100, height: 100) // 设置矩形的大小
}
// 另一种条件语句:如果 showCircle 为 true,则显示一个蓝色的圆形
if showCircle {
Circle()
.fill(.blue) // 填充颜色为蓝色
.frame(width: 100, height: 100) // 设置圆形的大小
} else {
// 如果 showCircle 为 false,则显示一个粉色的圆角矩形
RoundedRectangle(cornerRadius: 15.0) // 创建一个圆角矩形
.fill(.pink) // 填充颜色为粉色
.frame(width: 100, height: 100) // 设置矩形的大小
}
Spacer() // 添加空间分隔符,推送内容到顶部
}
}
}
// 提供一个预览视图
#Preview {
Conditionnal() // 预览 Conditionnal 视图
}
在 SwiftUI 中,条件渲染是根据特定条件选择性地显示视图的常见方式。你可以使用 if
、if-else if
和 if-else
语句来控制哪些视图会被渲染。以下是一些典型的条件渲染的例子,展示了如何在 SwiftUI 中使用这些语句。
示例 1:简单的 if
语句
在这个例子中,我们使用 if
语句来决定是否显示一个圆形。
import SwiftUI
struct SimpleConditionalView: View {
@State private var showCircle: Bool = false // 状态变量
var body: some View {
VStack {
Button("Toggle Circle") {
showCircle.toggle() // 切换状态
}
.padding()
if showCircle {
Circle()
.fill(Color.blue)
.frame(width: 100, height: 100) // 当 showCircle 为 true 时显示蓝色圆形
}
}
.padding()
}
}
#Preview {
SimpleConditionalView()
}
示例 2:使用 if-else
语句
在这个例子中,我们使用 if-else
语句来在两个视图之间切换:一个圆形和一个矩形。
import SwiftUI
struct IfElseConditionalView: View {
@State private var showCircle: Bool = true // 状态变量
var body: some View {
VStack {
Button("Toggle Shape") {
showCircle.toggle() // 切换状态
}
.padding()
if showCircle {
Circle()
.fill(Color.red)
.frame(width: 100, height: 100) // 显示圆形
} else {
Rectangle()
.fill(Color.green)
.frame(width: 100, height: 100) // 显示矩形
}
}
.padding()
}
}
#Preview {
IfElseConditionalView()
}
示例 3:使用 if-else if
语句
在这个例子中,我们使用 if-else if
语句来根据一个数字的值显示不同的视图。
import SwiftUI
struct IfElseIfConditionalView: View {
@State private var score: Int = 0 // 状态变量
var body: some View {
VStack {
Button("Increase Score") {
score += 10 // 点击按钮增加分数
}
.padding()
// 使用 if-else if 语句根据分数显示不同的视图
if score < 50 {
Text("Score is low")
.foregroundColor(.red)
} else if score < 100 {
Text("Score is medium")
.foregroundColor(.orange)
} else {
Text("Score is high")
.foregroundColor(.green)
}
}
.padding()
}
}
#Preview {
IfElseIfConditionalView()
}
示例 4:嵌套条件
在这个例子中,我们使用嵌套条件来根据不同的状态显示不同的视图。
import SwiftUI
struct NestedConditionalView: View {
@State private var showCircle: Bool = false
@State private var showRectangle: Bool = false
var body: some View {
VStack {
Button("Toggle Circle") {
showCircle.toggle() // 切换圆形的状态
}
.padding()
Button("Toggle Rectangle") {
showRectangle.toggle() // 切换矩形的状态
}
.padding()
if showCircle {
Circle()
.fill(Color.blue)
.frame(width: 100, height: 100) // 显示蓝色圆形
}
if showRectangle {
Rectangle()
.fill(Color.green)
.frame(width: 100, height: 100) // 显示绿色矩形
}
// 嵌套条件
if showCircle && showRectangle {
Text("Both shapes are shown")
.foregroundColor(.purple) // 当两个形状都显示时显示文本
}
}
.padding()
}
}
#Preview {
NestedConditionalView()
}
1-23 三元运算符
三元运算符(Ternary Operator)是编程语言中常见的条件表达式。它可以用来根据条件的真伪快速选择一个值。Swift 的三元运算符的语法如下:
condition ? valueIfTrue : valueIfFalse
1. 语法解释
- condition: 一个返回布尔值的表达式。
- valueIfTrue: 如果条件为真,则返回的值。
- valueIfFalse: 如果条件为假,则返回的值。
2. 使用场景
三元运算符通常用于需要基于条件快速赋值或显示内容的场景,尤其在视图构建中非常常见。
3. 示例
示例 1:基本的三元运算符
import SwiftUI
struct TernaryOperatorExample: View {
@State private var isRed: Bool = true // 状态变量,用于切换颜色
var body: some View {
VStack {
Text("Hello, World!")
.foregroundColor(isRed ? .red : .blue) // 使用三元运算符选择颜色
Button("Toggle Color") {
isRed.toggle() // 切换状态
}
.padding()
}
}
}
#Preview {
TernaryOperatorExample() // 预览视图
}
说明:
- 在这个例子中,
foregroundColor
的值由三元运算符决定:如果isRed
为真,文本颜色为红色;如果为假,则为蓝色。
示例 2:动态文本内容
import SwiftUI
struct DynamicTextExample: View {
@State private var score: Int = 75 // 状态变量,表示分数
var body: some View {
VStack {
Text("Your score is \(score) - \(score >= 50 ? "Pass" : "Fail")") // 使用三元运算符决定文本内容
.font(.title)
Button("Increase Score") {
score += 10 // 点击按钮增加分数
}
.padding()
}
}
}
#Preview {
DynamicTextExample() // 预览视图
}
说明:
- 在这个例子中,文本内容会根据
score
的值显示不同的字符串:如果分数大于或等于 50,显示 "Pass",否则显示 "Fail"。
示例 3:计算结果
import SwiftUI
struct TernaryCalculationExample: View {
@State private var number: Int = 10 // 状态变量,表示一个数字
var body: some View {
VStack {
Text("The number is \(number) and it is \(number % 2 == 0 ? "Even" : "Odd")") // 使用三元运算符检查数字的奇偶性
Button("Change Number") {
number = Int.random(in: 1...100) // 点击按钮随机改变数字
}
.padding()
}
}
}
#Preview {
TernaryCalculationExample() // 预览视图
}
说明:
- 在这个例子中,使用三元运算符来判断
number
是偶数还是奇数。如果number
除以 2 的余数为 0,显示 "Even",否则显示 "Odd"。
1-24 animation
以下是你提供的 SwiftUI 动画代码的详细注释。这些注释将帮助你理解每一部分的作用,以及如何使用 SwiftUI 组件和动画。
//
// animations.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/5.
//
import SwiftUI
// 定义一个名为 animations 的视图结构体
struct animations: View {
// 使用 @State 属性包装器声明一个状态变量 isAnimated,初始值为 false
@State var isAnimated: Bool = false
// 视图的主体
var body: some View {
VStack {
// 创建一个按钮,点击时触发动画
Button("Button") {
// 使用 withAnimation 来包裹动画
withAnimation(Animation
.default // 使用默认动画
.repeatCount(5, autoreverses: false)) // 设置动画重复 5 次,不自动反转
{
isAnimated.toggle() // 切换 isAnimated 的值
}
}
Spacer() // 添加一个空白分隔符,推送内容到顶部
// 创建一个圆角矩形,样式和位置根据 isAnimated 的值变化
RoundedRectangle(cornerRadius: isAnimated ? 50 : 25) // 根据 isAnimated 决定圆角大小
.fill(isAnimated ? .red : .green) // 根据 isAnimated 决定填充颜色
.frame(width: isAnimated ? 100 : 200, height: isAnimated ? 200 : 100) // 根据 isAnimated 决定大小
.rotationEffect(Angle(degrees: isAnimated ? 360 : 0)) // 根据 isAnimated 决定旋转角度
.offset(y: isAnimated ? 100 : 0) // 根据 isAnimated 决定偏移量
Spacer() // 添加一个空白分隔符,推送内容到底部
}
}
}
// 提供一个预览视图
#Preview {
animations() // 预览 animations 视图
}
结构体和视图:
struct animations: View
: 定义一个名为animations
的视图结构体,并遵循View
协议。该结构体将包含动画的逻辑和 UI 组件。
状态管理:
@State var isAnimated: Bool = false
: 使用@State
属性包装器声明一个状态变量isAnimated
,初始值为false
。这个变量用于控制动画的触发和状态。
布局:
VStack
: 创建一个垂直堆叠的视图,以便将按钮和形状组件垂直排列。
按钮:
Button("Button")
: 创建一个按钮,显示文本 "Button"。withAnimation(...)
: 使用withAnimation
包裹动画逻辑。当按钮被点击时,触发动画效果。Animation.default.repeatCount(5, autoreverses: false)
: 设置动画使用默认类型,重复 5 次,不进行自动反转。即动画会顺序执行,而不是来回切换。
切换状态:
isAnimated.toggle()
: 当按钮被点击时,切换isAnimated
的值,以触发相应的动画效果。
创建形状:
RoundedRectangle(cornerRadius: isAnimated ? 50 : 25)
: 创建一个圆角矩形,圆角半径根据isAnimated
的值变化。动画时变为 50,否则为 25。.fill(isAnimated ? .red : .green)
: 根据isAnimated
的值设置填充颜色。如果为true
,则填充红色;如果为false
,则填充绿色。.frame(width: isAnimated ? 100 : 200, height: isAnimated ? 200 : 100)
: 根据isAnimated
的值设置矩形的宽度和高度。.rotationEffect(Angle(degrees: isAnimated ? 360 : 0))
: 根据isAnimated
的值设置旋转角度,true
时旋转 360 度,false
时不旋转。.offset(y: isAnimated ? 100 : 0)
: 根据isAnimated
的值设置矩形的 y 偏移量。动画时向下偏移 100 点。
空间分隔符:
Spacer()
: 添加一个空间分隔符,推送内容到顶部和底部,使布局更加美观。
预览:
#Preview
: 提供了视图的预览,便于在 Xcode 的设计界面中实时查看效果。
import SwiftUI
struct Animations: View {
@State var isAnimated: Bool = false // 声明一个状态变量 isAnimated,初始值为 false
var body: some View {
VStack {
Button("Button") {
isAnimated.toggle() // 点击按钮时切换 isAnimated 的值
}
.padding()
Spacer()
RoundedRectangle(cornerRadius: isAnimated ? 50 : 25) // 根据 isAnimated 控制圆角
.fill(isAnimated ? .red : .green) // 根据 isAnimated 控制颜色
.frame(width: isAnimated ? 100 : 200, height: isAnimated ? 200 : 100) // 根据 isAnimated 控制尺寸
.rotationEffect(Angle(degrees: isAnimated ? 360 : 0)) // 根据 isAnimated 控制旋转
.offset(y: isAnimated ? 100 : 0) // 根据 isAnimated 控制偏移量
.animation(.default.repeatCount(5, autoreverses: false), value: isAnimated) // 使用 animation(_:) 修饰符
Spacer()
}
}
}
#Preview {
Animations() // 预览 Animations 视图
}
使用
animation(_:)
:- 通过
animation(_:value:)
修饰符,将.default.repeatCount(5, autoreverses: false)
应用于RoundedRectangle
视图。 value: isAnimated
参数用于指示当isAnimated
的值变化时应用动画。
- 通过
去掉
withAnimation
:- 因为我们现在直接在视图上应用动画,所以不需要使用
withAnimation
。
- 因为我们现在直接在视图上应用动画,所以不需要使用
按钮逻辑保持不变:
- 点击按钮仍然会切换
isAnimated
的值,触发动画。
- 点击按钮仍然会切换
在 SwiftUI 中,animation
和 withAnimation
是用于添加动画的两种常见方式。它们有不同的使用场景和方式。以下是对这两者的详细解释以及相应的示例。
1. withAnimation
withAnimation
是一个函数,它在调用闭包时应用指定的动画。你可以使用 withAnimation
来包裹状态变化,从而在状态变化时触发动画。
示例 1:使用 withAnimation
import SwiftUI
struct WithAnimationExample: View {
@State private var isAnimated: Bool = false // 状态变量
var body: some View {
VStack {
Button("Toggle Animation") {
withAnimation {
isAnimated.toggle() // 使用 withAnimation 包裹状态变化
}
}
.padding()
// 根据 isAnimated 的值改变视图
RoundedRectangle(cornerRadius: isAnimated ? 50 : 10)
.fill(isAnimated ? Color.red : Color.green)
.frame(width: isAnimated ? 200 : 100, height: 100)
.rotationEffect(Angle(degrees: isAnimated ? 360 : 0))
}
}
}
#Preview {
WithAnimationExample()
}
- 点击按钮会切换
isAnimated
的值,并且withAnimation
确保在状态变化时应用动画。 - 这里可以看到,动画是在状态变化时立即生效的。
2. animation
animation
是一个修饰符,用于在特定的视图上应用动画。它允许你为视图添加动画效果,且可以指定一个绑定的值以触发动画。
示例 2:使用 animation
import SwiftUI
struct AnimationExample: View {
@State private var isAnimated: Bool = false // 状态变量
var body: some View {
VStack {
Button("Toggle Animation") {
isAnimated.toggle() // 切换状态
}
.padding()
// 使用 animation 修饰符
RoundedRectangle(cornerRadius: isAnimated ? 50 : 10)
.fill(isAnimated ? Color.red : Color.green)
.frame(width: isAnimated ? 200 : 100, height: 100)
.rotationEffect(Angle(degrees: isAnimated ? 360 : 0))
.animation(.easeInOut, value: isAnimated) // 当 isAnimated 变化时应用动画
}
}
}
#Preview {
AnimationExample()
}
说明:
- 点击按钮同样会切换
isAnimated
的值。 - 视图使用
animation(.easeInOut, value: isAnimated)
修饰符,当isAnimated
发生变化时,应用该动画。 - 这种方式使得视图可以自动响应状态变化,并显示动画效果。
3. 区别和使用场景
withAnimation
:- 更适用于需要在状态变化中进行复杂操作的场景,或者在需要多个状态变化时。
- 适合于需要同时改变多个状态或同时执行多个视图更新的情况。
animation
:- 更适用于直接在视图上指定动画的场景,简洁且易于理解。
- 适合于单一视图的变化响应。
示例 3:同时使用 withAnimation
和 animation
import SwiftUI
struct CombinedExample: View {
@State private var isAnimated: Bool = false // 状态变量
var body: some View {
VStack {
Button("Toggle Animation") {
withAnimation(.easeInOut(duration: 1.0)) {
isAnimated.toggle() // 使用 withAnimation 包裹状态变化
}
}
.padding()
// 使用 animation 修饰符
RoundedRectangle(cornerRadius: isAnimated ? 50 : 10)
.fill(isAnimated ? Color.red : Color.green)
.frame(width: isAnimated ? 200 : 100, height: 100)
.rotationEffect(Angle(degrees: isAnimated ? 360 : 0))
.animation(.spring(), value: isAnimated) // 当 isAnimated 变化时应用动画
}
}
}
#Preview {
CombinedExample()
}
1-25 Animation Curnes
import SwiftUI
// 定义一个名为 Animations 的视图结构体
struct Animations: View {
@State var isAnimated: Bool = false // 声明一个状态变量 isAnimated,初始值为 false
let timing: Double = 10.0 // 定义动画持续时间常量
var body: some View {
VStack {
// 创建一个按钮,点击时切换 isAnimated 的值
Button("Button") {
isAnimated.toggle() // 切换状态
}
.padding()
Spacer() // 添加一个空白分隔符,推送内容到顶部
// 创建一个圆角矩形,使用默认动画
RoundedRectangle(cornerRadius: 25)
.frame(width: isAnimated ? 300 : 50, height: 150) // 根据 isAnimated 的值决定宽度
.animation(Animation.default, value: isAnimated) // 使用默认动画,当 isAnimated 变化时执行动画
// 创建另一个圆角矩形,使用线性动画
RoundedRectangle(cornerRadius: 25)
.frame(width: isAnimated ? 300 : 50, height: 150) // 根据 isAnimated 的值决定宽度
.animation(Animation.linear(duration: timing), value: isAnimated) // 使用线性动画
// 创建一个使用 easeIn 动画的圆角矩形
RoundedRectangle(cornerRadius: 25)
.frame(width: isAnimated ? 300 : 50, height: 150) // 根据 isAnimated 的值决定宽度
.animation(Animation.easeIn(duration: timing), value: isAnimated) // 使用 easeIn 动画
// 创建一个使用 easeInOut 动画的圆角矩形
RoundedRectangle(cornerRadius: 25)
.frame(width: isAnimated ? 300 : 50, height: 150) // 根据 isAnimated 的值决定宽度
.animation(Animation.easeInOut(duration: timing), value: isAnimated) // 使用 easeInOut 动画
// 创建一个使用 easeOut 动画的圆角矩形
RoundedRectangle(cornerRadius: 25)
.frame(width: isAnimated ? 300 : 50, height: 150) // 根据 isAnimated 的值决定宽度
.animation(Animation.easeOut(duration: timing), value: isAnimated) // 使用 easeOut 动画
Spacer() // 添加一个空白分隔符,推送内容到底部
}
}
}
// 提供一个预览视图
#Preview {
Animations() // 预览 Animations 视图
}
import SwiftUI
// 定义一个名为 Animations 的视图结构体
struct Animations: View {
@State var isAnimated: Bool = false // 声明一个状态变量 isAnimated,初始值为 false
let timing: Double = 10.0 // 定义动画的时间常量
var body: some View {
VStack {
// 创建一个按钮,点击时切换 isAnimated 的值
Button("Button") {
isAnimated.toggle() // 切换状态
}
.padding() // 添加内边距
Spacer() // 添加一个空白分隔符,推送内容到顶部
// 创建一个圆角矩形,使用弹簧动画
RoundedRectangle(cornerRadius: 25) // 创建一个圆角矩形,圆角半径为 25
.frame(width: isAnimated ? 300 : 50, height: 150) // 根据 isAnimated 的值决定宽度
.animation(
Animation.spring( // 使用弹簧动画
response: 1.0, // 动画响应时间
dampingFraction: 0.2, // 阻尼系数,影响动画的震动程度
blendDuration: 1.0 // 混合持续时间
),
value: isAnimated // 当 isAnimated 变化时应用动画
)
Spacer() // 添加一个空白分隔符,推送内容到底部
}
}
}
// 提供一个预览视图
#Preview {
Animations() // 预览 Animations 视图
}
1-26 transition()
import SwiftUI
struct Transitions: View {
@State var showView: Bool = false // 声明一个状态变量,初始值为 false
var body: some View {
ZStack(alignment: .bottom) {
VStack {
Button("BUTTON") {
withAnimation(.spring) { // 使用 withAnimation 包裹状态变化
showView.toggle() // 切换显示视图的状态
}
}
.padding()
Spacer()
}
// 只有当 showView 为 true 时才显示圆角矩形
if showView {
RoundedRectangle(cornerRadius: 30)
.frame(height: UIScreen.main.bounds.height * 0.5)
// .transition(.slide) // 设置进入和退出的过渡效果
// .transition(.move(edge: .bottom))
// .transition(.opacity)
// .transition(AnyTransition.scale)
.transition(AnyTransition.asymmetric(
insertion: .move(edge: .leading),
removal: AnyTransition.opacity.animation(.easeIn))
)
}
}
.edgesIgnoringSafeArea(.bottom) // 忽略底部的安全区域
}
}
#Preview {
Transitions() // 预览 Transitions 视图
}
1-26 .sheet() && .fullScreenCover()
import SwiftUI
struct Sheets: View {
@State var showSheet : Bool = false
var body: some View {
ZStack {
Color.green
.ignoresSafeArea(.all)
Button {
showSheet.toggle()
} label: {
Text("BUTTON")
.foregroundColor(.green)
.font(.headline)
.padding(20)
.background(.white)
.cornerRadius(15)
}
.sheet(isPresented: $showSheet) {
SecondScreen()
}
}
}
}
struct SecondScreen : View {
var body : some View {
ZStack {
Color.red
.ignoresSafeArea(.all)
Button {
} label: {
Text("BUTTON")
.foregroundColor(.red)
.font(.headline)
.padding(20)
.background(.white)
.cornerRadius(15)
}
}
}
}
#Preview {
Sheets()
}
import SwiftUI
struct Sheets: View {
@State var showSheet : Bool = false
var body: some View {
ZStack {
Color.green
.ignoresSafeArea(.all)
Button {
showSheet.toggle()
} label: {
Text("BUTTON")
.foregroundColor(.green)
.font(.headline)
.padding(20)
.background(.white)
.cornerRadius(15)
}
// .sheet(isPresented: $showSheet) {
// SecondScreen()
// }
.fullScreenCover(isPresented: $showSheet) {
SecondScreen()
}
}
}
}
struct SecondScreen : View {
@Environment(\.presentationMode) var presentationMode
var body : some View {
ZStack(alignment:.topLeading) {
Color.red
.ignoresSafeArea(.all)
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: "xmark")
.foregroundColor(.white)
.font(.largeTitle)
.padding(20)
}
}
}
}
#Preview {
Sheets()
}
1-27 sheet() vs transition vs animation
在 SwiftUI 中,.sheet()
, .transition()
和 .animation()
都与视图的显示、隐藏和动画效果相关,但它们的使用场景和效果有所不同。以下是对它们的详细对比和解释:
1. .sheet()
.sheet()
是用来弹出模态视图的修饰符。它通常用于展示一个新视图,这个新视图通常是模态的,也就是会覆盖当前视图的内容,用户可以通过手势或编程来关闭它。
用法:
.sheet(isPresented: $isPresented) {
// 需要显示的内容
}
特点:
.sheet()
是 模态弹出 的行为,它会将视图以系统定义的方式从屏幕底部弹出,在 iPhone 上通常是全屏的模态视图,而在 iPad 或 Mac 上可能是部分视图(比如一个小弹窗)。.sheet()
通常带有系统预设的弹出和关闭动画,不能自定义其动画。- 使用
.sheet()
弹出的视图在关闭时是从内存中销毁的。
示例:
struct ContentView: View {
@State private var showSheet = false
var body: some View {
Button("Show Sheet") {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
Text("This is a modal sheet")
}
}
}
总结:
.sheet()
用于模态弹出内容的展示,适合用来展示详细信息、表单或其他需要用户交互的模态视图。- 动画和弹出效果由系统控制,无法通过
.animation()
或.transition()
自定义。
2. .transition()
.transition()
用于 视图出现和消失时 的动画效果。它需要配合视图的状态变化(比如 if
或 opacity
等)来控制视图何时显示和何时隐藏。transition()
定义了视图进入和退出时的动画样式。
用法:
.transition(.slide)
特点:
.transition()
必须与 视图的添加和移除 逻辑结合使用。例如,通过if
控制一个视图是否存在于视图层次结构中,transition()
就决定了这个视图如何进入或退出屏幕。- 常见的过渡类型有
.slide
,.opacity
,.move(edge:)
,.scale
等。 .transition()
仅控制视图的 进入和退出动画,但不控制其持续存在时的动画效果。
示例:
struct ContentView: View {
@State private var showView = false
var body: some View {
VStack {
Button("Toggle View") {
withAnimation {
showView.toggle()
}
}
if showView {
Text("Hello, World!")
.padding()
.background(Color.blue)
.cornerRadius(10)
.transition(.slide)
}
}
}
}
在上面的例子中,当 showView
为 true
时,Text
视图会从侧边滑入,当 showView
为 false
时,Text
视图会滑出。
总结:
.transition()
控制视图的 进入和退出效果,需要结合状态变化(例如if
或opacity
)。- 可以自定义进入或退出动画,但只影响视图 消失和出现 的过程,不影响视图在屏幕上显示时的交互或其他变化。
3. .animation()
.animation()
用于为 视图的属性变化 添加动画效果。当视图的某个属性(如位置、颜色、大小等)发生变化时,.animation()
会为该变化添加平滑的过渡效果。
用法:
.animation(.easeInOut)
特点:
.animation()
作用于 任何属性变化,例如视图的颜色、大小、位置等,都会受到动画的影响。- 与
.transition()
不同,.animation()
并不与视图的显隐绑定,而是作用于视图在界面上时的 状态变化。 .animation()
可以与withAnimation
一起使用,指定某个操作的动画时长、速度和曲线。
示例:
struct ContentView: View {
@State private var isLarge = false
var body: some View {
VStack {
Button("Animate View") {
withAnimation {
isLarge.toggle()
}
}
Text("Hello, SwiftUI!")
.padding()
.background(Color.blue)
.scaleEffect(isLarge ? 2.0 : 1.0)
.animation(.easeInOut, value: isLarge)
}
}
}
在这个示例中,点击按钮后,Text
的 scaleEffect
会从 1.0
动画到 2.0
,并且应用 .easeInOut
动画曲线。
总结:
.animation()
作用于 属性变化,比如位置、大小、颜色的改变。它为这些变化增加平滑的过渡效果。.animation()
不控制视图的显隐,而是控制视图的 状态变化动画。
4. 对比总结
功能 | .sheet() | .transition() | .animation() |
---|---|---|---|
作用场景 | 模态弹出一个视图 | 控制视图的出现和消失 | 为视图的属性变化添加动画 |
控制范围 | 只控制弹出的模态视图 | 控制视图的进入和退出 | 视图的任何属性(大小、颜色、位置等)的变化 |
动画自定义 | 由系统控制,无法自定义动画 | 可以自定义进入和退出的动画 | 可以自定义状态变化的动画 |
使用条件 | 显示模态视图 | 需要结合 if 或视图移除逻辑 | 视图的状态变化 |
典型应用 | 展示详细内容、表单、模态窗口 | 视图的显示和隐藏,比如 slide 或 fade in/out | 改变大小、颜色、位置等属性 |
5. 综合使用场景
有时您可能需要将它们结合使用:
- 使用
.sheet()
:如果需要弹出一个详细内容或表单时,可以使用.sheet()
,让用户点击按钮后弹出新的模态视图。 - 使用
.transition()
:当您需要在页面中添加和移除视图(如卡片、列表项等),并为它们的显示和隐藏添加自定义动画时使用。 - 使用
.animation()
:当需要平滑过渡视图的颜色、大小、透明度等变化时,可以使用.animation()
。
例如,您可以让用户点击按钮后,首先弹出一个 sheet()
,在 sheet
中可以为内部元素的属性变化设置 animation()
。如果某个视图是临时的,比如切换子视图的显隐,您可以结合 transition()
来实现更加生动的过渡效果。
1-28 navigationView() && navigationLink()
//
// NavigationView&Link.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/6.
//
import SwiftUI
struct NavigationView_Link: View {
var body: some View {
NavigationView {
ScrollView {
NavigationLink("hello world",
destination: MyOpenNewScreen())
Text("hello world!")
Text("hello world!")
Text("hello world!")
Text("hello world!")
}
.navigationTitle("hello aini")
.navigationBarTitleDisplayMode(.automatic)
// .navigationBarHidden(true)
.navigationBarItems(
leading: Image(systemName: "person.fill"),
trailing: NavigationLink(
destination: MyOpenNewScreen(),
label: {
Image(systemName: "gear")
}
)
.accentColor(.green)
)
}
}
}
struct MyOpenNewScreen : View {
@Environment(\.presentationMode) var presentationMode
var body : some View {
ZStack {
Color.green.ignoresSafeArea(.all)
.navigationTitle("Seconde Screen")
.navigationBarHidden(true)
VStack {
Button("Back Button"){
presentationMode.wrappedValue.dismiss()
}
NavigationLink("Click Me", destination: Text("Third Screen"))
}
}
}
}
#Preview {
NavigationView_Link()
}
1. NavigationView
基础用法
NavigationView
是 SwiftUI 中用于管理多个视图之间导航的容器,它会为内部的子视图提供导航栏(Navigation Bar
),并在导航层级变化时自动管理视图之间的跳转。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
Text("Home Screen")
.navigationTitle("Home") // 设置当前导航栏标题
}
}
}
- 作用:
NavigationView
提供了一个基本的导航框架,让你可以在多个视图之间进行导航。导航栏的标题由.navigationTitle
设置。 - 注意:
NavigationView
只在 iOS 中提供导航栏,对于 watchOS 和 macOS,它的表现可能有所不同。
2. NavigationLink
基础用法
NavigationLink
是用来创建视图之间导航关系的关键组件,通常嵌套在 NavigationView
中使用。用户点击 NavigationLink
后,会跳转到目标视图(destination
)。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(
destination: DetailView(),
label: {
Text("Go to Detail")
.foregroundColor(.blue)
.padding()
.background(Color.yellow)
.cornerRadius(10)
})
}
.navigationTitle("Home Screen")
}
}
}
struct DetailView: View {
var body: some View {
Text("This is the Detail View")
.navigationTitle("Detail")
}
}
关键点:
- destination:
NavigationLink
的destination
参数是用户点击后要导航到的视图。这里的DetailView
是点击后展示的内容。 - label:
NavigationLink
的label
参数是用来展示的视图,可以是任何自定义的 SwiftUI 视图,例如按钮、文本、图像等。
3. 自定义导航栏和返回按钮
NavigationView
默认提供了一个系统的返回按钮,但你可以自定义导航栏中的左右按钮或图标。使用 .navigationBarItems
或 .navigationBarBackButtonHidden
可以隐藏或自定义返回按钮。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Go to Detail")
}
}
.navigationTitle("Home")
.navigationBarItems(leading: Button(action: {
print("Edit tapped")
}, label: {
Text("Edit")
}), trailing: Button(action: {
print("Settings tapped")
}, label: {
Image(systemName: "gear")
}))
}
}
}
struct DetailView: View {
var body: some View {
VStack {
Text("This is the detail view")
}
.navigationTitle("Detail")
.navigationBarBackButtonHidden(true) // 隐藏默认返回按钮
.navigationBarItems(leading: Button(action: {
print("Custom Back")
}, label: {
HStack {
Image(systemName: "chevron.left")
Text("Home")
}
}))
}
}
关键点:
- 自定义按钮:使用
.navigationBarItems
可以为导航栏左右两边添加自定义按钮或图标。 - 隐藏默认返回按钮:使用
.navigationBarBackButtonHidden(true)
可以隐藏系统默认的返回按钮,并用自定义按钮替代它。
4. 页面返回的实现 (@Environment
)
在 SwiftUI 中,你可以使用 @Environment
来获取当前的导航上下文,通过手动控制视图的返回。最常见的做法是使用 @Environment(\.presentationMode)
来管理视图的展示与关闭。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("Go to Detail", destination: DetailView())
.navigationTitle("Home")
}
}
}
struct DetailView: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("Detail Screen")
Button("Go Back") {
presentationMode.wrappedValue.dismiss() // 手动返回上一级视图
}
}
.navigationTitle("Detail")
}
}
关键点:
@Environment(\.presentationMode)
:使用这个属性,你可以手动调用dismiss()
来返回上一层视图,而不依赖系统默认的返回按钮。
5. 使用 List
与 NavigationLink
实现复杂导航
在更复杂的场景中,你可能需要从一个列表中选择某项并跳转到对应的详情页面。结合 List
和 NavigationLink
,你可以轻松实现这一需求。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(1..<6) { index in
NavigationLink(
destination: DetailView(item: "Item \(index)"),
label: {
Text("Go to Item \(index)")
})
}
}
.navigationTitle("Items List")
}
}
}
struct DetailView: View {
var item: String
var body: some View {
Text("Details for \(item)")
.navigationTitle(item)
}
}
关键点:
List
与ForEach
:通过List
和ForEach
的组合,创建一个动态的列表,每一项都可以使用NavigationLink
跳转到相应的详情页面。
NavigationView
- 用于构建多层级的视图结构,自动提供导航栏和系统默认的返回按钮。
navigationTitle
可以设置当前视图的标题,navigationBarItems
可以自定义导航栏左右按钮。
NavigationLink
- 提供视图间的跳转,支持简单的文本和复杂的自定义视图作为触发标签。
destination
参数指定目标视图。
@Environment(\.presentationMode)
- 用于手动控制视图的返回,适合自定义返回按钮或需要手动控制视图跳转的场景。
3.SwiftUI基础入门-下
1-29 List()
//
// List_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/6.
//
import SwiftUI
struct List_L: View {
@State var fruits : [String] = ["apple","banana","peach","orange","wotermalen","xigua","bolo"]
var body: some View {
NavigationView {
List {
Section(
header: Text("Fruits"),
content: {
ForEach(fruits, id: \.self) { item in
Text(item.capitalized)
// .background(Color.pink)
.foregroundColor(Color.white)
// .frame(maxWidth: .infinity,maxHeight: .infinity)
}
// .onDelete { IndexSet in
// delete(indexSet: IndexSet)
// }
.onDelete(perform: delete)
// .onMove { IndexSet, Int in
// move(indexSet: IndexSet, toOffset: Int)
// }
.onMove(perform: move)
.listRowBackground(Color.green.opacity(0.4))
})
Section(
header: HStack {
Text("Fruits")
Image(systemName: "flame.fill")
}
.font(.largeTitle)
.foregroundColor(Color.orange)
,
content: {
ForEach(fruits, id: \.self) { item in
Text(item.capitalized)
}
// .onDelete { IndexSet in
// delete(indexSet: IndexSet)
// }
.onDelete(perform: delete)
// .onMove { IndexSet, Int in
// move(indexSet: IndexSet, toOffset: Int)
// }
.onMove(perform: move)
})
.tint(Color.green)
}
// .listStyle(GroupedListStyle())
.tint(Color.green)
.listStyle(InsetGroupedListStyle())
.navigationTitle("Groery List")
.toolbar{
ToolbarItem(placement: .topBarLeading) {
EditButton()
}
ToolbarItem(placement: .topBarTrailing) {
Button("ADD"){
fruits.append("aini")
}
}
}
}
}
func delete(indexSet: IndexSet) {
fruits.remove(atOffsets: indexSet)
}
func move(indexSet:IndexSet, toOffset : Int) {
fruits.move(fromOffsets: indexSet, toOffset: toOffset)
}
}
#Preview {
List_L()
}
List
是 SwiftUI 中用于显示可滚动的、可重用的多行数据视图组件,类似于 UIKit 中的 UITableView
。List
可以很容易地处理动态数据,并支持选择、编辑、拖动等操作。下面我会从 List
的基本用法到高级功能进行详细说明。
1. List
基本用法
List
可以直接传入静态数据或动态数组来生成多行列表,每一行都可以显示自定义的内容。
静态列表:
import SwiftUI
struct ContentView: View {
var body: some View {
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}
}
动态数组:
可以通过 ForEach
循环将数组中的每个元素展示出来:
import SwiftUI
struct ContentView: View {
let items = ["Apple", "Banana", "Orange"]
var body: some View {
List(items, id: \.self) { item in
Text(item)
}
}
}
id
参数:SwiftUI 需要知道每个元素的唯一标识符来识别和更新数据,因此我们为List
传入了id: \.self
,这意味着数组元素是唯一的,且可以通过自身的值进行标识。
2. 自定义单元格内容
你可以通过自定义单元格内容来展示更复杂的视图,List
支持将任何 SwiftUI 视图作为单元格内容。
import SwiftUI
struct ContentView: View {
let fruits = ["Apple", "Banana", "Cherry"]
var body: some View {
List(fruits, id: \.self) { fruit in
HStack {
Image(systemName: "applelogo")
Text(fruit)
}
}
}
}
在这个例子中,我们使用了 HStack
使每个单元格包含一个图标和文本。
3. 数据模型与 List
更常见的情况是使用一个结构体来表示数据,而不是直接使用数组中的字符串。为了实现这一点,可以创建一个数据模型并通过 List
显示:
import SwiftUI
struct Fruit: Identifiable {
let id = UUID()
let name: String
let color: Color
}
struct ContentView: View {
let fruits = [
Fruit(name: "Apple", color: .red),
Fruit(name: "Banana", color: .yellow),
Fruit(name: "Grapes", color: .purple)
]
var body: some View {
List(fruits) { fruit in
HStack {
Circle()
.fill(fruit.color)
.frame(width: 20, height: 20)
Text(fruit.name)
}
}
}
}
Identifiable
协议:我们定义了Fruit
结构体并使其符合Identifiable
协议。List
需要数据模型具有唯一的标识符,因此使用了id
属性(通常可以是UUID
)。- 自定义内容:我们为每个水果定义了名称和颜色,并在
List
中展示了一个带颜色的圆形和水果的名称。
4. List
的选择与删除
List
还可以支持行的选择、删除和移动操作。通过添加状态变量和相应的修饰符,可以启用这些功能。
单选功能:
import SwiftUI
struct ContentView: View {
let items = ["Apple", "Banana", "Orange"]
@State private var selectedItem: String?
var body: some View {
List(items, id: \.self, selection: $selectedItem) { item in
Text(item)
}
.navigationTitle("Fruits")
.toolbar {
EditButton() // EditButton 用于启用多选模式
}
}
}
删除功能:
通过 onDelete
修饰符来启用删除功能。
import SwiftUI
struct ContentView: View {
@State private var fruits = ["Apple", "Banana", "Orange"]
var body: some View {
List {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
.onDelete(perform: deleteItems)
}
}
func deleteItems(at offsets: IndexSet) {
fruits.remove(atOffsets: offsets) // 删除选中的元素
}
}
onDelete
:通过为ForEach
循环添加.onDelete
,可以启用滑动删除功能。- 删除实现:
deleteItems
函数通过remove(atOffsets:)
来删除选中的项目。
移动功能:
onMove
修饰符用于实现列表的拖动排序功能。
import SwiftUI
struct ContentView: View {
@State private var fruits = ["Apple", "Banana", "Orange"]
var body: some View {
List {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
.onMove(perform: moveItems)
}
.toolbar {
EditButton() // 启用编辑模式
}
}
func moveItems(from source: IndexSet, to destination: Int) {
fruits.move(fromOffsets: source, toOffset: destination) // 实现移动操作
}
}
onMove
:启用拖动功能来重新排序列表。- 拖动实现:通过
move(fromOffsets:toOffset:)
函数来实现数组元素的排序。
5. 分组列表 (Section
)
List
支持分组展示,使用 Section
可以将数据按组分类显示。
import SwiftUI
struct ContentView: View {
var body: some View {
List {
Section(header: Text("Fruits")) {
Text("Apple")
Text("Banana")
}
Section(header: Text("Vegetables")) {
Text("Carrot")
Text("Broccoli")
}
}
}
}
关键点:
Section
:可以为每个分组添加头部或尾部视图,常见的场景是为分组数据添加标题。- 自定义分组内容:
Section
内可以包含任意视图,不仅限于文本。
6. 列表的样式 (ListStyle
)
SwiftUI 提供了多种不同的 ListStyle
,你可以根据需要来调整 List
的外观。
import SwiftUI
struct ContentView: View {
var body: some View {
List {
Text("Item 1")
Text("Item 2")
}
.listStyle(GroupedListStyle()) // 选择不同样式
}
}
常见的 ListStyle
包括:
PlainListStyle
:默认的简单列表样式,带分隔线。GroupedListStyle
:分组样式,适合展示分组的内容。InsetGroupedListStyle
:带有内边距的分组样式,通常用于较为现代的 UI。
7. 动态数据加载
你可以通过状态变量和 List
来动态更新数据。当状态变量改变时,List
会自动刷新视图。
import SwiftUI
struct ContentView: View {
@State private var items = [String]()
var body: some View {
VStack {
Button("Add Item") {
items.append("Item \(items.count + 1)")
}
List(items, id: \.self) { item in
Text(item)
}
}
}
}
关键点:
@State
状态变量:当用户点击按钮时,状态变量items
发生变化,List
自动更新并展示新的数据。
List
是 SwiftUI 中强大且灵活的列表展示组件,适合用来展示多行数据,并提供了许多实用的功能,例如:
- 动态数据展示
- 自定义内容布局
- 支持分组、删除、移动、选择等操作
- 支持不同的样式
- 动态更新数据并自动刷新界面
1-30 Alert()
import SwiftUI
struct AlertS: View {
@State var showAlert : Bool = false
@State var backgroundColor : Color = Color.red
var body: some View {
ZStack {
backgroundColor.ignoresSafeArea(.all)
Button("Click Here"){
showAlert.toggle()
}
.alert(isPresented: $showAlert) {
getAlert()
}
}
}
func getAlert() -> Alert {
return Alert(
title: Text("This is the title"),
message: Text("这是一段信息啊"),
primaryButton: .cancel(),
secondaryButton: .destructive(Text("change bgColor"), action: {
self.backgroundColor = .green
})
)
}
}
#Preview {
AlertS()
}
alert()
是 SwiftUI 中用于显示弹出式对话框(警告框)的常用方法。它通常用来提示用户一些重要信息,或者要求用户进行简单的确认操作。alert()
的使用非常灵活,可以根据需要配置不同的样式、按钮和动作。
1. 基本用法
在最简单的形式下,alert()
方法可以用于展示一条信息并提供一个关闭按钮。
import SwiftUI
struct ContentView: View {
@State private var showAlert = false
var body: some View {
VStack {
Button("Show Alert") {
showAlert = true
}
.alert("This is an alert", isPresented: $showAlert) {
Button("OK", role: .cancel) { }
}
}
}
}
isPresented
:这是一个绑定(@Binding
)属性,它决定是否显示警告框。true
时,警告框显示;false
时,警告框隐藏。- 警告标题:
"This is an alert"
是警告的标题,展示在弹出的对话框顶部。 - 按钮:通过按钮来关闭警告框。在这里,
Button("OK", role: .cancel)
用于显示一个“OK”按钮,并通过.cancel
指定按钮的角色。
2. 带有详细信息和多个按钮的警告框
可以为警告框添加详细的描述信息,并通过多个按钮让用户做出选择。
import SwiftUI
struct ContentView: View {
@State private var showAlert = false
var body: some View {
VStack {
Button("Show Alert") {
showAlert = true
}
.alert("Delete Item", isPresented: $showAlert) {
Button("Delete", role: .destructive) {
print("Item deleted")
}
Button("Cancel", role: .cancel) { }
} message: {
Text("Are you sure you want to delete this item? This action cannot be undone.")
}
}
}
}
关键点:
- 消息内容:
message
这个闭包用于提供详细描述,放置在警告标题下方,进一步说明警告的上下文。 - 多个按钮:可以通过多个
Button
来提供不同的选项,比如“Delete”按钮用于执行删除操作,而“Cancel”按钮用于取消操作。 - 按钮角色:你可以为按钮指定角色(
role
),例如.destructive
用于危险操作,.cancel
用于取消操作。不同的角色会影响按钮的外观和行为。
3. 条件警告框
有时,你需要根据不同的条件显示不同的警告框,SwiftUI 支持这种条件式警告。
import SwiftUI
struct ContentView: View {
@State private var showAlert = false
@State private var isDeleteAction = false
var body: some View {
VStack {
Button("Delete") {
isDeleteAction = true
showAlert = true
}
Button("Error") {
isDeleteAction = false
showAlert = true
}
}
.alert(isPresented: $showAlert) {
if isDeleteAction {
return Alert(
title: Text("Delete Item"),
message: Text("Are you sure you want to delete this item?"),
primaryButton: .destructive(Text("Delete")) {
print("Item deleted")
},
secondaryButton: .cancel()
)
} else {
return Alert(
title: Text("Error"),
message: Text("Something went wrong"),
dismissButton: .default(Text("OK"))
)
}
}
}
}
关键点:
- 条件警告框:根据
isDeleteAction
状态,分别显示不同的警告框。一个用于删除操作,一个用于错误提示。 primaryButton
和secondaryButton
:可以通过这两个参数为警告框提供两个按钮,比如“删除”和“取消”。
4. 自定义按钮样式和操作
你可以通过 Button
的不同构造函数和 role
来自定义按钮的行为。
示例:
import SwiftUI
struct ContentView: View {
@State private var showAlert = false
var body: some View {
VStack {
Button("Show Alert") {
showAlert = true
}
.alert("Important Message", isPresented: $showAlert) {
Button("Confirm", role: .none) {
print("Confirmed")
}
Button("Cancel", role: .cancel) {
print("Cancelled")
}
} message: {
Text("This action is very important. Please confirm or cancel.")
}
}
}
}
5. Alert
的完整构造器
SwiftUI 中 Alert
也可以直接用 Alert
构造器来创建,允许你指定更多的参数,包括标题、消息、按钮等。
import SwiftUI
struct ContentView: View {
@State private var showAlert = false
var body: some View {
VStack {
Button("Show Alert") {
showAlert = true
}
}
.alert(isPresented: $showAlert) {
Alert(
title: Text("Alert Title"),
message: Text("This is the alert message."),
primaryButton: .default(Text("OK")),
secondaryButton: .cancel(Text("Cancel"))
)
}
}
}
关键点:
primaryButton
和secondaryButton
:使用两个按钮为用户提供更多操作选项。- 按钮类型:
Alert.Button.default
,Alert.Button.cancel
,Alert.Button.destructive
是三种常见的按钮类型,每种类型都有不同的视觉表现。
6. 针对 macOS 的 alert()
特性
在 macOS 中,alert()
的用法类似于 iOS,但也有一些平台特性,比如 macOS 的警告框可能会默认提供额外的按钮,像“OK”和“Cancel”。
import SwiftUI
struct ContentView: View {
@State private var showAlert = false
var body: some View {
VStack {
Button("Show Alert") {
showAlert = true
}
.alert("This is a macOS alert", isPresented: $showAlert) {
Button("OK", role: .cancel) { }
}
}
}
}
7. alert()
的注意事项
- 限制交互:
alert()
是一种模态提示,它会阻塞当前视图的交互,直到用户关闭警告框。因此,要谨慎使用,避免频繁弹出干扰用户体验。 - 按钮角色:在
Button
的构造中,合理使用.destructive
和.cancel
等角色,可以帮助用户明确按钮的作用,并提升 UX。
1-31 actionSheet()
//
// ActionSheets.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/6.
//
import SwiftUI
struct ActionSheets: View {
@State var showActionSheet : Bool = false
@State var actionSheetType : ActionSheetOptions = .isOtherPost
enum ActionSheetOptions {
case isMyPost
case isOtherPost
}
var body: some View {
VStack {
HStack {
Circle()
.frame(width: 30,height: 30)
Text("Username")
Spacer()
Button {
actionSheetType = .isMyPost
showActionSheet.toggle()
} label: {
Image(systemName: "ellipsis")
}
// .tint(Color.primary)
.accentColor(Color.primary)
}
.padding(.horizontal)
Rectangle()
.aspectRatio(1.0, contentMode: .fill)
}
.actionSheet(isPresented: $showActionSheet) {
getActionSheet()
}
}
func getActionSheet() -> ActionSheet {
// let button1 : ActionSheet.Button = .default(Text("DEFAULT"))
// let button2 : ActionSheet.Button = .destructive(Text("DESTRUCTIVE"))
// let button3 : ActionSheet.Button = .cancel()
// return ActionSheet(title: Text("thi is the title"), message: Text("this is the message"), buttons: [button1,button2,button3]
//
// )
let shareButton : ActionSheet.Button = .default(Text("share"))
let reportButton : ActionSheet.Button = .default(Text("Report"))
let deleteButton : ActionSheet.Button = .destructive(Text("Delete"))
let cacelButton : ActionSheet.Button = .cancel()
switch actionSheetType {
case .isOtherPost:
return ActionSheet(
title: Text("what wo you like todo "),
message: nil,
buttons: [shareButton,reportButton,cacelButton]
)
case .isMyPost:
return ActionSheet(
title: Text("hahhahah"),
message: nil,
buttons: [shareButton,reportButton,cacelButton,deleteButton]
)
}
}
}
#Preview {
ActionSheets()
}
actionSheet()
是 SwiftUI 中用于显示底部弹出的操作选项(类似于 iOS 中的“操作表”或“操作菜单”)的修饰符。它通常用于提供多个选项供用户选择,这些选项通常是一些重要操作的快捷方式,比如“删除”、“编辑”或“取消”。
actionSheet()
的使用场景非常灵活,它可以为用户提供不同类型的操作按钮,并且支持自定义标题和描述。接下来,我将详细讲解 actionSheet()
的基本用法、按钮的自定义、不同情境下的使用方法等。
1. actionSheet()
基本用法
actionSheet()
通过绑定一个 Bool
类型的状态来决定是否显示弹出菜单,并可以自定义显示的内容和按钮。我们来看一个简单的例子:
import SwiftUI
struct ContentView: View {
@State private var showActionSheet = false
var body: some View {
VStack {
Button("Show Action Sheet") {
showActionSheet = true
}
}
.actionSheet(isPresented: $showActionSheet) {
ActionSheet(
title: Text("Select an Option"),
message: Text("Please choose one of the following options."),
buttons: [
.default(Text("Option 1"), action: {
print("Option 1 selected")
}),
.default(Text("Option 2"), action: {
print("Option 2 selected")
}),
.destructive(Text("Delete"), action: {
print("Delete action")
}),
.cancel()
]
)
}
}
}
isPresented
:这是一个@State
绑定,决定ActionSheet
是否显示。true
时显示,false
时隐藏。ActionSheet
构造器:title
:操作表的标题,通常用来描述这组选项的用途。message
:操作表的副标题或说明,进一步解释操作的背景或重要性(可选)。buttons
:操作表的按钮数组,通常包含多个操作按钮,比如.default
、.destructive
和.cancel
按钮。
2. 按钮类型
在 actionSheet()
中,按钮类型非常灵活,通常包含三种常用的按钮类型:
.default
:默认按钮,通常用于普通操作。你可以指定按钮的文本和点击后的操作。.default(Text("Option 1"), action: { print("Option 1 selected") })
.destructive
:危险操作按钮,通常用于删除、退出等操作,按钮颜色会变为红色,提示用户此操作具有一定风险。.destructive(Text("Delete"), action: { print("Delete action") })
.cancel
:取消按钮,用户可以通过点击取消操作表。这个按钮通常放在最后,并且不需要指定动作。.cancel()
3. 动态显示 ActionSheet
有时你可能需要根据不同的条件动态地显示不同的 ActionSheet
。这可以通过在 actionSheet()
的闭包中进行条件判断来实现。
示例:动态操作表
import SwiftUI
struct ContentView: View {
@State private var showActionSheet = false
@State private var actionType = ""
var body: some View {
VStack {
Button("Delete") {
actionType = "delete"
showActionSheet = true
}
Button("Options") {
actionType = "options"
showActionSheet = true
}
}
.actionSheet(isPresented: $showActionSheet) {
if actionType == "delete" {
return ActionSheet(
title: Text("Delete Confirmation"),
message: Text("Are you sure you want to delete this?"),
buttons: [
.destructive(Text("Delete")) {
print("Item deleted")
},
.cancel()
]
)
} else {
return ActionSheet(
title: Text("Choose an Option"),
message: Text("Please select an option"),
buttons: [
.default(Text("Option 1")) {
print("Option 1 selected")
},
.default(Text("Option 2")) {
print("Option 2 selected")
},
.cancel()
]
)
}
}
}
}
关键点:
- 动态按钮显示:根据
actionType
的值,动态展示不同的操作表。在这个例子中,当点击 "Delete" 按钮时,显示删除确认的ActionSheet
;而点击 "Options" 按钮时,显示普通选项。
4. ActionSheet
的自定义消息和副标题
有时你可能需要为 ActionSheet
添加更详细的消息说明,这样可以帮助用户理解操作的后果或含义。
示例:
import SwiftUI
struct ContentView: View {
@State private var showActionSheet = false
var body: some View {
Button("Show Action Sheet") {
showActionSheet = true
}
.actionSheet(isPresented: $showActionSheet) {
ActionSheet(
title: Text("Perform an Action"),
message: Text("This action will make changes to your settings. Proceed with caution."),
buttons: [
.default(Text("Proceed")) {
print("Proceed with action")
},
.destructive(Text("Delete")) {
print("Item deleted")
},
.cancel(Text("Cancel"))
]
)
}
}
}
5. ActionSheet
的注意事项
- UI 阻塞:
ActionSheet
和Alert
类似,都是模态视图,会阻塞界面上的其他交互,直到用户选择一个按钮或关闭它。 - 设计考量:由于
ActionSheet
是一个底部弹出的操作表,通常用于较为重要或需要用户关注的操作,建议不要频繁弹出以免干扰用户体验。 .destructive
和.cancel
的区别**:.destructive
按钮通常显示为红色,表明其行为是不可逆或有潜在风险的;.cancel
按钮则是为了给用户提供返回或取消操作的选项。
6. 在 iPad 和 iPhone 的表现差异
在 iPhone 上,ActionSheet
通常从屏幕底部滑动出来。而在 iPad 上,ActionSheet
会以弹出窗口的形式展示,通常在点击的控件附近显示,因此在 iPad 上的表现会略有不同。
import SwiftUI
struct ContentView: View {
@State private var showActionSheet = false
var body: some View {
VStack {
Button("Show Action Sheet") {
showActionSheet = true
}
}
.actionSheet(isPresented: $showActionSheet) {
ActionSheet(
title: Text("This is an Action Sheet"),
buttons: [
.default(Text("Option 1")),
.cancel()
]
)
}
}
}
1-32 contextMenu()
//
// ContextMenus.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/6.
//
import SwiftUI
struct ContextMenus: View {
@State var backgroundColor : Color = Color.pink
var body: some View {
VStack(alignment:.leading,spacing: 10.0) {
Image(systemName: "house.fill")
Text("Swiftful Thinking")
.font(.headline)
Text("How to useContext Menu")
.font(.subheadline)
}
.padding(30)
.foregroundColor(Color.white)
.background(backgroundColor.cornerRadius(30))
.contextMenu {
Button {
self.backgroundColor = Color.green
} label: {
Label("Button 01", systemImage: "flame.fill")
}
Button {
self.backgroundColor = Color.blue
} label: {
Text("Button 02")
}
Button {
self.backgroundColor = Color.yellow
} label: {
HStack {
Text("Button 03")
Image(systemName: "heart.fill")
}
}
}
}
}
#Preview {
ContextMenus()
}
contextMenu()
是 SwiftUI 中用于为视图提供上下文菜单(长按菜单或右键菜单)的修饰符。通过 contextMenu()
,用户可以在长按(iOS)或右键单击(macOS)时弹出一个可选的菜单操作,这些操作通常用于与当前视图相关的任务或选项。
下面,我将从 contextMenu()
的基本用法、按钮自定义、动态菜单生成等方面进行详细说明。
1. contextMenu()
基本用法
contextMenu()
的基础使用非常简单,它允许你为一个视图添加菜单,用户长按该视图时弹出菜单。
示例:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Long press me")
.padding()
.background(Color.blue)
.cornerRadius(10)
.foregroundColor(.white)
.contextMenu {
Button(action: {
print("Option 1 selected")
}) {
Text("Option 1")
Image(systemName: "star")
}
Button(action: {
print("Option 2 selected")
}) {
Text("Option 2")
Image(systemName: "heart")
}
}
}
}
关键点:
contextMenu {}
:这是上下文菜单的核心修饰符,接受一组视图或Button
来定义菜单选项。- 按钮定义:菜单选项可以是带有操作的按钮,你可以为每个按钮添加文字和图标(
Image
)。 - 交互方式:在 iOS 上,用户可以通过长按触发菜单;在 macOS 上,用户通过右键点击触发菜单。
2. 带图标的菜单项
如上所示,你可以为每个菜单项提供一个文本和一个图标。图标使用 Image(systemName:)
来显示系统自带的 SF Symbols 图标。
示例:
Button(action: {
print("Favorite selected")
}) {
Text("Favorite")
Image(systemName: "star.fill")
}
在这个例子中,菜单项显示为带有 "Favorite" 文本的按钮,同时旁边带有一个星形的图标。
3. 动态上下文菜单
有时候,你可能需要根据不同的条件动态地生成不同的菜单项。可以通过条件判断或者循环来实现动态生成菜单。
示例:动态生成菜单
import SwiftUI
struct ContentView: View {
@State private var favorite = false
var body: some View {
VStack {
Text(favorite ? "Favorite" : "Not Favorite")
.padding()
.background(favorite ? Color.yellow : Color.gray)
.cornerRadius(10)
.contextMenu {
Button(action: {
favorite.toggle()
}) {
Text(favorite ? "Unfavorite" : "Favorite")
Image(systemName: favorite ? "star.slash.fill" : "star.fill")
}
Button(action: {
print("Share selected")
}) {
Text("Share")
Image(systemName: "square.and.arrow.up")
}
}
}
}
}
关键点:
- 动态菜单项:菜单中的第一个按钮根据
favorite
状态动态改变标题和图标("Favorite"/"Unfavorite")。 toggle()
:通过按钮点击改变状态,进而修改菜单项的显示内容。
4. 使用 ForEach
循环创建菜单项
如果你需要为上下文菜单动态生成大量选项,可以使用 ForEach
循环来生成。
示例:
import SwiftUI
struct ContentView: View {
let actions = ["Option 1", "Option 2", "Option 3"]
var body: some View {
Text("Long press for options")
.padding()
.background(Color.blue)
.cornerRadius(10)
.foregroundColor(.white)
.contextMenu {
ForEach(actions, id: \.self) { action in
Button(action: {
print("\(action) selected")
}) {
Text(action)
}
}
}
}
}
关键点:
ForEach
:可以用来生成多个菜单项,根据数组内容动态展示不同的选项。- 菜单项内容:每个菜单项展示不同的文本,并根据点击的选项执行不同的动作。
5. 条件显示上下文菜单
有时你可能只希望在特定条件下显示上下文菜单,SwiftUI 允许你根据逻辑来决定是否显示上下文菜单。
示例:条件显示 contextMenu
import SwiftUI
struct ContentView: View {
@State private var showMenu = false
var body: some View {
VStack {
Toggle("Show Menu", isOn: $showMenu)
.padding()
Text("Long press me")
.padding()
.background(Color.green)
.cornerRadius(10)
.foregroundColor(.white)
.contextMenu(showMenu ? ContextMenu {
Button("Option 1") { print("Option 1 selected") }
Button("Option 2") { print("Option 2 selected") }
} : nil)
}
}
}
关键点:
- 条件上下文菜单:通过判断
showMenu
的值,决定是否显示上下文菜单。如果showMenu
为false
,则contextMenu
不会显示。 nil
作为参数:如果你希望不显示菜单,可以传递nil
,这样contextMenu
会被禁用。
6. 带有 Destructive
按钮的上下文菜单
contextMenu()
中的按钮可以设置为“危险操作”按钮,比如删除操作。SwiftUI 提供了 .destructive
的按钮角色,常见于执行不可逆的操作。
示例:带有删除按钮
import SwiftUI
struct ContentView: View {
@State private var items = ["Item 1", "Item 2", "Item 3"]
var body: some View {
List(items, id: \.self) { item in
Text(item)
.contextMenu {
Button("Edit") {
print("Editing \(item)")
}
Button("Delete", role: .destructive) {
print("Deleting \(item)")
}
}
}
}
}
关键点:
.destructive
:通过为Button
添加.destructive
角色,表示该操作是危险操作,如删除数据。SwiftUI 会为这种按钮显示红色文本,提示用户操作的危险性。
7. contextMenu()
和平台特性
- iOS:在 iOS 上,
contextMenu()
通常通过长按触发,并从屏幕底部弹出菜单。 - macOS:在 macOS 上,
contextMenu()
通常通过右键单击触发。和 iOS 不同,macOS 的上下文菜单不占据整个屏幕,而是显示在光标附近。
1-33 TextFiels()
//
// TextFiels.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/6.
//
import SwiftUI
struct TextFiels: View {
@State var textFieldText : String = ""
@State var textContent : [String] = []
var body: some View {
NavigationView {
VStack {
TextField("Type something here..", text: $textFieldText)
// .textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.background(Color.gray.opacity(0.3).cornerRadius(10))
.foregroundColor(.red)
.font(.headline)
Button {
save()
} label: {
Text("Save".uppercased())
.font(.title)
.foregroundColor(Color.white)
.frame(maxWidth: .infinity)
.frame(height: 60)
.background(checkText() ? Color.blue : Color.gray)
.cornerRadius(15)
}
.disabled(!checkText())
Spacer()
List {
ForEach(textContent, id: \.self) { item in
Text(item)
}
}
}
.padding()
.navigationTitle(Text("Sign up"))
}
}
func checkText() -> Bool {
if textFieldText.count >= 3 {
return true
}
return false
}
func save() {
if checkText() {
textContent.append(textFieldText)
textFieldText = ""
}
}
}
#Preview {
TextFiels()
}
TextField
是 SwiftUI 中用于创建用户输入框的组件,允许用户在应用中输入文本信息。它常用于表单、搜索框、登录界面等场景。TextField
提供了丰富的功能来处理用户输入,并支持自定义样式、格式化、输入限制、键盘类型等。
1. 基本用法
最基础的 TextField
只需要绑定一个 @State
状态变量来存储用户的输入。以下是一个简单的示例:
import SwiftUI
struct ContentView: View {
@State private var name = ""
var body: some View {
VStack {
TextField("Enter your name", text: $name)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle()) // 添加样式
Text("Hello, \(name)")
}
.padding()
}
}
关键点:
- 绑定到
@State
:TextField
需要绑定到一个@State
或@Binding
变量,这个变量用来存储输入的文本。 - 占位符:
"Enter your name"
是占位符,在用户输入前显示提示信息。 - 样式:使用
.textFieldStyle()
为TextField
添加边框样式,例如RoundedBorderTextFieldStyle()
提供一个常见的圆角边框。
2. 添加提示文本
占位符(placeholder
)是 TextField
的重要组成部分,用于在输入框为空时为用户提供提示。你可以通过第一个参数传递占位符。
TextField("Enter your email", text: $email)
在这个例子中,"Enter your email"
会在文本框为空时显示。
3. 自定义样式
可以通过多种修饰符来自定义 TextField
的外观,如颜色、边框、背景等。
示例:
TextField("Enter your name", text: $name)
.padding()
.background(Color.gray.opacity(0.2))
.cornerRadius(5)
.foregroundColor(.blue) // 文本颜色
.font(.system(size: 18, weight: .medium, design: .rounded)) // 自定义字体
.textFieldStyle(PlainTextFieldStyle()) // 使用普通样式
关键点:
- 背景颜色:通过
.background()
修改TextField
的背景颜色。 - 文本颜色:使用
.foregroundColor()
修改输入文本的颜色。 - 字体:可以通过
.font()
设置字体样式、大小、字体粗细等。 - 样式:
PlainTextFieldStyle()
是默认的样式,除此之外,还有RoundedBorderTextFieldStyle()
。
4. 添加占位符(提示文本)
在默认情况下,TextField
只显示一个占位符作为提示。我们可以使用第一个参数设置占位符,如下:
TextField("Enter your username", text: $username)
这里 "Enter your username"
是占位符,当用户没有输入内容时,会显示这段提示文本。
5. 控制输入的格式和内容
SwiftUI 提供了多种修饰符来控制用户在 TextField
中的输入,比如限制输入字符数、禁用自动大写、设置键盘类型等。
5.1 限制输入字符数
你可以通过 onChange(of:)
修饰符来监听输入内容的变化,从而限制字符数。
import SwiftUI
struct ContentView: View {
@State private var username = ""
var body: some View {
TextField("Enter your username", text: $username)
.onChange(of: username) { newValue in
if newValue.count > 10 {
username = String(newValue.prefix(10)) // 限制最多 10 个字符
}
}
.padding()
.border(Color.gray)
}
}
5.2 设置键盘类型
你可以通过 .keyboardType()
来设置 TextField
需要的键盘类型(如数字键盘、邮件键盘等),这在处理特定输入类型时非常有用。
TextField("Enter your email", text: $email)
.keyboardType(.emailAddress) // 使用电子邮件键盘
常见的键盘类型有:
.default
:默认键盘.emailAddress
:电子邮件键盘.numberPad
:数字键盘.decimalPad
:带有小数点的数字键盘.asciiCapable
:只显示 ASCII 字符的键盘
5.3 禁用自动大写
默认情况下,iOS 会在某些情况下自动将用户输入的第一个字母大写。可以通过 .autocapitalization()
修饰符来控制这种行为。
TextField("Enter your name", text: $name)
.autocapitalization(.none) // 禁用自动大写
其他常见的选项:
.none
:禁用自动大写。.words
:每个单词首字母大写。.sentences
:每个句子首字母大写。
6. 安全输入(密码输入)
SecureField
是专门用于处理密码输入的组件,它会隐藏用户的输入内容,显示成类似于圆点或星号的符号。
import SwiftUI
struct ContentView: View {
@State private var password = ""
var body: some View {
SecureField("Enter your password", text: $password)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
关键点:
SecureField
:用于输入密码或敏感信息,输入内容不可见。- 样式:
SecureField
支持和TextField
相同的修饰符和样式。
7. 添加输入提示(Label)
可以在 TextField
左右侧添加一个 Label
或图标,提供额外的视觉提示。
HStack {
Image(systemName: "person.fill")
TextField("Enter your username", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding()
关键点:
HStack
:通过HStack
将TextField
和图标组合在一起,形成带提示的输入框。- 图标:使用 SF Symbols 图标作为视觉提示。
8. 监听输入完成(Return/Enter键)
通过 onSubmit
监听用户按下键盘上的 Return/Enter 键来触发某些操作。
TextField("Enter your name", text: $name)
.onSubmit {
print("User pressed return with input: \(name)")
}
9. 文本输入验证
你可以通过自定义逻辑验证输入的文本,并根据条件给用户反馈。
import SwiftUI
struct ContentView: View {
@State private var email = ""
@State private var isValid = true
var body: some View {
VStack {
TextField("Enter your email", text: $email)
.keyboardType(.emailAddress)
.autocapitalization(.none)
.onChange(of: email) { newValue in
isValid = isValidEmail(newValue)
}
.padding()
.border(isValid ? Color.gray : Color.red)
Text(isValid ? "" : "Invalid email address")
.foregroundColor(.red)
}
.padding()
}
func isValidEmail(_ email: String) -> Bool {
// 简单的 email 验证逻辑
return email.contains("@") && email.contains(".")
}
}
关键点:
- 自定义验证:可以通过
onChange(of:)
实现自定义的输入验证逻辑。 - 验证反馈:通过更改边框颜色和文本提示来向用户提供反馈。
1-36 TextEditer()
//
// TextEditers.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/6.
//
import SwiftUI
struct TextEditers: View {
@State var textEditorText : String = "this is the starting text"
var body: some View {
NavigationView {
VStack {
TextEditor(text: $textEditorText)
.frame(height: 150)
.colorMultiply(Color.gray)
.cornerRadius(15)
Button {
} label: {
Text("Save".uppercased())
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
Spacer()
}
.padding()
.background(Color.green.opacity(0.5))
.navigationTitle("TextEditor")
}
}
}
#Preview {
TextEditers()
}
1-37 Toggle()
//
// toggles.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/6.
//
import SwiftUI
struct Toggles: View {
@State var tonggleState : Bool = false
var body: some View {
Toggle(isOn: $tonggleState) {
Text("Toggle01")
}
.padding()
.toggleStyle(SwitchToggleStyle(tint: Color.red))
Toggle(isOn: $tonggleState) {
Text("Toggle02")
}
.padding()
.toggleStyle(SwitchToggleStyle(tint: Color.green))
Toggle(isOn: $tonggleState) {
Text("Toggle03")
}
.padding()
.toggleStyle(SwitchToggleStyle(tint: Color.purple))
}
}
#Preview {
Toggles()
}
在 SwiftUI 中,Toggle
是一个常用的控件,用于创建开关,用户可以通过点击切换开关的状态(开
或关
)。具体来说,Toggle
可以与布尔值(如 $toggleState
)绑定,表示它的当前状态。当用户与控件交互时,绑定的值会自动更新。
1,基本使用
@State private var toggleState = false
var body: some View {
Toggle(isOn: $toggleState) {
Text("Toggle01")
}
}
isOn: $toggleState
:表示开关当前状态与toggleState
绑定。当用户切换开关时,toggleState
的值会在true
和false
之间变化。Text("Toggle01")
:Toggle
控件的标签部分。在上面的例子中,Text
作为标签显示文本。
2,重要参数
- isOn (Binding<Bool>): 这个是核心参数,它表示
Toggle
的当前状态,并且与绑定的布尔值保持同步。 - label: 你可以通过提供一个视图作为标签,通常是文本(
Text
),但你可以使用任何 SwiftUI 视图。
3,完整示例
struct ContentView: View {
@State private var toggleState = false
var body: some View {
VStack {
Toggle(isOn: $toggleState) {
Text("Enable Feature")
}
.padding()
if toggleState {
Text("Feature is enabled")
.foregroundColor(.green)
} else {
Text("Feature is disabled")
.foregroundColor(.red)
}
}
}
}
4,说明:
- 当
toggleState
为true
时,显示 "Feature is enabled";当为false
时,显示 "Feature is disabled"。 .padding()
为控件增加一些内边距。@State
修饰符用于声明一个状态变量,用来持有Toggle
的状态。
5,自定义样式
你还可以对 Toggle
进行自定义样式,比如改变颜色、大小等:
Toggle(isOn: $toggleState) {
Text("Custom Toggle")
}
.toggleStyle(SwitchToggleStyle(tint: .blue)) // 自定义切换开关颜色
这里我们使用了 SwitchToggleStyle(tint:)
来自定义开关的颜色。
1-38 Picker()
import SwiftUI
struct PickerView: View {
@State private var selectedOption = 0
let options = ["Most Recent", "Most Popular", "Most Liked"]
var body: some View {
VStack {
HStack {
ForEach(0..<options.count, id: \.self) { index in
Text(self.options[index])
.padding()
.frame(maxWidth: .infinity) // 占满可用空间
.background(self.selectedOption == index ? Color.green : Color.gray.opacity(0.2))
.foregroundColor(self.selectedOption == index ? Color.white : Color.black)
.cornerRadius(8)
.onTapGesture {
self.selectedOption = index
}
}
}
.padding()
List {
if selectedOption == 0 {
Text("Displaying Most Recent")
} else if selectedOption == 1 {
Text("Displaying Most Popular")
} else {
Text("Displaying Most Liked")
}
}
}
.padding()
}
}
struct PickerView_Previews: PreviewProvider {
static var previews: some View {
PickerView()
}
}
1. 基础 Picker 示例
我们从一个简单的例子开始,展示如何创建一个带有固定选项的 Picker
。你可以从一组选项中选择其中之一。
import SwiftUI
struct ContentView: View {
// 定义选择的选项
let fruits = ["Apple", "Banana", "Orange", "Grapes"]
// 使用 @State 保存用户选择的索引
@State private var selectedFruit = 0
var body: some View {
VStack {
// Picker 控件
Picker("Select your favorite fruit", selection: $selectedFruit) {
ForEach(0..<fruits.count) { index in
Text(self.fruits[index]).tag(index)
}
}
.pickerStyle(WheelPickerStyle()) // 使用滚轮风格
.padding()
// 显示选择结果
Text("You selected: \(fruits[selectedFruit])")
.font(.title)
.padding()
}
}
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
解析
fruits
: 这是我们可以选择的选项数组。@State private var selectedFruit = 0
:@State
是一种 SwiftUI 的属性包装器,它会在视图中保存一个状态。这里我们用它来保存选中的水果索引,默认为 0。Picker
使用selection: $selectedFruit
绑定用户选择的值到selectedFruit
变量。ForEach(0..<fruits.count)
:ForEach
循环用于动态生成 Picker 中的选项。每个选项使用tag()
来标记其唯一的索引。.pickerStyle(WheelPickerStyle())
: 这是Picker
的样式之一,类似于 iOS 系统的滚轮选择器。
2. 使用不同的 Picker 样式
SwiftUI 提供了多种 Picker
样式。除了上面的 WheelPickerStyle()
,还有其他几种常见的样式。你可以根据需求选择适合的样式。
示例:使用 SegmentedPickerStyle
Picker("Select your favorite fruit", selection: $selectedFruit) {
ForEach(0..<fruits.count) { index in
Text(self.fruits[index]).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle()) // 分段控制样式
这种样式会将选项显示为一个分段控制(类似于 UISegmentedControl)。适合选项较少时使用。
示例:使用 MenuPickerStyle
Picker("Select your favorite fruit", selection: $selectedFruit) {
ForEach(0..<fruits.count) { index in
Text(self.fruits[index]).tag(index)
}
}
.pickerStyle(MenuPickerStyle()) // 菜单样式
MenuPickerStyle
会将选项显示为下拉菜单,适合在 macOS 或 iPad 上使用,或当你希望使用更紧凑的选择方式时。
3. 结合枚举使用 Picker
有时你可能会使用枚举类型作为 Picker 的数据源。枚举让代码更具可读性,并且避免了手动管理字符串或索引。
import SwiftUI
struct ContentView: View {
// 定义水果枚举
enum Fruit: String, CaseIterable, Identifiable {
case apple = "Apple"
case banana = "Banana"
case orange = "Orange"
case grapes = "Grapes"
var id: String { self.rawValue }
}
// 使用 @State 保存用户选择的值
@State private var selectedFruit: Fruit = .apple
var body: some View {
VStack {
// 枚举 Picker
Picker("Select your favorite fruit", selection: $selectedFruit) {
ForEach(Fruit.allCases) { fruit in
Text(fruit.rawValue).tag(fruit)
}
}
.pickerStyle(SegmentedPickerStyle()) // 使用分段控制样式
.padding()
// 显示选择结果
Text("You selected: \(selectedFruit.rawValue)")
.font(.title)
.padding()
}
}
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
解析
enum Fruit: String, CaseIterable, Identifiable
: 我们创建了一个枚举Fruit
,每个值代表一种水果。CaseIterable
让我们可以访问所有枚举的值,而Identifiable
确保每个值都是唯一的。Picker
使用ForEach(Fruit.allCases)
迭代枚举中的所有选项。tag(fruit)
绑定每个选项的值到 Picker 的选择状态中。
4. Picker 与自定义对象
有时候,你可能需要展示复杂的数据对象,而不仅仅是简单的字符串。以下是如何将 Picker
与自定义对象结合使用的例子:
import SwiftUI
struct ContentView: View {
struct Car: Identifiable {
let id = UUID()
let name: String
}
// 定义汽车数组
let cars = [
Car(name: "Tesla Model S"),
Car(name: "BMW i8"),
Car(name: "Audi e-tron"),
Car(name: "Mercedes EQS")
]
// 使用 @State 保存用户选择的汽车
@State private var selectedCar: Car?
var body: some View {
VStack {
// Picker 控件
Picker("Select your favorite car", selection: $selectedCar) {
ForEach(cars) { car in
Text(car.name).tag(car as Car?)
}
}
.pickerStyle(WheelPickerStyle())
.padding()
// 显示选择结果
if let selectedCar = selectedCar {
Text("You selected: \(selectedCar.name)")
.font(.title)
.padding()
} else {
Text("Please select a car")
.font(.title)
.padding()
}
}
}
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
解析
Car
: 自定义结构体,包含name
和id
属性(通过Identifiable
协议使其可以在ForEach
中使用)。@State private var selectedCar: Car?
: 使用可选的Car
类型来保存用户选择。tag(car as Car?)
: 每个选项使用Car?
类型的tag
,这样我们可以将nil
作为一种选择。
1-39 ColorPicker()
//
// ColorPicker.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/22.
//
import SwiftUI
struct ColorsPicker: View {
@State var backgroundColor : Color = .green
var body: some View {
ZStack {
backgroundColor.edgesIgnoringSafeArea(.all)
ColorPicker("Select a Color",
selection: $backgroundColor,
supportsOpacity: true)
.padding()
.background(Color.black)
.cornerRadius(10)
.foregroundColor(Color.white)
.font(.headline)
.padding(50)
}
}
}
#Preview {
ColorsPicker()
}
当然可以!ColorPicker
是 SwiftUI 中用于让用户选择颜色的控件。它允许用户从颜色选择器中挑选颜色,并且可以选择是否支持透明度。接下来,我会详细讲解 ColorPicker
的用法,并展示多个用例。
1,基本用法
ColorPicker
的基本语法非常简单。它有一个标题,用于描述它的功能,还有一个绑定的 Color
类型的变量,用于存储用户选择的颜色。
import SwiftUI
struct CustomColorPickerView: View {
// 绑定颜色变量
@State var selectedColor: Color = .blue
var body: some View {
VStack {
// 显示当前选择的颜色
Rectangle()
.fill(selectedColor)
.frame(width: 200, height: 200)
.padding()
// ColorPicker 控件
ColorPicker("Select a color", selection: $selectedColor)
.padding()
}
}
}
#Preview {
CustomColorPickerView()
}
@State var selectedColor: Color
: 这是一个使用@State
声明的颜色变量,存储用户通过ColorPicker
选择的颜色。ColorPicker("Select a color", selection: $selectedColor)
:ColorPicker
是 SwiftUI 提供的选择颜色的控件。selection
参数绑定到我们声明的@State
变量,使得用户选择的颜色会自动更新这个变量。Rectangle().fill(selectedColor)
: 我们用一个简单的矩形来显示用户选择的颜色。
2,添加透明度支持
你可以通过设置 supportsOpacity
来让 ColorPicker
支持透明度调整。设置 supportsOpacity: true
后,用户可以选择颜色的透明度。
import SwiftUI
struct CustomColorPickerViewWithOpacity: View {
@State var selectedColor: Color = .blue
var body: some View {
VStack {
Rectangle()
.fill(selectedColor)
.frame(width: 200, height: 200)
.padding()
// 支持透明度的 ColorPicker
ColorPicker("Select a color with opacity", selection: $selectedColor, supportsOpacity: true)
.padding()
}
}
}
#Preview {
CustomColorPickerViewWithOpacity()
}
supportsOpacity: true
: 启用透明度支持,用户可以选择不透明度。选择的颜色会包括透明度信息。
3,自定义布局和样式
你可以将 ColorPicker
结合其他视图,创建更复杂的布局。例如,将 ColorPicker
嵌入到表单或自定义布局中。
import SwiftUI
struct StyledColorPickerView: View {
@State private var backgroundColor: Color = .yellow
@State private var textColor: Color = .black
var body: some View {
VStack(spacing: 20) {
// 显示当前选择的背景色和文字颜色
Text("Color Picker Example")
.foregroundColor(textColor)
.font(.largeTitle)
.padding()
.background(backgroundColor)
.cornerRadius(10)
// 背景色选择器
HStack {
Text("Select background color:")
ColorPicker("", selection: $backgroundColor)
.labelsHidden() // 隐藏默认的文字标签
}
.padding()
// 文字颜色选择器
HStack {
Text("Select text color:")
ColorPicker("", selection: $textColor)
.labelsHidden()
}
.padding()
}
.padding()
}
}
#Preview {
StyledColorPickerView()
}
labelsHidden()
: 隐藏默认的标签,适合你想完全自定义布局时使用。- 两个
ColorPicker
分别用于选择背景颜色和文字颜色,结果会即时显示在文本和背景中。
4,使用自定义颜色对象
有时候你可能想要将颜色选择结果用于自定义对象中,或将颜色存储在复杂的数据结构中。
import SwiftUI
struct Car {
var name: String
var color: Color
}
struct CarColorPickerView: View {
@State private var car = Car(name: "Sports Car", color: .red)
var body: some View {
VStack {
Text("Customize your car")
.font(.title)
.padding()
// 显示当前选择的颜色
Rectangle()
.fill(car.color)
.frame(width: 200, height: 100)
.overlay(
Text(car.name)
.foregroundColor(.white)
.font(.headline)
)
.padding()
// 选择汽车颜色
ColorPicker("Select car color", selection: $car.color)
.padding()
}
}
}
#Preview {
CarColorPickerView()
}
- 这里我们创建了一个自定义的
Car
对象,它包含name
和color
两个属性。 @State private var car = Car(name: "Sports Car", color: .red)
: 使用@State
存储汽车的颜色信息,并绑定到ColorPicker
。- 用户通过
ColorPicker
修改颜色后,car.color
会即时更新。
5,结合 Form
使用
ColorPicker
也可以和 Form
控件一起使用,适用于设置页面或表单中的颜色选择功能。
import SwiftUI
struct FormColorPickerView: View {
@State private var backgroundColor: Color = .green
@State private var textColor: Color = .white
var body: some View {
Form {
Section(header: Text("Customize Colors")) {
ColorPicker("Background color", selection: $backgroundColor)
ColorPicker("Text color", selection: $textColor)
}
Section {
Text("Preview")
.font(.largeTitle)
.padding()
.foregroundColor(textColor)
.background(backgroundColor)
.cornerRadius(10)
}
}
}
}
#Preview {
FormColorPickerView()
}
Form
:Form
是 SwiftUI 中常用的表单控件,通常用于构建设置页面。Section
:Section
用于分组不同的表单元素,并且支持添加标题和分隔线。
1-40 DataPicker()
基本用法
DatePicker
允许用户从日历中选择日期或时间。以下是一个简单的例子,展示了如何使用 DatePicker
选择日期。
示例 1:简单的日期选择器
import SwiftUI
struct SimpleDatePickerView: View {
// 使用 @State 来保存选定的日期
@State private var selectedDate = Date()
var body: some View {
VStack {
// DatePicker 控件
DatePicker("Select a date", selection: $selectedDate)
.datePickerStyle(GraphicalDatePickerStyle()) // 图形风格
.padding()
// 显示选定日期
Text("Selected date: \(selectedDate, formatter: dateFormatter)")
.padding()
}
}
// 日期格式化器
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
}
#Preview {
SimpleDatePickerView()
}
解析
@State private var selectedDate = Date()
: 使用@State
声明一个Date
类型的变量,来存储用户选择的日期。DatePicker("Select a date", selection: $selectedDate)
:DatePicker
控件允许用户选择日期,selection
参数绑定到我们定义的@State
变量,使得选择的日期会自动保存到selectedDate
中。.datePickerStyle(GraphicalDatePickerStyle())
: 这里我们使用图形化的日期选择器风格(类似于日历的界面)。
结果
这段代码将展示一个图形化的日历视图,用户可以从中选择日期。下方会显示当前选择的日期,格式为长格式(例如 "October 22, 2024")。
DatePicker 样式
SwiftUI 提供了几种不同的 DatePicker
样式,你可以根据需求选择不同的风格来展现日期选择器。
示例 2:不同的样式
import SwiftUI
struct DatePickerStylesView: View {
@State private var selectedDate = Date()
var body: some View {
VStack {
// 默认样式
DatePicker("Default Style", selection: $selectedDate)
.padding()
// 轮播选择样式
DatePicker("Wheel Style", selection: $selectedDate)
.datePickerStyle(WheelDatePickerStyle()) // 使用轮播风格
.padding()
// 图形化样式
DatePicker("Graphical Style", selection: $selectedDate)
.datePickerStyle(GraphicalDatePickerStyle()) // 使用图形化风格
.padding()
// 紧凑样式(适合放在表单或小空间)
DatePicker("Compact Style", selection: $selectedDate)
.datePickerStyle(CompactDatePickerStyle()) // 使用紧凑风格
.padding()
}
}
}
#Preview {
DatePickerStylesView()
}
解析
WheelDatePickerStyle()
: 类似 iOS 上的滚轮选择器。GraphicalDatePickerStyle()
: 图形化的日历样式。CompactDatePickerStyle()
: 紧凑的选择器,通常在较小的空间里使用,例如表单中。
选择时间或日期与时间
你可以通过配置 DatePicker
来让它选择时间、日期,或者两者的组合。
示例 3:选择日期和时间
import SwiftUI
struct DateTimePickerView: View {
@State private var selectedDate = Date()
var body: some View {
VStack {
// 日期和时间选择
DatePicker("Select date and time", selection: $selectedDate, displayedComponents: [.date, .hourAndMinute])
.padding()
// 显示选定的日期和时间
Text("Selected: \(selectedDate, formatter: dateTimeFormatter)")
.padding()
}
}
// 日期时间格式化器
var dateTimeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .short
return formatter
}
}
#Preview {
DateTimePickerView()
}
解析
displayedComponents: [.date, .hourAndMinute]
: 使用displayedComponents
参数来控制DatePicker
显示日期和时间。你可以选择.date
仅显示日期,或者.hourAndMinute
仅显示时间,或者两者组合。formatter.dateStyle = .long
和formatter.timeStyle = .short
: 通过DateFormatter
来定义日期和时间的显示格式。
限制日期选择范围
在某些情况下,你可能希望限制用户只能选择某个范围内的日期。可以通过 in
参数来设置日期选择的范围。
示例 4:限制选择的日期范围
import SwiftUI
struct LimitedDatePickerView: View {
@State private var selectedDate = Date()
// 定义开始日期和结束日期
let startDate = Calendar.current.date(byAdding: .year, value: -1, to: Date()) ?? Date()
let endDate = Calendar.current.date(byAdding: .year, value: 1, to: Date()) ?? Date()
var body: some View {
VStack {
// 限制选择范围
DatePicker("Select a date within range", selection: $selectedDate, in: startDate...endDate)
.padding()
Text("Selected date: \(selectedDate, formatter: dateFormatter)")
.padding()
}
}
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
}
#Preview {
LimitedDatePickerView()
}
解析
in: startDate...endDate
: 使用这个参数来限制用户只能选择startDate
和endDate
之间的日期。这里的范围是当前日期的前一年到后一年。Calendar.current.date(byAdding: .year, value: -1, to: Date())
: 通过Calendar
来动态计算日期,例如过去一年或未来一年。
选择仅限时间
你可以创建一个只选择时间的 DatePicker
,用于例如提醒、倒计时等场景。
示例 5:只选择时间
import SwiftUI
struct TimePickerView: View {
@State private var selectedTime = Date()
var body: some View {
VStack {
// 只选择时间
DatePicker("Select a time", selection: $selectedTime, displayedComponents: .hourAndMinute)
.labelsHidden() // 隐藏标签
.padding()
Text("Selected time: \(selectedTime, formatter: timeFormatter)")
.padding()
}
}
var timeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.timeStyle = .short
return formatter
}
}
#Preview {
TimePickerView()
}
解析
displayedComponents: .hourAndMinute
: 只显示时间部分。labelsHidden()
: 隐藏DatePicker
的默认标签,如果你想让选择器独立显示,这个方法非常有用。
自定义 DatePicker
与 Form
结合
在实际应用中,DatePicker
通常会和表单配合使用,尤其是当你需要收集用户的多个输入时,比如一个事件的日期和时间。
示例 6:在表单中使用 DatePicker
import SwiftUI
struct FormWithDatePickerView: View {
@State private var eventDate = Date()
var body: some View {
Form {
Section(header: Text("Event Details")) {
DatePicker("Event Date", selection: $eventDate, displayedComponents: [.date])
DatePicker("Event Time", selection: $eventDate, displayedComponents: [.hourAndMinute])
}
Section(header: Text("Summary")) {
Text("Event Date: \(eventDate, formatter: dateFormatter)")
Text("Event Time: \(eventDate, formatter: timeFormatter)")
}
}
}
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
var timeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.timeStyle = .short
return formatter
}
}
#Preview {
FormWithDatePickerView()
}
解析
Form
是一个用于构建表单的 SwiftUI 组件,常用于输入页面。- 通过
DatePicker
可以让用户选择事件的日期和时间,并在表单中回显选择结果。
DatePicker
是 SwiftUI 中非常灵活的组件,允许你轻松地选择日期和
时间。它有多种配置方式和样式,可以根据你的 UI 需求进行调整。以下是常用功能的总结:
- 样式选择:通过
.datePickerStyle()
可以选择不同的样式,如GraphicalDatePickerStyle
、WheelDatePickerStyle
和CompactDatePickerStyle
。 - 日期范围限制:通过
in:
参数可以限制日期选择的范围。 - 选择日期、时间或两者组合:通过
displayedComponents
参数,你可以控制DatePicker
显示日期、时间或两者。 - 结合表单使用:
DatePicker
经常和Form
配合使用,以构建更复杂的输入界面。
1-41 Stepper()
//
// Stepper_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/22.
//
import SwiftUI
struct Stepper_L: View {
@State var stepperValue: Int = 10
@State var widthInc: CGFloat = 10
@State var heightInc: CGFloat = 10
var body: some View {
VStack {
// 显示当前的 stepperValue
Text("Stepper Value: \(stepperValue)")
// 第一个 Stepper
Stepper("Stepper", value: $stepperValue, in: 0...100, step: 1)
.padding(30)
// 动态调整矩形大小,并将 Int 转换为 CGFloat
RoundedRectangle(cornerRadius: 10.0)
.frame(width: CGFloat(100 + widthInc), height: CGFloat(100 + heightInc))
.padding()
// 第二个 Stepper 用于调整宽度和高度的增量
Stepper("Adjust Rectangle Size") {
// onIncrement:增加宽度和高度
incremenatWidth(amount: 10)
} onDecrement: {
// onDecrement:减少宽度和高度
incremenatWidth(amount: -10)
}
.padding(30)
}
}
func incremenatWidth(amount: CGFloat){
withAnimation(.easeInOut){
widthInc += amount
heightInc += amount
}
}
}
#Preview {
Stepper_L()
}
1-42 Slider()
Slider
的基本用法
Slider
允许你在一个指定的范围内选择一个浮点数值。你可以通过绑定的 @State
变量来获取和更新滑块的值。
示例 1:简单的滑块控件
import SwiftUI
struct SimpleSliderView: View {
// 绑定滑块的值
@State private var sliderValue: Double = 50.0
var body: some View {
VStack {
// 显示当前滑块的值
Text("Slider Value: \(sliderValue, specifier: "%.2f")")
.font(.title)
.padding()
// Slider 控件
Slider(value: $sliderValue, in: 0...100)
.padding()
}
}
}
#Preview {
SimpleSliderView()
}
解析:
@State private var sliderValue: Double = 50.0
:使用@State
来声明一个双精度浮点数(Double
),它会保存用户通过滑块选择的值。Slider(value: $sliderValue, in: 0...100)
:Slider
控件允许用户选择的值范围是 0 到 100。specifier: "%.2f"
:用来限制显示的值的小数点后两位。
Slider
的基本参数
value
: 必需参数,用于绑定滑块当前值,通常使用@State
来持有这个值。in
: 用来指定滑块的范围(最小值和最大值)。step
: 可选参数,用来指定滑块的步长(即滑块的移动精度),例如步长为1
表示每次滑动最小变化单位是1
。label
: 滑块旁边的描述性文字,通常隐藏或自定义。
示例 2:带步长的滑块
import SwiftUI
struct StepSliderView: View {
@State private var sliderValue: Double = 10.0
var body: some View {
VStack {
Text("Slider Value: \(Int(sliderValue))")
.font(.title)
.padding()
// 滑块步长为 1
Slider(value: $sliderValue, in: 0...20, step: 1)
.padding()
}
}
}
#Preview {
StepSliderView()
}
解析:
step: 1
:步长为1
,意味着滑块每次滑动的最小单位是1
。这适合用于整数范围选择,比如音量、亮度调节。
定制滑块的最小值和最大值
你可以通过 minimumValueLabel
和 maximumValueLabel
自定义滑块的起始值和终点值的显示方式。
示例 3:自定义最小值和最大值的显示
import SwiftUI
struct CustomLabelSliderView: View {
@State private var sliderValue: Double = 25.0
var body: some View {
VStack {
Text("Slider Value: \(Int(sliderValue))")
.font(.title)
.padding()
// 带有自定义最小值和最大值标签的滑块
Slider(value: $sliderValue, in: 0...100, step: 1) {
Text("Custom Slider")
} minimumValueLabel: {
Text("0%") // 自定义最小值标签
} maximumValueLabel: {
Text("100%") // 自定义最大值标签
}
.padding()
}
}
}
#Preview {
CustomLabelSliderView()
}
解析:
minimumValueLabel
和maximumValueLabel
:自定义滑块的最小值和最大值的标签,帮助用户更好地理解当前值范围。Text("Custom Slider")
:可以为滑块提供描述性文字,不过通常不显示它,除非你在界面布局中有特别的需求。
将滑块与颜色或形状等其他视图结合
你可以将 Slider
的值与其他视图相结合,例如动态调整视图的颜色、大小、透明度等。
示例 4:动态调整颜色的滑块
import SwiftUI
struct ColorSliderView: View {
@State private var red: Double = 0.5
@State private var green: Double = 0.5
@State private var blue: Double = 0.5
var body: some View {
VStack {
// 显示颜色
RoundedRectangle(cornerRadius: 10)
.fill(Color(red: red, green: green, blue: blue))
.frame(width: 200, height: 200)
.padding()
// 红色滑块
Slider(value: $red, in: 0...1)
.accentColor(.red)
.padding()
// 绿色滑块
Slider(value: $green, in: 0...1)
.accentColor(.green)
.padding()
// 蓝色滑块
Slider(value: $blue, in: 0...1)
.accentColor(.blue)
.padding()
}
}
}
#Preview {
ColorSliderView()
}
解析:
Color(red: green: blue:)
:使用滑块的值动态调整颜色的 RGB 值,范围是0.0
到1.0
。accentColor(.red)
:为滑块自定义颜色,直观显示每个滑块控制的是哪个颜色通道。
带图标的滑块
你可以将滑块与图标结合,例如音量调节时加上音量的图标。
示例 5:音量调节滑块
import SwiftUI
struct VolumeSliderView: View {
@State private var volume: Double = 50.0
var body: some View {
VStack {
HStack {
// 最小音量图标
Image(systemName: "speaker.fill")
// 音量滑块
Slider(value: $volume, in: 0...100, step: 1)
.padding()
// 最大音量图标
Image(systemName: "speaker.3.fill")
}
// 显示当前音量
Text("Volume: \(Int(volume))")
.font(.headline)
.padding()
}
}
}
#Preview {
VolumeSliderView()
}
解析:
- 使用
Image(systemName:)
加入系统图标,使滑块更直观。 Slider
的值绑定到volume
,滑块的范围是0
到100
。
滑块与进度条结合
滑块的值可以与进度条(ProgressView
)结合,显示滑动进度。
示例 6:进度条滑块
import SwiftUI
struct ProgressSliderView: View {
@State private var progress: Double = 0.0
var body: some View {
VStack {
// 显示当前进度
ProgressView(value: progress)
.padding()
// 滑块控制进度
Slider(value: $progress, in: 0...1)
.padding()
Text("Progress: \(Int(progress * 100))%")
.font(.headline)
.padding()
}
}
}
#Preview {
ProgressSliderView()
}
解析:
ProgressView(value: progress)
:使用滑块的值来控制进度条,滑块范围设置为0...1
。progress * 100
:将进度转换为百分比显示。
总结
Slider
是 SwiftUI 中一个功能强大的控件,能够灵活调整数值,并通过与其他 UI 组件结合,创建出丰富的用户交互。总结一下常见的用法:
基础参数:
value
: 绑定滑块的值。in
: 指定滑块的数值范围。step
: 指定滑块的步长,默认为1
,可以使用更精细的步长如0.1
。
自定义滑块样式:
minimumValueLabel
和maximumValueLabel
可以用来设置滑块的最小值和最大值的自定义标签。- 使用
accentColor()
可以改变滑块的颜色。
结合其他 UI 元素:
- 可以动态调整视图的属性,比如颜色、大小、透明度。
- 与
Image
和Text
相结合,可以创建更加直观的控件,例如音量控制、亮度调节等。
1-43 TabView()
//
// TabView_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/23.
//
import SwiftUI
struct TabView_L: View {
var body: some View {
TabView {
HomeView()
.tabItem{
Image(systemName: "house.fill")
Text("Home")
}
Text("Brown Tab")
.tabItem{
Image(systemName: "globe")
Text("Brower")
}
Text("PROFILE Tab")
.tabItem{
Image(systemName: "person.fill")
Text("Profile")
}
}
.tint(Color.pink)
}
}
#Preview {
TabView_L()
}
struct HomeView: View {
var body: some View {
ZStack{
Color.green.ignoresSafeArea()
Text("Home Tab")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
//
// TabView_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/23.
//
import SwiftUI
struct TabView_L: View {
@State var selectedTag : Int = 1
var body: some View {
TabView (selection:$selectedTag){
HomeView(selectedTab: $selectedTag)
.tabItem{
Image(systemName: "house.fill")
Text("Home")
}
.tag(0)
Text("Brown Tab")
.tabItem{
Image(systemName: "globe")
Text("Brower")
}
.tag(1)
Text("PROFILE Tab")
.tabItem{
Image(systemName: "person.fill")
Text("Profile")
}
.tag(2)
}
.tint(Color.pink)
}
}
#Preview {
TabView_L()
}
struct HomeView: View {
@Binding var selectedTab : Int
var body: some View {
ZStack{
Color.green.edgesIgnoringSafeArea(.top)
VStack {
Text("Home Tab")
.font(.largeTitle)
.foregroundColor(.white)
Button {
selectedTab = 2
} label: {
Text("Go To Profile")
.font(.headline)
.padding()
.padding(.horizontal)
.background(Color.white.clipShape(RoundedRectangle(cornerRadius: 10)))
}
}
}
}
}
//
// TabView_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/23.
//
import SwiftUI
struct TabView_L: View {
@State var selectedTag : Int = 1
let icons : [String] = [
"heart.fill","globe","house.fill","person.fill"
]
var body: some View {
TabView (selection:$selectedTag){
ForEach(icons, id: \.self) { icon in
Image(systemName: icon)
.resizable()
.scaledToFit()
}
}
.background(
RadialGradient(
gradient: Gradient(colors: [Color.red,Color.green,Color.pink]),
center: .center,
startRadius: 5,
endRadius: 300
)
)
.frame(height: 300)
.tabViewStyle(PageTabViewStyle())
}
}
#Preview {
TabView_L()
}
1. TabView
简介
TabView
是 SwiftUI 中用于创建带有多个选项卡(Tabs)的容器视图。每个选项卡可以显示不同的内容,用户可以通过点击不同的选项卡在各个视图之间切换。这种界面非常适合用于应用的主要导航结构,例如底部导航栏。
1.1 TabView
的基本结构
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
Text("首页")
.tabItem {
Image(systemName: "house.fill")
Text("首页")
}
Text("收藏")
.tabItem {
Image(systemName: "star.fill")
Text("收藏")
}
}
}
}
解释:
TabView
包含多个子视图,每个子视图对应一个选项卡。tabItem
修饰符用于定义每个选项卡的图标和标题。Image(systemName:)
使用 SF Symbols 提供的系统图标。
2. 创建基本的 TabView
让我们创建一个包含两个选项卡的简单 TabView
,分别显示“首页”和“个人资料”视图。
2.1 定义视图
首先,定义两个简单的视图 HomeView
和 ProfileView
。
struct HomeView: View {
var body: some View {
VStack {
Text("欢迎来到首页")
.font(.largeTitle)
Image(systemName: "house")
.resizable()
.frame(width: 100, height: 100)
.foregroundColor(.blue)
}
}
}
struct ProfileView: View {
var body: some View {
VStack {
Text("这是你的个人资料")
.font(.largeTitle)
Image(systemName: "person.circle")
.resizable()
.frame(width: 100, height: 100)
.foregroundColor(.green)
}
}
}
2.2 将视图添加到 TabView
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text("首页")
}
ProfileView()
.tabItem {
Image(systemName: "person.fill")
Text("个人")
}
}
}
}
解释:
HomeView
和ProfileView
被添加到TabView
中,每个视图通过tabItem
定义对应的图标和标题。
3. 使用 PageTabViewStyle
PageTabViewStyle
允许 TabView
以分页(滑动)的方式展示内容,类似于 UIPageViewController
。这对于需要横向滑动浏览多个页面的场景非常有用。
3.1 基本使用
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
Text("第一页")
.font(.largeTitle)
.foregroundColor(.white)
.background(Color.red)
.cornerRadius(10)
Text("第二页")
.font(.largeTitle)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(10)
Text("第三页")
.font(.largeTitle)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(10)
}
.tabViewStyle(PageTabViewStyle())
}
}
解释:
- 使用
.tabViewStyle(PageTabViewStyle())
将TabView
设置为分页样式。 - 用户可以通过左右滑动来切换不同的页面。
3.2 添加分页指示器
默认情况下,PageTabViewStyle
会显示一个分页指示器(点点)。你可以自定义其显示方式。
TabView {
// 页面内容
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
选项:
.automatic
: 根据设备自动决定是否显示指示器。.always
: 始终显示指示器。.never
: 从不显示指示器。
4. 结合 TabView
和 PageTabViewStyle
有时,你可能需要在不同的场景中切换 TabView
的样式。例如,主界面使用底部选项卡导航,而某些特定页面使用分页样式。
4.1 主界面使用底部选项卡,详情页使用分页
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text("首页")
}
ProfileView()
.tabItem {
Image(systemName: "person.fill")
Text("个人")
}
GalleryView()
.tabItem {
Image(systemName: "photo.on.rectangle")
Text("画廊")
}
}
}
}
struct GalleryView: View {
var body: some View {
TabView {
Color.red
.overlay(Text("图片 1").foregroundColor(.white))
Color.green
.overlay(Text("图片 2").foregroundColor(.white))
Color.blue
.overlay(Text("图片 3").foregroundColor(.white))
}
.tabViewStyle(PageTabViewStyle())
}
}
解释:
- 主
TabView
包含三个选项卡:“首页”、“个人”和“画廊”。 - “画廊”选项卡内部嵌套了另一个
TabView
,并使用PageTabViewStyle
实现分页滑动效果。
5. 控制选中的 Tab
你可以使用 @State
变量来控制当前选中的选项卡。
5.1 示例代码
import SwiftUI
struct ContentView: View {
@State private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text("首页")
}
.tag(0)
ProfileView()
.tabItem {
Image(systemName: "person.fill")
Text("个人")
}
.tag(1)
SettingsView()
.tabItem {
Image(systemName: "gearshape.fill")
Text("设置")
}
.tag(2)
}
.onAppear {
// 默认选中第二个选项卡(个人)
selectedTab = 1
}
}
}
struct SettingsView: View {
var body: some View {
Text("设置页面")
.font(.largeTitle)
}
}
解释:
- 使用
@State
变量selectedTab
来绑定当前选中的选项卡。 - 每个选项卡通过
.tag()
分配一个唯一的标签值。 - 通过修改
selectedTab
的值,可以动态切换选中的选项卡。
6. 自定义选项卡样式
你可以自定义选项卡的外观,例如更改图标颜色、字体样式等。
6.1 更改图标和文字颜色
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text("首页")
}
ProfileView()
.tabItem {
Image(systemName: "person.fill")
Text("个人")
}
}
.accentColor(.purple) // 更改选中图标和文字的颜色
}
}
解释:
- 使用
.accentColor()
修改选中状态下的图标和文字颜色。
6.2 使用自定义图标
你可以使用自定义的图片资源作为选项卡图标。
.tabItem {
Image("customHomeIcon") // 使用你的图片资源
Text("首页")
}
注意:
- 确保在项目的 Assets 目录中添加了名为
customHomeIcon
的图片资源。
7. 响应选项卡切换事件
有时你需要在用户切换选项卡时执行特定操作,可以使用 onChange
修饰符监听 selectedTab
的变化。
7.1 示例代码
import SwiftUI
struct ContentView: View {
@State private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text("首页")
}
.tag(0)
ProfileView()
.tabItem {
Image(systemName: "person.fill")
Text("个人")
}
.tag(1)
}
.onChange(of: selectedTab) { newValue in
print("选中的标签索引:\(newValue)")
// 在这里执行你需要的操作
}
}
}
解释:
- 使用
.onChange(of: selectedTab)
监听选中的标签变化,当用户切换标签时触发相应的操作。
8. 结合导航视图使用 TabView
为了在选项卡中的每个视图中实现更复杂的导航,可以将 NavigationView
与 TabView
结合使用。
8.1 示例代码
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
HomeView()
.navigationTitle("首页")
}
.tabItem {
Image(systemName: "house.fill")
Text("首页")
}
NavigationView {
ProfileView()
.navigationTitle("个人")
}
.tabItem {
Image(systemName: "person.fill")
Text("个人")
}
}
}
}
解释:
- 每个选项卡内嵌入一个
NavigationView
,这样可以在每个选项卡内实现独立的导航堆栈。
9. 动态生成选项卡
如果选项卡的内容是动态的(例如从网络获取),可以使用 ForEach
来生成选项卡。
9.1 示例代码
import SwiftUI
struct TabItemData: Identifiable {
let id = UUID()
let title: String
let systemImage: String
let color: Color
}
struct ContentView: View {
let tabs = [
TabItemData(title: "首页", systemImage: "house.fill", color: .red),
TabItemData(title: "收藏", systemImage: "star.fill", color: .yellow),
TabItemData(title: "个人", systemImage: "person.fill", color: .green)
]
var body: some View {
TabView {
ForEach(tabs) { tab in
VStack {
Text("\(tab.title)内容")
.font(.largeTitle)
.foregroundColor(.white)
Image(systemName: tab.systemImage)
.resizable()
.frame(width: 100, height: 100)
.foregroundColor(.white)
}
.background(tab.color)
.cornerRadius(10)
.tabItem {
Image(systemName: tab.systemImage)
Text(tab.title)
}
}
}
}
}
解释:
- 定义一个
TabItemData
结构体来存储每个选项卡的数据。 - 使用
ForEach
遍历tabs
数组,动态生成选项卡内容。
10. 总结
TabView
是创建多选项卡界面的强大工具,适用于主要导航结构。PageTabViewStyle
允许TabView
以分页滑动的方式展示内容,适合展示类似于图片库或引导页的场景。- 通过结合使用
@State
、自定义样式和导航视图,可以创建功能丰富且用户体验良好的界面。 - 利用 SwiftUI 的灵活性,可以根据应用需求动态生成和定制选项卡。
1-44 DarkMode深色主题
//
// DakMode.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/23.
//
import SwiftUI
struct DakMode: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
NavigationView {
ScrollView {
Text("Primary")
.foregroundColor(Color.primary)
Text("secondary")
.foregroundColor(Color.secondary)
Text("balck")
.foregroundColor(Color.black)
Text("white")
.foregroundColor(Color.white)
Text("red")
.foregroundColor(Color.red)
Text("This Color is locally Adaptive")
.foregroundColor(colorScheme == .light ? Color.green : Color.yellow)
}
.navigationTitle("Dark Mode")
}
}
}
#Preview {
Group {
DakMode()
.preferredColorScheme(.light)
}
}
1-45 Markups & Documentations
1. Swift 文档标记(Markups)与文档注释简介
在 Swift 编程中,文档标记(Markups) 和 文档注释(Documentation Comments) 是用于编写清晰、结构化的代码文档的重要工具。这些文档不仅帮助开发者理解代码,还能被工具(如 Xcode)解析,以生成详细的 API 文档和提供代码提示。
1.1 为什么使用文档标记?
- 提高代码可读性:清晰的文档注释让代码更易于理解,特别是对于团队协作和后期维护。
- 自动生成文档:工具可以解析文档注释,生成 HTML 或其他格式的文档,方便发布和分享。
- 增强开发体验:在 IDE(如 Xcode)中,文档注释会显示在代码提示中,帮助开发者快速了解函数、类等的用途和用法。
2. Swift 中的文档注释语法
Swift 提供了两种主要的文档注释语法:单行注释 和 多行注释。两者都支持使用 Markdown 语法来格式化内容。
2.1 单行文档注释 (///
)
使用三个斜杠 ///
开始的注释用于为单个声明(如函数、类、属性)添加文档说明。
/// 这是一个示例函数,用于演示单行文档注释。
func exampleFunction() {
// 函数实现
}
2.2 多行文档注释 (/** */
)
使用 /**
开始和 */
结束的注释适用于需要更复杂文档结构的情况。
/**
这是一个示例类,用于演示多行文档注释。
该类包含多个属性和方法,用于展示如何使用文档标记。
*/
class ExampleClass {
// 类实现
}
3. 常用的文档标记指令
Swift 的文档注释支持多种标记指令,这些指令帮助定义文档的结构和内容。
3.1 @param
和 @parameter
用于描述函数或方法的参数。
/**
计算两个整数的和。
- Parameters:
- a: 第一个整数。
- b: 第二个整数。
- Returns: 两个整数的和。
*/
func add(a: Int, b: Int) -> Int {
return a + b
}
3.2 @return
或 @returns
用于描述函数或方法的返回值。
/**
获取用户的全名。
- Returns: 用户的全名,格式为“名 姓”。
*/
func getFullName() -> String {
return "\(firstName) \(lastName)"
}
3.3 @throws
和 @throw
用于描述函数或方法可能抛出的错误。
/**
从指定路径读取文件内容。
- Parameter path: 文件的路径。
- Throws: 如果文件不存在或读取失败,将抛出 `FileError`。
- Returns: 文件的内容。
*/
func readFile(at path: String) throws -> String {
// 实现代码
}
3.4 @available
用于标记 API 的可用性,比如支持的操作系统版本。
/**
开始播放视频。
- Note: 仅在 iOS 14.0 及以上版本可用。
*/
@available(iOS 14.0, *)
func startVideoPlayback() {
// 实现代码
}
3.5 @deprecated
和 @deprecated(message: , renamed: )
用于标记已弃用的 API,并提供替代方案。
/**
旧的播放方法,已弃用。
- Deprecated: 请使用 `playVideo()` 方法代替。
*/
@available(*, deprecated, message: "请使用 playVideo() 方法代替。")
func play() {
// 实现代码
}
3.6 @example
用于提供代码示例,展示如何使用特定的 API。
/**
发送网络请求。
- Example:
let url = "https://api.example.com/data" sendRequest(to: url) { response in print(response) }
*/
func sendRequest(to url: String, completion: (String) -> Void) {
// 实现代码
}
3.7 @seealso
用于引用相关的类、方法或文档。
/**
计算圆的面积。
- SeeAlso: `Circle` 类
*/
func calculateArea(ofCircleWithRadius radius: Double) -> Double {
return .pi * radius * radius
}
4. 文档中的格式化
Swift 文档注释支持 Markdown 语法,使文档内容更加丰富和易读。
4.1 标题和文本样式
- 标题:使用
#
、##
等符号创建标题。 - 加粗:使用
**文本**
或__文本__
。 - 斜体:使用
*文本*
或_文本_
。 - 代码:使用反引号
`代码`
。
/**
# 主要功能
这个函数用于 **计算** 两个数的 *和*。
- Parameter a: 第一个数。
- Parameter b: 第二个数。
- Returns: `Int` 类型的和。
*/
func add(a: Int, b: Int) -> Int {
return a + b
}
4.2 列表
- 无序列表:使用
-
或*
。 - 有序列表:使用数字加点
1.
。
/**
这个类支持以下功能:
- 创建新用户
- 更新用户信息
- 删除用户
使用步骤:
1. 初始化用户对象。
2. 调用相关方法进行操作。
*/
class UserManager {
// 类实现
}
4.3 链接
使用 [链接文本](URL)
语法创建超链接。
/**
详细信息请参见 [Swift 官方文档](https://swift.org/documentation/).
*/
func fetchDocumentation() {
// 实现代码
}
5. 最佳实践
编写文档注释时,遵循一些最佳实践可以提高文档的质量和可维护性。
5.1 清晰和简洁
- 明确描述:准确描述函数、类或属性的用途和行为。
- 避免冗余:不要重复代码中的信息,专注于提供额外的上下文。
/**
计算用户的年龄。
- Parameter birthDate: 用户的出生日期。
- Returns: 用户的年龄,以年为单位。
*/
func calculateAge(from birthDate: Date) -> Int {
// 实现代码
}
5.2 使用一致的格式
- 统一风格:保持文档注释的格式一致,如使用相同的标记顺序和样式。
- 结构化内容:使用标题、列表等结构化元素组织内容。
5.3 重点文档化公共接口
- 公共 API:优先为公开的类、方法和属性编写详细的文档注释。
- 内部实现:内部使用的代码可以有较少的文档注释,除非需要解释复杂逻辑。
6. 生成和查看文档
Swift 提供了工具来生成和查看文档注释,最常用的是 Xcode 和 Swift-DocC。
6.1 使用 Xcode 查看文档
- 代码提示:在编写代码时,Xcode 会显示文档注释内容,帮助理解 API。
- 快速查看:按住
Option
键并点击某个方法或类,可以查看其文档注释。
6.2 使用 Swift-DocC 生成文档
Swift-DocC 是 Swift 的官方文档编译器,能够生成 HTML 格式的文档。
6.2.1 安装和配置
- 安装 DocC:确保使用的 Xcode 版本支持 DocC(Xcode 13 及以上)。
- 创建文档:在项目中添加
.docc
文件夹,编写文档内容。
6.2.2 生成文档
使用以下命令生成文档:
swift package --allow-writing-to-directory <output-directory> generate-documentation
6.2.3 查看文档
生成的文档可以在浏览器中打开,查看详细的 API 说明和示例。
7. 高级主题
深入了解 Swift 文档标记,可以进一步提升文档的丰富性和实用性。
7.1 自定义文档页面
除了自动生成的 API 文档,还可以创建自定义页面,提供项目的总体介绍、指南和教程。
// 在 .docc 文件夹中创建自定义页面,例如 Overview.md
# 项目概述
欢迎使用我的 Swift 项目!这个项目旨在解决 XYZ 问题,并提供以下功能:
- 功能一
- 功能二
- 功能三
## 如何使用
1. 安装依赖
2. 配置项目
3. 运行应用
7.2 使用 DocC 提供丰富的文档
利用 DocC 的高级功能,如交互式示例、图表和代码片段,增强文档的可读性和互动性。
/**
使用示例:
```swift
let result = add(a: 5, b: 3)
print(result) // 输出: 8
*/ func add(a: Int, b: Int) -> Int { return a + b }
### 8. 实例演示
通过具体的代码示例,展示如何编写和使用文档标记。
#### 8.1 为函数编写文档注释
```swift
/**
计算两个数的和。
- Parameters:
- a: 第一个加数。
- b: 第二个加数。
- Returns: 两个加数的和。
- Example:
let sum = add(a: 10, b: 20) print(sum) // 输出: 30
*/
func add(a: Int, b: Int) -> Int {
return a + b
}
解释:
- 使用
/** */
包裹多行注释。 - 使用
- Parameters:
列出函数的参数。 - 使用
- Returns:
描述返回值。 - 使用
- Example:
提供代码示例。
8.2 为类编写文档注释
/**
用户管理器类,用于处理用户的创建、更新和删除操作。
## 功能
- 创建新用户
- 更新现有用户的信息
- 删除用户
## 使用示例
let userManager = UserManager() userManager.createUser(name: "张三", age: 25)
*/
class UserManager {
/**
创建一个新用户。
- Parameters:
- name: 用户的姓名。
- age: 用户的年龄。
- Returns: 新创建的 `User` 对象。
*/
func createUser(name: String, age: Int) -> User {
return User(name: name, age: age)
}
/**
更新用户的信息。
- Parameter user: 需要更新的用户对象。
- Throws: 如果用户不存在,将抛出 `UserError.notFound`。
*/
func updateUser(_ user: User) throws {
// 实现代码
}
/**
删除指定的用户。
- Parameter user: 需要删除的用户对象。
*/
func deleteUser(_ user: User) {
// 实现代码
}
}
解释:
- 在类级别添加详细的文档注释,介绍类的功能和使用方法。
- 为类中的每个方法添加独立的文档注释,描述其功能、参数和可能的错误。
9. 总结
- 文档注释的重要性:良好的文档注释提升代码可读性,便于团队协作和后期维护。
- 文档标记指令:熟练掌握常用的文档标记指令,如
@param
、@return
、@throws
等,有助于编写结构化的文档。 - 格式化技巧:利用 Markdown 语法丰富文档内容,使其更具可读性和专业性。
- 工具支持:利用 Xcode 和 Swift-DocC 等工具自动生成和查看文档,提升开发效率。
- 最佳实践:保持文档的清晰、简洁和一致,优先为公共 API 编写详细文档。
1-46 onAppear & onDisappear
//
// onAppear&onDisapear.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/23.
//
import SwiftUI
struct onAppear_onDisapear: View {
@State var myText : String = "Stare text."
@State var count : Int = 0
var body: some View {
NavigationView {
ScrollView {
Text(myText)
LazyVStack {
ForEach(0 ..< 50){ _ in
RoundedRectangle(cornerRadius: 25)
.frame(height: 200)
.padding()
.onAppear{
count += 5
}
.onDisappear{
count -= 3
}
}
}
}
.onAppear(perform: {
// 5秒钟以后才执行
DispatchQueue.main.asyncAfter(deadline: .now() + 5){
myText = "my Text is Changed"
}
})
.onDisappear(perform: {
myText = "Ending text"
})
.navigationTitle("On Appear \(count)")
}
}
}
#Preview {
onAppear_onDisapear()
}
1. onAppear
和 onDisappear
简介
在 SwiftUI 中,onAppear
和 onDisappear
是两个生命周期修饰符,用于在视图出现或消失时执行特定的代码。这两个方法对于管理视图的状态、启动或停止任务(如网络请求、动画等)非常有用。
1.1 onAppear
和 onDisappear
的作用
onAppear
:当视图即将出现在屏幕上时调用。适用于初始化数据、启动动画或开始监听通知等操作。onDisappear
:当视图即将从屏幕上消失时调用。适用于清理资源、停止任务或保存状态等操作。
2. 基本用法
让我们通过一个简单的示例来了解如何使用 onAppear
和 onDisappear
。
2.1 示例代码
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("欢迎来到主页")
.font(.largeTitle)
.padding()
.onAppear {
print("主页视图已出现")
// 在这里可以执行初始化操作
}
.onDisappear {
print("主页视图即将消失")
// 在这里可以执行清理操作
}
NavigationLink(destination: DetailView()) {
Text("前往详情页")
.foregroundColor(.blue)
.padding()
.background(Color.gray.opacity(0.2))
.cornerRadius(8)
}
}
.navigationTitle("首页")
}
}
struct DetailView: View {
var body: some View {
Text("这是详情页")
.font(.title)
.onAppear {
print("详情页视图已出现")
}
.onDisappear {
print("详情页视图即将消失")
}
}
}
2.2 解释
- 主页视图 (
ContentView
):- 当主页视图出现在屏幕上时,
onAppear
会打印"主页视图已出现"
。 - 当主页视图即将消失(例如导航到详情页)时,
onDisappear
会打印"主页视图即将消失"
。
- 当主页视图出现在屏幕上时,
- 详情页视图 (
DetailView
):- 类似地,
onAppear
和onDisappear
会在视图出现和消失时打印相应的信息。
- 类似地,
3. 使用场景
onAppear
和 onDisappear
可以用于多种场景,以下是一些常见的应用场景。
3.1 数据加载
在视图出现时加载数据,确保数据在视图显示前已经准备好。
struct DataView: View {
@State private var data: [String] = []
var body: some View {
List(data, id: \.self) { item in
Text(item)
}
.onAppear {
loadData()
}
}
func loadData() {
// 模拟数据加载
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
data = ["苹果", "香蕉", "橘子"]
}
}
}
3.2 启动和停止动画
在视图出现时启动动画,视图消失时停止动画。
struct AnimationView: View {
@State private var isAnimating = false
var body: some View {
Circle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.scaleEffect(isAnimating ? 1.5 : 1.0)
.animation(.easeInOut(duration: 1).repeatForever(autoreverses: true), value: isAnimating)
.onAppear {
isAnimating = true
}
.onDisappear {
isAnimating = false
}
}
}
3.3 监听通知
在视图出现时开始监听通知,视图消失时取消监听。
struct NotificationView: View {
var body: some View {
Text("监听通知的视图")
.onAppear {
NotificationCenter.default.addObserver(forName: .customNotification, object: nil, queue: .main) { notification in
print("收到自定义通知")
}
}
.onDisappear {
NotificationCenter.default.removeObserver(self, name: .customNotification, object: nil)
}
}
}
extension Notification.Name {
static let customNotification = Notification.Name("customNotification")
}
4. 与状态管理结合使用
onAppear
和 onDisappear
常与状态管理结合使用,以便在视图生命周期内更新状态。
4.1 示例:计时器
在视图出现时启动计时器,视图消失时停止计时器。
struct TimerView: View {
@State private var counter = 0
@State private var timer: Timer? = nil
var body: some View {
Text("计数: \(counter)")
.font(.largeTitle)
.onAppear {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
counter += 1
}
}
.onDisappear {
timer?.invalidate()
timer = nil
}
}
}
5. 注意事项
在使用 onAppear
和 onDisappear
时,需要注意以下几点:
5.1 多次调用
在 SwiftUI 中,视图可能会因为各种原因被多次加载或卸载。因此,onAppear
和 onDisappear
可能会被多次调用。确保相关操作是幂等的(即多次执行不会产生副作用)。
5.2 避免强引用循环
在 onAppear
或 onDisappear
中使用闭包时,避免捕获 self
导致强引用循环。可以使用 [weak self]
来防止这种情况。
.onAppear { [weak self] in
self?.startTask()
}
5.3 性能考虑
确保在 onAppear
和 onDisappear
中执行的操作不会阻塞主线程,避免影响界面流畅度。对于耗时操作,应使用异步处理。
6. 高级用法
除了基本用法,onAppear
和 onDisappear
还可以结合其他 SwiftUI 功能,实现更复杂的逻辑。
6.1 条件视图
根据视图的出现与否,动态改变其他视图的状态。
struct ConditionalView: View {
@State private var showDetail = false
var body: some View {
VStack {
Toggle("显示详情", isOn: $showDetail)
.padding()
if showDetail {
DetailView()
.transition(.slide)
}
}
.animation(.default, value: showDetail)
}
}
struct DetailView: View {
var body: some View {
Text("这是一个可切换的详情视图")
.padding()
.background(Color.yellow)
.cornerRadius(8)
.onAppear {
print("DetailView 出现")
}
.onDisappear {
print("DetailView 消失")
}
}
}
6.2 与导航结合
在导航视图中,管理子视图的生命周期。
struct NavigationExampleView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: SubView()) {
Text("前往子视图")
.foregroundColor(.blue)
.padding()
.background(Color.gray.opacity(0.2))
.cornerRadius(8)
}
}
.navigationTitle("导航示例")
}
}
}
struct SubView: View {
var body: some View {
Text("这是子视图")
.font(.title)
.onAppear {
print("SubView 出现")
}
.onDisappear {
print("SubView 消失")
}
}
}
7. 实践案例
通过一个实际的案例,展示如何使用 onAppear
和 onDisappear
管理网络请求和资源。
7.1 示例:网络数据加载
struct NetworkView: View {
@State private var data: String = "加载中..."
var body: some View {
Text(data)
.font(.title)
.padding()
.onAppear {
fetchData()
}
.onDisappear {
cancelFetch()
}
}
func fetchData() {
// 模拟网络请求
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
DispatchQueue.main.async {
data = "数据加载完成"
}
}
}
func cancelFetch() {
// 取消网络请求的逻辑(如果有)
print("取消数据加载")
}
}
7.2 解释
fetchData
:在视图出现时模拟一个网络请求,2 秒后更新data
。cancelFetch
:在视图消失时执行取消操作(实际项目中应实现具体的取消逻辑)。
8. 总结
- 生命周期管理:
onAppear
和onDisappear
提供了在视图生命周期内执行代码的能力,帮助管理数据加载、资源释放等操作。 - 灵活性:这两个修饰符可以与状态管理、导航、动画等其他 SwiftUI 功能结合使用,构建复杂且高效的用户界面。
- 注意事项:在使用时需注意多次调用、避免强引用循环以及性能优化,确保代码的健壮性和应用的流畅度。
- 实践应用:通过实际案例,理解如何在不同场景下有效使用
onAppear
和onDisappear
,提升 SwiftUI 开发技能。
1-47 if let && guard 语句
在 SwiftUI 中,if let
和 guard
都是非常常用的语法,用于处理可选类型(Optional)。它们在保证代码安全和可读性方面有很大的帮助。
1, if let
的用法
if let
语句用于安全地解包可选值。它的语法是检查一个可选值是否包含一个非 nil
的值,如果是,则创建一个新的常量或变量来存储解包后的值。
示例: 在 SwiftUI 中使用 if let
处理一个可选值
import SwiftUI
struct ContentView: View {
var name: String? = "John Doe" // 可选值
var body: some View {
VStack {
if let unwrappedName = name {
Text("Hello, \(unwrappedName)!")
.font(.largeTitle)
.padding()
} else {
Text("Hello, Guest!")
.font(.largeTitle)
.padding()
}
}
}
}
说明:
- 在这个例子中,我们有一个可选的
name
,它的类型是String?
。我们使用if let
来解包这个可选值。 - 如果
name
不为nil
,它将被解包,并赋值给unwrappedName
,然后显示解包后的值。如果name
是nil
,我们会显示默认的文本。
2,guard
的用法
guard
语句的功能与 if let
类似,但语法和逻辑结构略有不同。guard
通常用于提前退出函数或视图体中,以确保解包后的值可以继续被使用。
guard
语句的常见用途是:检查某些条件是否成立,如果不成立则提前退出代码块。与 if let
不同,guard
语句在条件不满足时必须执行某些退出(例如 return
或 break
)。
示例: 在 SwiftUI 中使用 guard
来处理一个可选值
import SwiftUI
struct ContentView: View {
var age: Int? = 25 // 可选值
var body: some View {
VStack {
// 使用 guard 来解包 age
let ageDescription = describeAge(age: age)
Text(ageDescription)
.font(.largeTitle)
.padding()
}
}
// 使用 guard 来处理可选值
func describeAge(age: Int?) -> String {
guard let unwrappedAge = age else {
return "Age is not available"
}
return "Your age is \(unwrappedAge)"
}
}
说明:
- 在这个例子中,我们定义了一个可选的
age
。在describeAge
函数中,我们使用guard let
来解包age
。 - 如果
age
是nil
,guard
语句会让函数提前返回"Age is not available"
。如果age
不为nil
,则可以继续使用解包后的值。
3,if let
和 guard
的对比
if let
:用于局部范围内安全解包,它的解包范围仅限于if
语句的内部。适用于短的条件处理,适合不涉及提前返回或退出的情况。guard let
:用于提前退出的场景,适合需要在整个函数或视图中使用解包后的值。它的逻辑更加流畅和简洁,当不满足条件时,它会立即退出。
示例:结合 SwiftUI 中 if let
和 guard let
import SwiftUI
struct ContentView: View {
var userName: String? = "Alice"
var userAge: Int? = 30
var body: some View {
VStack {
// 使用 if let 解包 userName
if let name = userName {
Text("Welcome, \(name)!")
.font(.title)
.padding()
} else {
Text("Welcome, Guest!")
.font(.title)
.padding()
}
// 使用 guard let 解包 userAge
let ageMessage = checkUserAge(age: userAge)
Text(ageMessage)
.font(.body)
.padding()
}
}
// 使用 guard let 处理年龄
func checkUserAge(age: Int?) -> String {
guard let age = age else {
return "Age not provided"
}
return "You are \(age) years old"
}
}
1-48 onTabGesture
//
// TabGestures.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/24.
//
import SwiftUI
struct TabGestures: View {
@State var rectangleColor : Color = .red
var body: some View {
VStack(spacing: 40) {
RoundedRectangle(cornerRadius: 25.0)
.frame(height: 200)
.foregroundColor(rectangleColor)
Button {
rectangleColor = .blue
} label: {
Text("Button")
.font(.headline)
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.blue.clipShape(RoundedRectangle(cornerRadius: 15.0)))
.padding(.horizontal)
}
Text("Tab Gesture")
.font(.headline)
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.blue.clipShape(RoundedRectangle(cornerRadius: 15.0)))
.padding(.horizontal)
.onTapGesture {
rectangleColor = .green
}
Spacer()
}
.padding(40)
}
}
#Preview {
TabGestures()
}
在 SwiftUI 中,onTapGesture
是一个常用的手势修饰符,它允许你在用户点击(单击或多次点击)视图时执行某些动作。onTapGesture
可以灵活地用于各种视图,比如按钮、文本、图片等。在 SwiftUI 中,你可以自定义单击次数,并且很容易地将其与状态变量绑定,以响应用户的点击操作。
基本用法
onTapGesture
的基本用法非常简单。下面是一些常见的例子来帮助你理解。
1. 单击手势
在最简单的形式下,onTapGesture
监听一个点击并执行一些代码。下面的例子展示了如何在点击文本时改变文本内容:
import SwiftUI
struct ContentView: View {
@State private var message = "Hello, World!"
var body: some View {
Text(message)
.font(.largeTitle)
.padding()
.onTapGesture {
message = "Text Clicked!"
}
}
}
说明:
- 我们使用了一个
@State
修饰的变量message
来跟踪文本内容的变化。 - 在
Text
视图上,我们添加了一个.onTapGesture
修饰符,当用户点击Text
时,会触发闭包中的代码,将message
的值更改为"Text Clicked!"
。
2. 处理多次点击
onTapGesture
允许你指定点击次数。例如,如果你想要处理双击事件,可以传入 count
参数:
import SwiftUI
struct ContentView: View {
@State private var message = "Hello, World!"
var body: some View {
Text(message)
.font(.largeTitle)
.padding()
.onTapGesture(count: 2) {
message = "Double Tapped!"
}
}
}
说明:
- 这里,我们将
.onTapGesture
的count
参数设置为2
,表示我们只响应双击操作。 - 当用户双击
Text
时,message
将被设置为"Double Tapped!"
。
3. 与其他手势组合
onTapGesture
可以与其他手势组合使用,例如 LongPressGesture
或 DragGesture
。下面的例子展示了如何同时处理单击和长按手势:
import SwiftUI
struct ContentView: View {
@State private var message = "Hello, World!"
var body: some View {
Text(message)
.font(.largeTitle)
.padding()
.onTapGesture {
message = "Single Tapped!"
}
.onLongPressGesture {
message = "Long Pressed!"
}
}
}
说明:
- 在这个例子中,我们给
Text
添加了两个手势:一个是单击手势,另一个是长按手势。 - 根据用户的操作,
message
会被相应地更改。
4. 使用 onTapGesture
更新视图状态
onTapGesture
通常与 @State
变量结合使用,以实现交互式的视图。我们可以通过点击按钮来改变视图的背景颜色:
import SwiftUI
struct ContentView: View {
@State private var isRed = false
var body: some View {
VStack {
Rectangle()
.fill(isRed ? Color.red : Color.blue)
.frame(width: 200, height: 200)
.onTapGesture {
isRed.toggle() // 切换颜色状态
}
Text("Tap the square to change color")
.padding()
}
}
}
说明:
- 在这个例子中,我们有一个矩形 (
Rectangle
) ,它的颜色根据isRed
变量的值来变化。 - 点击
Rectangle
会触发.onTapGesture
,调用isRed.toggle()
切换颜色。
5. 使用 onTapGesture
与动画
SwiftUI 中的动画可以和 onTapGesture
结合使用,以实现交互动画效果:
import SwiftUI
struct ContentView: View {
@State private var isScaled = false
var body: some View {
VStack {
Circle()
.fill(Color.green)
.frame(width: 100, height: 100)
.scaleEffect(isScaled ? 1.5 : 1.0)
.animation(.easeInOut(duration: 0.5), value: isScaled)
.onTapGesture {
isScaled.toggle() // 点击后放大或缩小
}
Text("Tap the circle to animate")
.padding()
}
}
}
说明:
- 我们有一个圆形视图,它的缩放比例根据
isScaled
变量的值来变化。 - 通过点击圆形,可以触发
.onTapGesture
来切换isScaled
的值,从而触发缩放动画。
1-49 Custom Data(Structs)
//
// CustomData.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/24.
//
import SwiftUI
struct UserModel:Identifiable {
let id : String = UUID().uuidString
let dispalyName : String
let userName : String
let followerCount : Int
}
struct CustomData: View {
@State var users : [UserModel] = [
UserModel(dispalyName: "Aini", userName: "aini0424", followerCount: 1000),
UserModel(dispalyName: "Json", userName: "jasonLin", followerCount: 500),
UserModel(dispalyName: "Alice", userName: "Alice000", followerCount: 3000)
]
var body: some View {
NavigationView {
List {
ForEach(users) { user in
HStack(spacing:15.0) {
Circle()
.frame(width: 35, height: 35)
VStack(alignment:.leading) {
Text(user.userName)
.font(.headline)
Text("@\(user.dispalyName)")
.foregroundColor(.gray)
.font(.caption)
}
Spacer()
VStack {
Text("\(user.followerCount)")
.font(.headline)
Text("Followers")
.font(.caption)
.foregroundStyle(Color.gray)
}
}
.padding(.vertical,10)
}
}
.listStyle(InsetListStyle())
.navigationTitle("User Name")
}
}
}
#Preview {
CustomData()
}
在 SwiftUI 中,你可以使用 struct
来定义自定义的数据模型,然后将这些数据用于视图中。使用 struct
来表示自定义数据模型是 SwiftUI 的惯用做法,因为它非常轻量级且性能良好。
基本示例:定义自定义数据模型
假设我们要创建一个应用来显示用户的个人信息。我们首先需要定义一个 User
结构体来表示用户的数据:
import SwiftUI
// 定义自定义数据模型
struct User {
let id: Int
let name: String
let age: Int
let email: String
}
struct ContentView: View {
// 创建一个 User 实例
let user = User(id: 1, name: "Alice", age: 30, email: "[email protected]")
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("User Information")
.font(.headline)
Text("Name: \(user.name)")
Text("Age: \(user.age)")
Text("Email: \(user.email)")
}
.padding()
}
}
说明:
- 我们定义了一个
User
结构体,它包含用户的id
、name
、age
和email
。 - 在
ContentView
中,我们创建了一个User
实例,并将用户的属性用于Text
视图中。
进一步示例:使用自定义模型列表
接下来,我们可以扩展这个概念,将自定义的数据模型用于一个列表中。假设我们要显示一个用户列表:
import SwiftUI
// 定义自定义数据模型
struct User: Identifiable {
let id: Int
let name: String
let age: Int
}
// 主视图
struct ContentView: View {
// 创建一个用户数组
let users = [
User(id: 1, name: "Alice", age: 25),
User(id: 2, name: "Bob", age: 28),
User(id: 3, name: "Charlie", age: 22)
]
var body: some View {
NavigationView {
List(users) { user in
// 在列表中使用自定义视图
NavigationLink(destination: UserDetailView(user: user)) {
HStack {
Text(user.name)
.font(.headline)
Spacer()
Text("\(user.age) years old")
.foregroundColor(.secondary)
}
}
}
.navigationTitle("User List")
}
}
}
// 详细信息视图
struct UserDetailView: View {
let user: User
var body: some View {
VStack(spacing: 20) {
Text(user.name)
.font(.largeTitle)
.padding()
Text("Age: \(user.age)")
.font(.title2)
}
}
}
说明:
- 我们定义了一个
User
结构体,并遵循了Identifiable
协议。Identifiable
协议用于确保每个User
都有一个唯一的id
,这样List
可以轻松地识别每个项。 - 在
ContentView
中,我们创建了一个users
数组,它包含多个User
实例。 - 我们使用
List
来显示用户列表,并且通过NavigationLink
实现导航到详细信息页面。 UserDetailView
显示每个用户的详细信息。
自定义视图的进一步拓展
我们可以进一步定义自定义视图来显示用户的信息。这样做的好处是,代码更加模块化和易于维护。
import SwiftUI
// 定义自定义数据模型
struct User: Identifiable {
let id: Int
let name: String
let age: Int
}
// 用户信息视图
struct UserRowView: View {
let user: User
var body: some View {
HStack {
Text(user.name)
.font(.headline)
Spacer()
Text("\(user.age) years old")
.foregroundColor(.secondary)
}
}
}
struct ContentView: View {
// 创建一个用户数组
let users = [
User(id: 1, name: "Alice", age: 25),
User(id: 2, name: "Bob", age: 28),
User(id: 3, name: "Charlie", age: 22)
]
var body: some View {
NavigationView {
List(users) { user in
NavigationLink(destination: UserDetailView(user: user)) {
UserRowView(user: user) // 使用自定义视图来显示用户
}
}
.navigationTitle("User List")
}
}
}
struct UserDetailView: View {
let user: User
var body: some View {
VStack(spacing: 20) {
Text(user.name)
.font(.largeTitle)
.padding()
Text("Age: \(user.age)")
.font(.title2)
}
}
}
说明:
- 我们创建了一个
UserRowView
视图,它接收一个User
实例,并且在HStack
中显示用户的名称和年龄。 - 在
ContentView
中,我们使用UserRowView
作为列表项的内容。 - 这种方法使得我们的代码更加模块化,易于阅读和维护。
使用数据模型与绑定
在某些情况下,我们希望用户能够在应用中修改数据。这时候,我们需要使用 @State
或 @Binding
来进行数据的双向绑定。
import SwiftUI
// 定义自定义数据模型
struct User: Identifiable {
let id: Int
var name: String
var age: Int
}
struct ContentView: View {
@State private var users = [
User(id: 1, name: "Alice", age: 25),
User(id: 2, name: "Bob", age: 28),
User(id: 3, name: "Charlie", age: 22)
]
var body: some View {
NavigationView {
List($users) { $user in
NavigationLink(destination: EditUserView(user: $user)) {
UserRowView(user: user)
}
}
.navigationTitle("User List")
}
}
}
// 编辑用户视图
struct EditUserView: View {
@Binding var user: User
var body: some View {
Form {
Section(header: Text("User Information")) {
TextField("Name", text: $user.name)
Stepper("Age: \(user.age)", value: $user.age, in: 0...100)
}
}
.navigationTitle("Edit User")
}
}
struct UserRowView: View {
let user: User
var body: some View {
HStack {
Text(user.name)
.font(.headline)
Spacer()
Text("\(user.age) years old")
.foregroundColor(.secondary)
}
}
}
说明:
- 我们定义了一个
@State
数组users
,它包含多个可修改的用户实例。 - 在
List
中,我们使用$users
来创建绑定,以便在编辑时直接修改原数组中的用户数据。 - 在
EditUserView
中,我们使用@Binding
来接收和修改用户数据,使用TextField
和Stepper
来更新user
的属性。 - 这种做法可以轻松实现数据的双向绑定,使得视图与数据保持同步。
1-50 @StateObject && @ObservableObject
//
// ViewModelLearning.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/25.
//
import SwiftUI
struct FruitModel: Identifiable {
let id: String = UUID().uuidString
let name: String
let count: Int
}
class FruitViewModel: ObservableObject {
@Published var fruitArry: [FruitModel] = []
@Published var isLoading: Bool = false
init() {
getFruits()
}
func getFruits() {
isLoading = true
let fruit1 = FruitModel(name: "apple", count: 100)
let fruit2 = FruitModel(name: "Orange", count: 200)
let fruit3 = FruitModel(name: "banana", count: 300)
let fruit4 = FruitModel(name: "watermelen", count: 400)
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.fruitArry.append(contentsOf: [fruit1, fruit2, fruit3, fruit4])
self.isLoading = false
}
}
func deleteFruit(index: IndexSet) {
fruitArry.remove(atOffsets: index)
}
}
struct ViewModelLearning: View {
@StateObject var fruitViewModel: FruitViewModel = FruitViewModel()
var body: some View {
NavigationView {
List {
if fruitViewModel.isLoading {
ProgressView()
} else {
ForEach(fruitViewModel.fruitArry) { fruit in
HStack {
Text("\(fruit.count)")
.foregroundColor(Color.red)
Text(fruit.name)
}
}
.onDelete { indexSet in
fruitViewModel.deleteFruit(index: indexSet)
}
}
}
.listStyle(GroupedListStyle())
.navigationTitle("Fruits List")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
NavigationLink(destination: SecondScreens( fruitViewModel: fruitViewModel)) {
Image(systemName: "arrow.right")
.font(.title)
}
}
}
}
}
}
struct SecondScreens: View {
@Environment(\.presentationMode) var presentationMode
@ObservedObject var fruitViewModel: FruitViewModel
var body: some View {
ZStack {
Color.green.ignoresSafeArea()
VStack {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Go Back")
.foregroundColor(Color.white)
.padding()
.font(.largeTitle)
.fontWeight(.semibold)
}
Spacer()
ForEach(fruitViewModel.fruitArry) { fruit in
HStack {
Text("\(fruit.count)")
.foregroundColor(Color.red)
Text(fruit.name)
}
}
Spacer()
}
}
}
}
#Preview {
ViewModelLearning()
}
在 SwiftUI 中,@StateObject
和 @ObservableObject
是用于管理和监听数据变化的两种属性包装器。它们在 SwiftUI 的响应式数据驱动模型中起到关键作用,但有一些细微的差别。下面,我会详细介绍它们的作用、使用场景以及它们的区别。
1. @ObservableObject
定义:
@ObservableObject
是一种协议,通常用于数据模型(Model)类。遵循这个协议的对象可以向视图(View)发送数据变化的通知,以便视图在数据改变时自动刷新。
特点:
- 协议:
ObservableObject
是一个协议,通常需要自定义的类去实现它。 - 数据变化监听:当
@Published
属性的值发生变化时,它会自动通知使用该对象的视图进行更新。
使用步骤:
- 创建数据模型类:定义一个遵循
ObservableObject
协议的类,并在需要监听的属性上使用@Published
标注。 - 在视图中使用模型对象:使用
@ObservedObject
或@StateObject
来引用这个对象。
示例:
import SwiftUI
import Combine
class CounterModel: ObservableObject {
@Published var count = 0 // 使用 @Published 标注的属性会触发视图更新
}
struct ContentView: View {
@ObservedObject var counter = CounterModel() // 使用 @ObservedObject 引用
var body: some View {
VStack {
Text("Count: \(counter.count)")
Button("Increase Count") {
counter.count += 1
}
}
}
}
在上面的代码中,CounterModel
类遵循了 ObservableObject
协议,并且 count
属性使用了 @Published
修饰。因此,每当 count
发生变化时,视图中的 Text
会自动更新。
2. @StateObject
定义:
@StateObject
是 SwiftUI 中专门用于创建和持有 ObservableObject
对象的属性包装器。@StateObject
是在 SwiftUI 视图中创建和管理数据模型对象的最佳方式,特别是当该对象在视图的生命周期内应该保持唯一实例时。
特点:
- 唯一持有者:
@StateObject
用于在视图中创建一个数据对象,并成为它的唯一持有者。 - 生命周期管理:SwiftUI 会管理
@StateObject
的生命周期,确保视图重绘时不会重复创建对象。
使用步骤:
- 在视图中声明
@StateObject
:当一个ObservableObject
对象需要在视图中创建并管理时,使用@StateObject
声明。 - 保证唯一性:
@StateObject
通常只在一个视图层级中使用,避免在同一对象上多次声明@StateObject
。
示例:
import SwiftUI
class CounterModel: ObservableObject {
@Published var count = 0
}
struct ContentView: View {
@StateObject var counter = CounterModel() // 使用 @StateObject 创建并管理对象
var body: some View {
VStack {
Text("Count: \(counter.count)")
Button("Increase Count") {
counter.count += 1
}
}
}
}
在上面的代码中,ContentView
中的 counter
对象使用了 @StateObject
,因此它在 ContentView
的生命周期内会保持唯一并被 SwiftUI 管理。
3. @StateObject
和 @ObservableObject
的区别
特性 | @StateObject | @ObservableObject |
---|---|---|
定义方式 | 属性包装器,用于创建和管理对象 | 协议,用于数据模型类 |
适用场景 | 用于视图内部创建并持有一个 ObservableObject 对象 | 用于数据模型类,使数据变化能被视图监听 |
生命周期管理 | SwiftUI 自动管理对象生命周期,不会在视图更新时重新创建 | 由外部持有者管理 |
使用位置 | 视图的最初声明位置,通常在视图层级的顶层 | 数据模型类的定义位置 |
通常搭配的修饰符 | @StateObject 通常用于 @Published 的属性 | @ObservedObject 或 @StateObject |
4. 总结
- @StateObject:用于在视图中创建并持有一个
ObservableObject
对象。适合在视图层级的最上层使用,以确保对象在整个视图生命周期内保持唯一。 - @ObservableObject:一种协议,通常用于数据模型类,结合
@Published
属性使得数据变化自动通知到视图。
1-51 @EnvironmentObject
//
// EnviromentObjectLearning.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/25.
//
import SwiftUI
class EnviromentViewModel : ObservableObject {
@Published var dataArray : [String] = []
init () {
getDate()
}
func getDate() {
self.dataArray.append("iPhone")
self.dataArray.append("iPad")
self.dataArray.append(contentsOf: ["iMax","Apple Watch"])
}
}
struct EnviromentObjectLearning: View {
@StateObject var viewModel : EnviromentViewModel = EnviromentViewModel()
var body: some View {
NavigationView{
List{
ForEach(viewModel.dataArray, id: \.self) { item in
NavigationLink {
DetailScreen(selectedItem: item)
} label: {
Text(item)
}
}
}
.navigationTitle("IOS Devices")
}
.environmentObject(viewModel)
}
}
struct DetailScreen : View {
let selectedItem : String
var body : some View {
ZStack {
Color.green.ignoresSafeArea()
NavigationLink {
FinalView()
} label: {
Text(selectedItem)
.font(.headline)
.foregroundStyle(Color.orange)
.padding()
.padding(.horizontal)
.background(Color.white.clipShape(RoundedRectangle(cornerRadius: 30)))
}
}
}
}
struct FinalView : View {
@EnvironmentObject var viewModel : EnviromentViewModel
var body: some View {
ZStack{
LinearGradient(
gradient: Gradient(colors: [Color.red, Color.blue, Color.green]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
ScrollView{
VStack(spacing: 20) {
ForEach(viewModel.dataArray, id: \.self) { item in
Text(item)
}
}
.foregroundStyle(Color.white)
.font(.largeTitle)
}
}
}
}
#Preview {
EnviromentObjectLearning()
// DetailScreen(selectedItem: "iPhone")
// FinalView()
}
@EnvironmentObject
是 SwiftUI 中的一个属性包装器,用于将一个共享的数据对象传递给多个视图(views),无需通过参数显式地传递它。这在需要多个视图共享和观察同一个数据源时特别有用,因为它简化了代码结构和数据流的管理。
1,作用与功能
- 数据共享:
@EnvironmentObject
允许在视图层次结构中共享数据,所有使用它的子视图都能访问和修改该对象的值。 - 自动更新:当
@EnvironmentObject
的数据发生变化时,所有依赖它的视图会自动刷新。这个功能依赖于ObservableObject
协议,使数据变化后自动通知视图更新。 - 父视图注入,子视图读取:
@EnvironmentObject
对象通常在应用的更高层次(比如根视图)中注入,而在子视图中使用@EnvironmentObject
进行读取。
2,基本用法
- 创建共享的数据模型:这个数据模型必须遵守
ObservableObject
协议。 - 在父视图中注入
@EnvironmentObject
。 - 在子视图中声明
@EnvironmentObject
。
3,实例
以下是一些简单的示例,帮助理解 @EnvironmentObject
的用法。
示例 1:计数器应用
假设我们有一个计数器模型 CounterModel
,所有子视图都可以访问和修改它的计数值。
- 创建数据模型
import SwiftUI
class CounterModel: ObservableObject {
@Published var count: Int = 0
}
- 在根视图中注入
CounterModel
对象
struct ContentView: View {
@StateObject var counterModel = CounterModel() // 创建一个共享数据实例
var body: some View {
NavigationView {
VStack {
Text("Main View")
NavigationLink(destination: CounterView()) {
Text("Go to Counter View")
}
}
.environmentObject(counterModel) // 注入共享对象
}
}
}
- 在子视图中使用
@EnvironmentObject
读取数据
struct CounterView: View {
@EnvironmentObject var counterModel: CounterModel
var body: some View {
VStack {
Text("Counter: \(counterModel.count)")
.font(.largeTitle)
Button(action: {
counterModel.count += 1 // 修改共享对象的属性
}) {
Text("Increment")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
}
示例 2:用户信息共享
我们可以使用 @EnvironmentObject
来共享用户信息,比如用户的名字和登录状态。
- 创建用户数据模型
class UserModel: ObservableObject {
@Published var username: String = "Guest"
@Published var isLoggedIn: Bool = false
}
- 在根视图中注入
UserModel
struct RootView: View {
@StateObject var userModel = UserModel()
var body: some View {
VStack {
NavigationLink(destination: ProfileView()) {
Text("Go to Profile")
}
}
.environmentObject(userModel)
}
}
- 在多个视图中读取或修改用户数据
struct ProfileView: View {
@EnvironmentObject var userModel: UserModel
var body: some View {
VStack {
Text("Username: \(userModel.username)")
Button("Login") {
userModel.isLoggedIn.toggle() // 修改登录状态
userModel.username = userModel.isLoggedIn ? "User123" : "Guest"
}
Text(userModel.isLoggedIn ? "Logged In" : "Logged Out")
.foregroundColor(userModel.isLoggedIn ? .green : .red)
}
}
}
4,使用注意事项
@EnvironmentObject
必须提前注入:每个使用@EnvironmentObject
的视图在访问数据之前,必须确保该对象已经在父视图中通过.environmentObject()
方法注入,否则会导致应用崩溃。使用场景:
@EnvironmentObject
适用于全局数据共享的场景,如用户设置、主题、语言环境等。对于局部数据或仅在子视图之间传递的数据,更适合使用@ObservedObject
或@StateObject
。
1-52 @AppStorage
//
// AppStoranges.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/25.
//
import SwiftUI
struct AppStoranges: View {
// @State var currentUserName : String?
@AppStorage("name") var currentUserName : String?
var body: some View {
VStack (spacing: 20) {
Text(currentUserName ?? "Add Name Here")
if let name = currentUserName {
Text(name)
}
Button("Save".uppercased()){
let name = "Nick Aini"
currentUserName = name
// UserDefaults.standard.set(name, forKey: "name")
}
}
// .onAppear{
// currentUserName = UserDefaults.standard.string(forKey: "name")
// }
}
}
#Preview {
AppStoranges()
}
好的!我简化一下内容,主要聚焦于实际应用和代码示例。
1. @AppStorage
简介
@AppStorage
用于将简单数据(如 Bool
、String
、Int
等)保存在应用中,即使关闭应用也能保持这些数据,类似于 UserDefaults
,但更简单。
2. 基本用法示例
2.1 存储开关状态
以下是一个存储开关状态的小例子,保存用户选择的主题模式(暗黑模式或普通模式)。
import SwiftUI
struct ContentView: View {
@AppStorage("isDarkMode") private var isDarkMode: Bool = false
var body: some View {
VStack {
Toggle("Dark Mode", isOn: $isDarkMode)
Text("Current Mode: \(isDarkMode ? "Dark" : "Light")")
}
.padding()
}
}
这里,isDarkMode
的值被存储在 UserDefaults
中,即使关闭应用也会保存。
3. 共享数据示例
@AppStorage
可以用相同的键在多个视图间共享数据。例如,保存用户的首次使用状态:
3.1 主视图
struct FirstView: View {
@AppStorage("isFirstLaunch") private var isFirstLaunch: Bool = true
var body: some View {
VStack {
Text(isFirstLaunch ? "Welcome!" : "Welcome back!")
Button("Dismiss") {
isFirstLaunch = false
}
}
}
}
3.2 另一个视图
struct SecondView: View {
@AppStorage("isFirstLaunch") private var isFirstLaunch: Bool
var body: some View {
Text(isFirstLaunch ? "This is your first visit!" : "You have been here before.")
}
}
在 FirstView
和 SecondView
中,isFirstLaunch
使用了相同的键,因此这两个视图共享这个数据。修改任意一个视图中的 isFirstLaunch
都会同步更新。
4. 存储字符串数据示例
以下代码保存并显示用户输入的用户名:
import SwiftUI
struct UsernameView: View {
@AppStorage("username") private var username: String = "Guest"
var body: some View {
VStack {
Text("Hello, \(username)!")
TextField("Enter your name", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
}
}
用户在 TextField
中输入的名字会被保存,应用重启后也能自动显示。
5. 注意事项
- 数据类型:
@AppStorage
支持String
、Int
、Bool
、Double
、Float
和URL
。 - 键名唯一:确保键名唯一,避免不同数据项覆盖。
1-53 综合案例
1,IntroView 的代码
import SwiftUI
struct IntroView: View {
@AppStorage("signed_in") var currentUserSignedIn: Bool = false
var body: some View {
ZStack {
// background
RadialGradient(
gradient: Gradient(colors: [Color(#colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)), Color(#colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1))]),
center: .topLeading,
startRadius: 5,
endRadius: UIScreen.main.bounds.height)
.ignoresSafeArea()
if currentUserSignedIn {
ProfileView()
.transition(.asymmetric(insertion: .move(edge: .bottom), removal: .move(edge: .top)))
} else {
OnboardingView()
.transition(.asymmetric(insertion: .move(edge: .top), removal: .move(edge: .bottom)))
}
}
}
}
struct IntroView_Previews: PreviewProvider {
static var previews: some View {
IntroView()
}
}
2,OnboardingView 代码
import SwiftUI
struct OnboardingView: View {
// Onboarding states:
/*
0 - Welcome screen
1 - Add name
2 - Add age
3 - Add gender
*/
@State var onboardingState: Int = 0
let transition: AnyTransition = .asymmetric(
insertion: .move(edge: .trailing),
removal: .move(edge: .leading))
// onboarding inputs
@State var name: String = ""
@State var age: Double = 50
@State var gender: String = ""
// for the alert
@State var alertTitle: String = ""
@State var showAlert: Bool = false
// app storage
@AppStorage("name") var currentUserName: String?
@AppStorage("age") var currentUserAge: Int?
@AppStorage("gender") var currentUserGender: String?
@AppStorage("signed_in") var currentUserSignedIn: Bool = false
var body: some View {
ZStack {
// content
ZStack {
switch onboardingState {
case 0:
welcomeSection
.transition(transition)
case 1:
addNameSection
.transition(transition)
case 2:
addAgeSection
.transition(transition)
case 3:
addGenderSection
.transition(transition)
default:
RoundedRectangle(cornerRadius: 25.0)
.foregroundColor(.green)
}
}
// buttons
VStack {
Spacer()
bottomButton
}
.padding(30)
}
.alert(isPresented: $showAlert, content: {
return Alert(title: Text(alertTitle))
})
}
}
struct OnboardingView_Previews: PreviewProvider {
static var previews: some View {
OnboardingView()
.background(Color.purple)
}
}
// MARK: COMPONENTS
extension OnboardingView {
private var bottomButton: some View {
Text(onboardingState == 0 ? "SIGN UP" :
onboardingState == 3 ? "FINISH" :
"NEXT"
)
.font(.headline)
.foregroundColor(.purple)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.white)
.cornerRadius(10)
.animation(nil, value: false)
.onTapGesture {
handleNextButtonPressed()
}
}
private var welcomeSection: some View {
VStack(spacing: 40) {
Spacer()
Image(systemName: "heart.text.square.fill")
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
.foregroundColor(.white)
Text("Find your match.")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
.overlay(
Capsule(style: .continuous)
.frame(height: 3)
.offset(y: 5)
.foregroundColor(.white)
, alignment: .bottom
)
Text("This is the #1 app for finding your match online! In this tutorial we are practicing using AppStorage and other SwiftUI techniques.")
.fontWeight(.medium)
.foregroundColor(.white)
Spacer()
Spacer()
}
.multilineTextAlignment(.center)
.padding(30)
}
private var addNameSection: some View {
VStack(spacing: 20) {
Spacer()
Text("What's your name?")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
TextField("Your name here...", text: $name)
.font(.headline)
.frame(height: 55)
.padding(.horizontal)
.background(Color.white)
.cornerRadius(10)
Spacer()
Spacer()
}
.padding(30)
}
private var addAgeSection: some View {
VStack(spacing: 20) {
Spacer()
Text("What's your age?")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
Text("\(String(format: "%.0f", age))")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
Slider(value: $age, in: 18...100, step: 1)
.accentColor(.white)
Spacer()
Spacer()
}
.padding(30)
}
private var addGenderSection: some View {
VStack(spacing: 20) {
Spacer()
Text("What's your gender?")
.font(.largeTitle)
.fontWeight(.semibold)
.foregroundColor(.white)
Picker(
selection: $gender) {
Text("Male").tag("Male")
Text("Female").tag("Female")
Text("Non-Binary").tag("Non-Binary")
} label: {
Text(gender.count > 1 ? gender : "Select a gender")
.font(.headline)
.foregroundColor(.purple)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.white)
.cornerRadius(10)
}
.pickerStyle(SegmentedPickerStyle())
Spacer()
Spacer()
}
.padding(30)
}
}
// MARK: FUNCTIONS
extension OnboardingView {
func handleNextButtonPressed() {
// CHECK INPUTS
switch onboardingState {
case 1:
guard name.count >= 3 else {
showAlert(title: "Your name must be at least 3 characters long! 😩")
return
}
case 3:
guard gender.count > 1 else {
showAlert(title: "Please select a gender before moving forward! 😳")
return
}
default:
break
}
// GO TO NEXT SECTION
if onboardingState == 3 {
signIn()
} else {
withAnimation(.spring()) {
onboardingState += 1
}
}
}
func signIn() {
currentUserName = name
currentUserAge = Int(age)
currentUserGender = gender
withAnimation(.spring()) {
currentUserSignedIn = true
}
}
func showAlert(title: String) {
alertTitle = title
showAlert.toggle()
}
}
3,ProfileView 的代码
//
// ProfileView.swift
// SwiftfulThinkingBootcamp
//
// Created by Nick Sarno on 3/1/21.
//
import SwiftUI
struct ProfileView: View {
@AppStorage("name") var currentUserName: String?
@AppStorage("age") var currentUserAge: Int?
@AppStorage("gender") var currentUserGender: String?
@AppStorage("signed_in") var currentUserSignedIn: Bool = false
var body: some View {
VStack(spacing: 20) {
Image(systemName: "person.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 150, height: 150)
Text(currentUserName ?? "Your name here")
Text("This user is \(currentUserAge ?? 0) years old!")
Text("Their gender is \(currentUserGender ?? "unknown")")
Text("SIGN OUT")
.foregroundColor(.white)
.font(.headline)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.black)
.cornerRadius(10)
.onTapGesture {
signOut()
}
}
.font(.title)
.foregroundColor(.purple)
.padding()
.padding(.vertical, 40)
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 10)
}
func signOut() {
currentUserName = nil
currentUserAge = nil
currentUserGender = nil
withAnimation(.spring()) {
currentUserSignedIn = false
}
}
}
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
ProfileView()
}
}
1-54 AsyncImage
//
// AdyncImage_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/26.
//
import SwiftUI
struct AdyncImage_L: View {
let url = URL(string: "https://picsum.photos/400")
var body: some View {
VStack {
AsyncImage(url: url) { returnedImage in
returnedImage
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.clipShape(RoundedRectangle(cornerRadius: 15))
} placeholder: {
ProgressView()
}
AsyncImage(url: url) { phase in
switch phase {
case .empty:
ProgressView()
case .success(let image):
image
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.clipShape(RoundedRectangle(cornerRadius: 15))
case .failure(_):
Image(systemName: "questionmark")
.font(.headline)
@unknown default:
Image(systemName: "questionmark")
.font(.headline)
}
}
}
}
}
#Preview {
AdyncImage_L()
}
AsyncImage
是 SwiftUI 中一个非常便捷的视图,用于从 URL 加载图像,并自动处理图像的异步加载过程。这在加载远程图片或需要动态更新图片的场景中非常有用。下面我会详细讲解 AsyncImage
的一些常用用法、参数以及各种场景下的实现方式。
1. 基本用法
AsyncImage
最基本的用法是仅提供一个 URL 参数。它会尝试加载图像,如果加载成功则显示图片,加载过程中显示默认的占位符,失败则显示一个空图像。
AsyncImage(url: URL(string: "https://example.com/image.jpg"))
2. 带占位符和加载失败处理的用法
可以为 AsyncImage
设置一个占位符视图,表示图像加载中状态,同时还可以处理加载失败的情况。
AsyncImage(url: URL(string: "https://example.com/image.jpg")) { image in
image
.resizable()
.scaledToFit()
} placeholder: {
ProgressView() // 占位符,通常用于显示加载进度
} failure: {
Image(systemName: "exclamationmark.triangle") // 加载失败时的替代图像
.foregroundColor(.red)
}
.frame(width: 200, height: 200)
resizable()
:使图片可缩放。scaledToFit()
:图片保持原比例缩放至适应视图的大小。ProgressView()
:一个加载动画,占位符常用来显示图像加载中的状态。Image(systemName:)
:在加载失败时,显示一个系统图片,例如红色感叹号来表示失败状态。
3. 使用 phase
参数的高级用法
AsyncImage
提供了一个 phase
参数,可以捕获三种不同的状态:empty
(正在加载中)、success
(加载成功)和 failure
(加载失败),这种用法非常灵活。
AsyncImage(url: URL(string: "https://example.com/image.jpg")) { phase in
switch phase {
case .empty:
ProgressView() // 正在加载
case .success(let image):
image
.resizable()
.scaledToFit()
.clipShape(RoundedRectangle(cornerRadius: 15)) // 加载成功,显示图像
case .failure:
Image(systemName: "xmark.octagon") // 加载失败
.font(.largeTitle)
.foregroundColor(.red)
@unknown default:
Image(systemName: "questionmark") // 处理未知情况
.font(.largeTitle)
.foregroundColor(.gray)
}
}
.frame(width: 150, height: 150)
phase.success(let image)
:表示图像加载成功,并将图像传递给image
。.failure
:图像加载失败时,显示占位图像。.unknown default
:处理未来可能出现的新状态,通常建议用作兜底状态。
4. 自定义占位符和错误图像
在实际开发中,可能需要使用自定义占位符或错误图像,比如自定义加载动画或特定的错误提示。
AsyncImage(url: URL(string: "https://example.com/image.jpg")) { phase in
switch phase {
case .empty:
VStack {
ProgressView("Loading...") // 带有文字的加载动画
.progressViewStyle(CircularProgressViewStyle(tint: .blue))
Text("Fetching Image")
.font(.caption)
.foregroundColor(.gray)
}
case .success(let image):
image
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.clipShape(Circle())
case .failure:
VStack {
Image(systemName: "wifi.exclamationmark")
.font(.largeTitle)
.foregroundColor(.orange)
Text("Failed to load image")
.font(.caption)
.foregroundColor(.red)
}
}
}
.frame(width: 150, height: 150)
5. 使用 AsyncImage
的尺寸控制
通常我们需要控制图像的显示尺寸,可以使用 frame
方法来设置宽度和高度,并结合 scaledToFit
或 scaledToFill
调整图片的比例:
AsyncImage(url: URL(string: "https://example.com/image.jpg")) { image in
image
.resizable()
.scaledToFill() // 填充图片
.frame(width: 120, height: 120)
.clipShape(RoundedRectangle(cornerRadius: 10))
} placeholder: {
ProgressView()
}
scaledToFit()
:让图片保持比例缩小或扩大,适应视图的尺寸。scaledToFill()
:让图片填满视图,有可能会裁剪掉部分内容。
6. AsyncImage
的缓存策略
从 iOS 15 开始,AsyncImage
自动对加载的图像进行缓存,默认情况下会缓存一段时间,减少对网络的重复请求。如果需要定制缓存策略,可以考虑使用更高级的 URLSession 或第三方库。
7. 总结
AsyncImage
是加载远程图像的高效方法,提供了灵活的占位符和错误处理,并且自动处理图像的异步加载。使用不同的参数组合,可以实现不同的 UI 效果。
关键点:
- 基本加载:简单地加载远程图像。
- 占位符:使用
ProgressView()
或其他视图来表示加载状态。 - 错误处理:显示替代图像或提示用户加载失败。
- 自定义外观:利用
resizable()
和scaledToFit()
、scaledToFill()
等来调整图像的显示方式。
1-55 BackgroundMaterials
//
// BackgroundMetarials_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/26.
//
import SwiftUI
struct BackgroundMetarials_L: View {
var body: some View {
VStack {
Spacer()
VStack{
RoundedRectangle(cornerRadius: 4)
.foregroundStyle(Color.black)
.frame(width: 30,height: 4)
.padding()
Spacer()
}
.frame(height: 350)
.frame(maxWidth: .infinity)
// .background(.thinMaterial)
// .background(.regularMaterial)
.background(.ultraThinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 20))
}
.ignoresSafeArea()
.background(
Image("beaty")
)
}
}
#Preview {
BackgroundMetarials_L()
}
在 SwiftUI 中,BackgroundMaterial
是一种用于创建模糊、半透明的背景效果的材质类型,常用于背景模糊、透明度效果。通常在实现视图的背景模糊、前景内容突显等效果时非常有用,特别是在需要在图像、文字上面添加可读性更好的背景时,比如 iOS
系统中常见的模糊透明效果。
1. 基本概念
BackgroundMaterial
提供了几种不同的材质,系统会根据这些材质和背景内容动态地生成半透明的模糊效果。常用的材质类型包括:
- .thinMaterial:轻度模糊,透明度较高。
- .regularMaterial:中度模糊,透明度适中。
- .thickMaterial:高强度模糊,透明度低。
- .ultraThinMaterial:非常轻的模糊效果,接近透明。
- .ultraThickMaterial:极高强度的模糊效果,几乎不透明。
2. 使用 BackgroundMaterial
设置背景
可以通过在视图上添加 .background
修饰符并指定材质来应用模糊效果。例如:
Text("Hello, SwiftUI!")
.padding()
.background(.thinMaterial)
.cornerRadius(10)
在这个例子中,Text
视图的背景应用了 thinMaterial
材质,显示一个轻微模糊的半透明背景,凸显了文字内容。
3. BackgroundMaterial
的不同应用场景
在不同的 UI 场景中,BackgroundMaterial
可以帮助突出重要信息,并增强可读性。以下是几种常见场景:
3.1 在图片或复杂背景上方使用
当文字或内容位于图片上时,使用 BackgroundMaterial
可以提高可读性:
ZStack {
Image("background-image")
.resizable()
.scaledToFill()
.ignoresSafeArea()
VStack {
Text("Welcome to SwiftUI!")
.font(.title)
.padding()
.background(.regularMaterial)
.cornerRadius(12)
}
}
在这个例子中,Text
视图的背景应用了 regularMaterial
材质,使文字在图片上更加清晰可见。
3.2 叠加多个 BackgroundMaterial
可以叠加不同的 BackgroundMaterial
类型来增强背景的层次感。例如:
ZStack {
Color.blue.ignoresSafeArea()
VStack {
RoundedRectangle(cornerRadius: 12)
.fill(.ultraThinMaterial)
.frame(width: 300, height: 200)
.overlay(
Text("Hello!")
.font(.headline)
.foregroundColor(.white)
)
.padding()
RoundedRectangle(cornerRadius: 12)
.fill(.thickMaterial)
.frame(width: 300, height: 200)
.overlay(
Text("SwiftUI Backgrounds")
.font(.headline)
.foregroundColor(.white)
)
.padding()
}
}
这里展示了 ultraThinMaterial
和 thickMaterial
的效果差异,较厚的材质更加模糊、遮蔽更多背景,而较薄的材质透过背景的程度更大。
4. BackgroundMaterial
与系统适配
BackgroundMaterial
是自适应的,随系统的亮暗模式调整不同的透明度和模糊强度。例如,ultraThinMaterial
在暗模式下会变得稍微不透明,以确保内容的可见性,而在亮模式下则保持更高的透明度。
5. 使用 .background
修饰符的扩展性
在使用 .background
修饰符时,可以将 BackgroundMaterial
与其他视图结合,构建更复杂的 UI。例如,将 BackgroundMaterial
叠加在圆角矩形、阴影等修饰符上:
Text("SwiftUI")
.padding()
.background {
RoundedRectangle(cornerRadius: 15)
.fill(.thinMaterial)
.shadow(radius: 5)
}
.padding()
6. 应用 BackgroundMaterial
的注意事项
- 性能:较厚的模糊效果(如
.thickMaterial
)会消耗更多的系统资源,尤其是在复杂的视图结构中。适当选择材质类型以平衡视觉效果和性能。 - 对比度:确保在使用
BackgroundMaterial
时,前景内容(如文字)有足够的对比度,增强可读性。 - 适配不同的界面:
BackgroundMaterial
能很好地适应各种 UI 风格和系统模式,可以用作多种场景的背景(如对话框、通知等)。
7. 综合示例
以下是一个完整的综合示例,展示了如何在不同的 BackgroundMaterial
背景上应用视图元素:
struct ContentView: View {
var body: some View {
ZStack {
Image("background-image")
.resizable()
.scaledToFill()
.ignoresSafeArea()
VStack(spacing: 20) {
Text("Ultra Thin Material")
.padding()
.background(.ultraThinMaterial)
.cornerRadius(12)
Text("Thin Material")
.padding()
.background(.thinMaterial)
.cornerRadius(12)
Text("Regular Material")
.padding()
.background(.regularMaterial)
.cornerRadius(12)
Text("Thick Material")
.padding()
.background(.thickMaterial)
.cornerRadius(12)
Text("Ultra Thick Material")
.padding()
.background(.ultraThickMaterial)
.cornerRadius(12)
}
.padding()
}
}
}
这个示例展示了所有 BackgroundMaterial
类型的效果。每种材质类型都有不同的透明度和模糊度,适合不同的 UI 场景。
8. 总结
BackgroundMaterial
是 SwiftUI 中非常实用的特性,用于在内容背景上应用模糊效果以增强可读性。常用的技巧包括:
- 根据需求选择合适的材质类型(如
thinMaterial
、thickMaterial
等)。 - 在图片或复杂背景上应用,以突出重要内容。
- 叠加不同的
BackgroundMaterial
类型,增加层次感。 - 利用
.background
修饰符与其他视图结合,创建自定义背景。
通过适当使用 BackgroundMaterial
,可以显著提高 UI 的清晰度和观感,同时适应系统的亮暗模式和风格。
1-56 TextSelection()
//
// TextSelection_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/26.
//
import SwiftUI
struct TextSelection_L: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
.textSelection(.enabled)
}
}
#Preview {
TextSelection_L()
}
在 SwiftUI 中,textSelection
是一个新的修饰符,用于控制视图中的文本是否可以被用户选择。这个功能在 iOS 15
和 macOS 12
及以上版本中引入,通常用于在需要复制、分享或引用文字的场景中。
1. 基本用法
要让文本可以被选择,直接在 Text
视图上使用 .textSelection()
修饰符即可。示例如下:
Text("This text is selectable!")
.textSelection(.enabled)
在这个例子中,用户可以长按文本,然后选择和复制文本内容。
2. 禁用文本选择
默认情况下,文本选择是禁用的。如果不希望文本在视图中被选中,可以显式地将 textSelection
设置为 .disabled
:
Text("This text cannot be selected.")
.textSelection(.disabled)
即使用户尝试选择,这段文字也无法被选中或复制。
3. 应用于多个视图
textSelection
也可以应用在包含多个 Text
视图的 VStack
、HStack
、ZStack
等容器视图上。这样,容器内的所有文本都会继承相同的选择行为。
VStack {
Text("First selectable text.")
Text("Second selectable text.")
}
.textSelection(.enabled)
在这个例子中,VStack
中的所有 Text
视图都可以被选中和复制。
4. 实际应用场景
- 显示文章或长文内容:当需要展示一篇文章或大量文字内容时,可以启用
.textSelection(.enabled)
,让用户方便地选择和复制部分内容。 - 展示代码或关键信息:在显示代码段或关键信息(例如电话号码、网址)时,启用文本选择可以帮助用户直接复制这些信息。
- 只读文本框:对于需要展示但不需要用户编辑的只读文本框,启用
.textSelection
也是一种便利的做法。
5. 综合示例
以下是一个展示不同 textSelection
应用场景的综合示例:
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Text("This text can be selected.")
.textSelection(.enabled)
Text("This text cannot be selected.")
.textSelection(.disabled)
VStack {
Text("Multiple selectable texts in a stack.")
Text("You can select any text here.")
}
.textSelection(.enabled)
}
.padding()
}
}
6. 总结
.textSelection(.enabled)
:允许文本选择,用户可以长按文本并复制内容。.textSelection(.disabled)
:禁用文本选择,文本不能被选中或复制。- 容器应用:在容器视图(如
VStack
、HStack
)上使用.textSelection()
,让容器内的所有文本继承该设置。
1-57 .buttonStyle & .controlSize
//
// buttonStyle&controlSize.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/26.
//
import SwiftUI
struct buttonStyle_controlSize: View {
var body: some View {
VStack {
Button("Button Title") {
}
.frame(height: 55)
.frame(maxWidth: .infinity)
.buttonStyle(.plain)
Button("Button Title") {
}
.frame(height: 55)
.frame(maxWidth: .infinity)
.buttonStyle(.bordered)
Button("Button Title") {
}
.frame(height: 55)
.frame(maxWidth: .infinity)
.buttonStyle(.borderless)
Button("Button Title") {
}
.frame(height: 55)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
}
.padding()
}
}
#Preview {
buttonStyle_controlSize()
}
//
// buttonStyle&controlSize.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/26.
//
import SwiftUI
struct buttonStyle_controlSize: View {
var body: some View {
VStack {
Button("Button Title") {
}
.frame(height: 55)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
.controlSize(.large)
Button("Button Title") {
}
.frame(height: 55)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
.controlSize(.extraLarge)
Button("Button Title") {
}
.frame(height: 55)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
.controlSize(.mini)
Button("Button Title") {
}
.frame(height: 55)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
.controlSize(.small)
}
.padding()
}
}
#Preview {
buttonStyle_controlSize()
}
在 SwiftUI 中,.buttonStyle
和 .controlSize
是两个用于控制按钮外观的修饰符。它们可以让我们更灵活地调整按钮的样式、大小和交互风格,以适应不同的界面需求和设计风格。接下来,我们将详细讲解这两个修饰符的用法。
1. .buttonStyle
修饰符
.buttonStyle
用于控制按钮的样式。SwiftUI 提供了一些内置样式,可以直接应用于按钮,比如 PlainButtonStyle
、DefaultButtonStyle
、BorderlessButtonStyle
等。同时,你也可以自定义样式来满足特殊需求。
1.1 常用内置按钮样式
1. DefaultButtonStyle
这是默认的按钮样式,根据平台和系统的视觉风格自动调整样式。在大多数情况下,使用 DefaultButtonStyle
就可以满足需求。
Button("Default Button") {
print("Default button tapped")
}
.buttonStyle(DefaultButtonStyle())
2. PlainButtonStyle
PlainButtonStyle
去除了按钮的默认视觉效果,按钮看起来更像是普通文本或图片。这种样式常用于不需要明显按钮感的交互,比如图片上的透明按钮。
Button("Plain Button") {
print("Plain button tapped")
}
.buttonStyle(PlainButtonStyle())
3. BorderlessButtonStyle
BorderlessButtonStyle
通常用于在 macOS 中,去除按钮的边框,但保留点击效果,适用于不需要明显边框的场景。
Button("Borderless Button") {
print("Borderless button tapped")
}
.buttonStyle(BorderlessButtonStyle())
4. LinkButtonStyle
LinkButtonStyle
可以让按钮看起来像一个链接,用在需要用户点击跳转的场景(类似网页链接)。
Button("Link Button") {
print("Link button tapped")
}
.buttonStyle(LinkButtonStyle())
1.2 自定义按钮样式
除了内置样式,你也可以自定义按钮样式,以实现特殊的视觉效果。自定义按钮样式需要实现 ButtonStyle
协议。
struct CustomButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding()
.background(configuration.isPressed ? Color.gray : Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
Button("Custom Button") {
print("Custom button tapped")
}
.buttonStyle(CustomButtonStyle())
在自定义按钮样式中,可以使用 configuration.isPressed
来检查按钮是否被按下,从而实现按下时的样式变化。
2. .controlSize
修饰符
.controlSize
用于控制按钮(或其他控件)的尺寸,SwiftUI 提供了几种常用的控制尺寸:.mini
、.small
、.regular
和 .large
。不同的 controlSize
值会影响按钮的大小,包括按钮内部的文本和图标。
2.1 常用尺寸选项
1. .mini
.mini
是最小的控制尺寸,适用于占用空间较小的场景,常用于工具栏或需要多个按钮紧密排列的情况。
Button("Mini Button") {
print("Mini button tapped")
}
.controlSize(.mini)
2. .small
.small
比 .mini
稍大一些,适用于小型按钮或文本较短的按钮。
Button("Small Button") {
print("Small button tapped")
}
.controlSize(.small)
3. .regular
.regular
是默认的按钮尺寸,适用于一般情况。
Button("Regular Button") {
print("Regular button tapped")
}
.controlSize(.regular)
4. .large
.large
是最大的控制尺寸,适合需要用户清晰注意的场景,比如强调按钮、呼叫按钮等。
Button("Large Button") {
print("Large button tapped")
}
.controlSize(.large)
2.2 在不同控件上使用
除了按钮,.controlSize
还可以用于其他控件,如 TextField
、Picker
和 Toggle
等。它会影响这些控件的整体尺寸,使其在不同的界面布局中显得更加协调。
VStack {
TextField("Mini TextField", text: .constant(""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.controlSize(.mini)
TextField("Small TextField", text: .constant(""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.controlSize(.small)
TextField("Regular TextField", text: .constant(""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.controlSize(.regular)
TextField("Large TextField", text: .constant(""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.controlSize(.large)
}
.padding()
2.3 .controlSize
与 .buttonStyle
配合使用
可以将 .controlSize
和 .buttonStyle
一起使用,以更好地控制按钮的样式和尺寸。例如,将按钮设置为自定义样式并应用不同尺寸:
Button("Custom Large Button") {
print("Custom large button tapped")
}
.buttonStyle(CustomButtonStyle())
.controlSize(.large)
通过结合这两个修饰符,按钮的视觉样式和大小都可以得到自定义控制。
3.buttonBorderShape
修饰符
在 SwiftUI 中,buttonBorderShape
是用于控制按钮边框形状的修饰符,它可以调整按钮的边框轮廓,使其适配不同的风格或设计需求。
1. 基本用法
buttonBorderShape
的用法非常简单,可以设置为 rounded
或 capsule
两种形状。
.automatic
:根据按钮内容和样式自动选择合适的边框形状。.rounded
:让按钮具有圆角矩形的边框。.capsule
:将按钮的边框形状设置为胶囊(pill-shaped)样式,按钮边缘完全圆滑。
Button("Rounded Button") {
print("Rounded button tapped")
}
.buttonStyle(.bordered)
.buttonBorderShape(.rounded)
Button("Capsule Button") {
print("Capsule button tapped")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
在这里,我们使用 .buttonStyle(.bordered)
来为按钮添加边框,随后使用 .buttonBorderShape(.rounded)
和 .buttonBorderShape(.capsule)
来设置按钮的边框形状。
2. 各种边框形状的效果
rounded
:按钮的边框形状为圆角矩形,通常适用于按钮内容较短的情况,视觉效果更现代且具有一定的视觉稳重感。capsule
:按钮的边框会像胶囊一样圆滑,通常适用于按钮内容较长或按钮需要较显眼的设计时。automatic
:默认情况下,automatic
会根据按钮的内容自动选择合适的边框形状。如果按钮内容较少,可能会选择capsule
样式;内容较多时,可能选择rounded
样式。
3. 综合示例
以下是一个完整的示例,展示了 buttonBorderShape
与不同按钮样式、控制大小的组合使用效果:
VStack(spacing: 20) {
Button("Automatic Shape") {
print("Automatic shape button tapped")
}
.buttonStyle(.bordered)
.buttonBorderShape(.automatic)
Button("Rounded Shape") {
print("Rounded shape button tapped")
}
.buttonStyle(.bordered)
.buttonBorderShape(.rounded)
Button("Capsule Shape") {
print("Capsule shape button tapped")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
Button("Large Capsule Button") {
print("Large capsule button tapped")
}
.buttonStyle(.borderedProminent)
.buttonBorderShape(.capsule)
.controlSize(.large)
}
4. 与 .buttonStyle
和 .controlSize
的结合
当与 .buttonStyle
和 .controlSize
一起使用时,buttonBorderShape
可以帮助你更好地控制按钮的整体外观和感受。例如,buttonStyle(.borderedProminent)
与 .capsule
搭配时,特别适合用于主按钮或呼叫按钮:
Button("Prominent Capsule Button") {
print("Capsule style prominent button tapped")
}
.buttonStyle(.borderedProminent)
.buttonBorderShape(.capsule)
.controlSize(.large)
1-58 .swipeAction()
//
// swipeActions_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/26.
//
import SwiftUI
struct swipeActions_L: View {
@State var fruits : [String] = [
"apple", "banana", "tomato", "pineapple" ,"oramge", "peach"
]
var body: some View {
List {
ForEach(fruits, id: \.self) { fruit in
Text(fruit.capitalized)
.swipeActions(
edge: .trailing,
allowsFullSwipe: true) {
Button("Archive") {
}
.tint(.green)
Button("Save") {
}
.tint(.blue)
Button("Junk") {
}
.tint(.red)
}
.swipeActions(
edge: .leading,
allowsFullSwipe: true) {
Button("Share") {
}
.tint(.yellow)
Button("Copy") {
}
.tint(.pink)
Button("Love") {
}
.tint(.red)
}
}
}
}
}
#Preview {
swipeActions_L()
}
在 SwiftUI 中,.swipeActions()
是一个修饰符,用于在列表中的行项(例如 List
或 ForEach
中的项)上添加滑动手势操作。它允许用户通过向左或向右滑动来触发不同的操作按钮,比如“删除”、“标记为已读”等操作,这类似于 iOS 的邮件或消息应用的滑动操作功能。
1. 基本用法
swipeActions
可以为列表项提供一个或多个按钮,这些按钮可以定义不同的操作。通常用于一些常见的交互,比如删除、编辑、分享等。基本用法如下:
List {
ForEach(0..<5) { item in
Text("Item \(item)")
.swipeActions {
Button("Delete") {
print("Delete action for Item \(item)")
}
.tint(.red) // 设置按钮的颜色
}
}
}
在这个示例中,用户可以向左滑动列表项,会显示一个“Delete”按钮。点击按钮后会触发 Delete
操作,并在控制台打印删除信息。
2. 添加多个滑动操作
你可以在 swipeActions
中添加多个按钮。多个按钮时,它们会以水平并排的方式显示,通常最多容纳 2-3 个按钮。
List {
ForEach(0..<5) { item in
Text("Item \(item)")
.swipeActions {
Button("Delete") {
print("Delete action for Item \(item)")
}
.tint(.red)
Button("Edit") {
print("Edit action for Item \(item)")
}
.tint(.blue)
Button("Share") {
print("Share action for Item \(item)")
}
.tint(.green)
}
}
}
在这个示例中,滑动列表项会显示“Delete”、“Edit”和“Share”三个按钮,每个按钮的颜色不同,点击后会触发各自的操作。
3. 控制滑动方向
默认情况下,swipeActions
会在向左滑动时显示操作按钮。但可以通过 edge
参数设置滑动方向,以便在向右滑动时显示按钮。可以将 edge
设置为 .leading
(从左滑出)或 .trailing
(从右滑出)。
List {
ForEach(0..<5) { item in
Text("Item \(item)")
.swipeActions(edge: .leading) { // 从左滑动
Button("Flag") {
print("Flag action for Item \(item)")
}
.tint(.yellow)
}
.swipeActions(edge: .trailing) { // 从右滑动
Button("Delete") {
print("Delete action for Item \(item)")
}
.tint(.red)
}
}
}
在这个示例中,从左滑会显示“Flag”按钮,从右滑会显示“Delete”按钮,这种用法非常适合在同一个项中支持多种操作。
4. 使用图标代替文本
swipeActions
中的按钮支持使用 SF Symbols 图标代替文本。通过 Label
或 Image
,可以创建更具视觉效果的按钮。
List {
ForEach(0..<5) { item in
Text("Item \(item)")
.swipeActions {
Button {
print("Delete action for Item \(item)")
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
Button {
print("Edit action for Item \(item)")
} label: {
Label("Edit", systemImage: "pencil")
}
.tint(.blue)
}
}
}
5. 添加角色 (role) 属性
从 iOS 15 开始,可以为 Button
添加 .role
属性,SwiftUI 会根据角色来自动应用适当的样式。例如,将 Button
的角色设置为 .destructive
会使其样式更加突出,用于危险操作(如删除)。
List {
ForEach(0..<5) { item in
Text("Item \(item)")
.swipeActions {
Button(role: .destructive) {
print("Delete action for Item \(item)")
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
在这个例子中,role: .destructive
表示这是一个删除操作,SwiftUI 会自动为该按钮应用一个红色样式。
6. 完整示例
以下是一个完整示例,展示了在滑动时执行多种操作,包括不同的滑动方向和图标。
struct ContentView: View {
var body: some View {
List {
ForEach(0..<5) { item in
Text("Item \(item)")
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
print("Delete action for Item \(item)")
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
Button {
print("Edit action for Item \(item)")
} label: {
Label("Edit", systemImage: "pencil")
}
.tint(.blue)
}
.swipeActions(edge: .leading) {
Button {
print("Flag action for Item \(item)")
} label: {
Label("Flag", systemImage: "flag")
}
.tint(.yellow)
}
}
}
}
}
在这个示例中:
- 向左滑动(
edge: .trailing
)时显示“Delete”和“Edit”按钮。 - 向右滑动(
edge: .leading
)时显示“Flag”按钮。
7. 总结
.swipeActions
:添加滑动手势,支持在单个列表项中触发不同操作。edge
参数:控制滑动的方向,可设置为.leading
或.trailing
。- 多按钮支持:可以在
.swipeActions
中添加多个按钮,通常最多容纳 2-3 个。 - 图标与角色:按钮支持图标(SF Symbols),也可以使用
.role
参数设置为.destructive
来表示危险操作。
swipeActions
提供了一个便捷的方式来丰富列表交互体验,非常适合应用在需要对列表项执行快速操作的场景。
1-59 badge()
//
// badge_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/26.
//
import SwiftUI
struct badge_L: View {
var body: some View {
TabView {
Color.red
.tabItem {
Image(systemName: "heart.fill")
Text("hello")
}
.badge(5)
Color.blue
.tabItem {
Image(systemName: "heart.fill")
Text("hello")
}
.badge(10)
Color.green
.tabItem {
Image(systemName: "heart.fill")
Text("hello")
}
.badge(90)
}
}
}
#Preview {
badge_L()
}
在 SwiftUI 中,badge
是一个非常有用的修饰符,用于在特定的视图(如 Tab
或 List
)上添加徽章。徽章可以是一个数字、文本,通常用于显示通知数量、未读消息或提醒状态。
1. 基本用法
badge
修饰符可以为支持它的视图添加一个视觉提示(通常是红色的小圆点),帮助用户识别有未处理的内容。
语法:
View.badge(_ content: BadgeContent?)
其中 BadgeContent
可以是 Int
、String
或其他可显示的内容。如果设置为 nil
,徽章不会显示。
2. 在 Tab
中使用 badge
在 SwiftUI 中的 TabView
内部,可以为每个 Tab
添加一个 badge
,用于展示每个标签页的通知数量或状态。
示例代码:
import SwiftUI
struct badge_L: View {
var body: some View {
TabView {
Tab("Messages", image: "message.fill") {
Color.blue
.badge(3) // 显示数字徽章,表示有 3 条未读消息
}
Tab("Favorites", image: "star.fill") {
Color.green
.badge(5) // 显示数字徽章,表示有 5 条新的收藏
}
}
}
}
#Preview {
badge_L()
}
在这个例子中:
Tab("Messages", image: "message.fill")
显示了一个3
的徽章,用于表示有 3 条未读消息。Tab("Favorites", image: "star.fill")
显示了一个5
的徽章,用于表示有 5 个新的收藏。
使用 .badge()
修饰符后,会在 Tab
图标的右上角显示一个小红点徽章。
3. 在 List
中使用 badge
badge
也可以用于 List
中的每个行项,例如邮件应用中展示每个文件夹的未读消息数。
示例代码:
List {
Text("Inbox")
.badge(10) // 显示 10 条未读消息
Text("Sent")
.badge(0) // 显示为 0,可以通过条件控制显示与隐藏
Text("Drafts")
.badge("New") // 可以使用字符串来表示特殊状态
}
在这个示例中:
- 每个
List
项目都有一个badge
,可以显示未读消息数量或状态。 badge
的内容可以是整数或字符串,使其适合各种状态表示。
4. 自定义 badge
内容
badge
不仅可以显示数字,还可以显示自定义的文本内容。例如,可以使用文本 "New"
作为徽章内容,用于指示新的消息或状态。
TabView {
Tab("Updates", image: "arrow.up.circle") {
Color.yellow
.badge("New") // 显示文本徽章 "New"
}
Tab("Downloads", image: "arrow.down.circle") {
Color.purple
.badge("5+") // 显示文本徽章 "5+"
}
}
在这个示例中:
- 使用字符串
"New"
作为徽章内容,表示有新更新。 - 使用
"5+"
来表示超过 5 条内容,带有更灵活的展示效果。
5. 动态更新 badge
值
通常,徽章的内容是动态的,比如未读消息数量会随时变化。可以结合 @State
或 @Binding
来动态更新 badge
内容。
示例代码:
struct ContentView: View {
@State private var unreadMessages = 3
var body: some View {
TabView {
Tab("Messages", image: "message.fill") {
Color.blue
.badge(unreadMessages) // 动态显示未读消息数
}
Tab("Favorites", image: "star.fill") {
Color.green
}
}
.onAppear {
// 模拟异步更新未读消息数
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
unreadMessages = 5
}
}
}
}
在这个示例中:
unreadMessages
是一个@State
变量,用于跟踪未读消息数。- 使用
.badge(unreadMessages)
绑定动态值,使徽章可以随数据更新自动变化。
6. 条件显示 badge
在一些场景中,我们希望徽章只在有内容时显示。可以使用条件判断来实现。
示例代码:
struct ContentView: View {
@State private var notifications = 0
var body: some View {
TabView {
Tab("Home", image: "house.fill") {
Color.blue
.badge(notifications > 0 ? notifications : nil) // 有内容时才显示徽章
}
}
.onAppear {
// 模拟网络请求获取通知数量
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
notifications = 3
}
}
}
}
在这个示例中:
- 只有
notifications
大于 0 时,徽章才会显示,避免在没有通知时出现0
的徽章。
7. 总结
Tab
中的badge
:在Tab
上使用badge
来显示未读消息、通知等状态。List
中的badge
:在List
项中使用badge
表示每个项目的状态。- 自定义内容:
badge
支持数字和字符串,可以根据需要自定义。 - 动态更新:通过
@State
或@Binding
动态更新badge
内容。 - 条件显示:可以通过条件控制
badge
是否显示,例如仅在有通知时显示徽章。
1-60 @FoucesState
//
// FocuesState_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct FocuesState_L: View {
@State private var username : String = ""
@FocusState private var usernameInFocues : Bool
@State private var password : String = ""
@FocusState private var passwordInFocus : Bool
var body: some View {
VStack {
TextField("Add Your Name here", text: $username)
.focused($usernameInFocues)
.padding(.leading)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.gray.brightness(0.3))
.clipShape(RoundedRectangle(cornerRadius: 10))
TextField("Add Your password here", text: $password)
.focused($passwordInFocus)
.padding(.leading)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.gray.brightness(0.3))
.clipShape(RoundedRectangle(cornerRadius: 10))
Button("Sign Up💥"){
let usernameIsValid = !username.isEmpty
let passwordIsValid = !password.isEmpty
if usernameIsValid && passwordIsValid {
print("SIGN UP SUCCESSFUL!")
}else if usernameIsValid {
usernameInFocues = false
passwordInFocus = true
}else {
passwordInFocus = false
usernameInFocues = true
}
}
Button("Toggle Focues State"){
usernameInFocues.toggle()
}
}
.padding(40)
// .onAppear{
// DispatchQueue.main.asyncAfter(deadline: .now() + 2){
// self.usernameInFocues = true
// }
// }
}
}
#Preview {
FocuesState_L()
}
//
// FocuesState_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct FocuesState_L: View {
enum OnboardingField:Hashable {
case username
case password
}
@State private var username : String = ""
// @FocusState private var usernameInFocues : Bool
@State private var password : String = ""
// @FocusState private var passwordInFocus : Bool
@FocusState private var fieldInFocus : OnboardingField?
var body: some View {
VStack {
TextField("Add Your Name here", text: $username)
// .focused($usernameInFocues)
.focused($fieldInFocus, equals: .username)
.padding(.leading)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.gray.brightness(0.3))
.clipShape(RoundedRectangle(cornerRadius: 10))
TextField("Add Your password here", text: $password)
.focused($fieldInFocus, equals: .password)
.padding(.leading)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.gray.brightness(0.3))
.clipShape(RoundedRectangle(cornerRadius: 10))
Button("Sign Up💥"){
let usernameIsValid = !username.isEmpty
let passwordIsValid = !password.isEmpty
if usernameIsValid && passwordIsValid {
print("SIGN UP SUCCESSFUL!")
}else if usernameIsValid {
fieldInFocus = .password
}else {
fieldInFocus = .username
}
}
}
.padding(40)
// .onAppear{
// DispatchQueue.main.asyncAfter(deadline: .now() + 2){
// self.usernameInFocues = true
// }
// }
}
}
#Preview {
FocuesState_L()
}
在 SwiftUI 中,@FocusState
是一个非常有用的属性包装器,它可以帮助我们管理视图中可聚焦(focusable)元素的焦点状态,通常用于文本输入字段(TextField
)和其他需要聚焦的组件。通过使用 @FocusState
,我们可以轻松地控制视图的焦点状态,从而改进用户体验。下面是关于 @FocusState
的详细讲解和示例。
1. 基本概念
@FocusState
的主要作用是跟踪或设置视图的焦点状态,它可以和 TextField
等控件一起使用,以便程序能够控制焦点位置。使用时,我们需要先声明一个 @FocusState
属性,并将其与需要聚焦的控件进行绑定,从而使程序可以通过修改这个属性的值来控制焦点的切换。
2. 使用步骤
要使用 @FocusState
,主要分为以下几步:
- 声明一个
@FocusState
属性,用于追踪或设置焦点状态。 - 将
@FocusState
属性绑定到某个控件(例如TextField
)的focused
修饰符上。 - 使用条件语句或交互来设置
@FocusState
属性的值,从而改变焦点状态。
3. 示例一:单个 TextField 的焦点控制
在这个例子中,我们将创建一个简单的 TextField
,并通过 @FocusState
来控制其焦点状态。
import SwiftUI
struct FocusStateExample1: View {
// 1. 声明一个用于控制焦点状态的变量
@FocusState private var isFocused: Bool
var body: some View {
VStack {
TextField("输入内容", text: .constant(""))
// 2. 将焦点状态与 TextField 进行绑定
.focused($isFocused)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
// 3. 创建一个按钮来切换焦点
Button("切换焦点") {
isFocused.toggle() // 切换焦点
}
.padding()
}
}
}
解释
- 第 1 步:我们声明了一个
@FocusState
属性isFocused
,它用于追踪TextField
的焦点状态。 - 第 2 步:将
isFocused
与TextField
的focused
修饰符进行绑定,控制TextField
是否处于焦点状态。 - 第 3 步:通过按钮点击事件来切换
isFocused
的状态,从而控制TextField
的焦点。
4. 示例二:多个 TextField 的焦点控制
当界面上有多个 TextField
时,可以使用枚举来管理不同的焦点状态。
import SwiftUI
struct FocusStateExample2: View {
// 1. 定义一个枚举来标识不同的 TextField
enum Field {
case username
case password
}
// 2. 使用枚举类型来创建一个 @FocusState 属性
@FocusState private var focusedField: Field?
@State private var username = ""
@State private var password = ""
var body: some View {
VStack {
TextField("用户名", text: $username)
// 3. 将 @FocusState 属性绑定到具体的 TextField 上
.focused($focusedField, equals: .username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
SecureField("密码", text: $password)
.focused($focusedField, equals: .password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
// 4. 创建一个按钮来控制焦点的切换
Button("聚焦到用户名") {
focusedField = .username // 设置焦点到用户名字段
}
.padding()
Button("聚焦到密码") {
focusedField = .password // 设置焦点到密码字段
}
.padding()
}
}
}
解释
- 第 1 步:定义一个
Field
枚举来表示不同的输入字段。 - 第 2 步:使用枚举类型声明
@FocusState
属性focusedField
,用于跟踪当前焦点所在的字段。 - 第 3 步:在每个
TextField
上,通过focused
修饰符绑定到特定的枚举值(如.username
和.password
),从而实现不同字段的焦点控制。 - 第 4 步:按钮可以通过设置
focusedField
的值来实现焦点在不同字段之间的切换。
5. 示例三:表单中的焦点控制
在一些需要逐步填写的表单中,@FocusState
可以帮助我们在用户填写完一个字段后自动跳转到下一个字段。
import SwiftUI
struct FocusStateExample3: View {
enum Field {
case firstName
case lastName
case email
}
@FocusState private var focusedField: Field?
@State private var firstName = ""
@State private var lastName = ""
@State private var email = ""
var body: some View {
VStack {
TextField("名字", text: $firstName)
.focused($focusedField, equals: .firstName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.onSubmit {
focusedField = .lastName // 跳转到下一个字段
}
TextField("姓氏", text: $lastName)
.focused($focusedField, equals: .lastName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.onSubmit {
focusedField = .email // 跳转到下一个字段
}
TextField("邮箱", text: $email)
.focused($focusedField, equals: .email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.onSubmit {
focusedField = nil // 完成最后一个字段的输入,移除焦点
}
}
.padding()
.onAppear {
focusedField = .firstName // 打开视图时默认焦点
}
}
}
解释
- 使用
onSubmit
修饰符来设置每个字段的提交行为。当用户在当前字段输入完并按下回车键后,焦点自动跳转到下一个字段。 - 通过
onAppear
将初始焦点设置在firstName
字段。
6. 总结
@FocusState
可以有效地帮助管理视图中输入控件的焦点状态,提升用户体验。- 对于单个控件的焦点控制,可以使用布尔值绑定
@FocusState
。 - 当有多个输入控件时,使用枚举来标识不同的焦点,便于管理不同的控件焦点。
- 在表单场景中,可以结合
onSubmit
等事件,自动切换到下一个输入字段,使表单填写更加流畅。
通过这些例子,希望可以帮助你更好地理解和应用 @FocusState
。
1-61 .onSubmit & .submitLabel
//
// onSubmit&onSubmitLabel.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct onSubmit_onSubmitLabel: View {
@State private var text : String = ""
var body: some View {
VStack {
TextField("username", text: $text)
.submitLabel(.route)
.onSubmit {
print("somthing to the console")
}
TextField("password", text: $text)
.submitLabel(.next)
.onSubmit {
print("somthing to the console")
}
TextField("gender", text: $text)
.submitLabel(.done)
.onSubmit {
print("somthing to the console")
}
}
.padding(40)
}
}
#Preview {
onSubmit_onSubmitLabel()
}
在 SwiftUI 中,.onSubmit
和 .submitLabel
是两个非常有用的修饰符,常用于文本输入控件(例如 TextField
、SecureField
等)上。它们的主要作用是改善表单输入体验,让开发者能够控制“提交”行为和提交按钮的显示。
下面我们详细讲解 .onSubmit
和 .submitLabel
的用法,并举例说明。
1. .onSubmit
修饰符
.onSubmit
是一个用于定义用户在完成输入时(通常是按下“完成”或“返回”按钮)所触发的行为的修饰符。可以用于 TextField
或 SecureField
,让我们在用户提交输入内容后执行一些操作。
用法
TextField("输入内容", text: $text)
.onSubmit {
// 用户提交时执行的操作
print("用户提交了输入内容:\(text)")
}
说明
.onSubmit
是一个触发事件,当用户在完成输入后(例如在键盘上按下“完成”按钮)会触发该闭包。- 常用于表单场景,比如提交表单、切换到下一个输入字段或执行其他操作。
示例一:在 TextField 上使用 .onSubmit
在下面的例子中,我们在 TextField
输入完内容并按下“完成”后,会打印出用户输入的内容。
import SwiftUI
struct OnSubmitExample: View {
@State private var username: String = ""
var body: some View {
VStack {
TextField("请输入用户名", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.onSubmit {
print("用户名已提交:\(username)")
}
}
.padding()
}
}
示例二:多个 TextField 使用 .onSubmit
进行焦点切换
.onSubmit
常用来在多个输入字段间自动切换焦点,优化用户体验。我们可以结合 @FocusState
在输入完一个字段后自动跳转到下一个字段。
import SwiftUI
struct OnSubmitWithFocusExample: View {
enum Field {
case username
case password
}
@FocusState private var focusedField: Field?
@State private var username = ""
@State private var password = ""
var body: some View {
VStack {
TextField("用户名", text: $username)
.focused($focusedField, equals: .username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.onSubmit {
focusedField = .password // 跳转到下一个输入字段
}
SecureField("密码", text: $password)
.focused($focusedField, equals: .password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.onSubmit {
focusedField = nil // 移除焦点
print("表单提交,用户名:\(username),密码:\(password)")
}
}
.padding()
.onAppear {
focusedField = .username // 打开视图时默认焦点
}
}
}
2. .submitLabel
修饰符
.submitLabel
用于设置键盘上“完成”按钮的显示文本,给用户更明确的提示。.submitLabel
可以改变键盘上的提交按钮,例如显示“下一项”、“搜索”、“发送”等等。
用法
TextField("输入内容", text: $text)
.submitLabel(.next) // 设置键盘上提交按钮的文本
常见的 SubmitLabel
值
.submitLabel
提供了一些常见的选项来更改键盘上的按钮文本:
.done
:显示“完成”。.go
:显示“前往”。.send
:显示“发送”。.next
:显示“下一项”。.search
:显示“搜索”。.continue
:显示“继续”。
示例三:设置 TextField 的 .submitLabel
在下面的例子中,我们为用户名设置“下一项”按钮,为密码设置“完成”按钮。这样用户在键盘上看到的按钮文本将会有所不同。
import SwiftUI
struct SubmitLabelExample: View {
enum Field {
case username
case password
}
@FocusState private var focusedField: Field?
@State private var username = ""
@State private var password = ""
var body: some View {
VStack {
TextField("用户名", text: $username)
.focused($focusedField, equals: .username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.submitLabel(.next) // 设置键盘按钮为“下一项”
.onSubmit {
focusedField = .password // 切换到密码输入字段
}
SecureField("密码", text: $password)
.focused($focusedField, equals: .password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.submitLabel(.done) // 设置键盘按钮为“完成”
.onSubmit {
focusedField = nil // 完成输入并移除焦点
print("表单提交,用户名:\(username),密码:\(password)")
}
}
.padding()
}
}
1-62 NavigationStack
//
// NavigationStack_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct NavigationStack_L: View {
var body: some View {
NavigationStack{
ScrollView {
VStack (spacing: 40) {
ForEach(0..<10){x in
NavigationLink(value: x) {
Text("Click Me \(x)")
}
// NavigationLink(destination: {
// MySecondScreen01(value: x)
// }, label: {
// Text("Click Me \(x)")
// })
}
}
}
.navigationTitle("Nav Title")
.navigationDestination(for: Int.self) { value in
MySecondScreen01(value: value)
}
}
}
}
struct MySecondScreen01 : View {
let value : Int
init(value : Int) {
self.value = value
print("INIT THE SCREEN \(value)")
}
var body : some View {
Text("Screen \(value)")
}
}
#Preview {
NavigationStack_L()
}
//
// NavigationStack_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct NavigationStack_L: View {
let fruits : [String] = [
"Orange", "Apple", "potato", "Banana"
]
var body: some View {
NavigationStack{
ScrollView {
VStack (spacing: 40) {
ForEach(fruits, id: \.self) { fruit in
NavigationLink(value: fruit) {
Text("\(fruit)")
}
}
ForEach(0..<10){x in
NavigationLink(value: x) {
Text("Click Me \(x)")
}
// NavigationLink(destination: {
// MySecondScreen01(value: x)
// }, label: {
// Text("Click Me \(x)")
// })
}
}
}
.navigationTitle("Nav Title")
.navigationDestination(for: Int.self) { value in
MySecondScreen01(value: value)
}
.navigationDestination(for: String.self) { value in
Text("ANOTHER SCREEN \(value)")
}
}
}
}
struct MySecondScreen01 : View {
let value : Int
init(value : Int) {
self.value = value
print("INIT THE SCREEN \(value)")
}
var body : some View {
Text("Screen \(value)")
}
}
#Preview {
NavigationStack_L()
}
NavigationStack
是 SwiftUI 中的一个导航容器,用于在应用中管理和显示不同视图之间的导航。它取代了之前的 NavigationView
,并提供了更灵活的 API,使得管理导航栈变得更加直观和易于控制。
1. 基本概念
NavigationStack
就像一组“视图堆栈”。每当用户导航到一个新视图时,视图被“推”到栈顶;返回时,视图被“弹出”栈顶。这种机制可以让用户在不同页面之间前进和后退。
2. 基本用法
要使用 NavigationStack
,需要将所有想要导航的内容放在 NavigationStack
中。然后,在每个需要导航的视图上使用 NavigationLink
来链接到目标视图。
示例:基本的 NavigationStack
和 NavigationLink
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
VStack {
Text("主页")
.font(.largeTitle)
.padding()
NavigationLink("前往详情页", destination: DetailView())
.padding()
}
}
}
}
struct DetailView: View {
var body: some View {
VStack {
Text("详情页")
.font(.title)
.padding()
}
}
}
说明
NavigationStack
是一个容器,所有导航操作都发生在这个容器内。NavigationLink
是一个链接,可以让用户从当前视图导航到目标视图。destination
参数是目标视图,当点击链接时会导航到这个视图。
3. 传递数据
NavigationLink
可以用于传递数据给下一个视图。例如,可以在目标视图中定义一个属性,用于接收数据。
示例:传递数据到目标视图
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
VStack {
NavigationLink("前往详情页", destination: DetailView(message: "你好,详情页!"))
.padding()
}
}
}
}
struct DetailView: View {
var message: String // 接收来自上一个视图的数据
var body: some View {
Text(message)
.font(.title)
.padding()
}
}
说明
NavigationLink
的destination
可以是一个带参数的视图,从而实现数据的传递。
4. 使用 path
进行动态导航
NavigationStack
提供了一个 path
属性,它可以控制导航栈的内容。通过添加或删除 path
中的元素,可以动态地导航到不同的视图。
示例:使用 path
进行动态导航
import SwiftUI
struct ContentView: View {
@State private var path: [String] = [] // 定义导航路径
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("主页")
.font(.largeTitle)
Button("前往详情页 A") {
path.append("详情页 A") // 添加导航路径
}
.padding()
Button("前往详情页 B") {
path.append("详情页 B")
}
.padding()
}
.navigationDestination(for: String.self) { value in
// 根据路径中的值导航到不同的视图
Text("当前页面: \(value)")
.font(.title)
}
}
}
}
说明
path
是一个数组,用于存储导航栈的路径。每个路径元素对应一个视图。navigationDestination(for:content:)
可以指定path
中的某个类型,并动态决定导航到哪个视图。
5. 返回上一级和清空导航栈
在 NavigationStack
中,可以使用 dismiss
或 path.removeLast()
来返回上一级,还可以使用 path.removeAll()
来返回到最初视图。
示例:返回上一级和清空导航栈
import SwiftUI
struct ContentView: View {
@State private var path: [String] = []
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("主页")
.font(.largeTitle)
Button("前往详情页 A") {
path.append("详情页 A")
}
.padding()
}
.navigationDestination(for: String.self) { value in
VStack {
Text("当前页面: \(value)")
.font(.title)
Button("返回上一级") {
path.removeLast() // 返回上一级
}
.padding()
Button("回到主页") {
path.removeAll() // 清空导航栈,返回主页
}
.padding()
}
}
}
}
}
说明
path.removeLast()
:将栈顶的视图移除,相当于返回上一级。path.removeAll()
:清空整个栈,相当于返回到初始页面。
6. 在 NavigationStack
中使用多个视图类型
NavigationStack
支持多个不同类型的视图在同一个导航栈中。通过在 path
中使用自定义的枚举或其他类型,可以动态选择不同的目标视图。
示例:在 NavigationStack
中使用枚举管理多种视图
import SwiftUI
enum Page {
case detailA
case detailB
}
struct ContentView: View {
@State private var path: [Page] = []
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("主页")
.font(.largeTitle)
Button("前往详情页 A") {
path.append(.detailA)
}
.padding()
Button("前往详情页 B") {
path.append(.detailB)
}
.padding()
}
.navigationDestination(for: Page.self) { page in
switch page {
case .detailA:
Text("详情页 A")
.font(.title)
case .detailB:
Text("详情页 B")
.font(.title)
}
}
}
}
}
说明
- 使用枚举
Page
代表不同的页面。 - 通过
switch
语句在navigationDestination
中根据不同的枚举值展示不同的视图。
1-63 toolBar
//
// toolBar_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct toolBar_L: View {
@State var text : String = ""
var body: some View {
NavigationStack {
ZStack {
Color.green.ignoresSafeArea()
ScrollView {
TextField("placeHolder...",text: $text)
Text("Hi👋")
.foregroundStyle(Color.white)
}
}
.navigationTitle("Tool Bar")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Image(systemName: "heart.fill")
}
ToolbarItem(placement: .topBarLeading) {
Image(systemName: "gear")
}
ToolbarItem(placement: .keyboard) {
Image(systemName: "gear")
.frame(maxWidth: .infinity,alignment: .leading)
}
}
}
}
}
#Preview {
toolBar_L()
}
在 SwiftUI 中,iOS 17 引入了 .topBarTrailing
和 .topBarLeading
,逐渐取代了 .navigationBarTrailing
和 .navigationBarLeading
,这是为了让工具栏更具灵活性、适用于更广泛的设备类型。
1. 基本概念
.topBarTrailing
和 .topBarLeading
是工具栏放置选项,将工具栏内容放在顶部栏的左右两侧,适用于大部分设备和不同的场景。
2. 基本用法
我们将工具栏按钮放在 NavigationStack
顶部栏的左右两侧。
示例:使用 .topBarTrailing
和 .topBarLeading
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
Text("主页内容")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(action: {
print("添加按钮被点击")
}) {
Image(systemName: "plus")
}
}
ToolbarItem(placement: .topBarLeading) {
Button(action: {
print("返回按钮被点击")
}) {
Image(systemName: "arrow.backward")
}
}
}
}
}
}
说明
.topBarTrailing
放置在顶部栏的右侧,通常用于“添加”或“设置”按钮。.topBarLeading
放置在顶部栏的左侧,通常用于返回按钮或导航。
3. 使用多个工具栏项
我们可以在 .topBarTrailing
和 .topBarLeading
添加多个工具栏项,或者使用 ToolbarItemGroup
组合多个按钮。
示例:多个工具栏项目
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
Text("主页内容")
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("取消") {
print("取消按钮被点击")
}
}
ToolbarItemGroup(placement: .topBarTrailing) {
Button("编辑") {
print("编辑按钮被点击")
}
Button("完成") {
print("完成按钮被点击")
}
}
}
}
}
}
说明
ToolbarItemGroup
在.topBarTrailing
中放置多个按钮,按钮会从右到左依次排列。
4. 使用动态工具栏项
根据不同的状态动态切换工具栏按钮,可以让用户界面更加智能。
示例:根据状态切换工具栏按钮
import SwiftUI
struct ContentView: View {
@State private var isEditing = false
var body: some View {
NavigationStack {
VStack {
Toggle("编辑模式", isOn: $isEditing)
.padding()
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
if isEditing {
Button("完成") {
isEditing = false
}
} else {
Button("编辑") {
isEditing = true
}
}
}
}
}
}
}
说明
- 当
isEditing
为true
时,工具栏中显示“完成”按钮;当为false
时显示“编辑”按钮。
5. 自定义工具栏内容
可以在 .topBarLeading
或 .topBarTrailing
中加入自定义视图。
示例:自定义工具栏内容
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
Text("主页内容")
.toolbar {
ToolbarItem(placement: .topBarLeading) {
HStack {
Image(systemName: "star.fill")
Text("自定义标题")
.font(.headline)
Image(systemName: "star.fill")
}
}
}
}
}
}
说明
- 在
.topBarLeading
中使用HStack
,可以放置图标和文本,实现自定义标题或布局样式。
6. 使用底部工具栏项 .bottomBar
.bottomBar
用于在页面底部工具栏添加按钮或操作。
示例:在底部工具栏添加按钮
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
Text("主页内容")
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button("收藏") {
print("收藏按钮被点击")
}
Spacer()
Button("分享") {
print("分享按钮被点击")
}
}
}
}
}
}
说明
.bottomBar
默认将工具栏项水平排列,可以用Spacer()
控制按钮间距。
7. 控制工具栏的显示与隐藏
可以根据状态条件来显示或隐藏工具栏。
示例:根据状态显示或隐藏工具栏
import SwiftUI
struct ContentView: View {
@State private var showToolbar = true
var body: some View {
NavigationStack {
VStack {
Toggle("显示工具栏", isOn: $showToolbar)
.padding()
Text("主页内容")
}
.toolbar(showToolbar ? .visible : .hidden) // 显示或隐藏工具栏
}
}
}
说明
- 通过
.visible
和.hidden
可以控制工具栏的显示和隐藏。
8. toolbarColorScheme
toolbarColorScheme
是 SwiftUI 中一个新的修饰符,用于设置工具栏的颜色方案。通过它,可以指定工具栏使用的颜色主题,例如让工具栏在浅色模式或深色模式下显示特定的颜色。
1. toolbarColorScheme
基本概念
toolbarColorScheme
允许我们为 NavigationStack
或其他视图容器设置工具栏的颜色方案。即使系统界面是浅色或深色模式,工具栏颜色可以独立于系统的颜色模式。
常见的 ColorScheme
选项包括:
.light
:浅色模式.dark
:深色模式
2. toolbarColorScheme
的用法
使用 toolbarColorScheme
可以指定工具栏在浅色或深色模式下的显示效果。例如,可以强制工具栏始终显示为深色模式,即使用户的系统主题是浅色模式。
示例:将工具栏设置为深色模式
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
Text("主页内容")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(action: {
print("设置按钮被点击")
}) {
Image(systemName: "gearshape")
}
}
}
.toolbarColorScheme(.dark) // 强制工具栏为深色模式
}
}
}
说明
toolbarColorScheme(.dark)
将工具栏设置为深色模式,即使整个系统界面是浅色模式。- 这种设置可以帮助在浅色模式下突显工具栏内容,或统一界面风格。
3. 条件切换 toolbarColorScheme
可以根据系统的 ColorScheme
或应用的状态动态切换工具栏的颜色方案。例如,当用户处于浅色模式时,强制工具栏为深色;在深色模式时,工具栏为浅色。
示例:根据系统模式动态切换工具栏颜色
import SwiftUI
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme // 获取系统的当前颜色模式
var body: some View {
NavigationStack {
Text("主页内容")
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button(action: {
print("返回按钮被点击")
}) {
Image(systemName: "arrow.backward")
}
}
ToolbarItem(placement: .topBarTrailing) {
Button(action: {
print("编辑按钮被点击")
}) {
Image(systemName: "pencil")
}
}
}
.toolbarColorScheme(colorScheme == .light ? .dark : .light) // 根据系统模式设置工具栏颜色
}
}
}
说明
- 通过
@Environment(\.colorScheme)
获取当前系统的颜色模式。 - 使用条件语句,当系统为浅色模式时,工具栏为深色;反之亦然。
4. 配合 toolbarBackground
使用
toolbarColorScheme
常与 toolbarBackground
一起使用,以确保工具栏颜色和背景风格一致。
示例:设置工具栏背景和颜色方案
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
Text("主页内容")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(action: {
print("设置按钮被点击")
}) {
Image(systemName: "gearshape")
}
}
}
.toolbarBackground(Color.blue, for: .navigationBar) // 设置工具栏背景为蓝色
.toolbarColorScheme(.light) // 工具栏内容为浅色模式
}
}
}
说明
toolbarBackground(Color.blue, for: .navigationBar)
设置工具栏背景色为蓝色。toolbarColorScheme(.light)
设置工具栏内容使用浅色模式,使图标和文字在蓝色背景上更清晰。
1-64 resizable sheets
//
// resiableSheets.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct resiableSheets: View {
@State private var showSheet : Bool = false
var body: some View {
Button("Click Me") {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
MyNextView()
// 显示的高度
// .presentationDetents([.fraction(0.7)])
.presentationDetents([.height(200)])
// .presentationDetents([.medium])
// 隐藏下拉条
// .presentationDragIndicator(.hidden)
// 禁止下拉隐藏
// .interactiveDismissDisabled()
}
}
}
struct MyNextView : View {
var body : some View {
ZStack {
Color.red.ignoresSafeArea()
Text("Hello world!!")
}
}
}
#Preview {
resiableSheets()
}
1. .presentationDetents(_:)
- 控制 sheet
的高度
以下代码示例展示了如何使用 .presentationDetents
设置 sheet
的高度,包括使用固定高度、百分比高度以及系统预设高度。
import SwiftUI
struct DetentsExample: View {
@State private var showSheet = false
var body: some View {
Button("Show Sheet") {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
MyNextView()
// 设置多个高度选择
.presentationDetents([.height(200), .fraction(0.5), .large])
}
}
}
struct MyNextView: View {
var body: some View {
ZStack {
Color.green.ignoresSafeArea()
Text("Hello, World!")
.foregroundColor(.white)
.font(.largeTitle)
}
}
}
#Preview {
DetentsExample()
}
说明
.height(200)
:设置sheet
高度为 200 点。.fraction(0.5)
:设置sheet
为屏幕高度的 50%。.large
:使用系统的large
高度(接近全屏)。
2. .presentationDragIndicator(_:)
- 控制拖拽指示器的显示
此代码展示了如何使用 .presentationDragIndicator
来控制 sheet
的拖拽指示器。可以显示或隐藏顶部的拖拽指示器。
import SwiftUI
struct DragIndicatorExample: View {
@State private var showSheet = false
var body: some View {
Button("Show Sheet") {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
MyNextView()
.presentationDetents([.medium, .large]) // 使用系统高度
.presentationDragIndicator(.hidden) // 隐藏拖拽指示器
}
}
}
struct MyNextView: View {
var body: some View {
ZStack {
Color.blue.ignoresSafeArea()
Text("Hello, World!")
.foregroundColor(.white)
.font(.largeTitle)
}
}
}
#Preview {
DragIndicatorExample()
}
说明
.presentationDragIndicator(.hidden)
:隐藏顶部的拖拽指示器。- 可以替换为
.presentationDragIndicator(.visible)
来显示拖拽指示器(默认显示)。
3. .interactiveDismissDisabled(_:)
- 控制是否允许拖拽关闭
以下代码展示了如何使用 .interactiveDismissDisabled
来控制 sheet
是否可以通过拖拽或点击背景关闭。
import SwiftUI
struct DismissDisabledExample: View {
@State private var showSheet = false
var body: some View {
Button("Show Sheet") {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
MyNextView()
.presentationDetents([.medium, .large]) // 支持多高度切换
.interactiveDismissDisabled(true) // 禁止通过拖拽关闭
}
}
}
struct MyNextView: View {
var body: some View {
ZStack {
Color.red.ignoresSafeArea()
Text("This sheet cannot be dismissed by dragging!")
.foregroundColor(.white)
.font(.title)
.padding()
}
}
}
#Preview {
DismissDisabledExample()
}
说明
.interactiveDismissDisabled(true)
:禁止用户通过拖拽或点击背景关闭sheet
,适用于需要强制完成任务的场景。- 如果设置为
.interactiveDismissDisabled(false)
,用户可以通过拖拽关闭视图(默认行为)。
4. 综合示例 - 组合使用多个属性
在下面的代码中,我们将 .presentationDetents
、.presentationDragIndicator
和 .interactiveDismissDisabled
组合在一起,创建一个更复杂的 sheet
。这个 sheet
支持多高度切换,隐藏拖拽指示器,且不允许通过拖拽关闭。
import SwiftUI
struct CombinedExample: View {
@State private var showSheet = false
var body: some View {
Button("Show Sheet") {
showSheet.toggle()
}
.sheet(isPresented: $showSheet) {
MyNextView()
.presentationDetents([.fraction(0.5), .large]) // 支持 50% 和全屏高度
.presentationDragIndicator(.hidden) // 隐藏拖拽指示器
.interactiveDismissDisabled(true) // 禁止拖拽关闭
}
}
}
struct MyNextView: View {
var body: some View {
ZStack {
Color.purple.ignoresSafeArea()
VStack {
Text("This is a resizable sheet")
.foregroundColor(.white)
.font(.largeTitle)
.padding()
Text("You can't dismiss it by dragging!")
.foregroundColor(.white)
.font(.title2)
}
}
}
}
#Preview {
CombinedExample()
}
说明
.presentationDetents([.fraction(0.5), .large])
:允许用户在 50% 和全屏高度之间切换。.presentationDragIndicator(.hidden)
:隐藏拖拽指示器。.interactiveDismissDisabled(true)
:用户无法通过拖拽或点击背景关闭视图。
5. 总结
.presentationDetents
控制sheet
的高度,可以指定多个高度选项以支持用户拖拽调整。.presentationDragIndicator
控制是否显示顶部拖拽指示器,让用户知道该视图是否可以拖动。.interactiveDismissDisabled
控制sheet
是否可以通过拖拽或点击背景关闭,非常适合需要强制用户完成任务的场景。
1-65 SafeAreaInset
//
// SafeAreaInsets.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct SafeAreaInsets: View {
var body: some View {
NavigationStack {
List(0..<10) { _ in
Rectangle()
.frame(height: 300)
}
.navigationTitle("Safe Area Insets")
.safeAreaInset(edge: .bottom, alignment: .center, spacing: nil) {
Text("hello Aini")
.padding()
.frame(maxWidth: .infinity)
.background(Color.yellow)
}
}
}
}
#Preview {
SafeAreaInsets()
}
在 SwiftUI 中,SafeAreaInsets
是一个用于管理安全区域(Safe Area)内边距的属性。安全区域是在 iOS 设备上内容不被遮挡的可见区域,比如在 iPhone 顶部的状态栏区域、底部的 Home 指示条区域等。SafeAreaInsets
帮助我们确保内容在各类设备和屏幕方向上都能正确显示,避免被遮挡。
1. 什么是 SafeAreaInsets
SafeAreaInsets
表示视图的内边距值,这些值取决于设备和视图的布局。它帮助我们获取当前设备的安全区域大小,从而根据这些值动态调整视图布局。SafeAreaInsets
的类型是 EdgeInsets
,包含四个方向的边距:top
、bottom
、leading
、trailing
。
2. 如何获取 SafeAreaInsets
在 SwiftUI 中,可以通过 @Environment(\.safeAreaInsets)
属性包装器来获取安全区域内边距的值。这种方式可以动态访问当前视图的安全区域。
示例:获取并显示 SafeAreaInsets
import SwiftUI
struct SafeAreaInsetsExample: View {
@Environment(\.safeAreaInsets) private var safeAreaInsets
var body: some View {
VStack {
Text("Safe Area Insets")
.font(.largeTitle)
.padding()
Text("Top: \(safeAreaInsets.top)")
Text("Bottom: \(safeAreaInsets.bottom)")
Text("Leading: \(safeAreaInsets.leading)")
Text("Trailing: \(safeAreaInsets.trailing)")
}
.padding()
}
}
#Preview {
SafeAreaInsetsExample()
}
解释
@Environment(\.safeAreaInsets)
通过环境变量获取当前视图的安全区域内边距。safeAreaInsets.top
、safeAreaInsets.bottom
、safeAreaInsets.leading
和safeAreaInsets.trailing
分别返回顶部、底部、左侧和右侧的安全区域边距。
3. 使用 SafeAreaInsets
动态布局视图
获取安全区域的内边距后,我们可以利用这些值来动态调整视图的布局,确保内容在所有设备上都清晰可见。
示例:根据安全区域内边距调整内容位置
import SwiftUI
struct SafeAreaPaddingExample: View {
@Environment(\.safeAreaInsets) private var safeAreaInsets
var body: some View {
ZStack {
Color.blue.ignoresSafeArea()
VStack {
Text("This text is adjusted")
.font(.title)
.padding(.top, safeAreaInsets.top) // 添加顶部安全区域内边距
.padding(.bottom, safeAreaInsets.bottom) // 添加底部安全区域内边距
Spacer()
Text("Adjusted bottom text")
.padding(.bottom, safeAreaInsets.bottom) // 添加底部安全区域内边距
}
.foregroundColor(.white)
}
}
}
#Preview {
SafeAreaPaddingExample()
}
解释
- 顶部和底部的文本分别加上了
safeAreaInsets.top
和safeAreaInsets.bottom
的内边距,确保内容在设备上不会被状态栏或 Home 指示条遮挡。 ignoresSafeArea()
用于背景视图填充整个屏幕,而不是局限于安全区域内部。
4. SafeAreaInsets
与 ignoresSafeArea
组合使用
ignoresSafeArea
可以让视图扩展到安全区域之外。这在全屏背景或图片需要覆盖整个屏幕时非常常见。我们可以将背景设置为忽略安全区域,同时为内容设置安全区域的内边距,确保内容不被遮挡。
示例:忽略安全区域的背景与内容安全布局
import SwiftUI
struct BackgroundSafeAreaExample: View {
@Environment(\.safeAreaInsets) private var safeAreaInsets
var body: some View {
ZStack {
// 背景颜色扩展到安全区域之外
Color.green.ignoresSafeArea()
VStack {
Text("Safe Content Area")
.padding(.top, safeAreaInsets.top) // 内容按安全区域调整
.font(.largeTitle)
Spacer()
Text("Bottom Aligned Text")
.padding(.bottom, safeAreaInsets.bottom) // 内容按安全区域调整
}
.foregroundColor(.white)
}
}
}
#Preview {
BackgroundSafeAreaExample()
}
解释
- 背景颜色使用
ignoresSafeArea
占满整个屏幕。 - 内容视图根据
safeAreaInsets.top
和safeAreaInsets.bottom
添加顶部和底部的安全区域内边距,避免被状态栏和 Home 指示条遮挡。
5. 使用 SafeAreaInsets
调整动画或自定义布局
在某些复杂布局中,SafeAreaInsets
可用于动态调整布局,确保动画或浮动按钮不会被遮挡。
示例:自定义布局中的安全区域调整
import SwiftUI
struct CustomLayoutExample: View {
@Environment(\.safeAreaInsets) private var safeAreaInsets
var body: some View {
ZStack(alignment: .bottomTrailing) {
Color.black.ignoresSafeArea()
VStack {
Text("Content Area")
.font(.title)
.foregroundColor(.white)
.padding(.top, safeAreaInsets.top)
Spacer()
}
Button(action: {
print("Button pressed")
}) {
Image(systemName: "plus.circle.fill")
.resizable()
.frame(width: 50, height: 50)
.foregroundColor(.white)
}
.padding(.trailing, safeAreaInsets.trailing + 16) // 调整右侧内边距
.padding(.bottom, safeAreaInsets.bottom + 16) // 调整底部内边距
}
}
}
#Preview {
CustomLayoutExample()
}
解释
- 底部浮动按钮使用了
safeAreaInsets.trailing
和safeAreaInsets.bottom
来设置内边距,确保按钮在所有设备上都不会被遮挡。
6. 总结
@Environment(\.safeAreaInsets)
:通过环境变量获取当前设备的安全区域内边距。.top
、.bottom
、.leading
、.trailing
:分别表示安全区域的顶部、底部、左侧和右侧内边距。- 应用场景:
- 确保内容在安全区域内可见,避免被状态栏、Home 指示条等遮挡。
- 动态调整视图的布局和位置,适应不同设备和屏幕尺寸。
- 配合
ignoresSafeArea
使用,使背景填充全屏而内容留在安全区域内。
1-66 Group
//
// Group_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct Group_L: View {
var body: some View {
VStack(spacing: 50) {
Group {
Text("hello world")
Text("hello world")
}
.font(.caption)
.foregroundStyle(Color.blue)
Group {
Text("hello world")
Text("hello world")
}
}
.foregroundStyle(Color.red)
}
}
#Preview {
Group_L()
}
在 SwiftUI 中,Group
是一种视图容器,可以将多个视图组合在一起,通常用于组织视图结构、避免视图数量限制,或动态控制内容显示。Group
本身不会影响视图布局或样式,它的主要作用是逻辑上的视图分组。
1. Group
的基本用法
在 SwiftUI 中,每个 ViewBuilder
(如 VStack
、HStack
、ZStack
)最多只能包含 10 个子视图。Group
可以将多个视图进行分组,从而突破这个限制。
示例:使用 Group
进行视图分组
import SwiftUI
struct BasicGroupExample: View {
var body: some View {
VStack {
Group {
Text("视图 1")
Text("视图 2")
Text("视图 3")
}
Group {
Text("视图 4")
Text("视图 5")
Text("视图 6")
}
}
.font(.title)
}
}
#Preview {
BasicGroupExample()
}
解释
Group
将多个Text
视图组合在一起,但不会影响VStack
的布局。Group
内的视图仍然会按VStack
的布局显示,相当于逻辑分组。
2. 动态内容和条件视图的分组
Group
常用于根据条件显示不同内容。在 SwiftUI 中,Group
的内容可以根据不同状态或条件来动态生成,便于管理复杂的视图逻辑。
示例:根据条件显示视图组
import SwiftUI
struct ConditionalGroupExample: View {
@State private var isVIP = true
var body: some View {
VStack {
Text("用户信息")
.font(.largeTitle)
Group {
if isVIP {
Text("尊贵的 VIP 用户")
Text("享有独家特权")
} else {
Text("普通用户")
Text("可以升级为 VIP")
}
}
.font(.title)
}
}
}
#Preview {
ConditionalGroupExample()
}
解释
Group
包含了根据isVIP
状态的不同而显示的不同内容。Group
使得条件分支代码更整洁,易于管理。
3. 使用 Group
在 ForEach
循环中分组视图
Group
可以和 ForEach
一起使用,将动态生成的视图组合在一起。这样可以在循环内容和其他视图之间创建逻辑分隔,而不影响布局。
示例:在 ForEach
中使用 Group
import SwiftUI
struct ForEachGroupExample: View {
let names = ["Alice", "Bob", "Charlie", "Diana"]
var body: some View {
VStack {
Text("用户列表")
.font(.largeTitle)
Group {
ForEach(names, id: \.self) { name in
Text("用户: \(name)")
}
}
.font(.title)
}
}
}
#Preview {
ForEachGroupExample()
}
解释
Group
包含了ForEach
循环,可以将整个循环视图作为一个逻辑单元管理。Group
本身不会改变VStack
的布局或样式。
4. 使用 Group
组织复杂视图结构
当视图包含多个层次时,Group
可以帮助我们将视图逻辑分段,从而提升代码可读性。它既可以用于静态内容,也可以用于动态内容或条件内容的分段。
示例:使用 Group
组织复杂布局
import SwiftUI
struct ComplexLayoutExample: View {
var body: some View {
VStack {
Group {
Text("Header")
.font(.largeTitle)
.padding(.bottom, 20)
HStack {
Text("Left Column")
Spacer()
Text("Right Column")
}
}
Divider()
Group {
Text("Footer")
.font(.title)
.padding(.top, 20)
}
}
.padding()
}
}
#Preview {
ComplexLayoutExample()
}
解释
Group
将头部、主体和页脚等内容进行逻辑分组,使视图层次结构清晰。Group
可以简化复杂视图布局,提高代码的清晰度和可维护性。
5. Group
与样式无关
Group
仅用于逻辑分组,不会添加任何样式或布局。即使将 Group
放入某个视图容器中,它也不会影响子视图的显示效果。例如,如果将 Group
放入 VStack
中,它不会影响 VStack
的排列方式。
示例:Group
不影响样式
import SwiftUI
struct NoStyleEffectExample: View {
var body: some View {
VStack {
Text("没有 Group 的内容")
.font(.title)
.padding()
Group {
Text("这是 Group 内的内容")
Text("Group 不影响视图样式")
}
.font(.title)
.padding()
}
}
}
#Preview {
NoStyleEffectExample()
}
解释
Group
内部的视图样式完全依赖于VStack
,Group
不会增加任何额外的样式。
6. 总结
- 视图分组:
Group
可以将多个视图逻辑上分组,但不会影响布局或样式。 - 条件内容:通过
Group
可以根据条件动态控制视图内容,简化条件视图的管理。 - 组合
ForEach
:可以在ForEach
中使用Group
,将动态生成的内容组合在一起。 - 代码结构化:在复杂布局中,
Group
提高了代码的清晰度和可读性。
1-67 Animation(新的用法)
//
// AnimationUpdated_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct AnimationUpdated_L: View {
@State private var animate1 : Bool = false
@State private var animate2 : Bool = false
var body: some View {
ZStack {
VStack(spacing: 40) {
Button("button 1"){
animate1.toggle()
}
Button("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)
}
}
.animation(.spring(), value: animate1)
.animation(.linear(duration: 5), value: animate2)
}
}
#Preview {
AnimationUpdated_L()
}
1-67 Menu
//
// menu_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct menu_L: View {
var body: some View {
Menu("Click Me"){
Button("One"){
}
Button("Two"){
}
Button("Three"){
}
Button("Four"){
}
}
}
}
#Preview {
menu_L()
}
1-68 .popover
//
// Popover_learning.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct Popover_learning: View {
@State private var showPopover : Bool = false
var body: some View {
ZStack {
Color.gray.ignoresSafeArea()
Button("Click Me"){
showPopover.toggle()
}
.popover(isPresented: $showPopover) {
Text("Hello Aini")
// .presentationCompactAdaptation(.fullScreenCover)
.presentationCompactAdaptation(.popover)
}
}
}
}
#Preview {
Popover_learning()
}
在 SwiftUI 中,.popover
和 .presentationCompactAdaptation
常常配合使用,尤其是在不同设备或屏幕尺寸上以不同的展示方式适配内容。.presentationCompactAdaptation
允许我们在更小的屏幕(如 iPhone)上展示 popover
弹窗内容时,自动适配为全屏展示,以便更好地利用空间。以下是这两个属性的详细讲解和一些应用示例。
1. .popover
的基础用法
.popover
用于在按钮或视图的附近展示一个浮动的弹窗,常用于 iPad 或 Mac 等大屏设备。在小屏设备上,默认会将内容以 sheet
的形式展示,但可以通过 .presentationCompactAdaptation
来调整这一行为。
示例:基础的 .popover
使用
import SwiftUI
struct BasicPopoverExample: View {
@State private var showPopover = false
var body: some View {
Button("Show Popover") {
showPopover.toggle()
}
.popover(isPresented: $showPopover) {
Text("This is a popover!")
.padding()
.font(.title)
}
}
}
#Preview {
BasicPopoverExample()
}
在 iPad 或 Mac 上,.popover
会以浮动窗口形式显示内容,而在小屏设备(如 iPhone)上会改为 sheet
弹窗。
2. .presentationCompactAdaptation
- 控制小屏设备的弹出样式
.presentationCompactAdaptation
是用于控制 popover
在小屏设备(如 iPhone)上的适配方式。默认情况下,popover
在 iPhone 上会变为一个 sheet
弹窗,但通过设置 .presentationCompactAdaptation(.fullScreen)
或 .presentationCompactAdaptation(.popover)
,我们可以改变这个默认行为。
可用的适配方式
.presentationCompactAdaptation(.automatic)
:系统自动选择合适的展示方式(默认)。.presentationCompactAdaptation(.popover)
:即使在小屏幕上也尝试以popover
形式展示。.presentationCompactAdaptation(.sheet)
:强制在小屏幕上以sheet
的形式展示。.presentationCompactAdaptation(.fullScreen)
:在小屏幕上将popover
内容展示为全屏。
3. .popover
和 .presentationCompactAdaptation
配合使用
当我们希望在大屏设备上展示 popover
,在小屏设备上展示全屏或 sheet
,可以通过 .presentationCompactAdaptation
设置适配方式。
示例:使用 .presentationCompactAdaptation
控制不同设备的展示方式
import SwiftUI
struct PopoverWithAdaptationExample: View {
@State private var showPopover = false
var body: some View {
Button("Show Adapted Popover") {
showPopover.toggle()
}
.popover(isPresented: $showPopover) {
VStack {
Text("Adapted Popover Content")
.font(.title)
.padding()
Button("Close") {
showPopover = false
}
.padding()
}
.frame(width: 300, height: 200)
}
.presentationCompactAdaptation(.fullScreenCover) // 小屏设备上展示为全屏
}
}
#Preview {
PopoverWithAdaptationExample()
}
解释
.popover
在 iPad 和 Mac 等大屏设备上会以浮动窗口形式显示内容。.presentationCompactAdaptation(.fullScreen)
设置小屏设备上以全屏方式展示popover
内容,适用于需要较大交互区域的内容。
4. .popover
与 .presentationCompactAdaptation(.popover)
的使用
在某些情况下,我们希望在 iPhone 等小屏设备上也强制以 popover
的形式展示内容,而不是自动切换为 sheet
或全屏。这可以通过 .presentationCompactAdaptation(.popover)
来实现。
示例:在小屏设备上强制显示 popover
import SwiftUI
struct ForcedPopoverExample: View {
@State private var showPopover = false
var body: some View {
Button("Show Forced Popover") {
showPopover.toggle()
}
.popover(isPresented: $showPopover) {
Text("Popover Content")
.padding()
.font(.title)
}
.presentationCompactAdaptation(.popover) // 强制在小屏设备上也使用 popover
}
}
#Preview {
ForcedPopoverExample()
}
解释
.presentationCompactAdaptation(.popover)
强制popover
即使在小屏设备上也使用弹窗,而不是自动切换为sheet
。
5. .presentationCompactAdaptation
与 .sheet
配合使用
在某些应用场景下,我们希望小屏设备上以 sheet
而非 popover
的形式展示内容。可以通过 .presentationCompactAdaptation(.sheet)
来强制在小屏设备上使用 sheet
。
示例:在小屏设备上强制显示 sheet
import SwiftUI
struct SheetAdaptationExample: View {
@State private var showPopover = false
var body: some View {
Button("Show Sheet Adaptation") {
showPopover.toggle()
}
.popover(isPresented: $showPopover) {
VStack {
Text("Sheet Adapted Content")
.font(.title)
.padding()
Button("Close") {
showPopover = false
}
.padding()
}
.frame(width: 300, height: 200)
}
.presentationCompactAdaptation(.sheet) // 在小屏设备上显示为 sheet
}
}
#Preview {
SheetAdaptationExample()
}
解释
.presentationCompactAdaptation(.sheet)
让小屏设备以sheet
的形式展示内容,而不是默认的popover
弹窗。- 在大屏设备上,
popover
仍然显示为浮动窗口。
6. 总结
.popover
:用于显示弹窗内容,默认在大屏设备上以浮动窗口显示,在小屏设备上以sheet
弹窗显示。.presentationCompactAdaptation
:控制popover
在小屏设备上的展示方式。.automatic
:系统自动选择展示方式(默认)。.popover
:即使在小屏设备上也尝试使用popover
弹窗。.sheet
:强制小屏设备上显示为sheet
弹窗。.fullScreen
:在小屏设备上将popover
内容全屏展示。
1-69 AnyLayout
在 SwiftUI 中,AnyLayout
是一个强大的布局工具,提供了灵活性和适配性,能够根据不同的屏幕尺寸或设备的方向动态调整布局。它允许你在同一视图中动态切换布局,而不必为不同布局类型创建单独的视图。结合 @Environment(\.horizontalSizeClass)
和 @Environment(\.verticalSizeClass)
可以实现对不同屏幕尺寸的自适应布局。
1. 什么是 AnyLayout
AnyLayout
是一个通用布局容器,它能够将任意的布局类型(如 HStack
、VStack
、ZStack
、Grid
)封装在一起,并根据条件在不同布局之间切换。这种方式让你可以动态管理布局,而无需编写复杂的条件分支代码。
通过 AnyLayout
,你可以在同一个视图中使用不同的布局,并根据环境(如设备方向或屏幕尺寸)灵活切换。
2. 基本用法
创建 AnyLayout
时,可以使用不同的布局类型来进行初始化,比如 HStack
、VStack
等。通过条件语句来选择适当的布局并将其封装在 AnyLayout
中。
示例:在 HStack
和 VStack
之间切换
import SwiftUI
struct AnyLayoutExample: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
var body: some View {
let layout = horizontalSizeClass == .compact ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
layout {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.padding()
.font(.title)
}
}
#Preview {
AnyLayoutExample()
}
解释
horizontalSizeClass
是一个环境变量,它检测设备的横向尺寸(compact
或regular
)。- 根据
horizontalSizeClass
的值,选择VStackLayout()
或HStackLayout()
。- 如果横向为
compact
,使用VStack
排列项目。 - 如果横向为
regular
,使用HStack
水平排列项目。
- 如果横向为
AnyLayout
封装了VStackLayout
和HStackLayout
,实现动态布局切换。
3. AnyLayout
与屏幕方向适配
结合 @Environment(\.horizontalSizeClass)
和 @Environment(\.verticalSizeClass)
可以帮助我们根据屏幕方向调整布局。
示例:根据屏幕方向切换布局
import SwiftUI
struct OrientationAdaptiveLayoutExample: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
@Environment(\.verticalSizeClass) private var verticalSizeClass
var body: some View {
let layout: AnyLayout
if horizontalSizeClass == .compact && verticalSizeClass == .regular {
layout = AnyLayout(VStackLayout())
} else {
layout = AnyLayout(HStackLayout())
}
layout {
Image(systemName: "house.fill")
.resizable()
.frame(width: 50, height: 50)
Text("Home")
Text("Profile")
Text("Settings")
}
.padding()
.font(.title)
}
}
#Preview {
OrientationAdaptiveLayoutExample()
}
解释
- 当设备横向为
compact
且纵向为regular
(例如竖屏手机),使用VStackLayout()
垂直排列。 - 其他尺寸组合(如横屏手机或 iPad 设备)使用
HStackLayout()
水平排列。 - 这样可以实现布局对屏幕方向的适配。
4. AnyLayout
与更复杂的布局切换
除了 HStack
和 VStack
,还可以结合 ZStack
、Grid
等布局类型,灵活处理不同的内容需求。在以下示例中,我们使用 AnyLayout
在 Grid
和 HStack
布局之间切换。
示例:在 Grid
和 HStack
之间切换布局
import SwiftUI
struct GridAndHStackExample: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
var body: some View {
let layout = horizontalSizeClass == .compact ? AnyLayout(GridLayout()) : AnyLayout(HStackLayout())
layout {
ForEach(1..<7) { index in
RoundedRectangle(cornerRadius: 10)
.fill(Color.blue)
.frame(width: 50, height: 50)
.overlay(Text("\(index)"))
}
}
.padding()
}
}
#Preview {
GridAndHStackExample()
}
解释
- 当设备横向为
compact
时,使用GridLayout()
,将内容显示为网格。 - 在其他情况下使用
HStackLayout()
,水平排列内容。 Grid
更适合在有限空间内展示更多内容,而HStack
适合大屏幕或横向排列。
5. AnyLayout
的动态更新
AnyLayout
能够响应设备的旋转或窗口大小的变化进行动态更新。这对于自适应性非常重要,尤其在 iPad 或多窗口模式中,视图可能会在不同的尺寸之间切换。
示例:根据窗口大小动态更新布局
import SwiftUI
struct DynamicLayoutExample: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
var body: some View {
let layout = horizontalSizeClass == .compact ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
layout {
Text("Dynamic Item 1")
.padding()
.background(Color.yellow)
Text("Dynamic Item 2")
.padding()
.background(Color.orange)
Text("Dynamic Item 3")
.padding()
.background(Color.pink)
}
.padding()
}
}
#Preview {
DynamicLayoutExample()
}
解释
- 当设备从竖屏切换到横屏或从横屏切换到竖屏时,
AnyLayout
将自动重新评估horizontalSizeClass
的值,并选择适当的布局类型。 - 这种方式让布局在不同屏幕尺寸或方向上都有良好的显示效果。
6. 总结
AnyLayout
:一个通用的布局容器,用于封装多个布局类型,并根据条件动态切换。- 结合
horizontalSizeClass
和verticalSizeClass
:可以根据设备方向和屏幕大小选择不同的布局方式,以适配各种设备。horizontalSizeClass
检测屏幕的横向尺寸(.compact
或.regular
)。verticalSizeClass
检测屏幕的纵向尺寸(.compact
或.regular
)。
- 灵活应用:
AnyLayout
允许你在VStack
、HStack
、Grid
等布局之间动态切换,便于根据设备特性自适应布局。
AnyLayout
与环境变量 horizontalSizeClass
和 verticalSizeClass
的配合,使得 SwiftUI 可以实现更灵活的自适应布局,在不同的设备、屏幕方向和大小上提供一致的用户体验。
1-70 ViewThatFits
//
// ViewThatFits_Learning.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct ViewThatFits_Learning: View {
var body: some View {
ZStack {
Color.red.ignoresSafeArea()
ViewThatFits{
Text("this is some text that I would like to display to the user!")
Text("this is some text that I would like to display!")
Text("this is some text")
}
}
.frame(height: 300)
.padding(16)
}
}
#Preview {
ViewThatFits_Learning()
}
ViewThatFits
是 SwiftUI 提供的一个视图容器,它可以帮助我们根据可用的空间自动选择最合适的子视图进行显示。与 AnyLayout
不同的是,ViewThatFits
不会动态调整布局,而是从提供的多个视图中选择一个最适合当前显示区域的视图。因此,ViewThatFits
非常适合用于适应不同的屏幕尺寸或动态空间约束。
1. ViewThatFits
的基本概念
ViewThatFits
会按照定义的顺序依次检查其子视图的适配性,并选择第一个适合当前空间的视图进行显示。它自动检测可用的显示空间,并动态选择最佳适配的视图,帮助我们在有限的屏幕空间内展示出最优的布局效果。
2. 基本用法
ViewThatFits
的用法很简单,只需要将多个视图放入其中,它会根据显示空间自动选择最适合的视图。以下是一个简单的示例,展示了 ViewThatFits
如何在不同的空间中选择适配的视图。
示例:根据空间显示不同视图
import SwiftUI
struct BasicViewThatFitsExample: View {
var body: some View {
ViewThatFits {
HStack {
Text("This is a horizontal layout")
Image(systemName: "arrow.right")
}
VStack {
Text("This is a vertical layout")
Image(systemName: "arrow.down")
}
}
.padding()
}
}
#Preview {
BasicViewThatFitsExample()
}
解释
- 当有足够的宽度时,
ViewThatFits
会选择HStack
(水平布局)并显示。 - 当宽度不足时,它会选择
VStack
(垂直布局)来适应窄屏空间。 ViewThatFits
会自动检测可用空间并在多个子视图之间做出选择。
3. ViewThatFits
适用于不同设备和屏幕方向
ViewThatFits
适合用于在不同设备和屏幕方向下显示不同的布局。我们可以利用这个特性实现响应式设计,比如在宽屏设备上显示更多信息,而在窄屏设备上显示简化内容。
示例:不同屏幕方向显示不同的内容
import SwiftUI
struct ResponsiveViewExample: View {
var body: some View {
ViewThatFits {
HStack {
Text("Wide Screen Content")
Image(systemName: "arrow.left.and.right")
Text("More Information")
}
VStack {
Text("Compact Screen Content")
Image(systemName: "arrow.up.and.down")
}
}
.padding()
.background(Color.blue.opacity(0.1))
}
}
#Preview {
ResponsiveViewExample()
}
解释
- 在宽屏幕(如 iPad 或横屏设备)上,
ViewThatFits
会选择HStack
显示完整内容。 - 在窄屏幕(如竖屏 iPhone)上,
ViewThatFits
会选择VStack
,展示更简洁的内容。
4. ViewThatFits
与动态内容
ViewThatFits
能够处理动态内容的适配。假设我们有一些可变内容,且内容可能超过屏幕显示范围。可以利用 ViewThatFits
为不同内容长度提供不同的布局。
示例:根据内容长度选择布局
import SwiftUI
struct DynamicContentExample: View {
@State private var longText = false
var body: some View {
VStack {
Toggle("Use Long Text", isOn: $longText)
.padding()
ViewThatFits {
Text(longText ? "This is a long piece of text that might not fit on smaller screens" : "Short text")
.padding()
.background(Color.yellow.opacity(0.3))
.cornerRadius(8)
ScrollView {
Text(longText ? "This is a long piece of text that might not fit on smaller screens" : "Short text")
.padding()
.background(Color.orange.opacity(0.3))
.cornerRadius(8)
}
}
.padding()
}
}
}
#Preview {
DynamicContentExample()
}
解释
- 视图会首先尝试显示
Text
视图(无滚动),当文本内容过长而无法适应当前空间时,会切换到带ScrollView
的Text
视图。 - 使用
ViewThatFits
可以确保内容在不同长度下都有合适的显示方式。
5. ViewThatFits
与自定义布局
ViewThatFits
支持多种布局样式,如 HStack
、VStack
、Grid
等,这让它能够根据空间选择最适合的布局方式。通过将这些布局放在 ViewThatFits
中,可以实现适应空间的自定义布局选择。
示例:结合 Grid
和 HStack
实现自适应布局
import SwiftUI
struct AdaptiveGridExample: View {
var body: some View {
ViewThatFits {
Grid {
ForEach(1..<5) { index in
GridRow {
RoundedRectangle(cornerRadius: 10)
.fill(Color.blue)
.frame(width: 60, height: 60)
.overlay(Text("\(index)"))
}
}
}
HStack {
ForEach(1..<5) { index in
RoundedRectangle(cornerRadius: 10)
.fill(Color.green)
.frame(width: 60, height: 60)
.overlay(Text("\(index)"))
}
}
}
.padding()
}
}
#Preview {
AdaptiveGridExample()
}
解释
Grid
会在较小空间下被选择,以更好利用空间。- 当空间足够时,
HStack
水平布局会被选中,展示不同排列方式。
6. ViewThatFits
的优先级
ViewThatFits
会按照子视图的定义顺序依次检查适配性,因此第一个适合当前空间的视图会被选择。可以利用视图顺序设置优先级,从而确保在不同条件下选择最合适的视图。
示例:根据优先级选择最优视图
import SwiftUI
struct PriorityViewExample: View {
var body: some View {
ViewThatFits {
Text("High Priority")
.font(.largeTitle)
.padding()
.background(Color.red.opacity(0.2))
Text("Medium Priority")
.font(.title)
.padding()
.background(Color.green.opacity(0.2))
Text("Low Priority")
.font(.body)
.padding()
.background(Color.blue.opacity(0.2))
}
.padding()
}
}
#Preview {
PriorityViewExample()
}
解释
ViewThatFits
会优先尝试显示High Priority
,如果空间不足,则依次尝试Medium Priority
和Low Priority
。- 这种方式让我们可以控制内容在不同空间下的优先显示内容。
7. 总结
- 基本功能:
ViewThatFits
根据空间自动选择最合适的子视图进行显示。 - 适用于不同设备和屏幕方向:可以根据屏幕宽度或方向在
HStack
、VStack
等布局间切换,实现响应式设计。 - 处理动态内容:适合长短内容的自适应显示,确保无论内容多少,都能有适当的布局展示。
- 视图优先级:通过视图定义顺序控制显示优先级,确保在空间受限时优先显示重要内容。
ViewThatFits
是一个适配性很强的容器,适合在不同设备、屏幕方向和空间大小下确保视图内容的最优显示。
1-71 NavigationSplitView
NavigationSplitView
是 SwiftUI 中提供的一种多栏导航视图布局,主要用于 iPad 和 Mac 等大屏设备上,支持多栏内容显示。NavigationSplitView
允许我们在屏幕的左侧和右侧分别展示不同的内容,通常用于列表-详情的界面布局。它通过分离主视图和详情视图,能够提供更好的用户体验。
1. 基本概念和结构
NavigationSplitView
的结构可以划分为三个区域:
- Sidebar:左侧边栏区域,通常用于展示一个可选择的项目列表。
- Content:内容区域,用于显示选中项的详细信息或其他内容。
- Detail:详情区域,用于展示更详细的内容,通常在更大的屏幕上显示(如 iPad 或 Mac)。
2. 基本用法
NavigationSplitView
使用时,可以设置 sidebar
和 content
,也可以选择性地添加 detail
区域。以下是一个基本的 NavigationSplitView
示例,展示了如何使用侧边栏和内容区域。
示例:基本的 NavigationSplitView
使用
import SwiftUI
struct BasicNavigationSplitViewExample: View {
@State private var selectedItem: String? = nil
var body: some View {
NavigationSplitView {
List(["Item 1", "Item 2", "Item 3"], id: \.self, selection: $selectedItem) { item in
Text(item)
}
.navigationTitle("Items")
} detail: {
if let selectedItem = selectedItem {
Text("Detail for \(selectedItem)")
.font(.title)
} else {
Text("Select an item")
.font(.title)
}
}
}
}
#Preview {
BasicNavigationSplitViewExample()
}
解释
List
:侧边栏中使用List
显示项目列表,selection
绑定到selectedItem
。detail
:右侧详情区域显示选中项目的详细信息。- 当没有选中任何项时,
detail
区域显示默认提示文本 “Select an item”。
3. NavigationSplitView
的三栏布局
NavigationSplitView
支持三栏布局,这在更大屏幕上非常有用,能够同时显示多个级别的信息,例如左侧的主列表、中间的子列表和右侧的详细信息。
示例:三栏布局
import SwiftUI
struct ThreeColumnSplitViewExample: View {
@State private var selectedCategory: String? = nil
@State private var selectedItem: String? = nil
var body: some View {
NavigationSplitView {
List(["Category 1", "Category 2", "Category 3"], id: \.self, selection: $selectedCategory) { category in
Text(category)
}
.navigationTitle("Categories")
} content: {
if let selectedCategory = selectedCategory {
List(["Item A", "Item B", "Item C"], id: \.self, selection: $selectedItem) { item in
Text(item)
}
.navigationTitle(selectedCategory)
} else {
Text("Select a category")
}
} detail: {
if let selectedItem = selectedItem {
Text("Details for \(selectedItem)")
.font(.title)
} else {
Text("Select an item")
}
}
}
}
#Preview {
ThreeColumnSplitViewExample()
}
解释
- 第一栏 (Sidebar):显示 “Categories” 列表,用户选择类别。
- 第二栏 (Content):显示选中类别下的项目列表。
- 第三栏 (Detail):显示选中项目的详细信息。
- 未选中任何项目或类别时,
content
和detail
区域显示默认的占位文本。
4. 使用选择绑定和状态管理
NavigationSplitView
中的 selection
可以使用 @State
或 @Binding
变量来控制当前选中的项目。通过绑定 selection
,我们可以动态更新视图内容。
示例:动态更新选中项的内容
import SwiftUI
struct DynamicSelectionExample: View {
@State private var selectedItem: String? = nil
var body: some View {
NavigationSplitView {
List(["Home", "Settings", "Profile"], id: \.self, selection: $selectedItem) { item in
Text(item)
}
.navigationTitle("Menu")
} detail: {
if let selectedItem = selectedItem {
DetailView(item: selectedItem)
} else {
Text("Select an option from the menu")
}
}
}
}
struct DetailView: View {
let item: String
var body: some View {
VStack {
Text("\(item) Details")
.font(.largeTitle)
.padding()
if item == "Home" {
Text("Welcome to the Home Page!")
} else if item == "Settings" {
Text("Here you can adjust your settings.")
} else if item == "Profile" {
Text("This is your profile page.")
}
}
}
}
#Preview {
DynamicSelectionExample()
}
解释
List
的selection
绑定到selectedItem
,点击不同的项会更新selectedItem
,并触发detail
区域内容的重新渲染。DetailView
根据传入的item
参数展示不同内容。
5. 自定义 NavigationSplitView
的显示样式
NavigationSplitView
提供了一些显示样式,可以调整边栏和内容的显示方式。例如,可以设置边栏的显示模式以便在小屏设备上自动隐藏。
示例:自定义显示样式
import SwiftUI
struct CustomStyleSplitViewExample: View {
@State private var selectedItem: String? = nil
var body: some View {
NavigationSplitView {
List(["Option 1", "Option 2", "Option 3"], id: \.self, selection: $selectedItem) { option in
Text(option)
}
.navigationTitle("Options")
} detail: {
if let selectedItem = selectedItem {
Text("Details for \(selectedItem)")
.font(.title)
.padding()
} else {
Text("Select an option")
}
}
.navigationSplitViewStyle(.automatic) // 自适应显示样式
}
}
#Preview {
CustomStyleSplitViewExample()
}
解释
.navigationSplitViewStyle(.automatic)
:设置NavigationSplitView
为自动适应样式。.navigationSplitViewStyle(.prominentDetail)
:在某些设备上突出显示详情区域,适合在 Mac 和 iPad 上提供更沉浸的用户体验。.navigationSplitViewStyle(.balanced)
:在不同栏之间均衡显示。
6. 总结
NavigationSplitView
的基本用法:用于分栏展示,适合 iPad 和 Mac 等大屏设备。- 多栏支持:最多支持三栏(
sidebar
、content
、detail
),适合展示多层级信息。 - 状态绑定:通过
@State
或@Binding
管理selection
,动态更新显示内容。 - 自适应显示样式:可根据不同设备和用户需求调整栏的显示样式(如
.automatic
、.prominentDetail
)。
NavigationSplitView
是一个强大的多栏导航布局工具,帮助我们在大屏设备上创建多层级、直观的用户界面。它通过绑定和状态管理,使内容可以根据用户选择实时更新,非常适合列表-详情的内容展示。
1-72 Grid
//
// Grid_Learning.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct Grid_Learning: View {
var body: some View {
Grid {
GridRow {
cell(int: 1)
cell(int: 2)
cell(int: 3)
}
GridRow {
cell(int: 4)
cell(int: 5)
cell(int: 6)
}
Divider()
.gridCellUnsizedAxes(.horizontal)
GridRow {
cell(int: 7)
cell(int: 8)
cell(int: 9)
}
}
}
private func cell(int: Int) -> some View {
Text("\(int)")
.font(.largeTitle)
.padding()
.background(Color.blue)
}
}
#Preview {
Grid_Learning()
}
在 SwiftUI 中,Grid
、GridRow
和 Divider
是非常重要的布局和分隔工具,特别适用于创建网格样式的内容展示和分隔不同的内容区域。它们帮助我们在界面上高效地组织和分隔内容。
1. Grid
的基本概念
Grid
是 SwiftUI 中的一个布局容器,用于创建网格布局。它能够在水平和垂直方向上展示内容,适合用于展示多列、多行的数据或图片等。与 HStack
和 VStack
不同的是,Grid
能够同时管理水平和垂直方向的布局,而不需要嵌套多个 HStack
或 VStack
。
2. GridRow
的基本概念
GridRow
是 Grid
中的一个行,用于定义每一行的内容。Grid
可以包含多个 GridRow
,每个 GridRow
中的子视图会自动按列排列。每一行的列数和布局可以单独控制,因此在同一个 Grid
中可以拥有不同数量的列。
3. Divider
的基本概念
Divider
是一个分隔线,用于在视图之间插入水平或垂直的分隔,增强内容的层次感。它是一个非常简单的视图,仅用于添加视觉分隔效果,通常与 HStack
、VStack
或 Grid
等布局容器一起使用。
4. Grid
和 GridRow
的基本用法
以下是一个基本的 Grid
和 GridRow
使用示例,展示了如何创建一个简单的 2x2 网格布局:
import SwiftUI
struct BasicGridExample: View {
var body: some View {
Grid {
GridRow {
Text("Item 1")
Text("Item 2")
}
GridRow {
Text("Item 3")
Text("Item 4")
}
}
.padding()
}
}
#Preview {
BasicGridExample()
}
解释
Grid
是整个网格的容器。- 每个
GridRow
是一行,其中包含两个Text
视图,表示每行的内容。 - 每个
GridRow
中的视图会自动按列排列,因此可以轻松创建多行多列的布局。
5. 自定义列宽和跨列
在 Grid
中,我们可以控制列的宽度,甚至让某些列跨越多个列,增强布局的灵活性。
示例:自定义列宽和跨列
import SwiftUI
struct CustomGridExample: View {
var body: some View {
Grid {
GridRow {
Text("Full Width").gridCellColumns(2) // 跨两列
.padding()
.background(Color.blue.opacity(0.2))
}
GridRow {
Text("Left").frame(maxWidth: .infinity)
Text("Right").frame(maxWidth: .infinity)
}
GridRow {
Text("Item A").frame(maxWidth: .infinity)
Text("Item B").frame(maxWidth: .infinity)
}
}
.padding()
}
}
#Preview {
CustomGridExample()
}
解释
.gridCellColumns(2)
:让第一行中的Text("Full Width")
跨越两列,从而占据整行。frame(maxWidth: .infinity)
:让每个视图在列中尽可能扩展,从而填满可用空间。- 通过这些设置,可以创建复杂的网格布局,比如跨列或全宽的标题栏。
6. Grid
、GridRow
和 Divider
的组合使用
Divider
可以与 Grid
一起使用,起到分隔行或列的作用。它在网格布局中可以用来增加层次感,使内容更清晰。
示例:使用 Divider
分隔内容
import SwiftUI
struct GridWithDividerExample: View {
var body: some View {
Grid {
GridRow {
Text("Header 1")
Text("Header 2")
}
.padding()
.background(Color.gray.opacity(0.2))
Divider() // 使用 Divider 分隔标题和内容
GridRow {
Text("Row 1, Col 1")
Text("Row 1, Col 2")
}
GridRow {
Text("Row 2, Col 1")
Text("Row 2, Col 2")
}
}
.padding()
}
}
#Preview {
GridWithDividerExample()
}
解释
Divider
被放置在标题和内容之间,起到分隔效果。- 每个
GridRow
在Grid
中按列自动排列。 Divider
增加了内容的分隔性,帮助用户更好地理解内容层次。
7. 在 GridRow
中组合不同类型的视图
Grid
和 GridRow
支持多种类型的子视图,不限于 Text
,因此可以在网格中组合使用 Image
、Button
等不同类型的视图。
示例:在 GridRow
中混合使用 Image
和 Text
import SwiftUI
struct MixedContentGridExample: View {
var body: some View {
Grid {
GridRow {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
Text("Favorite")
}
Divider() // 添加分隔线
GridRow {
Image(systemName: "trash.fill")
.foregroundColor(.red)
Text("Delete")
}
}
.padding()
}
}
#Preview {
MixedContentGridExample()
}
解释
GridRow
包含Image
和Text
,展示了如何在同一行中组合多种视图类型。Divider
被用于分隔两行内容。- 这种组合适合图标加文字的布局,比如收藏或删除功能的按钮布局。
9. 高级用法:自定义网格布局和动态数据展示
通过 ForEach
和 Grid
,可以实现动态的数据展示,比如展示一个可变长度的数据集合。以下示例展示了一个 3 列布局,通过 ForEach
循环展示多个项目。
示例:动态网格布局
import SwiftUI
struct DynamicGridExample: View {
let items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6"]
var body: some View {
Grid {
ForEach(0..<items.count, id: \.self) { index in
if index % 3 == 0 {
GridRow {
ForEach(0..<3) { col in
if index + col < items.count {
Text(items[index + col])
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue.opacity(0.2))
.cornerRadius(8)
}
}
}
}
}
}
.padding()
}
}
#Preview {
DynamicGridExample()
}
解释
ForEach
循环遍历items
数组,并将每 3 个项目放入一个GridRow
中,创建了一个 3 列布局。if index % 3 == 0
用于确保每行包含 3 个项目,避免超出数组索引范围。- 通过这种方式,可以灵活地展示动态数据,比如图片网格或产品列表。
10. 总结
Grid
:创建网格布局的容器,用于展示多行多列内容。GridRow
:网格的行容器,每个GridRow
中的内容会自动按列排列。Divider
:简单的分隔线,用于在内容之间添加视觉分隔。- 高级用法:通过
ForEach
和Grid
可以实现动态数据展示,使用.gridCellColumns()
实现跨列布局。
1-73 ContentUnavailableView
ContentUnavailableView
是 SwiftUI 在 iOS 16 及以上版本中提供的一种新的视图,用于在内容不可用时提供占位符信息。它可以在数据加载失败、网络连接断开、搜索无结果等情况下展示简洁的提示界面。这个视图会自动匹配系统样式,并且带有默认图标和消息,让用户在内容不可用时仍然能得到良好的用户体验。
1. ContentUnavailableView
的基本用法
ContentUnavailableView
提供了一个简单的接口,你可以通过提供标题、描述、系统图标等来定制它的内容。可以通过 init(_:description:symbol:)
初始化视图。
示例:基本的 ContentUnavailableView
使用
import SwiftUI
struct BasicContentUnavailableExample: View {
var body: some View {
ContentUnavailableView("No Content Available",
description: Text("Please try again later or check your connection."),
symbol: Image(systemName: "exclamationmark.triangle"))
.padding()
}
}
#Preview {
BasicContentUnavailableExample()
}
解释
- 标题:
"No Content Available"
是主标题,显示在视图的顶部。 - 描述:
Text("Please try again later or check your connection.")
是副标题或描述文字,提供额外的上下文信息。 - 图标:
Image(systemName: "exclamationmark.triangle")
是系统图标,用于直观地告知用户内容不可用的原因。
2. 使用 ContentUnavailableView
处理网络错误或加载失败
在应用中,通常会遇到数据加载失败的情况。可以使用 ContentUnavailableView
来提醒用户检查网络或稍后再试。
示例:在加载失败时显示 ContentUnavailableView
import SwiftUI
struct NetworkErrorExample: View {
@State private var isDataAvailable = false
var body: some View {
VStack {
if isDataAvailable {
Text("Content Loaded Successfully")
} else {
ContentUnavailableView("Unable to Load Data",
description: Text("Please check your internet connection."),
symbol: Image(systemName: "wifi.exclamationmark"))
.padding()
}
}
.onAppear {
// 模拟数据加载延迟
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
isDataAvailable = false // 数据加载失败
}
}
}
}
#Preview {
NetworkErrorExample()
}
解释
- 当
isDataAvailable
为false
时,显示ContentUnavailableView
提示用户检查网络连接。 - 这种用法可以在网络断开或数据请求失败时向用户展示错误提示,提升用户体验。
3. 使用 ContentUnavailableView
显示搜索无结果的界面
在搜索结果为空时,ContentUnavailableView
也能帮助用户快速了解当前状态。
示例:显示搜索无结果的界面
import SwiftUI
struct SearchNoResultExample: View {
@State private var searchText = ""
@State private var searchResults = [String]()
var body: some View {
VStack {
TextField("Search", text: $searchText)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.onChange(of: searchText) { newValue in
// 模拟搜索功能
searchResults = performSearch(for: newValue)
}
if searchResults.isEmpty {
ContentUnavailableView("No Results Found",
description: Text("Try searching for something else."),
symbol: Image(systemName: "magnifyingglass"))
.padding()
} else {
List(searchResults, id: \.self) { result in
Text(result)
}
}
}
.padding()
}
func performSearch(for query: String) -> [String] {
// 模拟一个空的搜索结果
return query.isEmpty ? [] : ["Result 1", "Result 2"]
}
}
#Preview {
SearchNoResultExample()
}
解释
TextField
用于输入搜索内容,实时更新searchResults
。- 当
searchResults
为空时,ContentUnavailableView
显示“无结果”的信息,提示用户重新尝试搜索其他内容。 - 这种用法在搜索应用或类似界面中非常常见。
4. 使用 ContentUnavailableView
在空列表时提供提示
有时候列表为空时,可以使用 ContentUnavailableView
进行提示,告诉用户当前没有内容可供展示。
示例:空列表的占位视图
import SwiftUI
struct EmptyListExample: View {
@State private var items: [String] = []
var body: some View {
VStack {
if items.isEmpty {
ContentUnavailableView("No Items Available",
description: Text("Your list is currently empty. Add new items to see them here."),
symbol: Image(systemName: "list.bullet"))
.padding()
} else {
List(items, id: \.self) { item in
Text(item)
}
}
}
}
}
#Preview {
EmptyListExample()
}
解释
- 当
items
列表为空时,显示ContentUnavailableView
提示用户添加新内容。 - 当有内容时显示
List
。 - 在应用中,特别是列表为空时提供这样的提示,能够有效避免空白界面。
5. 自定义 ContentUnavailableView
的图标和样式
ContentUnavailableView
支持使用自定义图标和文本,可以根据不同需求调整样式和信息。
示例:使用自定义图标和文本样式
import SwiftUI
struct CustomContentUnavailableExample: View {
var body: some View {
ContentUnavailableView("No Access",
description: Text("You do not have the required permissions to view this content."),
symbol: Image(systemName: "lock.shield"))
.foregroundColor(.red)
.padding()
}
}
#Preview {
CustomContentUnavailableExample()
}
解释
- 通过
foregroundColor(.red)
设置图标和文本颜色,适应不同的应用主题。 symbol
可以接受任何Image
,例如Image(systemName: "lock.shield")
,以便根据场景调整图标。- 这种灵活的自定义方式适合在权限不足或受限的场景中向用户展示不同的状态提示。
6. 总结
ContentUnavailableView
:用于内容不可用时的占位提示,适合在数据加载失败、搜索无结果、空列表等情况使用。- 基本属性:
- 标题:用于显示主提示信息。
- 描述:额外的解释信息,帮助用户理解不可用的原因。
- 图标:支持系统图标或自定义图标,直观展示内容状态。
- 应用场景:可以在网络加载失败、权限不足、列表为空、搜索无结果等场景中提供友好的用户反馈,提升用户体验。
1-74 ControlGroup
//
// ControlView_L.swift
// Swift_UI_Learning
//
// Created by aini aini on 2024/10/27.
//
import SwiftUI
struct ControlView_L: View {
var body: some View {
Menu("MyMenu"){
ControlGroup("One"){
Button("Aini"){
}
Button("Dilnur"){
}
Button("Ace"){
}
}
Button("Two"){
}
Menu("Three"){
Button("Hi"){
}
Button("Hello"){
}
}
}
}
}
#Preview {
ControlView_L()
}
ControlGroup
是 SwiftUI 中用于将多个控件组合在一起的容器视图,适合在界面上展示一组相关的控制项,比如按钮、选择器等。ControlGroup
提供了一种组织控件的方式,可以提升界面的整洁性和用户体验。它通常用于工具栏、选项组或多按钮操作的场景。
1. 基本用法
ControlGroup
可以将相关的控件放在一起,自动布局并显示分组效果。在 iOS 和 macOS 上,ControlGroup
默认会将控件排列在水平或垂直方向上,具体取决于设备的大小和布局环境。
示例:基本的 ControlGroup
使用
import SwiftUI
struct BasicControlGroupExample: View {
var body: some View {
ControlGroup {
Button(action: { print("Cut") }) {
Label("Cut", systemImage: "scissors")
}
Button(action: { print("Copy") }) {
Label("Copy", systemImage: "doc.on.doc")
}
Button(action: { print("Paste") }) {
Label("Paste", systemImage: "doc.on.clipboard")
}
}
.padding()
}
}
#Preview {
BasicControlGroupExample()
}
解释
ControlGroup
包含三个按钮(剪切、复制、粘贴),这些按钮会自动排列在一行。- 在
ControlGroup
中,按钮显示的图标和标签更紧凑,使界面整洁有序。 Label
允许我们将文本和图标结合在一起,提升可读性和用户体验。
2. 使用 ControlGroup
的显示样式
ControlGroup
提供了几种显示样式,可以使用 .controlGroupStyle()
修饰符来改变控件的显示方式。常用的样式有:
.automatic
:系统自动选择样式,适合大部分情况。.navigation
:适用于导航栏或工具栏中的控件。.compact
:显示为紧凑的布局,更适合空间受限的场景。
示例:设置 ControlGroup
的显示样式
import SwiftUI
struct StyledControlGroupExample: View {
var body: some View {
VStack(spacing: 20) {
ControlGroup {
Button("Bold") { }
Button("Italic") { }
Button("Underline") { }
}
.controlGroupStyle(.automatic)
ControlGroup {
Button("Bold") { }
Button("Italic") { }
Button("Underline") { }
}
.controlGroupStyle(.navigation)
ControlGroup {
Button("Bold") { }
Button("Italic") { }
Button("Underline") { }
}
.controlGroupStyle(.compact)
}
.padding()
}
}
#Preview {
StyledControlGroupExample()
}
解释
.controlGroupStyle(.automatic)
:默认样式,系统自动选择最合适的显示效果。.controlGroupStyle(.navigation)
:适合用于工具栏或导航栏中的操作按钮。.controlGroupStyle(.compact)
:显示为紧凑布局,减少控件间的间距。
3. ControlGroup
中使用 Menu
ControlGroup
还可以和 Menu
结合使用,用于将多个选项分组到一个菜单中。这样可以节省空间,避免在有限的区域中显示过多的控件。
示例:ControlGroup
中嵌入 Menu
import SwiftUI
struct ControlGroupWithMenuExample: View {
var body: some View {
ControlGroup {
Button("Bold") { }
Button("Italic") { }
Menu {
Button("Underline") { }
Button("Strikethrough") { }
Button("Highlight") { }
} label: {
Label("More", systemImage: "ellipsis.circle")
}
}
.padding()
}
}
#Preview {
ControlGroupWithMenuExample()
}
解释
ControlGroup
中包含两个按钮(Bold、Italic)和一个Menu
。Menu
将更多选项分组在一起,并使用Label
显示为带有图标的按钮。- 点击
More
按钮后,会弹出菜单显示更多选项,这种方式适合在界面有限的情况下展示更多控制项。
4. ControlGroup
的垂直布局
在较小屏幕或需要垂直排列的场景中,可以将 ControlGroup
设置为垂直布局,这样可以更好地适应界面的空间。
示例:垂直布局的 ControlGroup
import SwiftUI
struct VerticalControlGroupExample: View {
var body: some View {
ControlGroup {
Button(action: { print("Add") }) {
Label("Add", systemImage: "plus")
}
Button(action: { print("Remove") }) {
Label("Remove", systemImage: "minus")
}
}
.padding()
.controlGroupStyle(.navigation) // 可选择其他样式
.frame(maxWidth: .infinity, alignment: .leading)
}
}
#Preview {
VerticalControlGroupExample()
}
解释
ControlGroup
中包含“Add”和“Remove”按钮。- 使用
.frame(maxWidth: .infinity, alignment: .leading)
可以将ControlGroup
拉伸至整个宽度,并且靠左对齐,适合在侧边栏或较小屏幕上使用。
5. 结合 ControlGroup
和 Divider
在 ControlGroup
中可以添加 Divider
来分隔不同的按钮组,增强视觉层次感。Divider
是一种简单的分隔线,用于在界面上创建分层效果。
示例:在 ControlGroup
中使用 Divider
import SwiftUI
struct ControlGroupWithDividerExample: View {
var body: some View {
ControlGroup {
Button("Cut") { }
Button("Copy") { }
Divider()
Button("Paste") { }
Button("Delete") { }
}
.padding()
}
}
#Preview {
ControlGroupWithDividerExample()
}
解释
Divider
将 “Cut”和“Copy” 与 “Paste”和“Delete” 分隔开。- 这种分隔使得按钮组更加清晰,适合在多个操作较多的界面中使用,提升用户体验。
6. 总结
ControlGroup
:用于将相关的控件组合在一起,提升界面的组织性和用户体验。- 显示样式:可以使用
.controlGroupStyle()
修改控件的显示方式,如.automatic
、.navigation
、.compact
。 - 嵌入
Menu
:结合Menu
使用可以节省空间,将多个选项分组到一个菜单中。 - 垂直布局:适合在小屏幕或侧边栏中使用,使控件能更好适应界面。
- 与
Divider
组合:在ControlGroup
中添加Divider
可以分隔不同的控件组,增强界面层次感。
1-75 @Observable Macro
@Observable
宏是 Swift 的一个新特性,用于简化可观察对象的声明和使用。它是 Swift 提供的一种自动生成 @Published
属性和 ObservableObject
协议的方式,使得创建响应式数据结构更加简单和易读。通常用于 SwiftUI 应用程序中,将数据的变化自动绑定到 UI。
1. @Observable
的基本概念
在传统 SwiftUI 中,如果想要创建一个可观察的对象,我们需要手动遵循 ObservableObject
协议并使用 @Published
属性来标记那些希望被观察的属性。这种方式虽然有效,但在有多个属性时可能显得冗长。@Observable
宏通过自动生成这些代码,使代码更简洁和易于维护。
2. @Observable
的基本用法
只需在类前使用 @Observable
注解,就可以让该类自动成为一个可观察对象,类中的属性会自动具备 @Published
的功能,并在发生变化时通知视图更新。
示例:基本的 @Observable
用法
import SwiftUI
import Observation
@Observable
class UserData {
var name: String = "Alice"
var age: Int = 25
}
struct ContentView: View {
@StateObject private var userData = UserData()
var body: some View {
VStack {
Text("Name: \(userData.name)")
Text("Age: \(userData.age)")
Button("Increment Age") {
userData.age += 1
}
}
}
}
#Preview {
ContentView()
}
解释
@Observable
自动为UserData
添加了ObservableObject
协议,使得name
和age
属性的变化能够被视图检测到。@StateObject
用于在视图中创建userData
实例,以便绑定到 UI。- 按下按钮时,
age
属性递增,触发 UI 更新。
3. 使用 @Observable
声明多个属性
@Observable
支持在一个类中声明多个属性。可以将多个需要观察的属性直接添加到同一个类中,不需要单独声明 @Published
,这样代码更加简洁。
示例:多个可观察属性
import SwiftUI
import Observation
@Observable
class Profile {
var username: String = "JohnDoe"
var followers: Int = 100
var isVerified: Bool = false
}
struct ProfileView: View {
@StateObject private var profile = Profile()
var body: some View {
VStack {
Text("Username: \(profile.username)")
Text("Followers: \(profile.followers)")
Toggle("Verified", isOn: $profile.isVerified)
.toggleStyle(SwitchToggleStyle())
Button("Add Follower") {
profile.followers += 1
}
}
.padding()
}
}
#Preview {
ProfileView()
}
解释
Profile
类包含了多个需要观察的属性username
、followers
和isVerified
。@Observable
自动将这些属性设置为可观察属性,任何属性变化都会自动更新 UI。
4. 监听 @Observable
属性的变化
虽然 @Observable
自动生成了通知功能,但有时我们还希望在属性变化时执行一些特定的操作。这可以通过 SwiftUI 的 onChange
修饰符来实现。
示例:监听属性变化
import SwiftUI
import Observation
@Observable
class Settings {
var notificationsEnabled: Bool = false
}
struct SettingsView: View {
@StateObject private var settings = Settings()
var body: some View {
VStack {
Toggle("Enable Notifications", isOn: $settings.notificationsEnabled)
.onChange(of: settings.notificationsEnabled) { newValue in
print("Notifications Enabled: \(newValue)")
}
}
.padding()
}
}
#Preview {
SettingsView()
}
解释
- 使用
.onChange(of:)
修饰符,监听notificationsEnabled
属性的变化。 - 当用户切换开关时,
onChange
会捕捉到新值并执行指定的操作,例如在控制台打印出新值。
5. 使用 @Observable
的嵌套数据
@Observable
还支持嵌套的数据结构,即在一个 @Observable
对象中嵌入另一个 @Observable
对象,便于在复杂数据结构中使用。
示例:嵌套的 @Observable
对象
import SwiftUI
import Observation
@Observable
class Address {
var city: String = "New York"
var country: String = "USA"
}
@Observable
class UserProfile {
var name: String = "Alice"
var address: Address = Address()
}
struct NestedObservableView: View {
@StateObject private var userProfile = UserProfile()
var body: some View {
VStack {
Text("Name: \(userProfile.name)")
Text("City: \(userProfile.address.city)")
Text("Country: \(userProfile.address.country)")
Button("Move to San Francisco") {
userProfile.address.city = "San Francisco"
}
}
.padding()
}
}
#Preview {
NestedObservableView()
}
解释
UserProfile
中嵌套了一个Address
对象,两者都使用了@Observable
。- 当
address.city
改变时,视图会自动更新显示新的城市信息。
6. 与 SwiftUI 的 @StateObject
和 @ObservedObject
配合使用
在 SwiftUI 中,@Observable
对象可以直接与 @StateObject
或 @ObservedObject
一起使用,以实现绑定和自动更新。
@StateObject
:用于在视图内创建和管理一个@Observable
对象的生命周期。@ObservedObject
:用于将外部的@Observable
对象传递给视图,以便在父视图中共享状态。
示例:@ObservedObject
与 @Observable
的结合
import SwiftUI
import Observation
@Observable
class Counter {
var count: Int = 0
}
struct ParentView: View {
@StateObject private var counter = Counter()
var body: some View {
VStack {
Text("Count: \(counter.count)")
.font(.title)
ChildView(counter: counter)
}
}
}
struct ChildView: View {
@ObservedObject var counter: Counter
var body: some View {
Button("Increment") {
counter.count += 1
}
.padding()
}
}
#Preview {
ParentView()
}
解释
ParentView
创建Counter
对象,并将其传递给ChildView
。- 在
ChildView
中,Counter
对象使用@ObservedObject
接收,可以直接修改count
值,从而触发父视图的更新。
7. 总结
@Observable
:通过一个简单的宏,自动生成@Published
和ObservableObject
功能,简化可观察对象的声明。- 应用场景:适合需要响应数据变化的场景,例如用户设置、计数器、个人资料等。
- 嵌套支持:可以在
@Observable
对象中嵌套另一个@Observable
对象,方便管理复杂数据结构。 - 配合 SwiftUI 使用:与
@StateObject
和@ObservedObject
结合使用,便于在视图中管理和共享状态。