ALL BUSINESS
COMIDA
DIRECTORIES
EDUCATIONAL
ENTERTAINMENT
FASHION TIPS
FINER THINGS
FREE CREATOR TOOLS
HEALTH
MARKETPLACE
MEMBER's ONLY
MONEY MATTER$
MOTIVATIONAL
NEWS & WEATHER
TECHNOLOGIA
TELEVISION NETWORKS
USA VOTES 2024
VIDEOS
INVESTOR RELATIONS
IN DEVELOPMENT
Posted by - Latinos MediaSyndication -
on - Feb 17 -
Filed in - Technology -
-
284 Views - 0 Comments - 0 Likes - 0 Reviews
I am trying to recreate Discrete scrubber in SwiftUI with haptic feedback and snap to nearest integer step. I use ScrollView and LazyHStack as follows:
struct DiscreteScrubber: View { @State var numLines:Int = 100 var body: some View { ScrollView(.horizontal, showsIndicators: false) { LazyHStack { ForEach(0..<numLines, id: \.self) { _ in Rectangle().frame(width: 2, height: 10, alignment: .center) .foregroundStyle(Color.red) Spacer().frame(width: 10) } } } } }
Problem: I need to add content inset of half the frame width of ScrollView so that the first line in the scrubber starts at the center of the view and so does the last line, and also generate haptic feedback as it scrolls. This was easy in UIKit but not obvious in SwiftUI.
EDIT: Someone suggested to skip ScrollView and build it from scratch using Custom View. I have been able to make something, but it lacks scrolling behaviour of ScrollView including deceleration and bounce behaviour rather than abruptly stopping when user ends dragging (apart from using iOS 17+ API). I would still want to incorporate it in ScrollView or atleast mimic ScrollView behaviour. Here is the updated code for the same.
struct DiscreteScrubber: View { @State var numLines:Int = 100 @State var currentOffset:CGFloat = 0.0 @State var dragAmount:CGFloat = 0.0 @State var prevDragAmount:CGFloat? @State var currentStep:Int = 0 var body: some View { ZStack { Color.blue LinesPath(numLines: 100, offset: currentOffset) .stroke(Color.white, lineWidth: 1) } .frame(height: 50) .gesture(DragGesture() .onChanged({ if prevDragAmount == nil { prevDragAmount = $0.translation.width } dragAmount = $0.translation.width currentOffset = currentOffset + dragAmount - prevDragAmount! prevDragAmount = dragAmount // print("CurrentOffset \(currentOffset)") currentStep = Int(currentOffset / 11.0) }) .onEnded({ value in dragAmount = 0 prevDragAmount = nil }) ) .sensoryFeedback(trigger: currentStep, { oldValue, newValue in NSLog("Feedback trigger \(abs(oldValue - newValue))") let stepDiff = abs(oldValue - newValue) return .impact(flexibility: .soft, intensity: Double(stepDiff)) }) }
}
struct LinesPath:Shape { var numLines:Int = 100 var offset:CGFloat = 0.0 func path(in rect: CGRect) -> Path { let width = rect.width let height = rect.height let minX = rect.minX let minY = rect.minY let midY = rect.midY let lineHeight = height * 0.75 let lineSpace = CGFloat(10.0) let lineWidth = CGFloat(1.0) return Path { path in for i in 0..<numLines { let x = minX + offset + Double(i) * (lineSpace + lineWidth) if !(x < rect.minX || x > rect.maxX) { path.move(to: CGPoint(x: x , y: midY - lineHeight/2)) path.addLine(to: CGPoint(x: x, y: midY + lineHeight/2)) } } path.closeSubpath() } }