如何以可视化的方式自定义滑块的刻度
在iOS 26中为滑块添加了刻度标记,正如2025年 6月的SwiftUI更新所述:
滑块现在支持刻度标记。初始化一个带有step参数的Slider时,刻度会自动显示。
此外,在iOS 26中,Slider 增添了一些新的初始化变体。这些初始化变体使自定义滑块的刻度成为可能。
例如,[init(value:in:step:neutralValue:enabledBounds:label:currentValueLabel:minimumValueLabel:maximumValueLabel:tick:onEditingChanged:)] 可以与 step 参数一起使用。此特定初始化变体的文档写道:
创建一个滑块,用于从给定范围内选择一个值,受步进增量约束,同时显示提供的标签和可自定义的刻度。
在我看来,这种刻度自定义唯一能实现的是更改与刻度相关的(不可见的)标签。也许这对无障碍性有用。然而,我在想,是否也可以以可视化的方式改变刻度,即使只是省略某些刻度?
作为示例,下面的代码在范围0...1000上创建了一个步长为1 的滑块。这使用了自iOS 13起可用的初始化变体 [init(value:in:step:label:onEditingChanged:)]:
struct ContentView: View {
@State private var value = 500.0
var body: some View {
Slider(
value: $value,
in: 0...1000,
step: 1,
label: { Text("Value") }
)
.padding(.horizontal)
.padding(.vertical, 40)
.overlay(alignment: .top) {
Text("\(Int(value.rounded()))")
}
}
}
由于步长很小,刻度之间距离非常接近,在iPhone显示屏上运行时会合并成一条实线:

如果能够在视觉上区分主要刻度(例如每100个单位)和次要刻度,可能会更好,或许用不同的颜色或更大点的刻度。至少如果能够省略次要刻度,就更有帮助。
下面是一个尝试:使用上述新初始化变体创建步长为1 的滑块,但刻度仅在值为100的倍数时显示:
Slider(
value: $value,
in: 0...1000,
step: 1,
label: { Text("Value") },
tick: { val in
val.truncatingRemainder(dividingBy: 100) == 0 ? SliderTick(val) : nil
}
)
额外的 tick 回调似乎没有任何区别,刻度看起来和之前完全一样。
如何在任意新初始化变体中,以可视化的方式自定义一个 Slider 的刻度?
解决方案
简短回答
经过一些测试,我的结论是,改变滑块刻度在滑道上的分布(也就是改变刻度之间的间距)是唯一可以实现的“可视化”自定义方式。不过,这种做法并非无缝工作,原因如下所述。因此用这种方式自定义刻度可能并不太有用。
不幸的是,除了给滑块应用一个 .tint 之外,似乎不可能真正自定义刻度标记的实际外观。着色颜色在滑轨上也会生效。
为步进值自定义刻度的分布
当使用带有 step 和 tick 参数的初始化变体时,用于构建刻度的闭包会在每个步进值上被调用。闭包内部传给 SliderTick 的值决定了刻度在轨道上的位置。
- 通过为刻度位置提供一个与步进值不同的数值,可以自定义刻度之间的间距。
- 拇指按钮会吸附到显示的刻度位置上。但是,被选中的值仍然是按通常分布的最近步进值。
- 因为最后这一点,如果相邻的刻度对应相同的底层步进值,拇指按钮就无法移动到最近的刻度位置。
下面有一个示例,显示数值为10的倍数的刻度是分离的,而中间的数值则成簇显示:
struct ContentView: View {
@State private var value = 0.0
var body: some View {
Slider(
value: $value,
in: 0...50,
step: 1,
label: { Text("Value") },
tick: { val in
let remainder = Int(val) % 10
let pos = remainder > 0 ? val + (Double(5 - remainder) / 2) : val
return SliderTick(pos)
}
)
.padding(.horizontal)
.padding(.vertical, 40)
.overlay(alignment: .top) {
Text(String(format: "%.1f", value))
}
}
}

尽管这提供了相当不错的分布,但并非所有刻度位置都能被选中。特别是当拇指按钮在0 和10之间移动时,数值1、2、8和 9无法被选中。
下面再给出一个更极端的示例。范围是0...10,但刻度按数值的平方分布:
Slider(
value: $value,
in: 0...10,
step: 1,
label: { Text("Value") },
tick: { val in
SliderTick(val * val / 10)
}
)

注意,拇指按钮实际定位在数值为5 的刻度上方,但选中的值是3。因此并不太实用。
为任意数值显示刻度
另一种自定义选项是使用初始化变体 [init(value:in:neutralValue:enabledBounds:label:currentValueLabel:minimumValueLabel:maximumValueLabel:ticks:onEditingChanged:)]。这个变体没有 step 参数,因此刻度可以显示在任意数值处。
下例在滑轨两端以及另外三个任意位置显示刻度:
Slider(
value: $value,
in: 0...10,
label: { Text("Value") },
ticks: {
SliderTickContentForEach([0.0, 1.2, 2.7, 6.8, 10.0], id: \.self) { val in
SliderTick(val)
}
}
)

拇指按钮始终吸附到某个刻度位置。因此,当刻度显示在任意数值处时,滑块只能用于选择这几个任意值中的一个,无法选择中间值。
错误提示:在iPhone上使用VoiceOver测试iOS 26.5时,递增和递减操作导致数值按1 的步长上下跳动。拇指按钮会吸附到最近的刻度位置,但底层的值为四舍五入后的整数。这在我看来是一个bug。我不知道是否有通过无障碍修饰符来防止这个问题的方法。
附加说明
- 如果使用初始化变体 [
init(value:in:step:onEditingChanged:)],不会显示刻度标记,尽管它指定了一个step。为了显示刻度标记,必须使用一个也定义slider的label的初始化变体。据我所知,刻度标记与滑块标签定义之间的依赖关系未在文档中说明。 - 在iOS上,滑块的标签从不显示,但会被VoiceOver朗读。详见 [SwiftUI Slider的标签可见性] 以了解更多讨论。
- 显示刻度时,无法隐藏单独的刻度标记。如问题中所示,当使用带有
tick参数的初始化变体时,即使闭包返回nil,仍会显示一个刻度。 - 刻度标记可以被放置在屏幕之外的位置,当然就看不见了。然而,这帮助不大,因为这会使相关的值无法被选中。拇指按钮也不能被放置在通过这种方法创建的轨道的间隙中。
- 同一个值不能对应多个刻度。这会在控制台中引发错误。
- 在iOS上,刻度标记的标签从不显示。然而,在macOS上如果定义了标签则会显示。