Skip to content

使用绑定到值的自定义控件来提供您的应用所独有的交互。

SwiftUI 提供了许多输入控件,例如 Slider 、 Text Field 以及许多其他控件,它们可以绑定到某个值,并随着用户与控件的交互而更改值。但每个应用都各不相同。您可能需要一个自定义控件来提供与您的应用独有的行为。

SwiftUI 提供了创建自定义输入控件所需的基础模块,供您在应用中使用。本教程将介绍一个这样的控件示例,即评分控件。示例应用使用此控件,允许用户对食谱进行 1 到 5 星的评分。

要试验代码,请下载项目文件并在 Xcode 中打开示例。

第 1 部分

设计自定义控件 在实现自定义控件之前,请先思考一下该控件需要哪些数据、它如何处理这些数据,以及如何在应用中直观地呈现这些数据。例如,示例应用需要一个 Int 属性来表示菜谱的评分。该控件需要能够更改此属性的值。此外,由于该控件显示菜谱的评分,因此它需要显示一组反映评分值的星星;例如,当评分值为 5 时,该控件会显示五颗星。

步骤 1

该示例将自定义控件定义为名为 Star Rating 结构。

此结构符合 View 协议,因为控件作为应用程序用户界面的一部分出现。

import SwiftUI


struct StarRating: View {
    @Binding var rating: Int
    private let maxRating = 5


    var body: some View {
        HStack {
            ForEach(1..<maxRating + 1, id: \.self) { value in
                Image(systemName: "star")
                    .symbolVariant(value <= rating ? .fill : .none)
                    .foregroundColor(.accentColor)
                    .onTapGesture {
                        if value != rating {
                            rating = value
                        } else {
                            rating = 0
                        }
                    }
            }
        }
    }
}

第 2 步

该结构定义了一个名为 rating 的 Binding 变量,用于存储菜谱的评级。

通过将 rating 定义为绑定变量,即使另一个视图负责创建该值, Star Rating 也可以读取和写入该值。

import SwiftUI


struct StarRating: View {
    @Binding var rating: Int
    private let maxRating = 5


    var body: some View {
        HStack {
            ForEach(1..<maxRating + 1, id: \.self) { value in
                Image(systemName: "star")
                    .symbolVariant(value <= rating ? .fill : .none)
                    .foregroundColor(.accentColor)
                    .onTapGesture {
                        if value != rating {
                            rating = value
                        } else {
                            rating = 0
                        }
                    }
            }
        }
    }
}

步骤3

私有常数 max Rating 存储了人们可以给出的菜谱的最高评级。

import SwiftUI


struct StarRating: View {
    @Binding var rating: Int
    private let maxRating = 5


    var body: some View {
        HStack {
            ForEach(1..<maxRating + 1, id: \.self) { value in
                Image(systemName: "star")
                    .symbolVariant(value <= rating ? .fill : .none)
                    .foregroundColor(.accentColor)
                    .onTapGesture {
                        if value != rating {
                            rating = value
                        } else {
                            rating = 0
                        }
                    }
            }
        }
    }
}

步骤4

与所有其他 SwiftUI 视图一样, Star Rating 实现了所需的计算属性 body 。

笔记 每个 SwiftUI 视图都必须实现 body 来提供视图的内容。

步骤5

HStack 以水平线显示评级星级。

import SwiftUI


struct StarRating: View {
    @Binding var rating: Int
    private let maxRating = 5


    var body: some View {
        HStack {
            ForEach(1..<maxRating + 1, id: \.self) { value in
                Image(systemName: "star")
                    .symbolVariant(value <= rating ? .fill : .none)
                    .foregroundColor(.accentColor)
                    .onTapGesture {
                        if value != rating {
                            rating = value
                        } else {
                            rating = 0
                        }
                    }
            }
        }
    }
}

c步骤6

在 HStack 内部,控件使用 For Each 结构来显示 max Rating 常量指示的星星数量。

For Each 遍历定义为 Int 实例范围 1 到 5 的数据集合。

重要的 id 参数的类型为 ID ,即可 Hashable 。For For Each 结构使用此参数来标识数据,即从 1 到 5 整数值。参数值是标识键路径 .self ,它为每个整数指定一个 Int 实例。由于 Int 是可哈希的,因此使用此键路径可以满足 For Each 初始化器方法 init(_: id: content:) 的要求。并且由于数据是一个不断增长的整数范围,且永远不会有重复的值,因此可以使用每个整数值作为其标识符。

import SwiftUI


struct StarRating: View {
    @Binding var rating: Int
    private let maxRating = 5


    var body: some View {
        HStack {
            ForEach(1..<maxRating + 1, id: \.self) { value in
                Image(systemName: "star")
                    .symbolVariant(value <= rating ? .fill : .none)
                    .foregroundColor(.accentColor)
                    .onTapGesture {
                        if value != rating {
                            rating = value
                        } else {
                            rating = 0
                        }
                    }
            }
        }
    }
}

