如何在 SwiftUI 中停止快速移动视图的频闪

Posted

技术标签:

【中文标题】如何在 SwiftUI 中停止快速移动视图的频闪【英文标题】:How to stop strobing with fast moving view in SwiftUI 【发布时间】:2021-01-30 07:34:22 【问题描述】:

我正在尝试在 SwiftUI 中创建一个滑块(与原生 Slider 风格相同,但最终具有更多功能)。在下面的 gif 中,我的滑块位于顶部,SwiftUI 的原生滑块位于底部,两者的范围和值都相同。当我拖动原生滑块时,我的视图会更新,但是当我快速前后拖动时,圆圈会闪烁。请问有没有办法防止这种频闪? gif 不是很清楚,所以我提供了重现的代码。任何指向正确方向的指针表示赞赏!

这是我的滑块的代码:

struct BarSlider: View 

    @Binding var value: CGFloat
    var range: ClosedRange<CGFloat>
    
    var body: some View 

        GeometryReader  geo in
            ZStack 
                HStack(spacing: 0) 
                    Rectangle()
                        .fill(Color.red)
                        .frame(width: $value.wrappedValue.map(from: range, to: 0...geo.size.width))
                    Rectangle()
                        .fill(Color.green)
                
                HStack 
                    Circle()
                        .frame(width: 30, height: 30)
                        .offset(x: valueForKnob(geometry: geo))
                    Spacer()
                
            
            .background(Color.blue)
        
    

    private func valueForKnob(geometry: GeometryProxy) -> CGFloat 
        $value.wrappedValue.map(from: range, to: 0...geometry.size.width) - (30 * valueAsPercent())
    

    private func valueAsPercent() -> CGFloat 
        let percent = ((value - range.lowerBound) / (range.upperBound - range.lowerBound) * 100) / 100
        return percent.clamped(to: 0...1)
    

这是两个滑块:

struct ContentView: View 
    @State private var amount = CGFloat(100)

    var body: some View 
        VStack 
            BarSlider(value: $amount, range: 100...1000)
                .frame(height: 100)
            Slider(value: $amount, in: 100...1000)
        
    

还有这些扩展:

extension CGFloat 
    func map(from: ClosedRange<CGFloat>, to: ClosedRange<CGFloat>) -> CGFloat 

        let value = self.clamped(to: from)

        let fromRange = from.upperBound - from.lowerBound
        let toRange = to.upperBound - to.lowerBound
        let result = (((value - from.lowerBound) / fromRange) * toRange) + to.lowerBound
        return result
    


extension Comparable 
    func clamped(to limits: ClosedRange<Self>) -> Self 
        return min(max(self, limits.lowerBound), limits.upperBound)
    


【问题讨论】:

【参考方案1】:

我认为你的圈子不在中心

请测试一下:

struct BarSlider: View 

    @Binding var value: CGFloat
    var range: ClosedRange<CGFloat>
    
    private let circleSize:CGFloat = 20
    
    var body: some View 

        GeometryReader  geo in
            VStack 
                Text("\(value)")
                ZStack (alignment: Alignment(horizontal: .leading, vertical: .center))
                    HStack(spacing: 0) 
                        Rectangle()
                            .fill(Color.red)
                            .frame(width: $value.wrappedValue.map(from: range, to: 0...geo.size.width))
                            
                        Rectangle()
                            .fill(Color.green)
                    
                    
                    HStack 
                        Circle()
                            .frame(width: circleSize, height: circleSize)
                            .offset(x: valueForKnob(geometry: geo) - circleSize/2)
                       
                    
                
            
            
        
    

    private func valueForKnob(geometry: GeometryProxy) -> CGFloat 
        $value.wrappedValue.map(from: range, to: 0...geometry.size.width )
    

    private func valueAsPercent() -> CGFloat 
        let percent = ((value - range.lowerBound) / (range.upperBound - range.lowerBound) * 100) / 100
        return percent.clamped(to: 0...1)
    

extension CGFloat 
    func map(from: ClosedRange<CGFloat>, to: ClosedRange<CGFloat>) -> CGFloat 

        let value = self.clamped(to: from)

        let fromRange = from.upperBound - from.lowerBound
        let toRange = to.upperBound - to.lowerBound
        let result = (((value - from.lowerBound) / fromRange) * toRange) + to.lowerBound
        return result
    

extension Comparable 
    func clamped(to r: ClosedRange<Self>) -> Self 
        let min = r.lowerBound, max = r.upperBound
        return self < min ? min : (max < self ? max : self)
    



struct ContentView: View 
    @State private var amount = CGFloat(100)

    var body: some View 
        VStack 
            BarSlider(value: $amount, range: 100...1000)
                .frame(height: 70)
                .padding()
            Spacer()
                .frame(height: 70)
            Slider(value: $amount, in: 100...1000)
                .padding()
        
    


struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    

【讨论】:

感谢您查看,但这是故意的。如果你从头拖到尾,我希望圆圈始终完全在视图内。因此,当它处于 0% 时,我希望圆形前缘与视图前缘对齐,而当它处于 100% 时,我希望圆形后缘与视图后缘对齐。所以圆圈并不完全代表真实的位置,但它可以防止任何悬挂在视图之外。我希望这是有道理的。 好的,理解您的要求,但是如果您想要这种行为,您可以简单地使指针大于矩形。我认为原来的苹果滑块是一样的,指针比线大,你看不到这种行为

以上是关于如何在 SwiftUI 中停止快速移动视图的频闪的主要内容,如果未能解决你的问题,请参考以下文章

如何滚动滚动视图以使 TextField 在 SwiftUI 中移动到键盘上方?

js中hover事件时候的BUG以及解决方法

SwiftUI 如何快速识别视图(View)界面的刷新是由哪个状态的改变导致的?

SwiftUI 如何快速识别视图(View)界面的刷新是由哪个状态的改变导致的?

Xcode如何利用预览(Preview)让SwiftUI视图快速适配不同尺寸的设备

SwiftUI - 如何制作启动/停止计时器