Pressing on app notification always activates app window - swift

My application schedules notification and displays it well
NSUserNotificationCenter.default.delegate = self
let notification = NSUserNotification()
notification.deliveryDate = Date(timeIntervalSinceNow: TimeInterval(10))
notification.title = title
notification.actionButtonTitle = "Do something"
notification.soundName = notificationSound != "none" ? notificationSound : nil
notification.otherButtonTitle = "Close"
NSUserNotificationCenter.default.scheduleNotification(notification)
When I press on the button activation method is called well
func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) {
DDLogDebug("Did activate notification")
switch notification.activationType {
...
}
The problem is that opened window is activated after pressing on it that I don't need.
Application is agent (UIElement) with multiple windows opened. Tried non-agent application, the problem still persists - it activates main window
Any ideas?
UPDATE
Unfortunately, I didn't find the answer. I implemented my own mechanism of notifications that displays custom Panel

Related

How to Make macOS App Window Hidden When Closed and Reopened With Menu Bar Item?

I am developing a macOS app (using Swift & Storyboard) which window behaves like the Adobe Creative Cloud app. And I could not find the optimal solution after hours of research.
This means:
When the app launches, the main window shows up with various menus on the status bar, an icon appears in the dock, and an icon appears in the status bar.
When the user clicks the red X, the main window and the icon in the dock are hidden.
The main app window can be reopened by clicking the status bar icon. And the dock icon reappears.
My storyboard looks like this:
I have tried the following:
By setting Application is agent (UIElement) to YES, I was able to close the main app window while keeping the app alive. However, the app icon does not show up in the dock, and there are no menus in the left side of the status bar.
I was able to launch a new app window by clicking the status bar icon. But doing so simply opens a whole new window regardless of whether a window is already being presented (I only want one window to show up).
let storyboard = NSStoryboard(name: "Main", bundle: nil)
guard let window = storyboard.instantiateController(withIdentifier: .init(stringLiteral: "main")) as? WindowController else { return }
window.showWindow(self)
Much appreciation for anyone who can help!
Don't use the Application is agent approach, but change the activationPolicy of the NSApp.
To dynamically hide the icon after closing the (last) window use this in your AppDelegate:
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
NSApp.setActivationPolicy(.accessory)
return false
}
And use something simular to this to initialise your menubar icon and activate the window including a dock icon:
class ViewController: NSViewController {
var status: NSStatusItem?
override func viewDidLoad() {
super.viewDidLoad()
status = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
status?.button?.title = "Test"
status?.button?.action = #selector(activateWindow(_:))
status?.button?.target = self
}
#IBAction func activateWindow(_ sender: AnyObject) {
NSApp.setActivationPolicy(.regular)
DispatchQueue.main.async {
NSApp.windows.first?.orderFrontRegardless()
}
}
}

Showing notification banner on Mac with Swift

I tried to show up some notification banners on Mac OS with Swift. But i get them only in the notification center, not as banner.
Do you have an idea? Here my simple code:
func showNotification() -> Void {
let notification = NSUserNotification()
notification.title = "Title of notification"
notification.subtitle = "Subtitle of notification"
notification.soundName = NSUserNotificationDefaultSoundName
NSUserNotificationCenter.default.deliver(notification)
}
#IBAction func btnPressed(_ sender: NSButton) {
showNotification()
testLbl.stringValue = "Button was pressed"
}
You won't get a banner if your app is in the foreground.
Try using…
notification.deliveryDate = Date(timeIntervalSinceNow: 5)
NSUserNotificationCenter.default.scheduleNotification(notification)
and then switch to another app
If, at the time of sending the notification, the app sending the notification is focused, then the notification won't show as a banner. An app can only deliver a banner notification if it is not active in the foreground.
Your code works well when your app is not the main focus, I've just tested it.
So, because you send the notification on a button click, the app is focused when the notification is sent: the notification only goes to the Notification Center, it is not shown as a banner.
This is a rule made on purpose by Apple.

UNUserNotificationCenter don't work when app is killed

I just did change for my notification for iOs 10 and others:
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
let content = UNMutableNotificationContent()
content.body = notifMessage!
content.sound = UNNotificationSound.default()
// Deliver the notification in five seconds.
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 5, repeats: false)
let request = UNNotificationRequest.init(identifier: "Upload", content: content, trigger: trigger)
// Schedule the notification.
let center = UNUserNotificationCenter.current()
center.add(request)
}
} else {
let notification = UILocalNotification()
notification.alertBody = notifMessage
notification.fireDate = NSDate() as Date
notification.soundName = UILocalNotificationDefaultSoundName
UIApplication.shared.scheduleLocalNotification(notification)
}
When I run my apps on my device by connect it with the USB it works, but only when the app is in background, it doesn't work when:
i kill the app
when the app is displayed
If you kill the app (by double tapping on home button and then swiping up), that not only terminates the app, but prohibits further background operation of the app (until the user fires it up again). You have to just press the home button and let the app be jettisoned via the normal memory recovery processes. Or you can, for testing purposes, programmatically crash app. But you cannot use springboard (the double tap of home button trick), because affects the permissible background modes of an app.
Regarding the notification when the app is displayed vs if the user taps on the notification vs user manually starts app having disregarding a notification, those are all conveyed to the app in different ways. See the Responding to Notifications and Events section of the UIApplicationDelegate documentation. Or see the App Programming Guide for iOS: Background Execution for general information about background operation.
There are a couple of errors in your code :
1. Title for the notification is missing. You have added the body and sound to the content but the title is missing. Title is a must and if you don't add the title the notification won't show.
content.title = "Some Title"
Do not use init to initialise. These functions can be re-written as :
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest.init(identifier: "Upload", content: content, trigger: trigger)
The identifier value is same. The identifier value needs to be different for every notification that you schedule. Notifications with same identifier do not appear.
let request = UNNotificationRequest(identifier: some_value, content: content, trigger: trigger)
Trigger time. The time that you specifying in the trigger is for 5 seconds. Which may be less for you to close the app and test for the notification. Just to be on the safe side, make sure this value is at least 1 minute so that you can properly test out if it is working or not.
Hope this helps.

Swift: How to disable a button

I'm trying to disable a button for certain amount of time, but have an issue. My procedure is below:
I have 3 buttons and all buttons are enabled
After one button is clicked, disable all buttons. In the meantime, sending data via Bluetooth...
Enable all buttons after finishing sending data.
My goal is to prevent button click when sending data via Bluetooth. I tried to use Button.userInteractionEnabled = false and Button.enabled = false, but it will go to button action handler(The one that I press during data sending period) again whenever I enable button after finishing sending data. Does any one know how to disable buttons permanently for a certain amount of time?
What you have to do is disable the button upon clicked and then somehow enable it when the data transfer it's done.
If this data transfer is called asynchronously, it will probably have a parameter where you can send in a completion block:
button.isUserInteractionEnabled = false
sendData(data) {
success in
button.isUserInteractionEnabled = true
}
If it doesn't accept a completion block as a parameter, it might work in a different way, such as using notifications (firing a notification with a specific name):
button.isUserInteractionEnabled = false
sendData(data)
// adding the observer that will watch for the fired notification
NotificationCenter.default.addObserver(self, selector: #selector(self.didFinishSendingData(_:)), name: Notification.Name(rawValue: "NOTIFICATION NAME GOES HERE"), object: nil)
func didFinishSendingData(_ notification: Notification?) {
button.isUserInteractionEnabled = true
}
We could definitely help more if you post a sample of your code.
Why can you achieve this on the main thread with an activityIndicator like below:
let activityIndicator = UIActivityIndicatorView()
activityIndicator.frame = view.frame
activityIndicator.center = view.center
activityIndicator.activityIndicatorViewStyle = .gray
activityIndicator.hidesWhenStopped = true
view.addSubview(activityIndicator)
//start activity indicator
activityIndicator.startAnimating()
//send your data via bluetooth on main thread
DispatchQueue.main.async {
//put your sending via bluetooth code here with a completion handler when completes
//then in the completion handler, put below line
activityIndicator.stopAnimating()
}

Check if NSAlert is currently showing up

I am using an NSAlert to display error messages on the main screen of my app.
Basically, the NSAlert is a property of my main view controller
class ViewController: NSViewController {
var alert: NSAlert?
...
}
And when I receive some notifications, I display some messages
func operationDidFail(notification: NSNotification)
{
dispatch_async(dispatch_get_main_queue(), {
self.alert = NSAlert()
self.alert.messageText = "Operation failed"
alert.runModal();
})
}
Now, if I get several notifications, the alert shows up for every notification. I mean, it shows up with the first message, I click on "Ok", it disappears and then shows up again with the second message etc... Which is a normal behaviour.
What I would like to achieve is to avoid this sequence of error message. I actually only care about the first one.
Is there a way to know if my alert view is currently being displayed ?
Something like alert.isVisible as on iOS's UIAlertView ?
From your code, I suspect that notification is triggered in background thread. In this case, any checks that alert is visible right now will not help. Your code will not start subsequent block execution until first block will finish, because runModal method will block, running NSRunLoop in modal mode.
To fix your problem, you can introduce atomic bool property and check it before dispatch_async.
Objective-C solution:
- (void)operationDidFail:(NSNotification *)note {
if (!self.alertDispatched) {
self.alertDispatched = YES;
dispatch_async(dispatch_get_main_queue(), ^{
self.alert = [NSAlert new];
self.alert.messageText = #"Operation failed";
[self.alert runModal];
self.alertDispatched = NO;
});
}
}
Same code using Swift:
func operationDidFail(notification: NSNotification)
{
if !self.alertDispatched {
self.alertDispatched = true
dispatch_async(dispatch_get_main_queue(), {
self.alert = NSAlert()
self.alert.messageText = "Operation failed"
self.alert.runModal();
self.alertDispatched = false
})
}
}
Instead of run modal you could try
- beginSheetModalForWindow:completionHandler:
source: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSAlert_Class/#//apple_ref/occ/instm/NSAlert/beginSheetModalForWindow:completionHandler:
In the completion handler set the alert property to nil.
And only show the alert if the alert property is nil ( which would be every first time after dismissing the alert).
EDIT : I don't see the documentation say anything about any kind of flag you look for.