Adding a menu overlay to a UICollectionView using LongPress gesture - ios

I've got a UICollectionView that typically contains 100-200 cells that scrolls both horizontally and vertically. I am trying to provide a simple UIView popup with 3 icons when the user performs a LongPress gesture to allow them to easily navigate to a couple specific cells within the UICollectionView.
I'd like the start of the LongPress to bring up the popup, then the user would drag their finger to one of the three icons and release to select that icon.
The problem I'm having is I can't figure out how to get either the view containing the 3 icons or the icons themselves to respond to the end of the LongPress gesture.
Here's a simplification of the UIViewController:
class MyViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var myCollectionView: UICollectionView!
#IBAction func longTouch(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
self.menu!.frame = CGRect(x: sender.location(in: self.view).x - 20,
y: sender.location(in: self.view).y - 75,
width: 100, height: 100)
self.menu!.isHidden = false
}
else if sender.state == .ended {
self.menu!.isHidden = true
}
}
// Popup for long touch
var menu: UIView?
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
self.initMenu()
}
// MARK:- UICollectionViewDelegate
...
// MARK:- UICollectionViewDataSource
...
// MARK:- UIScrollViewDelegate
...
private func initMenu() -> Void {
self.menu = UIView()
self.menu!.isUserInteractionEnabled = true
self.menu!.frame = CGRect(x: 0,
y: 0,
width: 100,
height: 50)
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(menuLongPressHandler))
self.menu!.addGestureRecognizer(longPress)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
self.menu!.addGestureRecognizer(tapGesture)
// There would be ImageView icons here as well, which I've left out for simplicity
self.menu!.isHidden = true
}
func menuLongPressHandler() {
print("Long Press")
}
func handleTap(sender: UITapGestureRecognizer) {
print("Tap")
}
}
Neither the "Tap" or "Long Press" are printed when the LongPress ends while over the View. Any suggestions on how to get the view to capture the end of that LongPress gesture?

Related

UIScrollView zoomScale not updating after initial update

I have a tap gesture that runs this code and it works once but then stops updating the zoomScale.
#objc func sampleTapGestureTapped(_ recognizer: UITapGestureRecognizer) {
print("tapped")
if self.scrollView_Image.zoomScale > self.scrollView_Image.minimumZoomScale {
scrollView_Image.setZoomScale(1, animated: false)
} else {
scrollView_Image.setZoomScale(3, animated: false)
}
}
The function runs and the tapped print is logged out but the zoomScale doesn't seem to change.
Perhaps the problem is your hard coded numbers. Here is how I do it:
if sv.zoomScale < sv.maximumZoomScale {
sv.setZoomScale(sv.maximumZoomScale, animated:anim)
}
else {
sv.setZoomScale(sv.minimumZoomScale, animated:anim)
}
Notice there are no hard coded numbers here. It works for any scroll view.
I've tried this in a small View Controller and it is working fine. It is possible that an action you are taking in the selector is stopping the gesture from working. You should probably post the selector function code as well, and anything relevant to setting up the Recognizer and the Image View
class ViewController: UIViewController {
var tappableImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
tappableImageView = UIImageView(frame: CGRect(x: 40, y: 40, width: 100, height: 100))
tappableImageView.backgroundColor = .red
view.addSubview(tappableImageView)
tappableImageView.isUserInteractionEnabled = true
let t = UITapGestureRecognizer(target: self, action: #selector(imageViewDoubleTapped(_:)))
t.numberOfTapsRequired = 2
tappableImageView.addGestureRecognizer(t)
}
#objc func imageViewDoubleTapped(_ recognizer: UITapGestureRecognizer) {
print("Double Tapped")
}
}

Swift - How to add tap gesture to array of UIViews?

Looking to add a tap gesture to an array of UIViews - without success. Tap seems not to be recognised at this stage.
In the code (extract) below:
Have a series of PlayingCardViews (each a UIView) showing on the main view.
Brought together as an array: cardView.
Need to be able to tap each PlayingCardView independently (and then to be able to identify which one was tapped).
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
for index in cardView.indices {
cardView[index].isUserInteractionEnabled = true
cardView[index].addGestureRecognizer(tap)
cardView[index].tag = index
}
}
#objc func tapCard (sender: UITapGestureRecognizer) {
if sender.state == .ended {
let cardNumber = sender.view.tag
print("View tapped !")
}
}
You need
#objc func tapCard (sender: UITapGestureRecognizer) {
let clickedView = cardView[sender.view!.tag]
print("View tapped !" , clickedView )
}
No need to check state here as the method with this gesture type is called only once , also every view should have a separate tap so create it inside the for - loop
for index in cardView.indices {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
I will not recommend the selected answer. Because creating an array of tapGesture doesn't make sense to me in the loop. Better to add gesture within PlaycardView.
Instead, such layout should be designed using UICollectionView. If in case you need to custom layout and you wanted to use scrollView or even UIView, then the better approach is to create single Gesture Recognizer and add to the superview.
Using tap gesture, you can get the location of tap and then you can get the selectedView using that location.
Please refer to below example:
import UIKit
class PlayCardView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = UIColor.red
}
}
class SingleTapGestureForMultiView: UIViewController {
var viewArray: [UIView]!
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView(frame: UIScreen.main.bounds)
view.addSubview(scrollView)
let tapGesture = UITapGestureRecognizer(target: self,
action: #selector(tapGetsure(_:)))
scrollView.addGestureRecognizer(tapGesture)
addSubviews()
}
func addSubviews() {
var subView: PlayCardView
let width = UIScreen.main.bounds.width;
let height = UIScreen.main.bounds.height;
let spacing: CGFloat = 8.0
let noOfViewsInARow = 3
let viewWidth = (width - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
let viewHeight = (height - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
var yCordinate = spacing
var xCordinate = spacing
for index in 0..<20 {
subView = PlayCardView(frame: CGRect(x: xCordinate, y: yCordinate, width: viewWidth, height: viewHeight))
subView.tag = index
xCordinate += viewWidth + spacing
if xCordinate > width {
xCordinate = spacing
yCordinate += viewHeight + spacing
}
scrollView.addSubview(subView)
}
scrollView.contentSize = CGSize(width: width, height: yCordinate)
}
#objc
func tapGetsure(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: scrollView)
print("location = \(location)")
var locationInView = CGPoint.zero
let subViews = scrollView.subviews
for subView in subViews {
//check if it subclass of PlayCardView
locationInView = subView.convert(location, from: scrollView)
if subView.isKind(of: PlayCardView.self) {
if subView.point(inside: locationInView, with: nil) {
// this view contains that point
print("Subview at \(subView.tag) tapped");
break;
}
}
}
}
}
You can try to pass the view controller as parameter to the views so they can call a function on parent view controller from the view. To reduce memory you can use protocols. e.x
protocol testViewControllerDelegate: class {
func viewTapped(view: UIView)
}
class testClass: testViewControllerDelegate {
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
for cardView in self.cardView {
cardView.fatherVC = self
}
}
func viewTapped(view: UIView) {
// the view that tapped is passed ass parameter
}
}
class PlayingCardView: UIView {
weak var fatherVC: testViewControllerDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let gr = UITapGestureRecognizer(target: self, action: #selector(self.viewDidTap))
self.addGestureRecognizer(gr)
}
#objc func viewDidTap() {
fatherVC?.viewTapped(view: self)
}
}

UIPanGestureRecognizer doesn't trigger action

