UIMenuController

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).

Figure 19.3  A UIMenuController

Two buttons: Copy and Delete are shown.

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset