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)
Related
We're developing a video calling application and rely on APNS VoIP notifications. Due to our design it sometimes happens that the VoIP notification arrives to the device when the call has already ended or the recipient has declined it (missed call for example).
The problem with that approach is that iOS requires you to report all incoming VoIP notifications in some way - either that there's new incoming call or the current call has been updated.
Is there any way to ignore the unnecessary/redundant VoIP notification? The current approach I came up with is really nasty i.e. first I report new unknown incoming call and then immediately I report its end. This causes the native call UI to be shown for a brief moment.
private var provider: CXProvider?
private var uuid = UUID()
//...
func ignorePushNotification() {
self.provider?.reportNewIncomingCall(with: self.uuid, update: CXCallUpdate(),
completion: { error in
// ignore
})
self.provider?.reportCall(with: self.uuid, endedAt: nil, reason: reason)
}
Unfortunately, there isn't a better way to ignore a VoIP Push. But I suggest you to improve the code as follows.
func ignorePushNotification() {
provider?.reportNewIncomingCall(
with: self.uuid,
update: CXCallUpdate(),
completion: { error in
self.provider?.reportCall(with: self.uuid, endedAt: nil, reason: .failed)
})
}
Given the asynchronous nature of CallKit, if you don't do that, it could happen that the end of the call executes before the reportNewIncomingCall. It's probably very rare but it could happen.
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. 👍
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.
My application is to communicate between about 5 iPads and update according to the data received through multipeer connectivity framework.
One of the iPad do the followings: It get its current location and send it to other peers. The peers update the UI according to the location received.
I found that there is a problem that when the iPad need to send the packet to the others, it cause its UI did not update immediately. The UI events stuck until it finished sending the packet. Since the location keep on updated, all these events fired continuously.
I have tried to place the send packet in a thread:
let bgQueue = DispatchQueue(label: "hk.edu.polyu.isurf.sendqueue", qos: .utility, attributes: .concurrent)
And then put the code for sending packet inside this bgQueue:
func sendPacket {
bgQueue.async {
// create packet, and send
}
}
My location update code will cause this:
func receiveLocation() {
sendPacket()
updateUI()
}
How can I improve the efficiency? The UI basically cannot update now, it is of serious "lagging".
I have tried to change the type of the bgQueue, but no improvement.
Thank you.
You should make sure that your UI is being updated on the main queue.
If your trying to update UI in the session didRecieve function, this is a function that runs in the background. This means any UI changes made inside of it need to be put inside a Dispatch to the main queue so its updates take place immediately.
DispatchQueue.main.async
{
// UI updates go here
}
I'm writing an iOS extension that extends NEPacketTunnelProvider in the NetworkExtension framework released in iOS 9. I'm running into a situation where iOS is killing the extension once hits 6MB of memory used.
In a regular iOS app, there are two ways to detect memory warnings and do something about it. Either via [UIApplicationDelegate applicationDidReceiveMemoryWarning:(UIApplication*)app] or [UIViewController didReceiveMemoryWarning]
Is there a similar way to detect memory warnings within an extension? I've searched up and down the iOS extension documentation but have come up empty thus far.
ozgur's answer does not work. UIApplicationDidReceiveMemeoryWarningNotification is a UIKit event and I haven't found a way to get access to that from an extension. The way to go is the last of these options: DISPATCH_SOURCE_TYPE_MEMORYPRESSURE.
I've used the following code (Swift) in a Broadcast Upload Extension and have confirmed with breakpoints that it is called during a memory event right before the extension conks out.
let source = DispatchSource.makeMemoryPressureSource(eventMask: .all, queue: nil)
let q = DispatchQueue.init(label: "test")
q.async {
source.setEventHandler {
let event:DispatchSource.MemoryPressureEvent = source.mask
print(event)
switch event {
case DispatchSource.MemoryPressureEvent.normal:
print("normal")
case DispatchSource.MemoryPressureEvent.warning:
print("warning")
case DispatchSource.MemoryPressureEvent.critical:
print("critical")
default:
break
}
}
source.resume()
}
I am not very familiar with the extensions API, however my basic hunch says that you can register any of your object as observers of UIApplicationDidReceiveMemoryWarningNotification from within that class:
NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationDidReceiveMemoryWarningNotification,
object: nil, queue: .mainQueue()) { notification in
print("Memory warning received")
}