I'm trying to create a subclass of UIView in order to let expand the view with a pan over it. It should work this way: if the user makes a pan toward the top the view's height decrease, instead if the pan is toward the bottom it should increase. In order to achieve that functionality, I'm trying to add a UIPanGestureRecognizer to the view but it doesn't seem to work. I've done it this way:
The first snippet is the uiView subclass declaration
class ExpandibleView: UIView {
//Here I create the reference to the recognizer
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
//And here I set its minimumNumberOfTouches and maximumNumberOfTouches properties and add it to the view
func initialize() {
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
self.addGestureRecognizer(panGestureRecognizer)
}
//here's the function that should handle the pan but who instead doesn't seem to been called at all
#objc func handlePan(_ sender:UIPanGestureRecognizer) {
//Here's I handle the Pan
}
}
The second one instead is the implementation inside the View Controller.
class MapViewController: UIViewController {
#IBOutlet weak var profileView: ExpandibleView!
//MARK: - ViewController Delegate Methods
override func viewDidLoad() {
super.viewDidLoad()
//Here I set the View
profileView.isUserInteractionEnabled = true
profileView.initialize()
profileView.minHeight = 100
profileView.maxHeight = 190
}
}
I set inside the storyboard the view's class as the subclass I created but the recognizer doesn't trigger at all the handler.
The problem you're experiencing is due to your definition of the panGestureRecognizer variable in the class definition here:
//Here I create the reference to the recognizer
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
You can initialize it this way, but it seems like self is not setup when this variable is created. So your action is never registered.
There are a couple ways you can fix this using your code, you can continue to initialize it as an instance variable, but you'll need to setup your target/action in your initialize() function
let panGestureRecognizer = UIPanGestureRecognizer()
func initialize() {
// add your target/action here
panGestureRecognizer.addTarget(self, action: #selector(handlePan(_:)))
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
addGestureRecognizer(panGestureRecognizer)
}
Or you can simply initialize your gesture recognizer in your initialize function and not use an instance variable
func initialize() {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
addGestureRecognizer(panGestureRecognizer)
}
My original answer
Here's a solution that works using constraints with a view defined in the storyboard or by manipulating the frame directly.
Example with Constraints
import UIKit
// Delegate protocol for managing constraint updates if needed
protocol MorphDelegate: class {
// tells the delegate to change its size constraints
func morph(x: CGFloat, y: CGFloat)
}
class ExpandableView: UIView {
var delegate: MorphDelegate?
init() {
// frame is set later if needed by creator when using this init method
super.init(frame: CGRect.zero)
configureGestureRecognizers()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureGestureRecognizers()
}
override init(frame: CGRect) {
super.init(frame: frame)
configureGestureRecognizers()
}
// setup UIPanGestureRecognizer
internal func configureGestureRecognizers() {
let panGR = UIPanGestureRecognizer.init(target: self, action: #selector(didPan(_:)))
addGestureRecognizer(panGR)
}
#objc func didPan(_ panGR: UIPanGestureRecognizer) {
// get the translation
let translation = panGR.translation(in: self).applying(transform)
if let delegate = delegate {
// tell delegate to change the constraints using this translation
delegate.morph(x: translation.x, y: translation.y)
} else {
// If you want the view to expand/contract in opposite direction
// of drag then swap the + and - in the 2 lines below
let newOriginY = frame.origin.y + translation.y
let newHeight = frame.size.height - translation.y
// expand self via frame manipulation
let newFrame = CGRect(x: frame.origin.x, y: newOriginY, width: frame.size.width, height: newHeight)
frame = newFrame
}
// reset translation
panGR.setTranslation(CGPoint.zero, in: self)
}
}
If you want to use this class by defining it in the storyboard and manipulating it's constraints you'd go about it like this.
First define your view in the storyboard and constrain it's width and height. For the demo I constrained it's x position to the view center and it's bottom to the SafeArea.bottom
Create an IBOutlet for the view, as well as it's height constraint to your ViewController file.
I set the background color to blue for this example.
In the view controller I defined a function to setup the view (currently only sets the delegate for constraint manipulation callbacks) and then defined an extension to handle delegate calls for updating constraints.
class ViewController: UIViewController {
#IBOutlet weak var expandingView: ExpandableView!
#IBOutlet weak var constraint_expViewHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// call to configure our expanding view
configureExpandingView()
}
// this sets the delegate for an expanding view defined in the storyboard
func configureExpandingView() {
expandingView.delegate = self
}
}
// setup the delegate callback to handle constraint manipulation
extension ViewController: MorphDelegate {
func morph(x: CGFloat, y: CGFloat) {
// this will update the view's height based on the amount
// you drag your finger in the view. You can '+=' below if
// you want to reverse the expanding behavior based on pan
// movements.
constraint_expViewHeight.constant -= y
}
}
Doing it this way via constraints gives me this when I run the project:
Example with frame manipulation
To use this and manipulate the height using just the frame property the view controller might implement the view creation something like this:
override func viewDidLoad() {
super.viewDidLoad()
setupExpandingView()
}
func setupExpandingView() {
let newView = ExpandableView()
newView.backgroundColor = .red
newView.frame = CGRect(x: 20, y: 200, width: 100, height: 100)
view.addSubview(newView)
}
Using just frame manipulation I get this:
Try change this
class ExpandibleView: UIView {
func initialize() {
}
//here's the function that should handle the pan but who instead doesn't seem to been called at all
func handlePan() {
//your function
}
}
class MapViewController: UIViewController {
#IBOutlet weak var profileView: ExpandibleView!
//MARK: - ViewController Delegate Methods
override func viewDidLoad() {
super.viewDidLoad()
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
profileView.addGestureRecognizer(panGestureRecognizer)
//Here I set the View
profileView.isUserInteractionEnabled = true
profileView.initialize()
profileView.minHeight = 100
profileView.maxHeight = 190
}
#objc func handlePan(_ sender:UIPanGestureRecognizer) {
profileView.handlePan()
}
}
Or you can create a delegate for call in other view.
good luck!

Interactive ViewController transition triggered by pinch and pan gesture recognisers simultaneously

