了解如何使用 UIMenu 構(gòu)建現(xiàn)代 UI。本教程展示了基本示例、如何添加分隔符、如何使用子菜單等等。
UIMenu 是超級多功能的組件,它看起來很現(xiàn)代,有很酷的動畫和很多自定義選項。
基本 UI 菜單
讓我們從基本菜單開始,然后我們可以在此基礎(chǔ)上進行構(gòu)建。因為UIMenu也在 Mac 上使用,所以有些事情我們會忽略。我會指出這些。
在創(chuàng)建菜單之前,我們需要在其中顯示一些項目。這些都是UIMenuElement類型。這是一種傘式類型,涵蓋了可以作為菜單元素的所有內(nèi)容。
在我們的例子中,這將是主要的UIAction,但UIMenu也會被考慮UIMenuElement。但我們不要操之過急。
另外要記住的關(guān)鍵一點是,我們沒有以與該方法UIMenu類似的方式進行顯示。相反,我們提前定義何時應(yīng)顯示菜單。我們稍后會討論這個。UIAlertControllerpresent
定義菜單的 UIAction
我們可以像這樣創(chuàng)建基本菜單項:
let refreshItem = UIAction(title: "Refresh", image: UIImage(systemName: "arrow.clockwise")) { (_) in
// handle refresh
}
它init需要許多參數(shù),但只有title和handler是必需的。SF 符號在這里效果很好,我的大多數(shù)菜單項都有 SF 符號集。第一個菜單項準(zhǔn)備好后,我們可以創(chuàng)建UIMenu
let menu = UIMenu(title: "Options", children: [refreshItem])
再次init需要許多參數(shù),但它們是可選的。Mac 上使用它identifier來指定菜單是否是系統(tǒng)標(biāo)準(zhǔn)菜單之一,如“文件”、“編輯”等。
奇怪的是我們還可以指定image和options是 的類型UIMenu.Options。在這種情況下他們不會做任何事情,我們稍后會介紹這些。
讓我們創(chuàng)建第二個操作并看看菜單是什么樣子的。
let deleteItem = UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { (_) in
// delete item
}
這里我們也使用attributes參數(shù)來指示破壞性操作。這會使文本和圖標(biāo)變成紅色。您還可以用來.disabled指示某些選項當(dāng)前已禁用。
準(zhǔn)備好新操作后,我們可以更新菜單并查看它的外觀。
let menu = UIMenu(title: "Options", children: [refreshItem, deleteItem])
這是我們的實際菜單:

帶有分隔符和子菜單的 UIMenu
讓我們轉(zhuǎn)向更高級的東西。您實際上無法指定分隔符,但如果您創(chuàng)建子菜單,您會自動獲得這些分隔符。我們將使用現(xiàn)有的并對其進行修改。
我們可以準(zhǔn)備額外的物品
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參數(shù)來指定我們希望內(nèi)聯(lián)此菜單。這就是我們得到的:

如果我們不指定,.displayInline那么菜單將被嵌套,我們需要提供title和 ,理想情況image下,外觀與標(biāo)準(zhǔn)項目相同。
這是一個例子:
let submenu = UIMenu(title: "More", image: UIImage(systemName: "ellipsis"), children: [favoriteAction, editAction])
let menu = UIMenu(title: "Options", children: [submenu, deleteItem, refreshItem])
這是結(jié)果:

如果您想指示此菜單包含破壞性選項,可以使用.destructive在options參數(shù)。
UIAction 和狀態(tài)
創(chuàng)建的時候UIAction我們也可以指定state參數(shù)。您可以將其設(shè)置為.off、.on和.mixed。
我發(fā)現(xiàn)只有.on表明選項處于活動狀態(tài)才有意義。文本前面會有復(fù)選標(biāo)記。在我的測試中也.mixed做了同樣的事情。這是我們的子菜單,其中最喜歡的操作狀態(tài)設(shè)置為.on:
let favoriteAction = UIAction(title: "Favorite", image: UIImage(systemName: "star"), state: .on) { (_) in
}
結(jié)果:

UIMenu 哪里有意義?
在我們開始向用戶顯示菜單之前,讓我們看看我們真正需要的地方。我認為導(dǎo)航欄是菜單最有意義的地方,因為空間有限。例如,如果用戶可以創(chuàng)建多個“事物”,您將有一個“+”按鈕,它將打開新菜單以供實際選擇。
例如,Apple Home 應(yīng)用程序就是這樣做的。

