Reputation: 2276
In the iOS 16 Calendar app, there is a new drop-down menu style for options like "repeat", when tapping any place of the row, a menu appeared. And there is a chevron up and chevron down icon at the right side of the table view cell.
How to do this in iOS 16? The context menu is triggered by long press, but this new style is by single-tap.
Upvotes: 3
Views: 1529
Reputation: 14976
The question is asking about the popup menu being used in the Calendar app. The button is a popup button. In UIKit, this is implemented with UIButton
. You need to set the button's menu
, and set the showsMenuAsPrimaryAction
and changesSelectionAsPrimaryAction
properties to true
.
Here is some sample code to create such a button and apply it to a table cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ... // Create and setup your cell as needed
// Create the popup button
let cfg = UIButton.Configuration.plain()
let button = UIButton(configuration: cfg)
// Replace the following with your own code to support your own menu.
// The important part here is that zero or one actions is set with a
// state of on and the rest are set to off. This defines the initially
// selected value, if any. The state of the actions are updated as the
// user makes a selection.
button.menu = UIMenu(options: [.singleSelection], children: [
UIAction(title: "Option 1", state: self.option == .option1 ? .on : .off, handler: { [weak self] action in
self?.option = .option1
}),
UIAction(title: "Option 2", state: self.option == .option2 ? .on : .off, handler: { [weak self] action in
self?.option = .option2
}),
])
// After setting the menu, the following two properties signify that
// the button is to look and act like a popup button. This includes
// the up/down arrows on the right side of the button which is shown
// by default for a popup button.
button.showsMenuAsPrimaryAction = true
button.changesSelectionAsPrimaryAction = true
// This puts the button on the right end of the cell.
cell.accessoryView = button
return cell
}
The menu code shown above assumes there is some enum
representing the various selectable options and that there is an option
property for keeping track of the selected option. There are many possible ways and uses for the menu so I leave that to the needs of your app. The important part of this answer is showing the basic setup needed to replicate the popup button used in the Calendar app.
If you wish to display the button's menu when tapping on the table view row, in addition to tapping on the actual button, you need to implement the didSelectRowAt
method. The following is an example based on the above cellForRowAt
:
class FooController: UITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath), let button = cell.accessoryView as? UIButton {
button.trigger()
}
}
}
The trigger
function referenced above is from the following UIButton
extension:
extension UIButton {
func trigger() {
if let gestures = self.gestureRecognizers {
for gesture in gestures {
if NSStringFromClass(type(of: gesture)).contains("UITouch") {
gesture.touchesBegan(Set(), with: UIEvent())
break
}
}
}
}
}
There is no public API for programmatically triggering a button. This trigger
function tricks the button into thinking that it was tapped. And for the type of button shown in the above cellForRowAt
method, this will result in the button's menu being displayed.
Upvotes: 2
Reputation: 59
You can add UIButton display menu in the contentView of tableViewCell.
class TestCell: UITableViewCell {
lazy var menuButton: UIButton = {
let button = UIButton()
button.showsMenuAsPrimaryAction = true
// custom your menu
button.menu = UIMenu(title: "menus", children: [])
return button
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
contentView.addSubview(menuButton)
}
}
Upvotes: 0
Reputation: 12615
As of iOS 16, you can do it, albeit with a horizontally laid out edit menu, or, alternatively with long-press gesture, neither of which which the user may anticipate.
In the first case you provide the UIMenu to the edit menu delegate method, and in the latter, you use the context menu's delegate menu. For the horizontal menu, the shorter the menu names, the better, especially if you have more than 2 or 3 items, so the user doesn't have to click the right triangle at the end to expose more items.
The delegate methods respectively return a UIMenu, which you can create right in the delegate method, or you can set it up to reuse a mention that's triggered, perhaps with a pop-up UIButton() inside the tableView cell, that way you get better coverage in case the user doesn't tap the button itself.
var editMenuInteraction : UIEditMenuInteraction! = nil
override func viewDidLoad() {
editMenuInteraction = UIEditMenuInteraction(delegate: self)
}
extension MyViewController: UIEditMenuInteractionDelegate {
func editMenuInteraction(_ interaction: UIEditMenuInteraction, menuFor configuration: UIEditMenuConfiguration, suggestedActions: [UIMenuElement]) -> UIMenu? {
let myAction = UIAction(title: "Action1", image: UIImage(systemName: "bell"), identifier: nil) { action in
print("MyAction tapped")
}
let anotherAction = UIAction(title: "Action2", image: UIImage(systemName: "person"), identifier: nil) { action in
print("AnotherAction tapped")
}
.
.
.
return UIMenu(title: "", children: [myAction, anotherAction])
}
}
extension MyViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.addInteraction(editMenuInteraction)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let interaction = editMenuInteraction {
let rect = tableView.rectForRow(at: indexPath)
let absoluteRect = tableView.convert(rect, to: tableView.superview)
let configuration = UIEditMenuConfiguration(identifier: nil, sourcePoint: absoluteRect.origin)
interaction.presentEditMenu(with: configuration)
}
}
}
// In your cellForRowAt tableView delegate method:
let interaction = UIContextMenuInteraction(delegate: self)
cell?.addInteraction(interaction)
extension MyViewController: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
let myAction = UIAction(title: "MyAction", image: UIImage(systemName: "bell"), identifier: nil) { action in
print("MyAction tapped")
}
let anotherAction = UIAction(title: "AnotherAction", image: UIImage(systemName: "person"), identifier: nil) { action in
print("AnotherAction tapped")
}
.
.
.
return UIMenu(title: "", children: [myAction, anotherAction])
}
}
}
Upvotes: 0
Reputation: 1919
You can add a UIButton to the TableViewCell and fill the entire space. Use UIButton's menu property to achieve the system calendar effect.
button.menu = menu
You can check the code I wrote, hope it helps you. https://github.com/zhi6w/TableViewCellWithMenuButton
Upvotes: 2
Reputation: 2276
It seems this is SwiftUI only. I can't find changes in iOS 16 UIKit to achieve this. In SwiftUI:
import SwiftUI
struct SettingsView: View {
@State private var selectedFlavor: Flavor = .chocolate
var body: some View {
List {
Picker("Flavor", selection: $selectedFlavor) {
Text("Chocolate").tag(Flavor.chocolate)
Text("Vanilla").tag(Flavor.vanilla)
Text("Strawberry").tag(Flavor.strawberry)
}
}
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
}
}
enum Flavor: String, CaseIterable, Identifiable {
case chocolate, vanilla, strawberry
var id: Self { self }
}
Upvotes: 0