阅读视图
苹果、华为“撞档”上新 | Swift 周报 issue 62
Apple 新品发布会亮点有哪些 | Swift 周报 issue 61
苹果仍在研发更大尺寸的 iMac | Swift 周报 issue 60
提升代码调试技巧:从思维到实践
如何以编程方式解析 XCResult 包的内容
Swift 中的函数式核心与命令式外壳:单向数据流
如何在 CI/CD 过程中实施高效的自动化测试和部署
自定义 SwiftUI 中符号图像的外观
前言
符号图像是来自 Apple的SF Symbols 库的矢量图标,设计用于在 Apple 平台上使用。这些可缩放的图像适应不同的大小和重量,确保在我们的应用程序中具有一致的高质量图标。在 SwiftUI 中使用符号图像非常简单,只需使用 Image 视图和所需符号的系统名称。下面是一个快速示例:
import SwiftUI
struct ContentView: View {
var body: some View {
Image(systemName: "star")
}
}
大小
尽管符号被放置在Image视图中,但它应被视为文本。要调整符号的大小,我们可以应用 font() 修饰符,就像在Text视图中一样。这使我们能够将符号的大小与不同的文本样式对齐,确保UI的视觉一致性。
HStack {
Image(systemName: "star")
.font(.title)
Image(systemName: "star")
.font(.body)
Image(systemName: "star")
.font(.caption)
}
我们可以使用 fontWeight() 修饰符来调整符号的重量。这个修饰符改变符号笔画的粗细,使我们能够将符号与周围的文本匹配或对比。
HStack {
Image(systemName: "star")
.fontWeight(.light)
Image(systemName: "star")
.fontWeight(.bold)
Image(systemName: "star")
.fontWeight(.black)
}
要根据字体大小相对缩放图像,我们应该使用 imageScale() 修饰符。有三个选项:小、中、大,它们根据字体大小按比例缩放符号。如果没有明确设置字体,符号将从当前环境中继承字体。
HStack {
Image(systemName: "star")
.imageScale(.small)
Image(systemName: "star")
.imageScale(.medium)
Image(systemName: "star")
.imageScale(.large)
}
.font(.headline)
不建议通过应用resizable()修饰符并设置框架来调整符号图像的大小,因为这样做会使图像停止作为符号图像,从而影响其与文本的布局和对齐。
颜色
使用SwiftUI中的foregroundStyle()视图修饰符,可以轻松自定义符号图像的颜色。这个修饰符允许我们直接设置符号图像的颜色。
Image(systemName: "star")
.foregroundStyle(.orange)
foregroundStyle() 修饰符可以采用任何 ShapeStyle,包括渐变,这为我们的符号图像提供了广泛的自定义可能性。在这个例子中,星形符号使用了从黄色到红色的线性渐变,从顶部到底部过渡。
Image(systemName: "star")
.foregroundStyle(
LinearGradient(
colors: [.yellow, .red],
startPoint: .top,
endPoint: .bottom
)
)
渲染模式
我们可以通过使用不同的渲染模式进一步自定义符号图像的外观。SF Symbols有四种不同的渲染模式,这些模式会改变符号的颜色和外观。一些渲染模式使整个图标保持相同颜色,而其他模式则允许多种颜色。
要在SwiftUI中设置符号图像的首选渲染模式,我们使用 symbolRenderingMode() 修饰符。
单色
单色是默认的渲染模式。在这种模式下,符号的每一层都是相同的颜色。
Image(systemName: "thermometer.snowflake")
.symbolRenderingMode(.monochrome)
分层
分层模式将符号渲染为多个层,每层应用不同的不透明度。层次结构和不透明度在每个符号中是预定义的,但我们仍然可以使用 foregroundStyle() 修饰符自定义颜色。
HStack {
Image(systemName: "thermometer.snowflake")
Image(systemName: "thermometer.snowflake")
.foregroundStyle(.indigo)
}
.symbolRenderingMode(.hierarchical)
symbolRenderingMode() 修饰符既可以直接应用于图像视图,也可以通过将其应用于包含多个符号图像的父视图来在环境中设置。这样,父元素内的所有符号图像都会受到影响。
调色板
调色板模式允许符号以多层呈现,每层具有不同的颜色。这种模式非常适合创建色彩丰富的多层图标。
Image(systemName: "thermometer.snowflake")
.symbolRenderingMode(.palette)
.foregroundStyle(.blue, .teal, .gray)
我们不需要显式地指定调色板呈现模式。如果我们在 foregroundStyle() 修饰符中应用多个样式,则调色板模式将自动激活。
Image(systemName: "thermometer.snowflake")
.foregroundStyle(.blue, .teal, .gray)
如果我们为一个定义了三个层次结构的符号指定两种颜色,那么第二层和第三层将使用相同的颜色。
Image(systemName: "thermometer.snowflake")
.foregroundStyle(.blue, .gray)
多色
多色模式使用由 Apple 定义的一组固定颜色渲染符号。在使用多色渲染时,我们无法自定义符号的颜色,它将使用预定义的颜色。
HStack {
Image(systemName: "thermometer.snowflake")
Image(systemName: "thermometer.sun.fill")
}
.symbolRenderingMode(.multicolor)
值得注意的是,由于这些颜色是固定的,它们不适应明暗模式。例如,我们的温度计符号具有白色轮廓,在白色背景上是不可见的。
并非所有符号都支持每种呈现模式。图层较少的符号在不同模式下看起来可能相同,分层和调色板模式看起来类似于单色。
可变值
在 SwiftUI 中显示符号图像时,我们可以提供一个 0.0 到 1.0 之间的可选值,渲染的图像可以使用它来自定义外观。如果符号不支持可变值,此参数无效。我们应该在 SF Symbols 应用程序中检查哪些符号支持可变值。
HStack {
Image(systemName: "speaker.wave.3", variableValue: 0)
Image(systemName: "speaker.wave.3", variableValue: 0.3)
Image(systemName: "speaker.wave.3", variableValue: 0.6)
Image(systemName: "speaker.wave.3", variableValue: 0.9)
}
可变值可以表示一个随着时间变化的特性,例如容量或强度。这使得符号的外观可以根据应用程序的状态动态变化。
struct ContentView: View {
@State private var value = 0.5
var body: some View {
VStack {
Image(
systemName: "speaker.wave.3",
variableValue: value
)
Slider(value: $value, in: 0...1)
.padding()
}
.padding()
}
}
在这个例子中,符号 speaker.wave.3 根据 Slider 提供的值改变其外观。
我们应该使用可变值来传达状态的变化,例如音量、电池电量或信号强度,为用户提供动态状态的清晰视觉表示。为了传达深度和视觉层次,我们应该使用分层渲染模式,它可以提升某些图层,并区分符号内的前景和背景元素。
设计变体
符号可以有不同的设计变体,例如填充和斜杠,以帮助传达特定的状态和操作。斜杠变体可以表示项目或操作不可用,而填充变体可以表示选择。
在 SwiftUI 中,我们可以使用 symbolVariant() 修饰符来应用这些变体。
HStack {
Image(systemName: "heart")
Image(systemName: "heart")
.symbolVariant(.slash)
Image(systemName: "heart")
.symbolVariant(.fill)
}
不同的符号变体用于各种设计目的。轮廓变体在工具栏、导航栏和列表中非常有效,而填充变体则用于强调选择的状态。
HStack {
Image(systemName: "heart")
.symbolVariant(.circle)
Image(systemName: "heart")
.symbolVariant(.square)
Image(systemName: "heart")
.symbolVariant(.rectangle)
}
不同的符号变体具有不同的设计用途。轮廓变体在工具栏、导航栏和列表中非常有效,因为这些地方通常会与文本一起显示符号。将符号封装在圆形或方形等形状中可以增强其可读性,特别是在较小尺寸下。填充变体由于其实心区域,使符号更具视觉强调性,非常适合用于 iOS 标签栏、滑动操作以及指示选择的强调颜色场景。
在许多情况下,显示符号的视图会自动选择合适的变体。例如,iOS 标签栏通常使用填充变体,而导航栏则偏好轮廓变体。这种自动选择确保符号在不同上下文中有效使用,而无需明确指定。
示例代码
import SwiftUI
struct ContentView: View {
@State private var value = 0.5
var body: some View {
VStack {
Image(
systemName: "speaker.wave.3",
variableValue: value
)
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)
Slider(value: $value, in: 0...1)
.padding()
}
.padding()
}
}
运行 Demo
- 打开Xcode并创建一个新的 SwiftUI 项目。
- 将上述代码粘贴到
ContentView.swift文件中。 - 运行项目,查看效果。
结论
在SwiftUI中增强符号图像可以显著改善应用程序的外观和感觉。通过调整大小、颜色、渲染模式、可变值和设计变体,我们可以创建使应用程序更直观和视觉吸引力的图标。SwiftUI使这些调整变得简单易行,使我们能够轻松实现和改进这些自定义以提供更好的用户体验。
SwiftUI 中掌握 ScrollView 的使用:滚动可见性
苹果将为 Apple Watch X 铺路 | Swift 周报 issue 45
讨论在 Swift 中引入函数体宏
掌握 SwiftUI 中的 ScrollView:滚动几何
前言
本文探讨了如何使用 onScrollGeometryChange 视图修饰符有效地监控和管理滚动位置和几何。通过详细的代码示例和解释,你将学习如何利用这些工具创建动态和响应迅速的用户界面。
SwiftUI 是一个强大的框架,它简化了在苹果平台上构建用户界面的过程。SwiftUI 中的一个基本组件是 ScrollView,它允许用户通过滚动导航内容。然而,管理滚动位置和理解滚动交互可能是一个挑战。ScrollGeometry 和 onScrollGeometryChange 视图修饰符的引入解决了这些挑战,为开发者提供了更多的控制和对滚动行为的深入了解。
什么是 ScrollPosition
ScrollPosition 是一种类型,允许开发者以编程方式读取或更改滚动位置。虽然有用,但当用户使用手势与滚动视图交互时,它显得不够全面。以下是一个展示 ScrollPosition 使用的示例:
struct ContentView: View {
@State private var position = ScrollPosition(edge: .top)
var body: some View {
ScrollView {
Button("Scroll to offset") {
position.scrollTo(point: CGPoint(x: 0, y: 100))
}
ForEach(1..<100) { index in
Text(verbatim: index.formatted())
.id(index)
}
}
.scrollPosition($position)
.animation(.default, value: position)
}
}
在这个示例中,我们将滚动视图绑定到一个状态属性。当按下按钮时,滚动视图会将其内容偏移移动到指定点。然而,我们无法读取用户通过手势交互设置的具体内容偏移。
引入 ScrollGeometry
SwiftUI 的新 ScrollGeometry 类型以及 onScrollGeometryChange 视图修饰符提供了一个解决方案。这些工具允许开发者在用户交互期间准确读取内容偏移。
使用 onScrollGeometryChange
让我们探索如何使用 onScrollGeometryChange 视图修饰符与 ScrollGeometry:
struct ContentView: View {
@State private var scrollPosition = ScrollPosition(y: 0)
@State private var offsetY: CGFloat = 0
var body: some View {
ScrollView {
ForEach(1..<100, id: \.self) { number in
Text(verbatim: number.formatted())
.id(number)
}
}
.scrollPosition($scrollPosition)
.onScrollGeometryChange(for: CGFloat.self) { geometry in
geometry.contentOffset.y
} action: { oldValue, newValue in
if oldValue != newValue {
offsetY = newValue
}
}
.onChange(of: offsetY) {
print(offsetY)
}
}
}
onScrollGeometryChange 视图修饰符接受三个参数:
-
类型参数:指定要跟踪的滚动几何类型。在此示例中,我们使用
CGFloat来跟踪内容偏移的 Y 轴。 - 转换闭包:从 ScrollGeometry 实例中提取所需信息。
- 动作闭包:处理滚动几何的变化,通过比较旧值和新值,允许我们相应地更新状态属性。
高级滚动几何跟踪
ScrollGeometry 提供了许多有价值的属性,如内容偏移、边界、容器大小、可见矩形、内容插入和内容大小。开发者可以提取单个属性或组合多个属性以获得全面的见解。
以下是一个结合内容大小和可见矩形跟踪的示例:
struct ContentView: View {
struct ScrollData: Equatable {
let size: CGSize
let visible: CGRect
}
@State private var scrollPosition = ScrollPosition(y: 0)
@State private var scrollData = ScrollData(size: .zero, visible: .zero)
var body: some View {
ScrollView {
ForEach(1..<100, id: \.self) { number in
Text(verbatim: number.formatted())
.id(number)
}
}
.scrollPosition($scrollPosition)
.onScrollGeometryChange(for: ScrollData.self) { geometry in
ScrollData(size: geometry.contentSize, visible: geometry.visibleRect)
} action: { oldValue, newValue in
if oldValue != newValue {
scrollData = newValue
}
}
.onChange(of: scrollData) {
print(scrollData)
}
}
}
在这个示例中,我们定义了一个 ScrollData 结构来保存大小和可见矩形属性。在使用 onScrollGeometryChange 视图修饰符时,我们将 ScrollData 作为转换闭包的返回类型,从 ScrollGeometry 实例中提取所有所需的数据。
完整代码示例分析
下面是一个完整的 SwiftUI Demo,其中包含了我们刚刚讨论的 ScrollView、ScrollGeometry 和 onScrollGeometryChange 的使用示例。你可以在 Xcode 中运行这个项目来观察其效果。
完整代码示例
import SwiftUI
struct ContentView: View {
@State private var scrollPosition = ScrollPosition(y: 0)
@State private var offsetY: CGFloat = 0
var body: some View {
VStack {
Text("Scroll Offset: \(offsetY, specifier: "%.2f")")
.padding()
ScrollView {
ForEach(1..<100, id: \.self) { number in
Text(verbatim: number.formatted())
.padding()
.frame(maxWidth: .infinity)
.background(Color(.secondarySystemBackground))
.cornerRadius(8)
.padding(.horizontal)
.id(number)
}
}
.scrollPosition($scrollPosition)
.onScrollGeometryChange(for: CGFloat.self) { geometry in
geometry.contentOffset.y
} action: { oldValue, newValue in
if oldValue != newValue {
offsetY = newValue
}
}
.onChange(of: offsetY) {
print(offsetY)
}
}
}
}
struct ScrollData: Equatable {
let size: CGSize
let visible: CGRect
}
struct AdvancedContentView: View {
@State private var scrollPosition = ScrollPosition(y: 0)
@State private var scrollData = ScrollData(size: .zero, visible: .zero)
var body: some View {
VStack {
Text("Content Size: \(scrollData.size.width, specifier: "%.2f") x \(scrollData.size.height, specifier: "%.2f")")
.padding()
Text("Visible Rect: \(scrollData.visible.origin.x, specifier: "%.2f"), \(scrollData.visible.origin.y, specifier: "%.2f") - \(scrollData.visible.width, specifier: "%.2f") x \(scrollData.visible.height, specifier: "%.2f")")
.padding()
ScrollView {
ForEach(1..<100, id: \.self) { number in
Text(verbatim: number.formatted())
.padding()
.frame(maxWidth: .infinity)
.background(Color(.secondarySystemBackground))
.cornerRadius(8)
.padding(.horizontal)
.id(number)
}
}
.scrollPosition($scrollPosition)
.onScrollGeometryChange(for: ScrollData.self) { geometry in
ScrollData(size: geometry.contentSize, visible: geometry.visibleRect)
} action: { oldValue, newValue in
if oldValue != newValue {
scrollData = newValue
}
}
.onChange(of: scrollData) {
print(scrollData)
}
}
}
}
@main
struct ScrollViewDemoApp: App {
var body: some Scene {
WindowGroup {
TabView {
ContentView()
.tabItem {
Label("Basic", systemImage: "1.square.fill")
}
AdvancedContentView()
.tabItem {
Label("Advanced", systemImage: "2.square.fill")
}
}
}
}
}
如何运行
- 打开 Xcode 并创建一个新的 SwiftUI 项目。
- 将默认生成的
ContentView.swift文件替换为上面的完整代码。 - 在
@main注释下的应用程序入口点中,确保你的主视图是ScrollViewDemoApp。 - 运行项目。
功能解释
-
ContentView: 展示基本的滚动偏移追踪功能,通过onScrollGeometryChange视图修饰符追踪 Y 轴的内容偏移。 -
AdvancedContentView: 展示更高级的滚动几何追踪功能,追踪内容大小和可见矩形的变化。 -
ScrollViewDemoApp: 包含TabView,方便在基本和高级示例之间切换。
总结
今天,我们探讨了 SwiftUI 中的新 ScrollGeometry 类型和 onScrollGeometryChange 视图修饰符。这些工具为开发者提供了对滚动位置和交互的精确控制和洞察,增强了动态和响应迅速的用户界面的开发。通过利用这些功能,你可以创建更具吸引力和直观的应用程序。