步骤7

该控件使用 Image 实例显示一颗星。

该示例使用初始化方法 init(system Name:) 显示一颗星星的图片。此方法会创建一个显示系统符号图片的图片视图。像星星这样的符号图片来自 SF Symbols ,这是一个您可以在应用中使用的图标库。

实验 将符号从星形更改为其他符号,例如圆形。

import SwiftUI


struct StarRating: View {
    @Binding var rating: Int
    private let maxRating = 5


    var body: some View {
        HStack {
            ForEach(1..<maxRating + 1, id: \.self) { value in
                Image(systemName: "star")
                    .symbolVariant(value <= rating ? .fill : .none)
                    .foregroundColor(.accentColor)
                    .onTapGesture {
                        if value != rating {
                            rating = value
                        } else {
                            rating = 0
                        }
                    }
            }
        }
    }
}

步骤8

当整数值小于或等于 rating 值时,控件会显示一个实心星号;当整数值大于 rating 时,控件会显示一个空心星号,因此控件将 symbol Variant(_😃 修饰符应用于 Image 实例。

笔记 为了确定应用哪些 Symbol Variants ( fill 或 none ,控件使用三元条件运算符 。该运算符由三部分组成,形式为 question ? answer1 : answer2 。有关更多信息,请参阅三元条件运算符 。

步骤9

该控件使用 foreground Color(_😃 视图修饰符设置星星的颜色。

实验 通过将 accent Color 替换为其他颜色(例如 yellow 来更改星星的颜色。

第 2 节

使控件具有交互性 Star Rating 可以显示一组星星来指示食谱的评分。例如,如果食谱的评分为 4 ,该控件会显示四颗实心星星,后跟一颗空心星星。为了使 Star Rating 具有交互性,它使用了 on Tap Gesture(count: perform:) 操作。

步骤 1

为了使人们能够与评级控件进行交互, Star Rating 将 on Tap Gesture(count: perform:) 操作添加到 For Each 循环中创建的每个 Image 实例。

当用户点击或轻触星形 Image 实例时,轻触手势会执行闭包中定义的操作。星形表示 Star Rating 分配给菜谱的评级。例如,如果用户点击第四颗星,菜谱的评级将设置为 4 。再次点击第四颗星, Star Rating 会将菜谱的评级重置为 0 或无星 。

import SwiftUI


struct StarRating: View {
    @Binding var rating: Int
    private let maxRating = 5


    var body: some View {
        HStack {
            ForEach(1..<maxRating + 1, id: \.self) { value in
                Image(systemName: "star")
                    .symbolVariant(value <= rating ? .fill : .none)
                    .foregroundColor(.accentColor)
                    .onTapGesture {
                        if value != rating {
                            rating = value
                        } else {
                            rating = 0
                        }
                    }
            }
        }
    }
}

第 2 步

当整数值不等于 rating 值时,闭包会将 rating 设置为整数值,这表示该人为该菜谱分配的新评级。

通过将 rating 设置为整数值, Star Rating 会更新其外观,以显示填充的星星,直至达到 value 所标识的数量,然后显示空星星,直至达到 max Rating 所标识的数量。

步骤3

当整数值等于 rating 值时,闭包会将 rating 设置为 0 ,从而将菜谱的评级重置为无星 。

Star Rating 显示五颗空星,表示该菜谱没有评级。

第 3 节

在其他视图中显示自定义控件 Star Rating 已准备就绪。它拥有所需的数据,可以对这些数据进行更改,并且可以在应用的用户界面中直观地显示数据的当前状态。下一步是使用自定义输入控件。

在示例应用中, Star Rating 显示在菜谱详细视图中的菜谱标题下。

步骤 1

结构 Regular Title View 是一种显示菜谱的标题和副标题及其评级的视图。

第 2 步

Regular Title View 定义了一个绑定变量,用于存储从另一个视图接收的配方。

此绑定允许视图读取和写入 Recipe 实例的数据。但是,该视图并不是该配方的所有者。示例中的另一个视图负责创建和拥有 Recipe 实例。

import SwiftUI


struct RegularTitleView: View {
    @Binding var recipe: Recipe


    var body: some View {
        VStack(alignment: .leading) {
            Text(recipe.title)
                .font(.largeTitle)
            StarRating(rating: $recipe.rating)
        }
        Spacer()
        Text(recipe.subtitle)
            .font(.subheadline)
    }
}

步骤3

该视图与自定义控件 Star Rating 共享与菜谱 rating 属性的绑定,这允许控件读取和写入该属性。

当一个人与 Star Rating 控件交互时,SwiftUI 会重新绘制视图以反映所选的评级。

重要的 变量名 recipe 上的美元符号( $ )前缀表示该调用正在传递对 Star Rating 绑定。