I have two viewControllers:
ViewController1
A complex stack of sub viewcontrollers with somewhere in the middle an imageView
ViewController2
A scrollView with an imageView embedded in it
What I'm trying to achieve is a transition between the two viewControllers which gets triggered by pinching the imageView from viewController 1 causing it to zoom in and switch over to viewController 2. When the transition has ended, the imageView should be zoomed in as far as it's been zoomed during the pinch gesture triggered transition.
At the same time I want to support panning the image while performing the zoom transition so that just like with the zoom, the image in the end state will be transformed to the place it's been panned to.
So far I've tried the Hero transitions pod and a custom viewController transitions I wrote myself. The problem with the hero transitions is that the image doesn't properly get snapped to the end state in the second viewController. The problem I had with the custom viewController transition is that I couldn't get both zooming and panning to work at the same time.
Does anyone have an idea of how to implement this in Swift? Help is much appreciated.
The question can be divided in to two:
How to implement pinch zoom and dragging using pan gesture on an imageView
How to present a view controller with one of its subviews (imageView in vc2) positioned same as a subview (imageView in vc1) in the presenting view controller
Pinch gesture zoom: Pinch zooming is easier to implement using UIScrollView as it supports it out of the box with out a need to add the gesture recogniser. Create a scrollView and add the view you'd like to zoom with pinch as its subview (scrollView.addSubview(imageView)). Don't forget to add the scrollView itself as well (view.addSubview(scrollView)).
Configure the scrollView's min and max zoom scales: scrollView.minimumZoomScale, scrollView.maximumZoomScale. Set a delegate for scrollView.delegate and implement UIScrollViewDelegate:
func viewForZooming(in scrollView: UIScrollView) -> UIView?
Which should return your imageView in this case and,
Also conform to UIGestureRecognizerDelegate and implement:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
Which should return true. This is the key that allows us have pan gesture recogniser work with the internal pinch gesture recogniser.
Pan gesture dragging: Simply create a pan gesture recogniser with a target and add it to your scroll view scrollView.addGestureRecognizer(pan).
Handling gestures: Pinch zoom is working nicely by this stage except you'd like to present the second view controller when pinching ends. Implement one more UIScrollViewDelegate method to be notified when zooming ends:
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat)
And call your method that presents the detail view controller presentDetail(), we'll implement it in a bit.
Next step is to handle the pan gesture, I'll let the code explain itself:
// NOTE: Do NOT set from anywhere else than pan handler.
private var initialTouchPositionY: CGFloat = 0
private var initialTouchPositionX: CGFloat = 0
#objc func panned(_ pan: UIPanGestureRecognizer) {
let y = pan.location(in: scrollView).y
let x = pan.location(in: scrollView).x
switch pan.state {
case .began:
initialTouchPositionY = pan.location(in: imageView).y
initialTouchPositionX = pan.location(in: imageView).x
case .changed:
let offsetY = y - initialTouchPositionY
let offsetX = x - initialTouchPositionX
imageView.frame.origin = CGPoint(x: offsetX, y: offsetY)
case .ended:
presentDetail()
default: break
}
}
The implementation moves imageView around following the pan location and calls presentDetail() when gesture ends.
Before we implement presentDetail(), head to the detail view controller and add properties to hold imageViewFrame and the image itself. Now in vc1, we implement presentDetail() as such:
private func presentDetail() {
let frame = view.convert(imageView.frame, from: scrollView)
let detail = DetailViewController()
detail.imageViewFrame = frame
detail.image = imageView.image
// Note that we do not need the animation.
present(detail, animated: false, completion: nil)
}
In your DetailViewController, make sure to set the imageViewFrame and the image in e.g. viewDidLoad and you'll be set.
Complete working example:
class ViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
let imageView: UIImageView = UIImageView()
let scrollView: UIScrollView = UIScrollView()
lazy var pan: UIPanGestureRecognizer = {
return UIPanGestureRecognizer(target: self, action: #selector(panned(_:)))
}()
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = // set your image
scrollView.delegate = self
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0
scrollView.addSubview(imageView)
view.addSubview(scrollView)
scrollView.frame = view.frame
let w = view.bounds.width - 30 // padding of 15 on each side
imageView.frame = CGRect(x: 0, y: 0, width: w, height: w)
imageView.center = scrollView.center
scrollView.addGestureRecognizer(pan)
}
// NOTE: Do NOT set from anywhere else than pan handler.
private var initialTouchPositionY: CGFloat = 0
private var initialTouchPositionX: CGFloat = 0
#objc func panned(_ pan: UIPanGestureRecognizer) {
let y = pan.location(in: scrollView).y
let x = pan.location(in: scrollView).x
switch pan.state {
case .began:
initialTouchPositionY = pan.location(in: imageView).y
initialTouchPositionX = pan.location(in: imageView).x
case .changed:
let offsetY = y - initialTouchPositionY
let offsetX = x - initialTouchPositionX
imageView.frame.origin = CGPoint(x: offsetX, y: offsetY)
case .ended:
presentDetail()
default: break
}
}
// MARK: UIScrollViewDelegate
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
presentDetail()
}
// MARK: UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
// MARK: Private
private func presentDetail() {
let frame = view.convert(imageView.frame, from: scrollView)
let detail = DetailViewController()
detail.imageViewFrame = frame
detail.image = imageView.image
present(detail, animated: false, completion: nil)
}
}
class DetailViewController: UIViewController {
let imageView: UIImageView = UIImageView()
var imageViewFrame: CGRect!
var image: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
imageView.frame = imageViewFrame
imageView.image = image
view.addSubview(imageView)
view.addSubview(backButton)
}
lazy var backButton: UIButton = {
let button: UIButton = UIButton(frame: CGRect(x: 10, y: 30, width: 60, height: 30))
button.addTarget(self, action: #selector(back(_:)), for: .touchUpInside)
button.setTitle("back", for: .normal)
return button
}()
#objc func back(_ sender: UIButton) {
dismiss(animated: false, completion: nil)
}
}
seems like UIView.animate(withDuration: animations: completion:) should help you; for example, in animations block you can set new image frame, and in completion: - present second view controller (without animation);

