Alternative Silver Solution - Long Press Gesture

Because I didn’t initially know about the “flexible space bar button item” as presented in another solution here (and didn’t like the idea of the trash can icon crowding the photo icon in the detail view), I decided to go with something a little different: a “long press” gesture to delete a photo (not terribly intuitive, but a fun exercise.)

This turned out to be an interesting exercise because I had to figure out how to manage conflicting gestures - you’ll recall in an earlier chapter where the tap gesture in the Detail View was used to end the editing mode for the text fields. Work must be done to distinguish one gesture from the other.

I started by dragging a “Long Press Gesture Recognizer” from the object library onto the UIImageView in the Detail View - this requires the long press to be on the image itself. Then in imagePickerController(_:didFinishPickingMediaWithInfo) I had to add “imageView.isUserInteractionEnabled = true”.

    ...
    imageView.image = image
    imageView.isUserInteractionEnabled = true
    dismiss(animated: true, completion: nil)
}

When there are two overlapping gesture recognizers, one (not both) must have a delegate set. This can be done by control-dragging from the tap gesture in the IB Document Outline to the Detail View Controller and selecting “delegate” as the outlet. Don’t forget to update your class with the UIGestureRecognizerDelegate protocol:

class DetailViewController: UIViewController, UITextFieldDelegate,
        UINavigationControllerDelegate, UIImagePickerControllerDelegate,
        UIGestureRecognizerDelegate {

The long press gesture must call an action on the Detail View to delete the photo. For this I referred to a previous chapter where we implemented the delete confirmation sheet on the Items View when deleting items from the list - the sheet gives a nice pop-over effect to confirm deleting the image. With the assistant editor open, control drag from the Long Press Gesture in the document outline to the Detail View Controller and create an action called “longPressOnImage”:

@IBAction func longPressOnImage(_ sender: UILongPressGestureRecognizer) {

    if sender.state == .ended {
        return
    }

    let title = "Delete Image?"
    let message = "Are you sure you want to delete this image?"
    let ac = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)

    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
    ac.addAction(cancelAction)

    let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: { (action) -> Void in
        self.imageView.image = nil
        self.imageView.isUserInteractionEnabled = false
        self.imageStore.deleteImage(forKey: self.item.itemKey)
    })
    ac.addAction(deleteAction)

    present(ac, animated: true, completion: nil)
}

The gesture actually fires twice, once when it begins (".began") and again when it ends (".ended"). We prevent the sheet from opening twice (it’ll just cause a warning in the console) by returning early from the “.ended” state.

Build and run and test it out - create an item, add a photo, edit a text field, tap to end the editing mode (closing the keyboard), then try a long press on the image. Nothing happens! This is where we have to address the gesture conflicts.

Let’s start by creating references (outlets) to our gesture recognizers in the Detail View Controller. Open the assistant editor and control drag from the gestures in the Document Outline to the top of the controller class:

@IBOutlet var tapGesture: UITapGestureRecognizer!
@IBOutlet var longPressGesture: UILongPressGestureRecognizer!

Then implement gestureRecognizer(_: shouldRequireFailureOf) - this requires the failure of the “tap gesture” in order for the “long press gesture” to succeed. It can be read as “gesture recognizer longPressGesture should require failure of tapGesture”:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    if gestureRecognizer == self.longPressGesture && otherGestureRecognizer == self.tapGesture {
        return true
    }

    return false
}

Build and run and test it again and you should get a pop-over sheet confirming if you want to delete the image! (Hopefully - I did this yesterday and wrote it up today, it should work if I’ve remembered all the details!)

Give it a shot and let me know what you think!

See here for more details: Coordinating Multiple Gesture Recognizers