Reputation: 437
I am pulling my hair out on this and I did not find an answer that seems to fit.
I have got a View (see below) - and I need to support 2 gestures (Drag and Magnification) somehow on this control. The View is a Knob and drag modifies the value, Magnification is supposed to modify the precision of the knob.
I have tried the following:
drag.simultanously(with:magnification)
, which kinda works, but the problem seems to be that the MagnificationGesture does not end when one finger is lifted and therefore the drag does not continue - it also never gets a .onEnded
call. (I don't know why - I would think this is a bug?) The effect is a rather strange experience where the knob still magnified while a user would expect to change values..gesture(drag).gesture(magnification)
which seems to do the samemagnification.exclusively(before:drag)
never calls the drag.onChange
block, but only the .onEnded
for some reason. Effectively the drag does not work...drag.exclusively(before:magnification)
also combines both with the magnification never ending and passing back to drag.magnification
gesture on the surrounding VStack
and keep the drag gesture on the inner Path
view, but somehow this also seems to result in the same result as drag.simultanously(with:magnification)
on the inner area. I have not figured out how I can prevent the drag gesture from propagating through and combining with the magnification
on the inner view.I would very much appreciate feedback, as I am out of ideas at least for the moment...
struct VirtualKnobView<Content:View>: View {
init(model:VirtualKnobModel, contentSize:CGSize, @ViewBuilder _ contentView:()-> Content ){
self.model = model
self.contentSize = contentSize
self.contentView = contentView()
}
init(contentSize:CGSize, @ViewBuilder _ contentView:()-> Content){
self.model = VirtualKnobModel(inner: 0.7, outer: 0.8, ext: 0.05, angle: 30.0)
self.contentSize = contentSize
self.contentView = contentView()
}
@ObservedObject var model:VirtualKnobModel
@State var lastMagnitude:CGFloat = 1.0
@State var isDragging:Bool = false
var contentSize:CGSize
var contentView:Content
var body: some View {
let size = model.calclulateSize(for: contentSize)
let drag = DragGesture(minimumDistance: 0)
.onChanged({ state in
print ("Drag Changed")
let point = state.location
let refPoint = CGPoint(x: (point.x - size/2)/size,
y: (point.y - size/2)/size)
model.setTouchPoint(point: refPoint)
})
.onEnded({ _ in
print ("Drag ended")
model.reset()
})
let magnification = MagnificationGesture()
.onChanged({ (magnitude:CGFloat) in
print ("Magnification changed")
let delta = magnitude / lastMagnitude
lastMagnitude = magnitude
let angle = model.clickAngle
print ("Magnitude: \(magnitude)")
let magnified = angle * delta
if magnified >= model.minClick && magnified <= model.maxClick {
model.clickAngle = magnified
}
})
.onEnded({ _ in
print("Magnification ended")
lastMagnitude = 1.0
model.reset()
})
let scaler = CGAffineTransform(scaleX: size, y: size)
let gesture = magnification.simultaneously(with: drag)
ZStack {
HStack {
Spacer()
VStack{
Spacer()
Path { path in
model.segmentList.forEach { segment in
let inner = segment.inner
let outer = segment.outer
let innerScaled = inner.applying(scaler)
let outerScaled = outer.applying(scaler)
path.move(to: innerScaled)
path.addLine(to: outerScaled)
}
}
.stroke(model.strokeColor, lineWidth: model.lineWidth)
.background(Color.black)
.frame(width: size, height: size)
Spacer()
}
Spacer()
}
.background(Color.black)
.gesture(gesture)
HStack {
Spacer()
VStack{
Spacer()
contentView
.frame(width: contentSize.width,
height: contentSize.height,
alignment: .center)
Spacer()
}
Spacer()
}
}
}
}
Upvotes: 3
Views: 1598
Reputation: 437
So here is the solution I ended up with for today:
I found no way to implement the behavior I had on UiKit where pinch and drag worked simultanously.
If you come a across a way - please let me know.
Interesting detail: I figured that gestures only work on pixels that are not transparent. So everything needs to have a background. No way to attach a gesture to a Color(.clear) or anything that would not show. That gave me some headache on the Path view, as it would only triggure the gesture where the Path actually painted something.
struct VirtualKnobView<Content:View>: View {
init(model:VirtualKnobModel, contentSize:CGSize, @ViewBuilder _ contentView:()-> Content ){
self.model = model
self.contentSize = contentSize
self.contentView = contentView()
}
init(contentSize:CGSize, @ViewBuilder _ contentView:()-> Content){
self.model = VirtualKnobModel(inner: 0.7, outer: 0.8, ext: 0.05, angle: 30.0)
self.contentSize = contentSize
self.contentView = contentView()
}
@ObservedObject var model:VirtualKnobModel
@State var lastMagnitude:CGFloat = 1.0
@State var isDragging:Bool = false
var contentSize:CGSize
var contentView:Content
// The bgcolor is needed for the views to receive gestures.
let bgColor = Color(UIColor.black.withAlphaComponent(0.001))
var body: some View {
let size = model.calclulateSize(for: contentSize)
let drag = DragGesture(minimumDistance: 0)
.onChanged({ state in
let point = state.location
let refPoint = CGPoint(x: (point.x - size/2)/size,
y: (point.y - size/2)/size)
model.setTouchPoint(point: refPoint)
})
.onEnded({ _ in
model.reset()
})
let magnification = MagnificationGesture()
.onChanged({ (magnitude:CGFloat) in
let delta = magnitude / lastMagnitude
lastMagnitude = magnitude
let angle = model.clickAngle
let magnified = angle * delta
if magnified >= model.minClick && magnified <= model.maxClick {
model.clickAngle = magnified
}
})
.onEnded({ _ in
lastMagnitude = 1.0
model.reset()
})
let scaler = CGAffineTransform(scaleX: size, y: size)
ZStack {
HStack(spacing:0) {
Rectangle()
.foregroundColor(bgColor)
.gesture(magnification)
VStack(spacing:0){
Rectangle()
.foregroundColor(bgColor)
Path { path in
model.segmentList.forEach { segment in
let inner = segment.inner
let outer = segment.outer
let innerScaled = inner.applying(scaler)
let outerScaled = outer.applying(scaler)
path.move(to: innerScaled)
path.addLine(to: outerScaled)
}
}
.stroke(model.strokeColor, lineWidth: model.lineWidth)
.foregroundColor(bgColor)
.gesture(drag)
.frame(width: size, height: size)
Rectangle()
.foregroundColor(bgColor)
}
Rectangle()
.foregroundColor(bgColor)
.gesture(magnification)
}
HStack {
Spacer()
VStack{
Spacer()
contentView
.frame(width: contentSize.width,
height: contentSize.height,
alignment: .center)
Spacer()
}
Spacer()
}
}
}
}
Upvotes: 1