iOS开发 UIMenu:综合使用指南

了解如何使用 UIMenu 构建现代 UI。本教程展示了基本示例、如何添加分隔符、如何使用子菜单等等。
UIMenu 是超级多功能的组件,它看起来很现代,有很酷的动画和很多自定义选项。

基本 UI 菜单

让我们从基本菜单开始,然后我们可以在此基础上进行构建。因为UIMenu也在 Mac 上使用,所以有些事情我们会忽略。我会指出这些。

在创建菜单之前,我们需要在其中显示一些项目。这些都是UIMenuElement类型。这是一种伞式类型,涵盖了可以作为菜单元素的所有内容。

在我们的例子中,这将是主要的UIAction,但UIMenu也会被考虑UIMenuElement。但我们不要操之过急。

另外要记住的关键一点是,我们没有以与该方法UIMenu类似的方式进行显示。相反,我们提前定义何时应显示菜单。我们稍后会讨论这个。UIAlertControllerpresent

定义菜单的 UIAction

我们可以像这样创建基本菜单项:

let refreshItem = UIAction(title: "Refresh", image: UIImage(systemName: "arrow.clockwise")) { (_) in
     // handle refresh
}

它init需要许多参数,但只有titlehandler是必需的。SF 符号在这里效果很好,我的大多数菜单项都有 SF 符号集。第一个菜单项准备好后,我们可以创建UIMenu

let menu = UIMenu(title: "Options", children: [refreshItem])

再次init需要许多参数,但它们是可选的。Mac 上使用它identifier来指定菜单是否是系统标准菜单之一,如“文件”、“编辑”等。

奇怪的是我们还可以指定imageoptions是 的类型UIMenu.Options。在这种情况下他们不会做任何事情,我们稍后会介绍这些。

让我们创建第二个操作并看看菜单是什么样子的。

let deleteItem = UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { (_) in
            // delete item
 }

这里我们也使用attributes参数来指示破坏性操作。这会使文本和图标变成红色。您还可以用来.disabled指示某些选项当前已禁用。

准备好新操作后,我们可以更新菜单并查看它的外观。

let menu = UIMenu(title: "Options", children: [refreshItem, deleteItem])

这是我们的实际菜单:


uimenu-basic-example.png

带有分隔符和子菜单的 UIMenu

让我们转向更高级的东西。您实际上无法指定分隔符,但如果您创建子菜单,您会自动获得这些分隔符。我们将使用现有的并对其进行修改。
我们可以准备额外的物品

let favoriteAction = UIAction(title: "Favorite", image: UIImage(systemName: "star")) { (_) in
}     
let editAction = UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { (_) in
}

然后是实际的菜单。

let submenu = UIMenu(title: "", options: .displayInline, children: [favoriteAction, editAction])

let menu = UIMenu(title: "Options", children: [deleteItem, refreshItem, submenu])

请注意,我们正在使用options参数来指定我们希望内联此菜单。这就是我们得到的:

uimenu-separator-example.png

如果我们不指定,.displayInline那么菜单将被嵌套,我们需要提供title和 ,理想情况image下,外观与标准项目相同。

这是一个例子:

let submenu = UIMenu(title: "More", image: UIImage(systemName: "ellipsis"), children: [favoriteAction, editAction])

let menu = UIMenu(title: "Options", children: [submenu, deleteItem, refreshItem])

这是结果:


uimenu-submenu-example.gif

如果您想指示此菜单包含破坏性选项,可以使用.destructiveoptions参数。

UIAction 和状态

创建的时候UIAction我们也可以指定state参数。您可以将其设置为.off.on.mixed

我发现只有.on表明选项处于活动状态才有意义。文本前面会有复选标记。在我的测试中也.mixed做了同样的事情。这是我们的子菜单,其中最喜欢的操作状态设置为.on

let favoriteAction = UIAction(title: "Favorite", image: UIImage(systemName: "star"), state: .on) { (_) in
}

结果:


uimenu-state-example.png

UIMenu 哪里有意义?

在我们开始向用户显示菜单之前,让我们看看我们真正需要的地方。我认为导航栏是菜单最有意义的地方,因为空间有限。例如,如果用户可以创建多个“事物”,您将有一个“+”按钮,它将打开新菜单以供实际选择。

例如,Apple Home 应用程序就是这样做的。


uimenu-uibarbutton-apple-home-example.png

或者,如果您有简单的排序,您可以使用UIMenu让用户选择应按哪些项目进行排序。

请记住,当用户做出选择时,菜单将始终消失。我认为它也是我们在 TableView 中使用的滑动操作的绝佳替代方案。TableView 和 CollectionView 本身都支持这些菜单。而且你还会得到一个很酷的动画作为奖励。

如何显示 UIMenu