或者,如果您有簡單的排序,您可以使用UIMenu讓用戶選擇應(yīng)按哪些項目進行排序。
請記住,當(dāng)用戶做出選擇時,菜單將始終消失。我認為它也是我們在 TableView 中使用的滑動操作的絕佳替代方案。TableView 和 CollectionView 本身都支持這些菜單。而且你還會得到一個很酷的動畫作為獎勵。
如何顯示 UIMenu
我們通過具有不同屬性、狀態(tài)等的菜單創(chuàng)建了各種類型。
讓我們看看如何實際向用戶顯示這些菜單。UIMenu從 iOS 13 開始,我們可以從 TableView 或 CollectionView 單元格中顯示,從 iOS 14 開始,我們還可以使用UIBarButtonItem和 plain UIButton。
顯示自UIBarButtonItem
我認為呈現(xiàn)菜單UIBarButtonItem將是相當(dāng)頻繁的用例,所以讓我們從它開始。在代碼中創(chuàng)建這些時,您可以使用新的初始值設(shè)定項,它將菜單作為參數(shù)之一。
navigationItem.rightBarButtonItem = UIBarButtonItem(title: nil, image: UIImage(systemName: "list.bullet"), primaryAction: nil, menu: menu)
主要操作的類型為UIAction。當(dāng)您傳遞 時nil,菜單將是該欄按鈕項目的主要操作。如果通過primaryAction,則只有長按按鈕后才會顯示菜單。
如果您正在創(chuàng)建更多標(biāo)準(zhǔn)UIBarButtonItem,那么可以使用init:
UIBarButtonItem(systemItem: .edit, primaryAction: nil, menu: menu)
這是結(jié)果:

將 UIMenu 添加到 UICollectionViewCell
顯示菜單的另一個有用的地方是前面提到的集合視圖單元格。通過這種方式,您可以為用戶提供一種刪除項目、收藏項目等的方法,而無需進入詳細信息屏幕或單元格本身上有按鈕。
有兩個簡短的步驟可以實現(xiàn)此目的。首先您需要符合UICollectionViewDelegate.
然后實現(xiàn)這個方法:
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
}
根據(jù)提供的信息,indexPath您可以決定是否顯示菜單。如果您不想顯示菜單,只需返回即可nil。
如果你想顯示菜單,你可以返回如下內(nèi)容:
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { _ in
return UIMenu(title: "Options", children: [share, copy, delete])
})
然后,UIMenu當(dāng)長按集合視圖單元格時,您會得到這個很酷的動畫。這個例子來自我在GitHub上的開源項目。

將 UIMenu 添加到 UITableViewCell
TableView 的流程基本相同。您需要遵守UITableViewDelegate然后實現(xiàn)此方法:
func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
}
從 UIButton 顯示 UIMenu
演示的最后一個選項UIMenu來自UIButton。類似地,UIBarButtonItem您可以選擇長按后立即顯示菜單。
UIButton自 iOS 14 以來就有屬性menu,而且showsMenuAsPrimaryAction這是非常不言自明的。設(shè)置菜單,然后showsMenuAsPrimaryAction = true點擊按鈕后就會出現(xiàn)菜單。
showMenuButton.menu = menu
showMenuButton.showsMenuAsPrimaryAction = true

異步 UIMenu
我們要看的最后一件事是UIDeferredMenuElement。這適用于我們無法立即提供項目的情況UIMenuElement,但我們想讓用戶知道加載后會出現(xiàn)一些內(nèi)容。
雖然這個概念聽起來很復(fù)雜,但實際實施并不需要太多時間。當(dāng)您創(chuàng)建時,UIDeferredMenuElement有一個參數(shù)是一個閉包,它需要UIMenuElement.
這意味著我們需要加載項目、構(gòu)建菜單并將其交給閉包。iOS 將完成剩下的工作。這意味著它將顯示占位符加載指示器,然后自動顯示創(chuàng)建的菜單。
它還內(nèi)置了緩存機制,因此如果用戶多次打開菜單,項目將被緩存。
基本示例如下所示:
let asyncItem = UIDeferredMenuElement { (completion) in
// load menu
}
當(dāng)然我們不必使用閉包,我們可以用prepare方法來代替:
func loadMenu(completion: @escaping (([UIMenuElement]) -> Void)) {
// load menu
}
然后構(gòu)造代碼就更清晰了
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])
現(xiàn)在讓我們看看結(jié)果:

結(jié)論
我認為這篇文章涵蓋了UIMenu. 這是一個很好的新組件,可以讓您構(gòu)建更現(xiàn)代的 UI。我們回顧了基礎(chǔ)知識、項目屬性、子菜單、“分隔符”、如何在屏幕上實際顯示這些菜單,我們還研究了延遲變體。
使用:Xcode 12和Swift 5.3