I would like to reposition sub layers by clicking and dragging. I am having difficulties and have put together a simple demo to illustrate the problems. See the code below and/or this runnable project, which includes a playground, on GitHub.
The demo's UI (see image below) consists of three elements: a main view (black), a subview (red), and a subview sublayer (gray). Both of the views have a tap gesture recognizer which reports to the console. Additionally, the subview has a pan gesture recognizer that allows the subview and/or its sublayer to be repositioned, as well as reporting to the console.
First Problem - The sublayer lags behind the gesture.
Second Problem - I cannot grab the gray sublayer close to its edge.
If I tap the gray sublayer close to its edge then the console message correctly reports that the gray sublayer was tapped. However, if I try to grab the same spot and move it then red subview moves instead.
Thank you for taking the time to consider this. Any advise will be appreciated.
import UIKit
public class ViewController: UIViewController {
override public func loadView() {
let mainView = UIView(frame: CGRect.zero)
mainView.backgroundColor = .black
mainView.layer.name = "Main View's Layer"
mainView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))))
self.view = mainView
}
override public func viewDidLayoutSubviews() {
let subview = UIView(frame: view.bounds.insetBy(dx: 50.0, dy: 100.0))
subview.backgroundColor = .red
subview.layer.name = "Sub View's Layer"
subview.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))))
subview.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:))))
let sublayer = CALayer()
sublayer.bounds = subview.layer.bounds.insetBy(dx: 50, dy: 100)
sublayer.name = "Sub View's Sublayer"
sublayer.position = CGPoint(x: subview.layer.bounds.midX, y: subview.layer.bounds.midY)
sublayer.backgroundColor = UIColor.gray.cgColor
subview.layer.addSublayer(sublayer)
view.addSubview(subview)
}
// **********************************************************************************************************************
private struct TouchedLayer : CustomStringConvertible {
var layer: CALayer
let startingPosition: CGPoint
init(recognizer: UIGestureRecognizer) {
let view = recognizer.view!
let touchLocation = recognizer.location(in: view)
let hitTestLocation = view.layer.superlayer!.convert(touchLocation, from: view.layer)
layer = view.layer.hitTest(hitTestLocation)!
startingPosition = layer.position
}
mutating func updateLayerPosition(translation: CGPoint) {
layer.position = CGPoint(x: startingPosition.x + translation.x, y: startingPosition.y + translation.y)
}
var description: String {
return "\(layer.name ?? "<Unknown>"): position = \(layer.position)"
}
}
@objc func tapGestureRecognized(_ sender: UITapGestureRecognizer) {
guard sender.state == .ended else { return }
let tappedLayer = TouchedLayer(recognizer: sender)
print("\nTapped", tappedLayer)
}
private var panningLayer: TouchedLayer!
@objc func panGestureRecognized(_ sender: UIPanGestureRecognizer) {
switch sender.state {
case .began:
panningLayer = TouchedLayer(recognizer: sender)
print("\nPanning Begun - \(panningLayer!)")
case .changed:
panningLayer.updateLayerPosition(translation: sender.translation(in: sender.view))
//print("\tPanning - \(panningLayer!)")
default:
print("Panning Ended - \(panningLayer!)")
}
}
}
I would like to reposition sub layers by clicking and dragging
I would suggest that you cease wanting to do that. A layer has no touchability, whereas a view does; indeed, it has been wisely said that a view is a layer plus touchability. If you would use subviews instead of sublayers, it would become possible to touch the subview directly, have a gesture recognizer on it, tap and pan in the normal way, and so on. You are just making life unnecessarily difficult on yourself, with (as far as I know) no perceptible gain, by using layers instead of views.