如何以可视化的方式自定义滑块的刻度

移动开发 2026-07-09

在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 之外,似乎不可能真正自定义刻度标记的实际外观。着色颜色在滑轨上也会生效。


为步进值自定义刻度的分布

当使用带有 steptick 参数的初始化变体时,用于构建刻度的闭包会在每个步进值上被调用。闭包内部传给 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上如果定义了标签则会显示。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章