简介

UIMenuController 是一个菜单修正界面,在很多地方都能用到,一般用于剪切、复制、粘贴、挑选、全选和删除指令等,也能够自界说想要的操作,它长这样:

iOS开发之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
}
// 上文说到的其他代码疏忽

运转代码仍是没反应,回看 APIUIResponder 的 canPerformAction(_:withSender:) 办法也和修正菜单有关。响应者完成此办法以在菜单显现之前启用和禁用修正菜单的指令,咱们完成一下试试

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

当当当当,成功了!!!

iOS开发之UIMenuController

完成 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 !!!

iOS开发之UIMenuController

其他的菜单也能够增加对应的完成,哈哈哈…搞定!!!

菜单 Item 太多???

问题:我不需求这么多菜单,咋整?

iOS开发之UIMenuController

之前没有因为没有完成 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)
}
// 上文说到的其他代码疏忽

iOS开发之UIMenuController

也便是说,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 有这个办法,依据头绪能够查到对应关系

graph TD
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)
}
// 上文说到的其他代码疏忽

增加之后效果如下:

iOS开发之UIMenuController

箭头的方向

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)

上述代码能够理解成:菜单显现在相对于 labelposition

总结

UIMenuController 的运用整体仍是比较简单的,主要是以下几点:

  • UIMenuController 是单例
  • 显现默许的 UIMenuController 菜单需求
    • 成为榜首响应者
    • UIResponder 的 canPerformAction(_:withSender:)返回能够增加的 UIMenuItem
    • 完成对应 UIMenuItem 的办法
  • 自界说 UIMenuItem
    • 经过 UIMenuController.menuItems 特点增加自界说的 UIMenuItem
    • canPerformAction(_:withSender:) 对应 UIMenuItemaction
  • 箭头的方位
    • 建议运用 ArrowDirection.default
    • 运用其他值可能会看不见菜单,除非确认必定能够显现,否则不引荐运用