I have a Mac OS menu bar which when clicked opens a popup view, I want to add upon this by incorporating a quit function. When the icon is right clicked another popup menu will be presented.
I am stuck on trying to get the another popup menu to present, I am able to detect a right click on the icon but I unable to show the popup menu view.
Preview when i left click
POPUP MENU VIEW FOR BOTH POPUPS
#main
struct MenuNoteApp: App {
let persistenceController = PersistenceController.shared
#Environment(\.scenePhase) var scenePhase
#NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
.onChange(of: scenePhase) { _ in
persistenceController.save()
}
}
}
extension NSStatusBarButton {
open override func rightMouseUp(with event: NSEvent) {
// Detect right click and prints right click
print("right click")
}
}
class AppDelegate: NSObject,NSApplicationDelegate{
let persistenceController = PersistenceController.shared
// Status Item
var statusItem: NSStatusItem?
// PopOver
var popOver = NSPopover()
var popOverOptions = NSPopover()
func applicationDidFinishLaunching( _ notification: Notification){
// Menu View
let menuView = MenuView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
// Creating PopOver
popOver.behavior = .transient
popOver.animates = true
// Setting Empty View Controller And Setting View as SwiftUI View with help of hosting controller
popOver.contentViewController = NSViewController()
popOver.contentViewController?.view = NSHostingView(rootView: menuView)
popOverOptions.contentViewController = NSViewController()
popOverOptions.contentViewController?.view = NSHostingView(rootView: OptionsView())
// Creating Status Bar Button
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
// Check if status button is available
if let MenuButton = statusItem?.button {
MenuButton.image = NSImage(systemSymbolName: "note", accessibilityDescription: nil)
MenuButton.action = #selector(MenuButtonToggle)
}
}
// Button Action
#objc func MenuButtonToggle(sender: AnyObject){
if popOver.isShown{
popOver.performClose(sender)
}
else if popOverOptions.isShown{
popOverOptions.performClose(sender)
}
else{
//Present Pop Over
if let menuButton = statusItem?.button{
// popOver is the main view while popOverOpitions is the second view
self.popOver.show(relativeTo: menuButton.bounds, of: menuButton, preferredEdge: NSRectEdge.minY)
}
}
}
}
Instead of overriding functions in NSStatusBarButton, you should detect the left and right click behavior within the MenuButtonToggle function.
First, you should register the right-click action, inside your the if-let inside applicationDidFinishLaunching, like so:
if let menuButton = statusItem?.button {
menuButton.image = NSImage(systemSymbolName: "note", accessibilityDescription: nil)
menuButton.action = #selector(MenuButtonToggle)
menuButton.sendAction(on: [.leftMouseUp, .rightMouseUp]) // register action on right click too
}
Then, you want to update the MenuButtonToggle function to act differently depending on left and right clicks.
#objc func menuButtonToggle(sender: AnyObject){
// ... previous logic here
if let event = NSApp.currentEvent {
if event.type == NSEventType.rightMouseUp {
// Right button click
self.popOver.show(...)
} else {
// Left button click
self.popOverOptions.show(...)
}
}
}
Related
This example is pretty contrived, but it illustrates the behavior. I know you can use .accessibilityIdentifier to uniquely identify a control, but I'm just trying to better understand the interplay between XCUIElement and XCUIElementQuery.
Let's say you have an app like this:
import SwiftUI
struct ContentView: View {
#State var showRedButton = true
var body: some View {
VStack {
if showRedButton {
Button("Click me") {
showRedButton = false
}
.background(.red)
}
else {
HStack {
Button("Click me") {
showRedButton = true
}
.background(.blue)
Spacer()
}
}
}
}
}
And you are UI testing like this:
import XCTest
final class MyAppUITests: XCTestCase {
func testExample() throws {
let app = XCUIApplication()
app.launch()
print(app.debugDescription)
// At this point, the Element subtree shows a single Button:
// Button, 0x14e40d290, {{162.3, 418.3}, {65.3, 20.3}}, label: 'Click me'
let btn = app.buttons["Click me"]
btn.tap() // <-- This tap makes the red button disappear and shows the blue button
print(app.debugDescription)
// Now, the Element subtree shows a single Button that has a different ID
// and different x-y coordinates:
// Button, 0x15dc12e50, {{0.0, 418.3}, {65.3, 20.3}}, label: 'Click me'
btn.tap() // <-- This tap now works on the blue button?? Without requerying?
print(app.debugDescription)
// The red button reappears, but with a different ID (which makes sense).
}
}
Why does the second tap work, even though it's a different control? This must mean that SwiftUI is automatically re-running the XCUIElementQuery to find the button that matches "Click me". Apparently the variable btn isn't linked to the control with the ID 0x14e40d290. Does this mean XCUIElement actually represents an XCUIElementQuery? I expected it to require me to explicitly re-run the query like this,
btn = app.buttons["Click me"]
prior to running the 2nd tap, or the tap would've said that btn was no longer available.
The final print of the Element subtree shows that the red button has a different ID now. This makes sense, because when SwiftUI redraws the red button, it's not the same instance as the last time. This is explained well in the WWDC videos. Nevertheless, at the moment I connected the variable "btn" to the control, I thought there was a tighter affiliation. Maybe UI testing has to behave this way because SwiftUI redraws controls so frequently?
I'm (attempting) switching over my AppDelegate macOS app to the SwiftUI lifecycle - but can't seem to find out how to handle the CommandMenu. I just want to delete these default menu items (Fie, Edit, View, etc...). In the past, I would just delete them from the Storyboard - but I'm not using a storyboard here. Is there a way to delete these items in SwiftUI?
The items I want to delete:
I know how to add new items via:
.commands {
MyAppMenus()
}
But that just adds them inline with the existing menu items.
swiftUI -- override AppDelegate with your custom:
#main
struct PixieApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
///........
}
code of appDelegate:
final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillUpdate(_ notification: Notification) {
if let menu = NSApplication.shared.mainMenu {
menu.items.removeFirst{ $0.title == "Edit" }
menu.items.removeFirst{ $0.title == "File" }
menu.items.removeFirst{ $0.title == "Window" }
menu.items.removeFirst{ $0.title == "View" }
}
}
}
result:
Until SwiftUI adds more support for adjusting menus, I think you have to worry about SwiftUI reseting the NSApp.mainMenu whenever it updates a window.body. I haven't tried every method for adjusting the mainMenu, but of the methods I tried, the flaw was that SwiftUI seems to have no check for whether it last set NSApp.mainMenu or if something else did.
So however you are managing the menu, update it after SwiftUI has.
Use KVO and watch the NSApp for changes on .mainMenu. Then make your changes with a xib, or reseting the whole thing, or editing SwiftUI's menus.
Example:
#objc
class AppDelegate: NSObject, NSApplicationDelegate {
var token: NSKeyValueObservation?
func applicationDidFinishLaunching(_ notification: Notification) {
// Adjust a menu initially
if let m = NSApp.mainMenu?.item(withTitle: "Edit") {
NSApp.mainMenu?.removeItem(m)
}
// Must refresh after every time SwiftUI re adds
token = NSApp.observe(\.mainMenu, options: .new) { (app, change) in
// Refresh your changes
guard let menu = app.mainMenu?.item(withTitle: "Edit") else { return }
app.mainMenu?.removeItem(menu)
}
}
}
struct MarblesApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some View {
//...
}
}
This seems to work in Xcode 13.4.1 with Swift 5 targeting macOS 12.3.
Hopefully Apple adds greater control soon. It seems Catalyst has other options. Or you can create a traditional AppKit app and insert the SwiftUI views into it.
You can remove command menu items through the AppDelegate file:
override func buildMenu(with builder: UIMenuBuilder) {
super.buildMenu(with: builder)
builder.remove(menu: .services)
builder.remove(menu: .format)
builder.remove(menu: .toolbar)
}
This thread on the Apple Developer forum might help as well: https://developer.apple.com/forums/thread/649096
CommandGroup(replacing: CommandGroupPlacement.appVisibility, addition: {})
I have created a custom menu on Google Sheets with two options:
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Custom')
menu.addItem('Add new store', 'openForm')
menu.addItem('Update Database', 'replacebackenddatabase') }
When a user selects "Update Database" I would like a message box to appear and ask for confirmation with "Do you want to proceed" and Yes/No basis. IF the user selects "Yes", I would like the function 'replacebackenddatabase' to run. If not, I would just like the message box to close and nothing to happen.
How can I do this?
Thank you!
Check out Prompt boxes here.
function replacebackenddatabase() {
var ui = SpreadsheetApp.getUi();
var result = ui.prompt(
'Ask a question...',
ui.ButtonSet.OK_CANCEL);
// Get the response...
var button = result.getSelectedButton();
var text = result.getResponseText();
if (button == ui.Button.OK) {
//If they clicked OK do something with 'text' variable
} else if (button == ui.Button.CANCEL) {
// If they clicked Cancel.
} else if (button == ui.Button.CLOSE) {
// If they closed the prompt
}
}
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Custom')
menu.addItem('Add new store', 'openForm')
menu.addItem('Update Database', 'checkResponse')
//.addSeparator()
//.addSubMenu(SpreadsheetApp.getUi().createMenu('Sub Menu')
//.addItem('One sub-menu item', 'subFunction1')
//.addItem('Another sub-menu item', 'subFunction2'))
.addToUi();
// do not set a variable to any chain of methods ending in .addToUi()
// after the Menu is created, the value of menu will be undefined
// the .addToUi() method does not return anything.
}
function checkResponse() {
var ui = SpreadsheetApp.getUi();
var response = ui.alert('Are you sure you want to proceed?', ui.ButtonSet.YES_NO);
if (response == ui.Button.YES) {
replacebackenddatabase();
} else {
Logger.log("The user wasn't sure.");
}
}
When I create multiple radio buttons through a for loop and using the function addTarget, how can I handle multiple radio buttons using addTarget by tag or sender in swift 3.0?
Here is the solution change it according to your requirement
(SWIFT 4)
// create button array to save all button
var buttonArray = [UIButton]()
func createButtons(){
//create 5 buttons
for i in 1...5 {
let button = UIButton(frame: CGRect(x: 0, y: i*55, width: 130, height: 50))
//set same background for each button
button.backgroundColor = UIColor.darkGray
// set different ittle for button
button.setTitle("Button No \(i)", for: .normal)
// set different tag to differentiate betwwen button
button.tag = i
// set same target for each button
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
buttonArray.append(button)
//add all buttons to main view
view.addSubview(button)
}
// we make first button of different color so that it can act as radio button
let firstBtn = buttonArray[0]
firstBtn.backgroundColor = UIColor.green
}
#objc func buttonTapped(_ tappedBtn : UIButton){
// here we get tapped button so from array of button we match the selected one using tag and make it of different color all all other buttons of some different color
for button in buttonArray {
if(button.tag == tappedBtn.tag){
button.backgroundColor = UIColor.green
}else{
button.backgroundColor = UIColor.darkGray
}
}
}
Swift Newbie here, trying to learn the code. I have a javascript background though. I've designed a simple interface using nested stackviews in the storyboard:
The hierarchy is
Vertical Stack
-Horizontal Stack
-Vertical Stack
Label
Textfield
-Vertical Stack
Label
TextField
I've basically set up a table like display, that i want to replicate everytime a user clicks the "Add Member button" It needs to duplicate what's there and add it top stack. Add a New horizontal stack composed of two vertical stacks with the label and text fields.
When I press the button nothing happens. No errors either. I used a print to confirm the button event is firing.
here is the controller code:
class createCrewController: UIViewController{
//MARK: Properties
var memberList:UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
memberList = view.viewWithTag(1) as! UIStackView!;
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var rowArray = [UIStackView]()
//MARK: Actions
#IBAction func addMemberBTN(_ sender: UIButton) {
//Create name and TextField
let nameLabel = UILabel();
nameLabel.text = "Name";
let nameTextField = UITextField();
nameTextField.placeholder = "Enter Name";
let phoneLabel = UILabel();
phoneLabel.text = "Name";
let phoneTextField = UITextField();
phoneTextField.placeholder = "Enter Phone";
//Insert them into vertical Stack
let nameRow = UIStackView(arrangedSubviews: [nameLabel,nameTextField]);
nameRow.axis = .vertical;
let phoneRow = UIStackView(arrangedSubviews: [phoneLabel,phoneTextField]);
phoneRow.axis = .vertical;
let columnView = UIStackView(arrangedSubviews:[nameRow,phoneRow]);
columnView.axis = .horizontal;
self.memberList.addSubview(columnView);
}