Skip to content

Instantly share code, notes, and snippets.

@ihamadfuad
Created December 27, 2023 10:36
Show Gist options
  • Save ihamadfuad/d423497dfdeb8b1c716f0731f2c79a87 to your computer and use it in GitHub Desktop.
Save ihamadfuad/d423497dfdeb8b1c716f0731f2c79a87 to your computer and use it in GitHub Desktop.
struct DraggableScaleableRotatableModifier: ViewModifier {
@State private var showingAlert = false
@Binding var lastOffset: CGSize
@Binding var lastRotation: Angle
@Binding var offset: CGSize
@Binding var scale: CGFloat
@Binding var angle: Angle
@State private var isHighlighted = false
let onDuplicate: (() -> Void)?
let onDelete: (() -> Void)?
@GestureState private var twistAngle: Angle = .zero
func body(content: Content) -> some View {
let rotationGesture = RotationGesture(minimumAngleDelta: .degrees(10))
.updating($twistAngle, body: { (value, state, _) in
state = value
DispatchQueue.main.async {
isHighlighted = true
}
})
.onEnded { value in
DispatchQueue.main.async {
self.angle += value
isHighlighted = false
}
}
let dragGesture = DragGesture()
.onChanged({ (value) in
DispatchQueue.main.async {
self.offset = value.translation
isHighlighted = true
}
})
.onEnded({ (value) in
DispatchQueue.main.async {
self.lastOffset.width += value.translation.width
self.lastOffset.height += value.translation.height
self.offset = .zero
isHighlighted = false
}
})
let scaleGesture = MagnificationGesture()
.onChanged { value in
DispatchQueue.main.async {
withAnimation(.easeInOut(duration: 0.2)) {
self.scale = value.magnitude
isHighlighted = true
}
}
}
.onEnded({ value in
DispatchQueue.main.async {
isHighlighted = false
}
})
let gestures = rotationGesture
.simultaneously(with: dragGesture)
.simultaneously(with: scaleGesture)
return content
.rotationEffect(angle + twistAngle)
.offset(x: offset.width + lastOffset.width, y: offset.height + lastOffset.height)
.scaleEffect(scale)
.gesture(gestures, including: .gesture)
.background(
Rectangle()
.foregroundStyle(.clear)
.padding()
.padding()
.padding()
.background(Rectangle().foregroundStyle(Color.green.opacity(isHighlighted ? 0.10 : 0.0)))
.rotationEffect(angle + twistAngle)
.offset(x: offset.width + lastOffset.width, y: offset.height + lastOffset.height)
.scaleEffect(scale)
.gesture(gestures, including: .gesture)
)
.onTapGesture(count: 1, perform: {
if onDelete != nil {
showingAlert.toggle()
}
})
.alert(isPresented: $showingAlert) {
Alert(
title: Text("Delete"),
message: Text("Would you like to delete this?"),
primaryButton: .destructive(Text("Delete")) {
onDelete?()
},
secondaryButton: .cancel()
)
}
.confirmationDialog("", isPresented: $showingAlert) {
if onDuplicate != nil {
Button("Duplicate") { onDuplicate?() }
}
Button("Delete", role: .destructive) { onDelete?() }
Button("Cancel", role: .cancel) { }
} message: {
Text("Options")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment