如何在SwiftUI中创建自定义滑块视图?

3
我想要创建一个自定义滑块,带有渐变背景、代表值的数字和在线上显示每个步骤位置的白色点。我想要的效果如下图所示:

Slider Image

我看了几个其他的教程,但是我所做的都没有达到我想要的结果,要么元素不对齐,要么我的代码不如默认滑块好用。
另一个棘手的问题是,我还想像默认滑块一样具有捕捉位置的功能,这样用户就不能选择6.5522这样的值,只能选择6或7。
以下是我目前的代码:
import SwiftUI

struct CustomSlider: View {
let textColor: Color
let thumbColor: Color
let height: CGFloat
let cornerRadius: CGFloat

@State var lastOffset: CGFloat = 0

@Binding var value: CGFloat

var range: ClosedRange<CGFloat>

var leadingOffset: CGFloat = 5
var trailingOffset: CGFloat = 5



var body: some View {
    GeometryReader { geo in
        VStack {
            ZStack {
                RoundedRectangle(cornerRadius: cornerRadius).fill(LinearGradient(gradient: Gradient(colors: [.paleRed, .mango, .neonYellow, .midGreen]), startPoint: .leading, endPoint: .trailing))
                    .frame(height: height)
                HStack {
                    ForEach(1..<11) { index in
                        Circle()
                            .foregroundColor(.white)
                            .frame(width: height, height: height)
                        if index < 10 {
                            Spacer()
                        }
                    }
                }
                HStack {
                    Circle()
                        .frame(width: height * 2, height: height * 2)
                        .foregroundColor(thumbColor)
                        .offset(x: CGFloat(self.$value.wrappedValue.map(from: self.range, to: 6...(geo.size.width - 6 - 22))))
                        .gesture(
                            DragGesture(minimumDistance: 0)
                                .onChanged { value in

                                    if abs(value.translation.width) < 0.1 {
                                        self.lastOffset = self.$value.wrappedValue.map(from: self.range, to: self.leadingOffset...(geo.size.width - (height * 2) - self.trailingOffset))
                                    }

                                    let sliderPos = max(0 + self.leadingOffset, min(self.lastOffset + value.translation.width, geo.size.width - (height * 2) - self.trailingOffset))
                                    let sliderVal = sliderPos.map(from: self.leadingOffset...(geo.size.width - (height * 2) - self.trailingOffset), to: self.range)

                                    self.value = sliderVal
                                }
                      )
                    Spacer()
                }
            }
            HStack {
                ForEach(1..<11) { index in
                    Text("\(index)")
                        .foregroundColor(.secondaryButtonLightGrey)
                        .font(.custom("Rubik-Medium", size: 16))
                    if index < 10 {
                        Spacer()
                    }
                }
            }
        }


    }
}
}

在视图代码中的实现方式:
CustomSlider(textColor: .textColor, thumbColor: .thumbColor, height: 10, cornerRadius: 10, value: $value, range: 0...10)

如果这些问题太简单了,我向您道歉,因为我对SwiftUI还很陌生,只使用了一个月。

感谢您提供任何建议。

2个回答

2
希望这可能有所帮助:

struct CustomSliderView: View {
@State var slider: Float = 0
@State var dragGestureTranslation: CGFloat = 0
@State var lastDragValue: CGFloat = 0

// Slider Draggable Control Settings
var sliderWidth: CGFloat = 30
var sliderPadding: CGFloat = 0 // This adds padding to each side

// Stepped Increment
@State var steppedSlider: Bool = false
@State var step: Int = 8
@State var stepInterval: CGFloat = 0

func interval(width: CGFloat, increment: Int) -> CGFloat {
    print("Screen Width: \(width)")
    let result = width * CGFloat(increment) / CGFloat(step)
    return result
}

func roundToFactor(value: CGFloat, factor: CGFloat) -> CGFloat {
    return factor * round(value / factor)
}

var body: some View {
    VStack {
        GeometryReader { geometry in
            // Slider Bar
            ZStack (alignment: .leading) {
                Rectangle()
                    .foregroundColor(.blue)
            } // End of Slider Bar
            .frame(width: geometry.size.width, height: 3)
            .padding(.vertical, 15) // Padding to centre the bar
            
            // Slider Stacker
            ZStack (alignment: .top) {
                if steppedSlider {
                    // Step - Minused the SliderWidth so that it is evenly spaced
                    ForEach(0...step, id: \.self) { number in
                        Rectangle()
                            .frame(width: 2, height: 10)
                            .offset(x: interval(width: (geometry.size.width - sliderWidth), increment: number), y: 46)
                        
                        Text("\(number)")
                            .fontWeight(.bold)
                            .offset(x: interval(width: (geometry.size.width - sliderWidth), increment: number), y: 64)
                            
                    }
                }
                
                // Draggable Slider
                ZStack {
                    Circle()
                        .frame(width: sliderWidth, height: 30)
                }
                .offset(x: CGFloat(slider))
                .padding(.horizontal, sliderPadding)
            } // End of Slider ZStack
            .gesture(DragGesture(minimumDistance: 0)
                        .onChanged({ dragValue in
                            let translation = dragValue.translation
                            
                            dragGestureTranslation = CGFloat(translation.width) + lastDragValue
                            
                            // Set the start marker of the slider
                            dragGestureTranslation = dragGestureTranslation >= 0 ? dragGestureTranslation : 0
                            
                            // Set the end marker of the slider
                            dragGestureTranslation = dragGestureTranslation > (geometry.size.width - sliderWidth - sliderPadding * 2) ? (geometry.size.width - sliderWidth - sliderPadding * 2) :  dragGestureTranslation
                            
                            // Set the slider value (Stepped)
                            if steppedSlider {
                                // Getting the stepper interval (where to place the marks)
                                stepInterval = roundToFactor(value: dragGestureTranslation, factor: (geometry.size.width - sliderWidth - (sliderPadding * 2)) / CGFloat(step))
                                
                                // Get the increments for the stepepdInterval
                                self.slider = min(max(0, Float(stepInterval)), Float(stepInterval))
                            } else {
                                // Set the slider value (Fluid)
                                self.slider = min(max(0, Float(dragGestureTranslation)), Float(dragGestureTranslation))
                            }

                        })
                        .onEnded({ dragValue in
                            // Set the start marker of the slider
                            dragGestureTranslation = dragGestureTranslation >= 0 ? dragGestureTranslation : 0
                            
                            // Set the end marker of the slider
                            dragGestureTranslation = dragGestureTranslation > (geometry.size.width - sliderWidth - sliderPadding * 2) ? (geometry.size.width - sliderWidth - sliderPadding * 2) : dragGestureTranslation
                            
                            // Storing last drag value
                            lastDragValue = dragGestureTranslation
                        })
            ) // End of DragGesture
        } // End of GeometryReader
        Text("Slider: \(slider)")
    } // End of VStack
    .padding()
}

}


0

我可以回答你问题的第二部分。 要获取整数值,你只需要将值进行强制类型转换。 例如:

self.value = Int(sliderVal)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接