I'm trying to have observe running constantly in the app background which will trigger custom action if Accessibility Bold Text feature has been enabled via Mobile Device Settings.
My understanding is I need to add observe to the default notification center and the name of the notification is 'boldTextStatusDidChangeNotification'.
Can someone advice on the code sample for this?
You can check the state of many accessibility options thanks to a bunch of events provided by the system.
Add an observer as follows:
NotificationCenter.default.addObserver(self,
selector: #selector(methodToBeCalled(notification:)),
name: UIAccessibility.boldTextStatusDidChangeNotification,
object: nil)
... and create the method to be fired when the appropriate event occurs:
#objc private func methodToBeCalled(notification: Notification) {
//Create your actions here.
}
If you want further information or check a complete list of these events with code snippets, I suggest to take a look at this site. 👍
Related
In my application, I have a home screen. Anytime this screen is loaded, it needs to make a network request (currently using Alamofire 5.2) to fetch the latest data needed to display. I am running into this issue that I believe has to do with my implementation of view lifecycles, but I am not sure how to get around it to achieve my desired effect.
Scenario 1
override func viewDidLoad() {
NotificationCenter.default.addObserver(
self,
selector: #selector(wokeUp),
name: UIApplication.didBecomeActiveNotification,
object: nil)
}
#objc func wokeUp() {
pageLoaded()
}
func pageLoaded(){
// network requests made here
}
deinit {
NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
}
Here I am registering the observer within viewDidLoad. The reason I need this is many of our users will not close the application. We've found on more than a few occasions that they will let the phone sleep while the application is open, so we need to make this request when the phone is woken up and immediately back at this screen. the didBecomeActiveNotification seems to be what takes care of that.
Scenario 2
override func viewDidAppear(_ animated: Bool) {
pageLoaded() // same network request as example above
}
We also need to call this request within viewDidAppear, as there are quite a few flows where the user is brought back to this home page from another view in the application, and it's important that the request is made here as well (what the user does in the other flows has an impact of what shows up here, so we have to make sure it's updated).
The problem is that what I am finding is these two scenario will occasionally clash - our server essentially gets the same request twice, which is not ideal and causing issues. I've noticed the majority (if not all) of the problems occur when opening the application when it's no longer in memory (viewDidLoad gets called); the case of bringing the app from the background to foreground while it's still in memory is working as expected, but I have no idea what other implementation I could take to cover all of my bases here. Any insight would be appreciated.
Why not just add a simple boolean flag to your networking logic to make sure only 1 request gets fired. e.g.
class SomeViewController {
private var isFetching = false
...
func pageLoaded() {
guard isFetching == false else {
return
}
isFetching = true
// do some networking
// ....
// inside the callback / error cases
isFetching = false
}
}
depending on how big your app is, if you have many requests and/or the same request being fired on many screens. Move all your networking to another class and have this logic inside the network service rather than the viewController
Issue: Different Behavior In 3 Different Contexts
Ok so Ok, in iOS it seems three different things can happen regarding Push Notifications:
When a Push Notification is received when the app is not in the foreground
something shows up in Notification Center
if the app is opened by tapping the notification, either AppDelegate.DidReceiveRemoteNotification(...) or AppDelegate.ReceivedRemoteNotification(...) is called, apparently depending on which one is implemented (??).
if the app is opened without tapping the notification, only AppDelegate.WillEnterForeground(...), is called, without any explicit mention of the notification, and nothing else happens to acknowledge that a notification was received.
When a Push Notification is received when the app is in the foreground it causes the UNUserNotificationCenterDelegate, if there is one, to execute UNUserNotificationCenterDelegate.WillPresentNotification(...).
Approach: Routing To One Method From All Contexts
So to cover all bases with Push I need to implement something in all three methods: AppDelegate.DidReceiveRemoteNotification(...) / AppDelegate.ReceivedRemoteNotification(...), AppDelegate.WillEnterForeground(...), and UNUserNotificationCenterDelegate .WillPresentNotification(...).
Here are some stubs to show my approach to all this.
First, I created a custom UNUserNotificationCenterDelegate, with a Shared static member:
public class IncomingNotificationHandler : UNUserNotificationCenterDelegate
{
public static IncomingNotificationHandler Shared = new IncomingNotificationHandler();
...
}
Second, inside that class I made a handler that I can route to in every case (again, this is just a stub for debugging purposes):
//sets all parameters to null by default, so it can be called from methods
//that don't know anything about notifications:
public void HandleNotificationsIfAny(UIApplication application = null,
NSDictionary userInfo = null,
Action<UIBackgroundFetchResult> completionHandler = null)
{
//checks if userInfo is null, and logs its conclusions about that:
if (userInfo == null)
{
//In the null case, we can get pending notifications from
//UNUserNotificationCenter:
UNNotification[] pendingNotifications = new UNNotification[] { };
UNUserNotificationCenter.Current.GetDeliveredNotifications(returnedValue => pendingNotifications = returnedValue);
//Then we log the number of pending notifications:
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): delivered notification count: " + pendingNotifications.Length);
//And make note of where this was probably called from:
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): may have been called from this.WillPresentNotification(...) OR AppDelegate.WillEnterForeground(...)");
return;
});
}
else
{
//In the non-null case, we log the userInfo
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): just got info: " + userInfo);
//And make note of where this was probably called from:
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): may have been called from AppDelegate.DidReceiveRemoteNotification(...)");
}
}
Third, inside the same class, I implemented the single method that's required by UNUserNotificationCenterDelegate, and I routed to the handler from it:
public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
HandleNotificationsIfAny();
}
Fourth, and last, inside AppDelegate, I routed to the same handler from both relevant methods:
//I prefer using DidReceiveRemoteNotification because in my experience
//the other one is sometimes not reliable:
public override void DidReceiveRemoteNotification(UIApplication application,
NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler)
{
//Simply passing on all the parameters called in this method:
IncomingNotificationHandler.Shared.HandleNotificationsIfAny(application, userInfo, completionHandler);
}
//WillEnterForeground also calls the handler without any parameters
//because it doesn't automatically know anything about notifications:
public override void WillEnterForeground(UIApplication application)
{
IncomingNotificationHandler.Shared.HandleNotificationsIfAny();
}
With that, as it stands, I think I'm handling a notification event in the same way no matter how my app is alerted about it, and even when it's not alerted at all.
Does anyone know if I now have it covered, or if there's some other cases I need to handle?
For the first scenario: AppDelegate.ReceivedRemoteNotification
It reflects the objective c method: application:didReceiveRemoteNotification:, but this event has been deprecated since iOS 10: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623117-application?language=objc. So I think there's no need to handle this event.
For the second scenario: AppDelegate.DidReceiveRemoteNotification
You can still utilize it to handle notifications now if you haven't implemented UNUserNotificationCenter and please notice it is only valid after iOS 7+. Moreover, this event will be triggered when app is on the foreground and if your app is on the background, this event only fires when the user clicks the notification to open your application. And there's no way to access the notification's information if the user clicks the icon to open the app.
I don't think handling AppDelegate.WillEnterForeground is a good approach, as it will be called each time the app resumes from background to foreground even though there are no notifications.
For the scenario: UNUserNotificationCenterDelegate
You could only use this feature after iOS 10. Once you have implemented it on the device iOS 10+, DidReceiveRemoteNotification and ReceivedRemoteNotification will never be triggered. WillPresentNotification will be called when app is on the foreground. DidReceiveNotificationResponse will be fired when the app is on the background and user clicks notifications to open it.
As a conclusion, if you want to easily handle the notification AppDelegate.DidReceiveRemoteNotification is enough. If you want to consume the new features of UNUserNotificationCenter, AppDelegate.DidReceiveRemoteNotification and UNUserNotificationCenter should be both involved. The prior one for the iOS 7+ devices and the later one for iOS 10+ devices.
Update:
For iOS 10+, you could use UNUserNotificationCenter.Current.GetDeliveredNotifications to obtain the notifications that are still displayed in Notification Center. And if you only want to support iOS version 10 and later. I think UNUserNotificationCenter is enough, there's no need to implement AppDelegate.DidReceiveRemoteNotification(...) or AppDelegate.ReceivedRemoteNotification(...).
If the app is on background / killed state and the user clicks notification to
open the app, DidReceiveNotificationResponse will be called.
If the
user clicks icon to open your app and the app is killed you should
place your logic code in FinishedLaunching.
If the user clicks icon
to open your app and app is on background, you can handle
WillEnterForeground as you did before.
If the app is on foreground,
handle WillPresentNotification.
I have UI Tests target for testing MyApp. To test specific MyApp conditions I need to post notifications from UI Test target to MyApp target. To post notification from UI Test target function I am using this:
NSNotificationCenter.defaultCenter().postNotificationName(name, object: nil, userInfo: aUserInfo)
It looks that this notification never reaches observer from UI Test target, but it works fine when posting this notification from MyApp target.
How to post notification from UI Target to MyApp target?
Using Xcode 7.
Have similar problem (trying to ensure NSNotification is being posted after certain UI action). Did small research on this.
NSNotification not being received because UI test and app are running in different processes. NSNotification cannot go through process bounds, and NSDistributedNotificationServer is not available on iOS. So, currently there is no default and easy way to post NSNotifications between UI test suite and app instance.
However, there is some ways to communicate between processes, and maybe write small wrapper or even NSDistributedNotificationCenter iOS surrogate for testing purposes. Check out this great article from Realm: https://academy.realm.io/posts/thomas-goyne-fast-inter-process-communication/
I wanted to use UI Testing to test for memory leaks and to do this I wanted to get informed in the UI test case when ever a view controller's deinit is called. So I came up with this to provide the IPC mechanism:
/**
Provides simple IPC messaging. To use this class you need to include
#include <notify.h>
in your bridging header. (Ab)uses UIPasteboard for message delivery, ie.
should not be used in production code.
*/
public class SimpleIPC {
/// Event notification name for libnotify.
private static let notifyEventName = "com.foo.SimpleIPC"
/// libnotify token.
private var token: Int32 = 0
/// Starts listening to the events
public func listen(callback: (String? -> Void)) {
notify_register_dispatch(SimpleIPC.notifyEventName, &token, dispatch_get_main_queue()) { token in
callback(UIPasteboard.generalPasteboard().string)
}
}
public class func send(message: String) {
UIPasteboard.generalPasteboard().string = message
notify_post(SimpleIPC.notifyEventName)
}
deinit {
notify_cancel(token)
}
}
It uses a combination of libnotify and UIPasteboard for the notification + data delivery. Usable for 1-way communication as is, for 2-way either make the payload include a sender token or use 2 instances with parametrized libnotify event names.
Matti
Is this for a Mac app or an iOS app? For a Mac app you can use NSDistributedNotificationCenter as such.
In your subscriber:
NSDistributedNotificationCenter.defaultCenter().addObserver(object, selector: #selector(ObjectsClass.myFuncWithoutParentheses), name: "uniqueString", object: nil)
In your publisher:
NSNotificationCenter.defaultCenter().postNotificationName("uniqueString" object:nil)
Is it correct to use NSNotificationCenter as the only handler for all the events inside the app?
Is it fine if I put a list of all possible events like this:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loginUser:", name: "userWillLogin", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "showError:", name: "userLoginError", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadMainScreen:", name: "userDidLogin", object: nil)
// Is it ok if I put 10 or 20 more event listeners here?
}
Or is the intention of this functionality different? I find it appealing to use event listeners and handlers in this manner, but not sure if it's the recommended way to pass events and data across the app regarding performance and best practices.
When using only notifications you'll find yourself in troubles to debug what happened that something is broken. Notifications make tracking down bugs hard, because not always it's obvious what was the flow. There are situations when you should use them, but don't do it always via notifications - it's less readable.
The best way would be to use delegate pattern / blocks for 1:1 relationships and NSNotificationCenter / KVO for 1:n relationships.
Check out this link: http://nshipster.com/nsnotification-and-nsnotificationcenter/
I have an app that uses the darwin notify center.
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
NULL, // observer
displayStatusChanged, // callback
CFSTR("com.apple.iokit.hid.displayStatus"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
I would really like to capture volume button presses in the background. I found these notifications listed
http://iphonedevwiki.net/index.php/SpringBoard.app/Notifications
Is there a way to trigger methods on SBMediaVolumeChangedNotification?
The SBMediaVolumeChangedNotification is not a Darwin notification, it is a default local notification. To capture it, you would need a common notification observer, like this one (in Swift):
NSNotificationCenter.defaultCenter().addObserver(self, selector: "volumeChanged:", name: "SBMediaVolumeChangedNotification", object: nil)
However, the documentation states that:
The object is an SBMediaController instance.
So I think we would need to pass a SBMediaController instance to the observer, as the object, to make it work. Since the SBMediaController class seems to be inaccessible from the SDK, I don't think there is a way to use that specific notification. I have tried without the object, but it didn't work for me.