在 SwiftUI 中创建一个环形 Slider

本文展示了如何定义响应拖动手势的圆环滑块控件。可以设置滑块视图的大小,并且滑块按预期工作。可以向控件添加更多参数以设置颜色或圆环内显示的值的格式。 GitHub 上提供了 Circular Slider 的代码。

本文展示了如何定义响应拖动手势的圆环滑块控件。可以设置滑块视图的大小,并且滑块按预期工作。可以向控件添加更多参数以设置颜色或圆环内显示的值的格式。 GitHub 上提供了 Circular Slider 的代码。

在 SwiftUI 中创建一个环形 Slider

前言

Slider 控件是一种允许用户从一系列值中选择一个值的 UI 控件。在 SwiftUI 中,它通常呈现为直线上的拇指选择器。有时将这种类型的选择器呈现为一个圆圈,拇指绕着圆周移动可能会更好。本文介绍如何在 SwiftUI 中定义一个环形的 Slider。

初始化环形轮廓

从ZStack中的三个圆环开始。一个灰色的圆环代表滑块的路径轮廓,一个淡红色的圆弧代表沿着圆环的进度,一个圆圈代表当前光标或拇指的位置。将滑块的范围设置为0.0到1.0,并硬编码一个直径和一个的当前位置进度 - 0.33。

struct CircularSliderView1: View {
let progress = 0.33
let ringDiameter = 300.0

private var rotationAngle: Angle {
return Angle(degrees: (360.0 * progress))
}

var body: some View {
VStack {
ZStack {
Circle()
.stroke(Color(hue: 0.0, saturation: 0.0, brightness: 0.9), lineWidth: 20.0)
Circle()
.trim(from: 0, to: progress)
.stroke(Color(hue: 0.0, saturation: 0.5, brightness: 0.9),
style: StrokeStyle(lineWidth: 20.0, lineCap: .round)
)
.rotationEffect(Angle(degrees: -90))
Circle()
.fill(Color.white)
.frame(width: 21, height: 21)
.offset(y: -ringDiameter / 2.0)
.rotationEffect(rotationAngle)
}
.frame(width: ringDiameter, height: ringDiameter)

Spacer()
}
.padding(80)
}
}

图片

将进度值和拇指位置绑定

将进度变量更改为状态[1]变量并添加默认 Slider。这个 Slider 用于修改进度值,并在圆形滑块上实现足够的代码以使拇指和进度弧响应。当前值显示在环形 Slider 的中心。

struct CircularSliderView2: View {
@State var progress = 0.33
let ringDiameter = 300.0

private var rotationAngle: Angle {
return Angle(degrees: (360.0 * progress))
}

var body: some View {
ZStack {
Color(hue: 0.58, saturation: 0.04, brightness: 1.0)
.edgesIgnoringSafeArea(.all)

VStack {
ZStack {
Circle()
.stroke(Color(hue: 0.0, saturation: 0.0, brightness: 0.9), lineWidth: 20.0)
.overlay() {
Text("\(progress, specifier: "%.1f")")
.font(.system(size: 78, weight: .bold, design:.rounded))
}
Circle()
.trim(from: 0, to: progress)
.stroke(Color(hue: 0.0, saturation: 0.5, brightness: 0.9),
style: StrokeStyle(lineWidth: 20.0, lineCap: .round)
)
.rotationEffect(Angle(degrees: -90))
Circle()
.fill(Color.white)
.shadow(radius: 3)
.frame(width: 21, height: 21)
.offset(y: -ringDiameter / 2.0)
.rotationEffect(rotationAngle)
}
.frame(width: ringDiameter, height: ringDiameter)


VStack {
Text("Progress: \(progress, specifier: "%.1f")")
Slider(value: $progress,
in: 0...1,
minimumValueLabel: Text("0.0"),
maximumValueLabel: Text("1.0")
) {}
}
.padding(.vertical, 40)

Spacer()
}
.padding(.vertical, 40)
.padding()
}
}
}

图片

添加触摸手势

DragGesture[2]被添加到滑块圆圈,并且使用临时文本视图显示拖动手势的当前位置。可以看到 x 和 y 坐标围绕包含环形 Slider 的位置中心的变化情况。

struct CircularSliderView3: View {
@State var progress = 0.33
let ringDiameter = 300.0

@State var loc = CGPoint(x: 0, y: 0)

private var rotationAngle: Angle {
return Angle(degrees: (360.0 * progress))
}

private func changeAngle(location: CGPoint) {
loc = location
}

var body: some View {
ZStack {
Color(hue: 0.58, saturation: 0.04, brightness: 1.0)
.edgesIgnoringSafeArea(.all)

VStack {
ZStack {
Circle()
.stroke(Color(hue: 0.0, saturation: 0.0, brightness: 0.9), lineWidth: 20.0)
.overlay() {
Text("\(progress, specifier: "%.1f")")
.font(.system(size: 78, weight: .bold, design:.rounded))
}
Circle()
.trim(from: 0, to: progress)
.stroke(Color(hue: 0.0, saturation: 0.5, brightness: 0.9),
style: StrokeStyle(lineWidth: 20.0, lineCap: .round)
)
.rotationEffect(Angle(degrees: -90))
Circle()
.fill(Color.blue)
.shadow(radius: 3)
.frame(width: 21, height: 21)
.offset(y: -ringDiameter / 2.0)
.rotationEffect(rotationAngle)
.gesture(
DragGesture(minimumDistance: 0.0)
.onChanged() { value in
changeAngle(location: value.location)
}
)
}
.frame(width: ringDiameter, height: ringDiameter)

Spacer().frame(height:50)

Text("Location = (\(loc.x, specifier: "%.1f"), \(loc.y, specifier: "%.1f"))")

Spacer()
}
.padding(.vertical, 40)
.padding()
}
}
}

图片

为不同的坐标值设置滑块位置

圆形滑块上有两个表示进度的值,用于显示进度弧度的progress值和用于显示滑块光标的rotationAngle。应该只有一个属性来保存滑块进度。视图被提取到一个单独的结构中,该结构具有圆形滑块上进度的一个绑定值。

滑块的range的可选参数也是可用的。这需要对进度进行一些调整,以计算已设置的角度以及拇指在圆形滑块上位置的旋转角度。另外调用onAppear根据View出现前的进度值计算旋转角度。

struct CircularSliderView: View {
@Binding var progress: Double

@State private var rotationAngle = Angle(degrees: 0)
private var minValue = 0.0
private var maxValue = 1.0

init(value progress: Binding<Double>, in bounds: ClosedRange<Int> = 0...1) {
self._progress = progress

self.minValue = Double(bounds.first ?? 0)
self.maxValue = Double(bounds.last ?? 1)
self.rotationAngle = Angle(degrees: progressFraction * 360.0)
}

private var progressFraction: Double {
return ((progress - minValue) / (maxValue - minValue))
}

private func changeAngle(location: CGPoint) {
// 为位置创建一个向量(在 iOS 上反转 y 坐标系统)
let vector = CGVector(dx: location.x, dy: -location.y)

// 计算向量的角度
let angleRadians = atan2(vector.dx, vector.dy)

// 将角度转换为 0 到 360 的范围(而不是负角度)
let positiveAngle = angleRadians < 0.0 ? angleRadians + (2.0 * .pi) : angleRadians

// 根据角度更新滑块进度值
progress = ((positiveAngle / (2.0 * .pi)) * (maxValue - minValue)) + minValue
rotationAngle = Angle(radians: positiveAngle)
}

var body: some View {
GeometryReader { gr in
let radius = (min(gr.size.width, gr.size.height) / 2.0) * 0.9
let sliderWidth = radius * 0.1

VStack(spacing:0) {
ZStack {
Circle()
.stroke(Color(hue: 0.0, saturation: 0.0, brightness: 0.9),
style: StrokeStyle(lineWidth: sliderWidth))
.overlay() {
Text("\(progress, specifier: "%.1f")")
.font(.system(size: radius * 0.7, weight: .bold, design:.rounded))
}
// 取消注释以显示刻度线
//Circle()
// .stroke(Color(hue: 0.0, saturation: 0.0, brightness: 0.6),
// style: StrokeStyle(lineWidth: sliderWidth * 0.75,
// dash: [2, (2 * .pi * radius)/24 - 2]))
// .rotationEffect(Angle(degrees: -90))
Circle()
.trim(from: 0, to: progressFraction)
.stroke(Color(hue: 0.0, saturation: 0.5, brightness: 0.9),
style: StrokeStyle(lineWidth: sliderWidth, lineCap: .round)
)
.rotationEffect(Angle(degrees: -90))
Circle()
.fill(Color.white)
.shadow(radius: (sliderWidth * 0.3))
.frame(width: sliderWidth, height: sliderWidth)
.offset(y: -radius)
.rotationEffect(rotationAngle)
.gesture(
DragGesture(minimumDistance: 0.0)
.onChanged() { value in
changeAngle(location: value.location)
}
)
}
.frame(width: radius * 2.0, height: radius * 2.0, alignment: .center)
.padding(radius * 0.1)
}

.onAppear {
self.rotationAngle = Angle(degrees: progressFraction * 360.0)
}
}
}
}

CircularSliderView的三种不同视图被添加到View中以测试和演示 Circular Slider 视图的不同功能。

struct CircularSliderView5: View {
@State var progress1 = 0.75
@State var progress2 = 37.5
@State var progress3 = 7.5

var body: some View {
ZStack {
Color(hue: 0.58, saturation: 0.06, brightness: 1.0)
.edgesIgnoringSafeArea(.all)

VStack {
CircularSliderView(value: $progress1)
.frame(width:250, height: 250)

HStack {
CircularSliderView(value: $progress2, in: 1...10)

CircularSliderView(value: $progress3, in: 0...100)
}

Spacer()
}
.padding()
}
}
}

图片

总结

本文展示了如何定义响应拖动手势的圆环滑块控件。可以设置滑块视图的大小,并且滑块按预期工作。可以向控件添加更多参数以设置颜色或圆环内显示的值的格式。 GitHub 上提供了Circular Slider[3]的代码。

参考资料

[1]state:https://developer.apple.com/documentation/swiftui/state​。

[2]DragGesture:https://developer.apple.com/documentation/swiftui/draggesture/​。

[3]Circular Slider:https://github.com/SwiftCommunityRes/swift​。

©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经

(0)
打赏 微信扫码打赏 微信扫码打赏 支付宝扫码打赏 支付宝扫码打赏
清一色的头像清一色管理团队
上一篇 2023年5月6日 23:35
下一篇 2023年5月6日 23:36

相关推荐

发表评论

登录后才能评论

联系我们

在线咨询:1643011589-QQbutton

手机:13798586780

QQ/微信:1074760229

QQ群:551893940

工作时间:工作日9:00-18:00,节假日休息

关注微信