如何使用 SwiftUI 创建自定义滑块?
How to create custom slider by using SwiftUI?
我可以使用 SwiftUI 创建一个滑块,但我无法更改滑块的样式,如下图所示。
问题:我无法在 SwiftUI 中找到任何选项来更改滑块样式。
注意:我只想使用 SwiftUI 创建它。我已经使用“https://github.com/daprice/iOS-Tactile-Slider”
在 Swift 中创建了这个滑块
我试过以下方法,但这不是解决方案:
1. Slider(value: .constant(0.3)).accentColor(Color.white)
2. Slider(value: $age, in: 18...20, step: 1, minimumValueLabel: Text("18"), maximumValueLabel: Text("20")) { Text("") }
3. Slider(value: $age, in: 18...20, step: 1, minimumValueLabel: Image(systemName: "18.circle"), maximumValueLabel: Image(systemName: "20.circle")) { Text("") }
How can I create a slider with the style as shown in the image using SwiftUI only?
事实证明,强调色取决于上下文和框架,所以我们不需要处理它。
就控件而言,我做了一个非常简单的虚拟示例。请不要将此视为解决方案,而是一个开始。
struct CustomView: View {
@Binding var percentage: Float // or some value binded
var body: some View {
GeometryReader { geometry in
// TODO: - there might be a need for horizontal and vertical alignments
ZStack(alignment: .leading) {
Rectangle()
.foregroundColor(.gray)
Rectangle()
.foregroundColor(.accentColor)
.frame(width: geometry.size.width * CGFloat(self.percentage / 100))
}
.cornerRadius(12)
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ value in
// TODO: - maybe use other logic here
self.percentage = min(max(0, Float(value.location.x / geometry.size.width * 100)), 100)
}))
}
}
}
你可以像
一样使用它
@State var percentage: Float = 50
...
var body: some View {
...
CustomView(percentage: $percentage)
.accentColor(.red)
.frame(width: 200, height: 44)
...
就我而言,我必须自定义缩略图。 (例如屏幕储物柜)
我留下一个类似问题的答案。
LockerSlider.swift
import SwiftUI
struct LockerSlider<V>: View where V : BinaryFloatingPoint, V.Stride : BinaryFloatingPoint {
// MARK: - Value
// MARK: Private
@Binding private var value: V
private let bounds: ClosedRange<V>
private let step: V.Stride
private let length: CGFloat = 50
private let lineWidth: CGFloat = 2
@State private var ratio: CGFloat = 0
@State private var startX: CGFloat? = nil
// MARK: - Initializer
init(value: Binding<V>, in bounds: ClosedRange<V>, step: V.Stride = 1) {
_value = value
self.bounds = bounds
self.step = step
}
// MARK: - View
// MARK: Public
var body: some View {
GeometryReader { proxy in
ZStack(alignment: .leading) {
// Track
RoundedRectangle(cornerRadius: length / 2)
.foregroundColor(Color(#colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)))
// Thumb
Circle()
.foregroundColor(.white)
.frame(width: length, height: length)
.offset(x: (proxy.size.width - length) * ratio)
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ updateStatus(value: [=10=], proxy: proxy) })
.onEnded { _ in startX = nil })
}
.frame(height: length)
.overlay(overlay)
.simultaneousGesture(DragGesture(minimumDistance: 0)
.onChanged({ update(value: [=10=], proxy: proxy) }))
.onAppear {
ratio = min(1, max(0,CGFloat(value / bounds.upperBound)))
}
}
}
// MARK: Private
private var overlay: some View {
RoundedRectangle(cornerRadius: (length + lineWidth) / 2)
.stroke(Color.gray, lineWidth: lineWidth)
.frame(height: length + lineWidth)
}
// MARK: - Function
// MARK: Private
private func updateStatus(value: DragGesture.Value, proxy: GeometryProxy) {
guard startX == nil else { return }
let delta = value.startLocation.x - (proxy.size.width - length) * ratio
startX = (length < value.startLocation.x && 0 < delta) ? delta : value.startLocation.x
}
private func update(value: DragGesture.Value, proxy: GeometryProxy) {
guard let x = startX else { return }
startX = min(length, max(0, x))
var point = value.location.x - x
let delta = proxy.size.width - length
// Check the boundary
if point < 0 {
startX = value.location.x
point = 0
} else if delta < point {
startX = value.location.x - delta
point = delta
}
// Ratio
var ratio = point / delta
// Step
if step != 1 {
let unit = CGFloat(step) / CGFloat(bounds.upperBound)
let remainder = ratio.remainder(dividingBy: unit)
if remainder != 0 {
ratio = ratio - CGFloat(remainder)
}
}
self.ratio = ratio
self.value = V(bounds.upperBound) * V(ratio)
}
}
Demo.swift
import SwiftUI
struct Demo: View {
// MARK: - Value
// MARK: Private
@State private var number = 150000.0
// MARK - View
// MARK: Public
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Number\n\(number)")
.bold()
.padding(.bottom, 20)
Text("OS Slider")
Slider(value: $number, in: 0...1050000, step: 0.02)
.padding(.bottom, 20)
Text("Custom Slider")
LockerSlider(value: $number, in: 0...1050000, step: 0.02)
.padding(.bottom, 20)
}
.padding(20)
}
}
这是自定义滑块的一个很好的解决方案。
https://swiftuirecipes.com/blog/custom-slider-in-swiftui
我可以确认这有效。
另外,如果你想让它活泼
你应该添加 onEnded 闭包如下
.onEnded({ _ in
// once the gesture ends, trigger `onEditingChanged` again
xOffset = (trackSize.width - thumbSize.width) * CGFloat(percentage)
lastOffset = xOffset
onEditingChanged?(false)
})
我可以使用 SwiftUI 创建一个滑块,但我无法更改滑块的样式,如下图所示。
问题:我无法在 SwiftUI 中找到任何选项来更改滑块样式。
注意:我只想使用 SwiftUI 创建它。我已经使用“https://github.com/daprice/iOS-Tactile-Slider”
在 Swift 中创建了这个滑块我试过以下方法,但这不是解决方案:
1. Slider(value: .constant(0.3)).accentColor(Color.white)
2. Slider(value: $age, in: 18...20, step: 1, minimumValueLabel: Text("18"), maximumValueLabel: Text("20")) { Text("") }
3. Slider(value: $age, in: 18...20, step: 1, minimumValueLabel: Image(systemName: "18.circle"), maximumValueLabel: Image(systemName: "20.circle")) { Text("") }
How can I create a slider with the style as shown in the image using SwiftUI only?
事实证明,强调色取决于上下文和框架,所以我们不需要处理它。
就控件而言,我做了一个非常简单的虚拟示例。请不要将此视为解决方案,而是一个开始。
struct CustomView: View {
@Binding var percentage: Float // or some value binded
var body: some View {
GeometryReader { geometry in
// TODO: - there might be a need for horizontal and vertical alignments
ZStack(alignment: .leading) {
Rectangle()
.foregroundColor(.gray)
Rectangle()
.foregroundColor(.accentColor)
.frame(width: geometry.size.width * CGFloat(self.percentage / 100))
}
.cornerRadius(12)
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ value in
// TODO: - maybe use other logic here
self.percentage = min(max(0, Float(value.location.x / geometry.size.width * 100)), 100)
}))
}
}
}
你可以像
一样使用它 @State var percentage: Float = 50
...
var body: some View {
...
CustomView(percentage: $percentage)
.accentColor(.red)
.frame(width: 200, height: 44)
...
就我而言,我必须自定义缩略图。 (例如屏幕储物柜)
我留下一个类似问题的答案。
LockerSlider.swift
import SwiftUI
struct LockerSlider<V>: View where V : BinaryFloatingPoint, V.Stride : BinaryFloatingPoint {
// MARK: - Value
// MARK: Private
@Binding private var value: V
private let bounds: ClosedRange<V>
private let step: V.Stride
private let length: CGFloat = 50
private let lineWidth: CGFloat = 2
@State private var ratio: CGFloat = 0
@State private var startX: CGFloat? = nil
// MARK: - Initializer
init(value: Binding<V>, in bounds: ClosedRange<V>, step: V.Stride = 1) {
_value = value
self.bounds = bounds
self.step = step
}
// MARK: - View
// MARK: Public
var body: some View {
GeometryReader { proxy in
ZStack(alignment: .leading) {
// Track
RoundedRectangle(cornerRadius: length / 2)
.foregroundColor(Color(#colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)))
// Thumb
Circle()
.foregroundColor(.white)
.frame(width: length, height: length)
.offset(x: (proxy.size.width - length) * ratio)
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ updateStatus(value: [=10=], proxy: proxy) })
.onEnded { _ in startX = nil })
}
.frame(height: length)
.overlay(overlay)
.simultaneousGesture(DragGesture(minimumDistance: 0)
.onChanged({ update(value: [=10=], proxy: proxy) }))
.onAppear {
ratio = min(1, max(0,CGFloat(value / bounds.upperBound)))
}
}
}
// MARK: Private
private var overlay: some View {
RoundedRectangle(cornerRadius: (length + lineWidth) / 2)
.stroke(Color.gray, lineWidth: lineWidth)
.frame(height: length + lineWidth)
}
// MARK: - Function
// MARK: Private
private func updateStatus(value: DragGesture.Value, proxy: GeometryProxy) {
guard startX == nil else { return }
let delta = value.startLocation.x - (proxy.size.width - length) * ratio
startX = (length < value.startLocation.x && 0 < delta) ? delta : value.startLocation.x
}
private func update(value: DragGesture.Value, proxy: GeometryProxy) {
guard let x = startX else { return }
startX = min(length, max(0, x))
var point = value.location.x - x
let delta = proxy.size.width - length
// Check the boundary
if point < 0 {
startX = value.location.x
point = 0
} else if delta < point {
startX = value.location.x - delta
point = delta
}
// Ratio
var ratio = point / delta
// Step
if step != 1 {
let unit = CGFloat(step) / CGFloat(bounds.upperBound)
let remainder = ratio.remainder(dividingBy: unit)
if remainder != 0 {
ratio = ratio - CGFloat(remainder)
}
}
self.ratio = ratio
self.value = V(bounds.upperBound) * V(ratio)
}
}
Demo.swift
import SwiftUI
struct Demo: View {
// MARK: - Value
// MARK: Private
@State private var number = 150000.0
// MARK - View
// MARK: Public
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Number\n\(number)")
.bold()
.padding(.bottom, 20)
Text("OS Slider")
Slider(value: $number, in: 0...1050000, step: 0.02)
.padding(.bottom, 20)
Text("Custom Slider")
LockerSlider(value: $number, in: 0...1050000, step: 0.02)
.padding(.bottom, 20)
}
.padding(20)
}
}
这是自定义滑块的一个很好的解决方案。 https://swiftuirecipes.com/blog/custom-slider-in-swiftui
我可以确认这有效。 另外,如果你想让它活泼 你应该添加 onEnded 闭包如下
.onEnded({ _ in
// once the gesture ends, trigger `onEditingChanged` again
xOffset = (trackSize.width - thumbSize.width) * CGFloat(percentage)
lastOffset = xOffset
onEditingChanged?(false)
})