I made an extension with a UIView that takes a SwiftUI view, gets its UIView, and then adds it as a subview. But now the subview isn't receiving any touches and idk how to fix that. I've already tried point(inside:with:)
but it doesn't seem to work. I've also tried forwarding the touches from touchesBegan, touchesMoved, etc., but that didn't work either.
Any help or advice would be greatly appreciated!
Extension with the UIView:
// Add view modifier to View
extension View {
func transformable() -> some View {
modifier(Transformable())
}
}
// View modifier
struct Transformable: ViewModifier {
func body(content: Content) -> some View {
TransformableUIView(content: content)
}
}
// Wrap UIView
struct TransformableUIView<V>: UIViewRepresentable where V: View {
private var content: V
init(content: V) {
self.content = content
}
func makeUIView(context: Context) -> TransformableView<V> {
TransformableView(content: content)
}
func updateUIView(_ uiView: TransformableView<V>, context: Context) {}
}
// View that handles zoom, pan, and rotate gestures
class TransformableView<V>: UIView, UIGestureRecognizerDelegate where V: View {
private var content: V
private var initialCenter: CGPoint = .zero
private var totalScale: CGFloat = 1.0
private var boundsDidSet = false
required init(content: V) {
self.content = content
super.init(frame: .zero)
self.addSubview(content.uiView)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panPiece(_:)))
panGesture.minimumNumberOfTouches = 2
panGesture.maximumNumberOfTouches = 2
panGesture.delegate = self
self.addGestureRecognizer(panGesture)
let scaleGesture = UIPinchGestureRecognizer(target: self, action: #selector(scalePiece(_:)))
scaleGesture.delegate = self
self.addGestureRecognizer(scaleGesture)
let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotatePiece(_:)))
rotateGesture.delegate = self
self.addGestureRecognizer(rotateGesture)
}
// Position content in center of view
override func layoutSubviews() {
super.layoutSubviews()
// Return if bounds are already set
if boundsDidSet {
return
}
guard let piece = self.subviews.first else { return }
piece.center = CGPoint(x: self.bounds.size.width / 2, y: self.bounds.size.height / 2)
boundsDidSet = true
}
// Function called when pan gesture is recognized
@objc private func panPiece(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let piece = gestureRecognizer.view?.subviews.first else { return }
// Get the changes in the X and Y directions relative to
// the superview's coordinate space.
let translation = gestureRecognizer.translation(in: piece.superview)
if gestureRecognizer.state == .began {
// Save the view's original position.
self.initialCenter = piece.center
}
// Update the position for the .began, .changed, and .ended states
if gestureRecognizer.state != .cancelled {
// Add the X and Y translation to the view's original position.
var newCenter = CGPoint(x: initialCenter.x + translation.x, y: initialCenter.y + translation.y)
// Prevent content from leaving view
newCenter.x = clamp(value: newCenter.x, min: 0, max: self.bounds.width)
newCenter.y = clamp(value: newCenter.y, min: 0, max: self.bounds.height)
piece.center = newCenter
}
else {
// On cancellation, return the piece to its original location.
piece.center = initialCenter
}
}
// Function called when scale gesture is recognized
@objc private func scalePiece(_ gestureRecognizer : UIPinchGestureRecognizer) {
guard let piece = gestureRecognizer.view?.subviews.first else { return }
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
// Set min/max zoom
let newScale = clamp(value: totalScale * gestureRecognizer.scale, min: 0.2, max: 20) / totalScale
piece.transform = (piece.transform.scaledBy(x: newScale, y: newScale))
gestureRecognizer.scale = 1.0
totalScale *= newScale
}
}
// Function called when rotate gesture is recognized
@objc private func rotatePiece(_ gestureRecognizer : UIRotationGestureRecognizer) {
guard let piece = gestureRecognizer.view?.subviews.first else { return }
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
piece.transform = piece.transform.rotated(by: gestureRecognizer.rotation)
gestureRecognizer.rotation = 0
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func clamp(value: CGFloat, min: CGFloat, max: CGFloat) -> CGFloat {
if value < min {
return min
} else if value > max {
return max
} else {
return value
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Get UIView from SwiftUI View:
// Get UIView from SwiftUI View
extension View {
var uiView: UIView {
UIHostingController(rootView: self).view
}
}
ContentView:
struct ContentView: View {
var body: some View {
CanvasView()
.frame(width: 880, height: 608)
.transformable()
.background(Color.blue)
}
}