我们通过具有不同属性、状态等的菜单创建了各种类型。

让我们看看如何实际向用户显示这些菜单。UIMenu从 iOS 13 开始,我们可以从 TableView 或 CollectionView 单元格中显示,从 iOS 14 开始,我们还可以使用UIBarButtonItemplain UIButton

显示自UIBarButtonItem

我认为呈现菜单UIBarButtonItem将是相当频繁的用例,所以让我们从它开始。在代码中创建这些时,您可以使用新的初始值设定项,它将菜单作为参数之一。

navigationItem.rightBarButtonItem = UIBarButtonItem(title: nil, image: UIImage(systemName: "list.bullet"), primaryAction: nil, menu: menu)

主要操作的类型为UIAction。当您传递 时nil,菜单将是该栏按钮项目的主要操作。如果通过primaryAction,则只有长按按钮后才会显示菜单。

如果您正在创建更多标准UIBarButtonItem,那么可以使用init:

UIBarButtonItem(systemItem: .edit, primaryAction: nil, menu: menu)

这是结果:


uimenu-uibarbuttonitem-example.png

将 UIMenu 添加到 UICollectionViewCell

显示菜单的另一个有用的地方是前面提到的集合视图单元格。通过这种方式,您可以为用户提供一种删除项目、收藏项目等的方法,而无需进入详细信息屏幕或单元格本身上有按钮。

有两个简短的步骤可以实现此目的。首先您需要符合UICollectionViewDelegate.

然后实现这个方法:

func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {

}

根据提供的信息,indexPath您可以决定是否显示菜单。如果您不想显示菜单,只需返回即可nil。

如果你想显示菜单,你可以返回如下内容:

return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { _ in
   return UIMenu(title: "Options", children: [share, copy, delete])
})

然后,UIMenu当长按集合视图单元格时,您会得到这个很酷的动画。这个例子来自我在GitHub上的开源项目。

uimenu-uicollectionviewcell-example.gif

将 UIMenu 添加到 UITableViewCell

TableView 的流程基本相同。您需要遵守UITableViewDelegate然后实现此方法:

func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {

} 

从 UIButton 显示 UIMenu

演示的最后一个选项UIMenu来自UIButton。类似地,UIBarButtonItem您可以选择长按后立即显示菜单。

UIButton自 iOS 14 以来就有属性menu,而且showsMenuAsPrimaryAction这是非常不言自明的。设置菜单,然后showsMenuAsPrimaryAction = true点击按钮后就会出现菜单。

showMenuButton.menu = menu
showMenuButton.showsMenuAsPrimaryAction = true
uimenu-uibutton-example.png

异步 UIMenu

我们要看的最后一件事是UIDeferredMenuElement。这适用于我们无法立即提供项目的情况UIMenuElement,但我们想让用户知道加载后会出现一些内容。

虽然这个概念听起来很复杂,但实际实施并不需要太多时间。当您创建时,UIDeferredMenuElement有一个参数是一个闭包,它需要UIMenuElement.

这意味着我们需要加载项目、构建菜单并将其交给闭包。iOS 将完成剩下的工作。这意味着它将显示占位符加载指示器,然后自动显示创建的菜单。

它还内置了缓存机制,因此如果用户多次打开菜单,项目将被缓存。

基本示例如下所示:

let asyncItem = UIDeferredMenuElement { (completion) in
      // load menu
}

当然我们不必使用闭包,我们可以用prepare方法来代替:

func loadMenu(completion: @escaping (([UIMenuElement]) -> Void)) {
        // load menu
}

然后构造代码就更清晰了

let asyncItem = UIDeferredMenuElement(loadMenu(completion:))

出于演示目的,我们可以引入轻微的延迟,DispatchQueue然后查看操作中的菜单

func loadMenu(completion: @escaping (([UIMenuElement]) -> Void)) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
        let favoriteAction = UIAction(title: "Favorite", image: UIImage(systemName: "star"), state: .on) { (_) in
        }
        let editAction = UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { (_) in
        }
        completion([favoriteAction, editAction])
    }
}

让我们修改旧的菜单定义:

let asyncItem = UIDeferredMenuElement(loadMenu(completion:))
let menu = UIMenu(title: "Options", children: [asyncItem, refreshItem, deleteItem])

现在让我们看看结果:


uimenu-asynchronous-deferred-menu-item-example.gif

结论

我认为这篇文章涵盖了UIMenu. 这是一个很好的新组件,可以让您构建更现代的 UI。我们回顾了基础知识、项目属性、子菜单、“分隔符”、如何在屏幕上实际显示这些菜单,我们还研究了延迟变体。

使用:Xcode 12Swift 5.3

翻译自:https://nemecek.be/blog/88/uimenu-comprehensive-guide

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容