I put UIBarButtonItem inside Toolbar as you can see in Storyboard. I put UIMenu in UIBarButtonItem.menu. When I click a button in UIMenu, the icon of the button I clicked is transferred to UIBarButtonItem. Why is this happening?
Question:
Why does the UIBarButtonItem's icon change when I press the button from ContextMenu?
Video:
Storyboard:
UIBarButtonItem Attributes:
ViewController:
#IBOutlet weak var fooItem: UIBarButtonItem!
//MARK: Functions
override func viewDidLoad() {
super.viewDidLoad()
fooItem.menu = ContextMenuManager.shared.makeUIMenu()
}
ContextMenuManager:
protocol ContextMenuFunctionable {
func didTappedEmptyFolder()
func didTappedImportFile()
}
class ContextMenuManager {
static var shared = ContextMenuManager()
var delegate: ContextMenuFunctionable?
var emptyFolder: UIAction?
var importFile: UIAction?
var importPhotoOrVideo: UIAction?
private init() {
emptyFolder = UIAction(
title: "Empty Folder",
image: .folderBadgePlus
.applyingSymbolConfiguration(.symbolConfig),
identifier: nil,
state: .off,
handler: { _ in self.delegate?.didTappedEmptyFolder() }
)
importFile = UIAction(
title: "Import File",
image: .squareAndArrowDown.applyingSymbolConfiguration(.symbolConfig),
identifier: nil,
state: .off,
handler: { _ in self.delegate?.didTappedImportFile() }
)
importPhotoOrVideo = UIAction(
title: "Import Photo & Video",
image: .photo.applyingSymbolConfiguration(.symbolConfig),
identifier: nil,
state: .off,
handler: { _ in self.delegate?.didTappedImportFile() }
)
}
func makeUIMenu() -> UIMenu {
guard let emptyFolder, let importFile, let importPhotoOrVideo else { return UIMenu() }
return UIMenu(title: "Add",
image: .folder.applyingSymbolConfiguration(.symbolConfig),
options: [.displayInline, .singleSelection],
children: [importPhotoOrVideo, importFile, emptyFolder])
}
}
Uncheck the "Selection as Primary Action" checkbox and the button's icon should no longer change when you make a menu selection.
I replicated what you are seeing in code. I created a menu basically using your ContextMenuManager code but I created the UIBarButtonItem as follows:
let menu = UIMenu(title: "Add",
options: [.displayInline, .singleSelection],
children: [importPhotoOrVideo, importFile, emptyFolder])
let button = UIBarButtonItem(image: UIImage(systemName: "doc.on.clipboard.fill"), menu: menu)
button.changesSelectionAsPrimaryAction = false
When changesSelectionAsPrimaryAction is set to true, the button's icon reflects the chosen menu. When set to false, it retains its own icon.
Related
Im trying to use action in the UIAction for my menu item in UIKit. So, For first button Im not able to apply action. It shows me error "Cannot find 'action' in scope"
I really want to use selector in this case. I want to know what's perfect way to take action on the selector
class MessageViewController : UIViewController, UITableViewDelegate {
private lazy var first = UIAction(title: "Edit", image: UIImage(systemName: "pencil.circle"), attributes: [], state: .off) { [self]_ in
action: #selector(self.RightSideBarButtonItemTapped(_:))
}
private lazy var second = UIAction(title: "Second", image: UIImage(systemName: "pencil.circle"), attributes: [.destructive], state: .on) { action in
print("Second")
#selector(self.sendMessageRightSideBarButtonItemTapped(_:))
}
private lazy var third = UIAction(title: "Third", image: UIImage(systemName: "pencil.circle"), attributes: [], state: .off) { action in
print("third")
}
private lazy var elements: [UIAction] = [first]
private lazy var menu = UIMenu(title: "new", children: elements)
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: nil)
menu = menu.replacingChildren([first, second, third])
if #available(iOS 14.0, *) {
navigationItem.rightBarButtonItem?.menu = menu
}
}
Tried Solution from #Hangar Rash
private lazy var first = UIAction(title: "Edit", image: UIImage(systemName: "pencil.circle"), attributes: [], state: .off)
{ [unowned self] action in
self.RightSideBarButtonItemTapped(_:)
// Getting error on this line which says its " Function is unused "
}
override func viewDidLoad() {}
override func viewDidAppear(_ animated: Bool) {}
#objc func RightSideBarButtonItemTapped(_ sender:UIBarButtonItem!)
{
let vc = IceWorldView()
present(vc, animated: true)
}
Your first action has two issues:
a syntax error with action
there's no need to use #selector. Just call the method directly
Change:
private lazy var first = UIAction(title: "Edit", image: UIImage(systemName: "pencil.circle"), attributes: [], state: .off) { [self]_ in
action: #selector(self.RightSideBarButtonItemTapped(_:))
}
to:
private lazy var first = UIAction(title: "Edit", image: UIImage(systemName: "pencil.circle"), attributes: [], state: .off) { [unowned self] action in
self.RightSideBarButtonItemTapped(someButton)
}
Your RightSideBarButtonItemTapped expects a UIBarButtonItem parameter but you don't have one in the menu. You can either made a dummy button instance to pass in or you can change the RightSideBarButtonItemTapped so it doesn't take any parameters. You don't seem to use the passed in sender anyway.
Fix the use of #selector in your second action as well.
Please refer to the screenshot, I want to implement a NSButton like the buttons on Finder window. It seems that the button style is similar with a NSButton recessed. I have tried the recessed button but it is a little different from the Finder button, even the one which have a drop down menu. I have no idea how to make a drop down menu with recessed button.
If I choose to make a bevel style borderless NSButton with a special image, I cannot set a highlighted hover background whose size is larger than the image inside. The hover area is always the same with the image.
So any body known how to implement this. Thanks
Use NSMenuToolbarItem instead and check the inline comment in the sample source:
Assign this custom class MainWindowController to the initial NSWindowController controller:
class MainWindowController: NSWindowController {
// `NSMenuToolbarItem` Items
var dropDownMenu: NSMenu = {
var menu = NSMenu(title: "DropDown")
let item1 = NSMenuItem(title: "Item 1", action: nil, keyEquivalent: "")
let item2 = NSMenuItem(title: "Item 1", action: nil, keyEquivalent: "")
let item3 = NSMenuItem(title: "Item 3", action: nil, keyEquivalent: "")
menu.items = [item1, item2, item3]
return menu
}()
override func windowDidLoad() {
super.windowDidLoad()
configureToolbar()
}
private func configureToolbar() {
if let safeWindow = self.window {
let toolbar = NSToolbar(identifier: "mainWindowToolbar")
toolbar.delegate = self
toolbar.displayMode = .default
safeWindow.toolbarStyle = .automatic
safeWindow.toolbar = toolbar
safeWindow.toolbar?.validateVisibleItems()
}
}
}
extension MainWindowController: NSToolbarDelegate {
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
let toolbarItem = NSMenuToolbarItem(itemIdentifier: itemIdentifier)
toolbarItem.showsIndicator = true // To display DropDown Indicator
toolbarItem.menu = self.dropDownMenu
toolbarItem.isBordered = true // Show ToolBar Item Background on Mouse Hover
toolbarItem.image = NSImage(systemSymbolName: "ellipsis.circle", accessibilityDescription: "")
return toolbarItem
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [NSToolbarItem.Identifier("DropDownAction")]
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [NSToolbarItem.Identifier("DropDownAction")]
}
}
Output:
I created a pull down button like below
#IBOutlet weak var pullDownButton: UIButton!
Then I called a method from viewDidLoad() to configure pull down menu reference
func setupMenu() {
let add = UIAction(title: "Add", image: UIImage(systemName: "plus")) { _ in
self.showToast(message: "Add", seconds: 1.0)
}
let edit = UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in
self.showToast(message: "Edit", seconds: 1.0)
}
let delete = UIAction(title: "Delete", image: UIImage(systemName: "minus")) { _ in
self.showToast(message: "Delete", seconds: 1.0)
}
let menu = UIMenu(title: "Menu", children: [add, edit, delete])
pullDownButton.menu = menu
pullDownButton.showsMenuAsPrimaryAction = true
}
But I am unable to show the pull down menu upon long press or simple press.
Is there any way to show the pull down menu
Normal Button Declaration
#IBOutlet weak var btnAllocationType: UIButton!
use of this button
let menuClosure = {(action: UIAction) in
self.update(number: action.title)
}
btnAllocationType.menu = UIMenu(children: [
UIAction(title: "option 1", state: .on, handler:
menuClosure),
UIAction(title: "option 2", handler: menuClosure),
UIAction(title: "option 3", handler: menuClosure),
])
btnAllocationType.showsMenuAsPrimaryAction = true
btnAllocationType.changesSelectionAsPrimaryAction = true
and update function,where you get the selection
func update(number:String) {
if number == "option 1" {
print("option 1 selected")
}
showButton.showsMenuAsPrimaryAction = true
Declare the IBOutlet for the button:
#IBOutlet weak var filterPullDownButtom: UIButton!
Create a menu and add UIAction for each item:
override func viewDidLoad() {
super.viewDidLoad()
filterPullDownButtom.showsMenuAsPrimaryAction = true
filterPullDownButtom.changesSelectionAsPrimaryAction = true
let optionClosure = {(action: UIAction) in
if (action.index(ofAccessibilityElement: Any))
}
filterPullDownButtom.menu = UIMenu(children: [
UIAction(title: "Within 10 kms", state: .on, handler: optionClosure),
UIAction(title: "10 <= 20 kms", handler: optionClosure),
UIAction(title: "20 <= 50 kms", handler: optionClosure),
UIAction(title: "Within 100 Kms", handler: optionClosure),
])
In iOS context menus can be shown by a long press or a tap. Currently the below code shows a context menu on a long press, how do I present the menu on a tap?
let interaction = UIContextMenuInteraction(delegate: self)
tagBtn.addInteraction(interaction)
func contextMenuInteraction(_ interaction: UIContextMenuInteraction,
configurationForMenuAtLocation location: CGPoint)
-> UIContextMenuConfiguration? {
let favorite = UIAction(title: "Favorite",
image: UIImage(systemName: "heart.fill")) { _ in
// Perform action
}
let share = UIAction(title: "Share",
image: UIImage(systemName: "square.and.arrow.up.fill")) { action in
// Perform action
}
let delete = UIAction(title: "Delete",
image: UIImage(systemName: "trash.fill"),
attributes: [.destructive]) { action in
// Perform action
}
return UIContextMenuConfiguration(identifier: nil,
previewProvider: nil) { _ in
UIMenu(title: "Actions", children: [favorite, share, delete])
}
}
UIContextMenuInteraction is intended only for contextual (long-press) menus.
If you want the primary action of the button to display a menu, you can just create a UIMenu and assign it directly to the button.menu property, then set button.showsMenuAsPrimaryAction = true, like this:
let favorite = UIAction(title: "Favorite",
image: UIImage(systemName: "heart.fill")) { _ in
// Perform action
}
...
let button = UIButton()
button.showsMenuAsPrimaryAction = true
button.menu = UIMenu(title: "", children: [favorite, ...])
I am having a weird issue with a button. So I have a NSViewController with many subviews in it. When I click a button, a new NSView with click gestures and buttons is added on top. But I can't press any of them, they don't respond unless a click for 2 seconds and then release. I've tried disabling the gestures of the holder but it didn't work. Any suggestions?
Well, some of the rest of us do. In my case, it's for buttons on a view in a sheet, so "many subviews" isn't likely it. My view controller for the sheet is about 100 lines. Still debugging...
At present the VC is as follows. The snp.makeConstraints calls are for SnapKit (from GitHub)
#objc
class ThreadEditSheetViewController: NSViewController {
/// The container for the graphics view
#IBOutlet var sheetView: NSView!
/// The information packet initialized by the invoking view controller
var info: ThreadEditInfo!
/// API
override func viewDidLoad() {
super.viewDidLoad()
}
/// API
override func viewWillAppear() {
guard let gvc = (try? self.bundleLoader(id: "GraphicsViewController")) as? GraphicsViewController else {
fatalUserAlert(error: AppError.UIConstructionFailure, message: "Can't find GraphicsViewController for ThreadEditSheetViewController")}
let gv = gvc.view
self.view.addSubview(gv)
// Spaces in title text move it left to avoid visual overlap with scroll bar. Don't know how to do it with
// constraints given the scrolling view
let done = makeButton(gvc: gvc, title: "done ", action: #selector(doneEditing(_:)))
done.snp.makeConstraints{ (make) in
make.top.equalTo(gv).offset(-5)
make.right.equalTo(gv).offset(-5)
}
let cancel = makeButton(gvc: gvc, title: "cancel", action: #selector(cancelEditing(_:)))
cancel.snp.makeConstraints{ (make) in
make.top.equalTo(gv).offset(-5)
make.left.equalTo(gv).offset(5)
}
self.view.becomeFirstResponder()
super.viewWillAppear()
return
}
func makeButton(gvc: NSViewController, title: String, action: Selector) -> NSButton {
let button = NSButton(title: title, target: self, action: action)
let gv = gvc.view
gv.addSubview(button)
button.backgroundColor = .clear
button.setButtonType(.momentaryChange)
button.isTransparent = true
return button
}
#objc
func doneEditing(_ sender: Any) {
self.dismissViewController(self)
}
#objc
func cancelEditing(_ sender: Any) {
self.dismissViewController(self)
}
}