阅读视图

发现新文章,点击刷新页面。

自定义 SwiftUI 中符号图像的外观

1722425826929.jpg

前言

符号图像是来自 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

  1. 打开Xcode并创建一个新的 SwiftUI 项目。
  2. 将上述代码粘贴到 ContentView.swift 文件中。
  3. 运行项目,查看效果。

结论

在SwiftUI中增强符号图像可以显著改善应用程序的外观和感觉。通过调整大小、颜色、渲染模式、可变值和设计变体,我们可以创建使应用程序更直观和视觉吸引力的图标。SwiftUI使这些调整变得简单易行,使我们能够轻松实现和改进这些自定义以提供更好的用户体验。

掌握 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 视图修饰符接受三个参数:

  1. 类型参数:指定要跟踪的滚动几何类型。在此示例中,我们使用 CGFloat 来跟踪内容偏移的 Y 轴。
  2. 转换闭包:从 ScrollGeometry 实例中提取所需信息。
  3. 动作闭包:处理滚动几何的变化,通过比较旧值和新值,允许我们相应地更新状态属性。

高级滚动几何跟踪

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

如何运行

  1. 打开 Xcode 并创建一个新的 SwiftUI 项目。
  2. 将默认生成的 ContentView.swift 文件替换为上面的完整代码。
  3. @main 注释下的应用程序入口点中,确保你的主视图是 ScrollViewDemoApp
  4. 运行项目。

功能解释

  • ContentView: 展示基本的滚动偏移追踪功能,通过 onScrollGeometryChange 视图修饰符追踪 Y 轴的内容偏移。
  • AdvancedContentView: 展示更高级的滚动几何追踪功能,追踪内容大小和可见矩形的变化。
  • ScrollViewDemoApp: 包含 TabView,方便在基本和高级示例之间切换。

总结

今天,我们探讨了 SwiftUI 中的新 ScrollGeometry 类型和 onScrollGeometryChange 视图修饰符。这些工具为开发者提供了对滚动位置和交互的精确控制和洞察,增强了动态和响应迅速的用户界面的开发。通过利用这些功能,你可以创建更具吸引力和直观的应用程序。

❌