了解如何使用 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需要许多参数,但只有title
和handler
是必需的。SF 符号在这里效果很好,我的大多数菜单项都有 SF 符号集。第一个菜单项准备好后,我们可以创建UIMenu
let menu = UIMenu(title: "Options", children: [refreshItem])
再次init需要许多参数,但它们是可选的。Mac 上使用它identifier
来指定菜单是否是系统标准菜单之一,如“文件”、“编辑”等。
奇怪的是我们还可以指定image
和options
是 的类型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
让我们转向更高级的东西。您实际上无法指定分隔符,但如果您创建子菜单,您会自动获得这些分隔符。我们将使用现有的并对其进行修改。
我们可以准备额外的物品
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
参数来指定我们希望内联此菜单。这就是我们得到的:
如果我们不指定,.displayInline
那么菜单将被嵌套,我们需要提供title
和 ,理想情况image
下,外观与标准项目相同。
这是一个例子:
let submenu = UIMenu(title: "More", image: UIImage(systemName: "ellipsis"), children: [favoriteAction, editAction])
let menu = UIMenu(title: "Options", children: [submenu, deleteItem, refreshItem])
这是结果:
如果您想指示此菜单包含破坏性选项,可以使用.destructive
在options
参数。
UIAction 和状态
创建的时候UIAction
我们也可以指定state
参数。您可以将其设置为.off
、.on
和.mixed
。
我发现只有.on表明选项处于活动状态才有意义。文本前面会有复选标记。在我的测试中也.mixed
做了同样的事情。这是我们的子菜单,其中最喜欢的操作状态设置为.on
:
let favoriteAction = UIAction(title: "Favorite", image: UIImage(systemName: "star"), state: .on) { (_) in
}
结果:
UIMenu 哪里有意义?
在我们开始向用户显示菜单之前,让我们看看我们真正需要的地方。我认为导航栏是菜单最有意义的地方,因为空间有限。例如,如果用户可以创建多个“事物”,您将有一个“+”按钮,它将打开新菜单以供实际选择。
例如,Apple Home 应用程序就是这样做的。
或者,如果您有简单的排序,您可以使用UIMenu让用户选择应按哪些项目进行排序。
请记住,当用户做出选择时,菜单将始终消失。我认为它也是我们在 TableView 中使用的滑动操作的绝佳替代方案。TableView 和 CollectionView 本身都支持这些菜单。而且你还会得到一个很酷的动画作为奖励。
如何显示 UIMenu
我们通过具有不同属性、状态等的菜单创建了各种类型。
让我们看看如何实际向用户显示这些菜单。UIMenu从 iOS 13 开始,我们可以从 TableView 或 CollectionView 单元格中显示,从 iOS 14 开始,我们还可以使用UIBarButtonItem
和 plain 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 添加到 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 添加到 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
我们要看的最后一件事是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. 这是一个很好的新组件,可以让您构建更现代的 UI。我们回顾了基础知识、项目属性、子菜单、“分隔符”、如何在屏幕上实际显示这些菜单,我们还研究了延迟变体。
使用:Xcode 12
和Swift 5.3