Next you are going to make it so that when the user selects a line, a menu with the option to delete that line appears where the user tapped. There is a built-in class for providing this sort of menu called UIMenuController (Figure 19.3). A menu controller has a list of UIMenuItem objects and is presented in an existing view. Each item has a title (what shows up in the menu) and an action (the message it sends the first responder of the window).
There is only one UIMenuController per application. When you wish to present this instance, you fill it with menu items, give it a rectangle to present from, and set it to be visible.
Do this in DrawView.swift’s tap(_:) method if the user has tapped on a line. If the user tapped somewhere that is not near a line, the currently selected line will be deselected and the menu controller will hide.
func tap(_ gestureRecognizer: UIGestureRecognizer) { print("Recognized a tap") let point = gestureRecognizer.location(in: self) selectedLineIndex = indexOfLine(at: point) // Grab the menu controller let menu = UIMenuController.shared if selectedLineIndex != nil { // Make DrawView the target of menu item action messages becomeFirstResponder() // Create a new "Delete" UIMenuItem let deleteItem = UIMenuItem(title: "Delete", action: #selector(DrawView.deleteLine(_:))) menu.menuItems = [deleteItem] // Tell the menu where it should come from and show it let targetRect = CGRect(x: point.x, y: point.y, width: 2, height: 2) menu.setTargetRect(targetRect, in: self) menu.setMenuVisible(true, animated: true) } else { // Hide the menu if no line is selected menu.setMenuVisible(false, animated: true) } setNeedsDisplay() }
For a menu controller to appear, a view that responds to at least one action message in the UIMenuController’s menu items must be the first responder of the window – this is why you called the method becomeFirstResponder() on the DrawView before setting up the menu controller.
If you have a custom view class that needs to become the first responder, you must also override canBecomeFirstResponder. In DrawView.swift, override this property to return true.
override var canBecomeFirstResponder: Bool { return true }
Finally, implement deleteLine(_:) in DrawView.swift.
func deleteLine(_ sender: UIMenuController) { // Remove the selected line from the list of finishedLines if let index = selectedLineIndex { finishedLines.remove(at: index) selectedLineIndex = nil // Redraw everything setNeedsDisplay() } }
When being presented, the menu controller goes through each menu item and asks the first responder if it implements the action method for that item. If the first responder does not implement that method, then the menu controller will not show the associated menu item. If no menu items have their action methods implemented by the first responder, the menu is not shown at all.
Build and run the application. Draw a line, tap on it, and then select Delete from the menu item.
If you select a line and then double-tap to clear all lines, the menu controller will still be visible. If the selectedLineIndex ever becomes nil, the menu controller should not be visible.
Add a property observer to selectedLineIndex in DrawView.swift that sets the menu controller to be not visible if the index is set to nil.
var selectedLineIndex: Int? { didSet { if selectedLineIndex == nil { let menu = UIMenuController.shared menu.setMenuVisible(false, animated: true) } } }
Build and run the application. Draw a line, select it, and then double-tap the background. The line and the menu controller will no longer be visible.