Infinite Outlets

Hi i'm making an app where you can decorate a Christmas tree with baubles i have wrote code to be able to drag it around the view but i run out of baubles how do i make it so i can have lots of baubles to put on the christmas tree. I have tried duplicating one bauble lots of times but then added them all to the code and it doesn't work it drags them all at the same time. If you look in the picture it has the bauble in it but i want it to drag one but one to still be there to put on
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var red_bauble_1: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let gesture = UIPanGestureRecognizer(target: self, action: #selector(ViewController.wasDragged(_ :)))
red_bauble_1.addGestureRecognizer(gesture)
red_bauble_1.isUserInteractionEnabled = true
}
#objc func wasDragged (_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: self.view)
let red_bauble_1 = gesture.view
red_bauble_1?.center = CGPoint(x: red_bauble_1!.center.x + translation.x, y: (red_bauble_1?.center.y)! + translation.y)
gesture.setTranslation(CGPoint.zero, in: self.view)
}
}
If you are adding a variable number of ornaments as subviews and want to keep a reference to them, just have an array for them, e.g.
var ornaments = [UIView]() // or, if they're image views, `[UIImageView]()`
then you can add your ornaments to that array as you add them to your tree and you have an array to keep track of all of them.
For example:
class ViewController: UIViewController {
#IBOutlet weak var treeImageView: UIImageView!
#IBOutlet weak var ornamentToolImageView: UIImageView!
private var currentOrnament: UIView!
private var ornaments = [UIView]()
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(dragOrnamentFromToolbar(_:)))
ornamentToolImageView.addGestureRecognizer(pan)
}
/// Gesture for dragging ornament from toolbar on to the tree
///
/// This creates new ornament and adds it to view hierarchy as well as to our array of `ornaments`
#IBAction func dragOrnamentFromToolbar(_ gesture: UIPanGestureRecognizer) {
let location = gesture.location(in: treeImageView)
switch gesture.state {
case .began:
gesture.view?.isHidden = true // temporarily hide toolbar view so it feels like we're dragging it
let image = UIImage(named: "ornament")!
currentOrnament = UIImageView(image: image)
ornaments.append(currentOrnament)
treeImageView.addSubview(currentOrnament)
fallthrough
case .changed:
currentOrnament.center = location
case .ended, .cancelled:
// when done, add new pan gesture for dragging around new ornament
gesture.view?.isHidden = false // restore toolbar view
currentOrnament.isUserInteractionEnabled = true
let pan = UIPanGestureRecognizer(target: self, action: #selector(dragExistingOrnament(_:)))
currentOrnament.addGestureRecognizer(pan)
currentOrnament = nil
default:
break
}
}
/// Gesture for dragging existing ornament
///
/// This grabs existing and allows you to drag it around
#IBAction func dragExistingOrnament(_ gesture: UIPanGestureRecognizer) {
let location = gesture.location(in: treeImageView)
switch gesture.state {
case .began:
currentOrnament = gesture.view
fallthrough
case .changed:
currentOrnament.center = location
default:
break
}
}
}
That yields:

Resources