iOS開發之UIMenuController使用示例詳解

簡介

UIMenuController 是一個菜單編輯界面,在很多地方都能用到,通常用於剪切、復制、粘貼、選擇、全選和刪除命令等,也可以自定義想要的操作,它長這樣:

接口介紹

open class UIMenuController : NSObject {
    open class var shared: UIMenuController { get }
    open var isMenuVisible: Bool // default is NO
    @available(iOS, introduced: 3.0, deprecated: 13.0, message: "Use showMenuFromView:rect: or hideMenuFromView: instead.")
    open func setMenuVisible(_ menuVisible: Bool, animated: Bool)
    @available(iOS, introduced: 3.0, deprecated: 13.0, message: "Use showMenuFromView:rect: instead.")
    open func setTargetRect(_ targetRect: CGRect, in targetView: UIView)
    @available(iOS 13.0, *)
    open func showMenu(from targetView: UIView, rect targetRect: CGRect)
    @available(iOS 13.0, *)
    open func hideMenu(from targetView: UIView)
    @available(iOS 13.0, *)
    open func hideMenu()
    @available(iOS 3.2, *)
    open var arrowDirection: UIMenuController.ArrowDirection // default is UIMenuControllerArrowDefault
    @available(iOS 3.2, *)
    open var menuItems: [UIMenuItem]? // default is nil. these are in addition to the standard items
    open func update()
    open var menuFrame: CGRect { get }
}
open class UIMenuItem : NSObject {
    public init(title: String, action: Selector)
    open var title: String
    open var action: Selector
}

從接口中可以看出 UIMenuController 應該使用它的單例對象,具體應該怎麼使用它呢?我們先來看一下 API 文檔對 UIMenuController 的說明:

The singleton UIMenuController instance is referred to as the editing menu. When you make this menu visible, UIMenuController positions it relative to a target rectangle on the screen; this rectangle usually defines a selection. The menu appears above the target rectangle or, if there is not enough space for it, below it. The menu’s pointer is placed at the center of the top or bottom of the target rectangle, as appropriate. Be sure to set the tracking rectangle before you make the menu visible. You are also responsible for detecting, tracking, and displaying selections.

The UIResponderStandardEditActions informal protocol declares methods that are invoked when the user taps a menu command. The canPerformAction(_:withSender:) method of UIResponder is also related to the editing menu. A responder implements this method to enable and disable commands of the editing menu just before the menu is displayed. You can force this updating of menu commands’ enabled state by calling the update() method.

You can also provide your own menu items via the menuItems property. When you modify the menu items, you can use the update() method to force the menu to update its display.

翻譯如下:

UIMenuController 單例稱為編輯菜單。當你使這個菜單可見時,UIMenuController 將它相對於屏幕上的目標矩形定位;這個矩形通常定義一個選擇。菜單顯示在目標矩形上方,如果沒有足夠的空間,則顯示在其下方。菜單指針放置在目標矩形頂部或底部的中心,視情況而定。確保在使菜單可見之前設置跟蹤矩形。您還負責檢測、跟蹤和顯示選擇。

UIResponderStandardEditActions 協議聲明瞭在用戶點擊菜單命令時調用的方法。 UIResponder 的 canPerformAction(_:withSender:) 方法也和編輯菜單有關。響應者實現此方法以在菜單顯示之前啟用和禁用編輯菜單的命令。您可以通過調用 update() 方法強制更新菜單命令的啟用狀態。

您還可以通過 menuItems 屬性提供您自己的菜單項。修改菜單項時,可以使用 update() 方法強制菜單更新其顯示。

使用探索

根據 API 說明可知

  • UIMenuController 顯示位置可以通過設置一個矩形來定位
  • 要想顯示 UIMenuController,需要成為響應者
  • 如果沒有設置 menuItems 時有自己默認的菜單,也可以通過 menuItems 添加自己的菜單

如何創建並顯示 UIMenuController

首先,API 說的很清楚,UIMenuController 是單例,直接使用 UIMenuController.shared 即可,然後調用 open func showMenu(from targetView: UIView, rect targetRect: CGRect) 方法來顯示,

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let menu = UIMenuController.shared
    menu.showMenu(from: view, rect: CGRect(x: 50, y: 50, width: 20, height: 20))
}

運行代碼發現並沒有什麼反應,回看 API,還需要設置第一響應者

override var canBecomeFirstResponder: Bool {
    true
}
// 上文提到的其他代碼忽略

運行代碼還是沒反應,回看 API,UIResponder 的 canPerformAction(_:withSender:) 方法也和編輯菜單有關。響應者實現此方法以在菜單顯示之前啟用和禁用編輯菜單的命令,我們實現一下試試

override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
    true
}
// 上文提到的其他代碼忽略

當當當當,成功瞭!!!

實現 Item 點擊事件

接下來,我鼠標輕輕的點在瞭菜單上 Cut,結果奔潰瞭:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SwiftTestiOS.ViewController cut:]: unrecognized selector sent to instance 0x7fec7300d480'

根據提示,我們實現 cut 方法。

override func cut(_ sender: Any?) {
    print("cut cut cut !!!")
}
// 上文提到的其他代碼忽略

nice,沒有奔潰,成功打印 cut cut cut !!!

其他的菜單也可以添加對應的實現,哈哈哈…搞定!!!

菜單 Item 太多???

問題:我不需要這麼多菜單,咋整?

之前沒有因為沒有實現 canPerformAction(_:withSender:) 方法時,UIMenuController 無法出現,實現瞭之後就出現瞭一大堆菜單,此方法有一個action參數,是不是此方法決定瞭哪些action可以顯示呢,試試看:

override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
    let actions = [#selector(cut(_:)), #selector(copy(_:)),
                   #selector(paste(_:)), #selector(delete(_:))]
    return actions.contains(action)
}
// 上文提到的其他代碼忽略

也就是說,canPerformAction(_:withSender:) 決定設置的哪些菜單可以生效。敲黑板,這點很重要!!!

UIResponderStandardEditActions 協議

根據 API 說明,UIResponderStandardEditActions 協議定義瞭 UIMenuController 的一些系統默認方法,內容如下

public protocol UIResponderStandardEditActions : NSObjectProtocol {
    @available(iOS 3.0, *)
    optional func cut(_ sender: Any?)
    @available(iOS 3.0, *)
    optional func copy(_ sender: Any?)
    @available(iOS 3.0, *)
    optional func paste(_ sender: Any?)
    @available(iOS 3.0, *)
    optional func select(_ sender: Any?)
    @available(iOS 3.0, *)
    optional func selectAll(_ sender: Any?)
    @available(iOS 3.2, *)
    optional func delete(_ sender: Any?)
    //... 其他方法略
}

而且上文實現 cut 方法的時候有override,也就是 UIViewController 有這個方法,根據線索可以查到對應關系

UIViewController

UIResponder

UIResponderStandardEditActions

添加自定義菜單

如果系統提供的菜單不滿足我們自己的需求,可以通過 menuItems 添加自定義菜單

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let menu = UIMenuController.shared
    let item1 = UIMenuItem(title: "hello", action: #selector(helloAction))
    let item2 = UIMenuItem(title: "world", action: #selector(worldAction))
    menu.menuItems = [item1, item2]
    menu.showMenu(from: view, rect: CGRect(x: 50, y: 50, width: 20, height: 20))
}
@objc func helloAction() {
    print(#function)
}
@objc func worldAction() {
    print(#function)
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
    let actions = [#selector(cut(_:)), #selector(copy(_:)),
                   #selector(paste(_:)), #selector(delete(_:)),
                   #selector(helloAction), #selector(worldAction)]
    return actions.contains(action)
}
// 上文提到的其他代碼忽略

添加之後效果如下:

箭頭的方向

UIMenuController 有個arrowDirection 屬性,用於設置箭頭的位置,它是ArrowDirection 類型的枚舉

extension UIMenuController {
    public enum ArrowDirection : Int, @unchecked Sendable {
        case `default` = 0
        case up = 1
        case down = 2
        case left = 3
        case right = 4
    }
}

默認的時候,會根據需要調整箭頭方向,而箭頭的位置根據 API 描述(菜單指針放置在目標矩形頂部或底部的中心,視情況而定)是在設置的矩形區域的上下邊的中間位置。

註意:如果強制設定瞭一個方向的話,而在該方向沒有足夠的空間,則不會顯示菜單

實際使用

在顯示 UIMenuController 的時候有一個方法 open func showMenu(from targetView: UIView, rect targetRect: CGRect),此方法主要是用來設置顯示位置的,targetView 指明位置參照對象,rect 表示參照 targetView 的位置,如:

let position = label.bounds
menu.showMenu(from: label, rect: position)

上述代碼可以理解成:菜單顯示在相對於 label 的 position 處

總結

UIMenuController 的使用總體還是比較簡單的,主要是以下幾點:

UIMenuController 是單例

顯示默認的 UIMenuController 菜單需要

  • 成為第一響應者
  • UIResponder 的 canPerformAction(_:withSender:)返回可以添加的 UIMenuItem
  • 實現對應 UIMenuItem 的方法

自定義 UIMenuItem

  • 通過 UIMenuController.menuItems 屬性添加自定義的 UIMenuItem
  • 在 canPerformAction(_:withSender:) 對應 UIMenuItem 的 action

箭頭的位置

  • 建議使用 ArrowDirection.default
  • 使用其他值可能會看不見菜單,除非確定一定可以顯示,否則不推薦使用

以上就是iOS開發之UIMenuController使用示例詳解的詳細內容,更多關於iOS開發UIMenuController的資料請關註WalkonNet其它相關文章!

推薦閱讀: