How can I update my iOS Today widget from the containing app? - ios

I'm including a Today extension with the next release of my iOS app. The content of the widget updates only when the user makes a specific change to the database managed by the containing app.
Is there a way to send some kind of signal from the containing app to the Today widget process to let it know that its data has been invalidated and that it should reload itself the next time the user pulls down the Notification Center?

You don't need to update the widget yourself, iOS tries to update it periodically. Every time iOS does that, a function in your widget gets called. This is the function:
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!)
If your data has changed, then call
completionHandler(.NewData)
else, if your data hasn't changed, call
completionHandler(.NoData)
That's it! And don't make anything inside that function that needs a lot of time, because iOS may "kill" your widget then.

Related

Is there a way to stop iOS today extension sending API requests in background?

I'm trying to stop sending API requests in background, when the today extension is not visible. API requests are pretty expensive, so I would like to optimize the number of sent requests. Where should I put API request so it will be called only when the today extension become visible and will not be called in background?
I have already tried to set NCUpdateResultNoData however viewDidLoad is called in background in that case. In viewDidLoad I send API request to update the today extension when it becomes visible.
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
completionHandler(NCUpdateResultNoData);
}
Well this is a tricky question, what you can try is to fire your request in viewWillAppear: but this means that your UI will probably not be ready in viewDidAppear: but you can handle this using an activity indicator.
From the docs:
// If the widget has local state that can be loaded quickly, it should
do so before returning from ‘viewWillAppear:’. // Otherwise, the
widget should ensure that the state and layout of its view when
returning from 'viewWillAppear:’ will match that of the last time it
returned from 'viewWillDisappear:', transitioning smoothly to the new
data later when it arrives.”

How to send a Firebase Notification when new data is available to user?

I have a coaching app that has a section where I can push realtime updates out to the players like: "No Practice - Do to inclement weather, practice will be pushed until Friday"
I have been trying to figure out how to send automatic notifications when I update this UpdatesTableView with a new post. Like "New Update Posted".
I post my updates to the Firebase Database. There must be a way to listen for changes and when there is to push a notification out to all the users?
I already have firebase notifications set up in my app but I have to utilize the Firebase console to push these notifications every time i push an update. Does anyone know how to automate this? Thanks!
You can easily do that by listening/Observing to any data change at a particular location in firebase. If new child is added to that path, associated block will be called.
In your case, you can observe UpdatesTableView. and whenever you post any update, call the block which will send notification to all users.
If you are using Swift:
func observe(_ eventType: FIRDataEventType, with block: #escaping (FIRDataSnapshot) -> Void) -> UInt
If you are using Objective C:
- (FIRDatabaseHandle)
observeEventType:(FIRDataEventType)eventType
withBlock:(nonnull void (^)(FIRDataSnapshot *_Nonnull))block;
According to official firebase documentation :
observeEventType:withBlock: is used to listen for data changes at a particular location. This is the primary way to read data from the
Firebase Database. Your block will be triggered for the initial data
and again whenever the data changes.
And, Whenever you would like to stop listening to data changes, you can Use removeObserverWithHandle
Use removeObserverWithHandle: to stop receiving updates. - parameter:
eventType The type of event to listen for. - parameter: block The
block that should be called with initial data and updates. It is
passed the data as a FIRDataSnapshot. - returns: A handle used to
unregister this block later using removeObserverWithHandle:
For more and detailed information, Read iOS firebase#Attaching Observers to read data Documentation.
Also, For sending Notifications to users effieciently, you can use Firebase Notification. Have a look at it. i dont know about your usecase properly, But i think this will help.
I also stucked with same problem where I wanted to show notification to users whenever data changes in Firebase irrespective of application in foreground or background.
I achieved it by binding ChildEventListener with a service which keeps running in background. At every childAdded event data is stored in sqlited db and a new notification object is created and shown to user.

How to check update API rest

My iOS App call a API with Restful webservice API. Then I used data and saved Json data on my disk.
On next time open app, I want to check API, if having update, the app will call and be reveice data, if not update I will use data on disk.
How to check API update? Thankyou.
If you have influence on the API then you can make a call that returns for example a timestamp of latest update. If it is newer than the one you have on your device then download the new data. Because you probably want to avoid downloading the (same) data twice. Is that what you were asking?
You can use this method in AppDelegate, which will be called when app come back to foreground. Call the API and check it's last modification date of you'r data.
func applicationWillEnterForeground(application: UIApplication) {
}

NSUserDefaultsDidChangeNotification and Today Extensions

I am developing an iPhone app with a Today Extension. The app has a Model module that loads from/saves toNSUserDefaults. Since I want this information to be available to both the main app and the extension, I use an app group:
let storage = NSUserDefaults(suiteName: "group.etc.etc.etc...")
Both the app and the extension can access the information without any problem.
The main app occasionally might create a local notification to present to the user. That notification has two actions associated with it (UIUserNotificationAction). One of those actions triggers some code run on the background on the main app. That code changes the NSUserDefaults information and triggers a synchronization. My code is something like this:
func application(application: UIApplication, handleActionWithIdentifier id: String?, forLocalNotification not: UILocalNotification, completionHandler: () -> ()) {
// Interact with model here
// New information gets saved to NSUserDefaults
userDefaultsStorage.synchronize()
completionHandler()
}
Now, on the Today Ext. I naturally observe any changes made to the information on NSUserDefaults so that I can reload the interface on the widget:
override func viewDidLoad() {
super.viewDidLoad()
// ...
NSNotificationCenter.defaultCenter().addObserverForName(NSUserDefaultsDidChangeNotification, object: nil, queue: NSOperationQueue.mainQueue()) { _ in
self.reload()
}
}
Now, here's my issue:
The main app schedules a UILocalNotification. I open the today view and look at my today widget.
When the notification fires, a banner appears on the top of the screen.
I slide down on that banner to reveal the two actions and I select the one that I mentioned earlier (the today widget is still live and on screen).
I know for a fact that the action runs correctly in the background, and that the changes are being made to the information on NSUserDefaults.
However, even though the today widget has been active and on screen all this time, no reload action is triggered. After further investigation, I can confirm that the NSUserDefaultsDidChangeNotification is not being fired (I placed a breakpoint and it did not trigger, and did some other checks as well).
I know the changes are being made by the notification action because if I force a reload of the widget (by closing and opening the today view) the widget updates correctly.
I have seen various tutorials online where the first thing they say is to listen to this notification and update the widget so that "the widget is in sync with NSUserDefaults". But the thing is that AFAICT this notification is absolutely useless! How come??
Note 1: When I change the information on NSUserDefaults from within the today widget the notification fires correctly.
Note 2: Debugging a today widget is absolutely horrible, btw. It is always necessary to tell Xcode to "Attach to process by name..." before it can react to breakpoints and crashes. And iOS is constantly creating a new process for the widget so I have to constantly tell Xcode to attach again.
From doc here:
Cocoa includes two types of notification centers:
The NSNotificationCenter class manages notifications within a single
process. The NSDistributedNotificationCenter class manages
notifications across multiple processes on a single computer.
Apparently the containing app and today extension are different processes, since when you debug today extension you want to attach containing app process, but NSNotificationCenter only work within a single process.
In order to communicate between containing app and extensions, you can use
Darwin Notify Center CFNotificationCenterthat works like NSDistributedNotificationCenter, which is only available for osx.
The idea is use a file inside the group folder that they share. in containing app, you write the data you want to send into the file, then post a CFNotification, which will be received by today extension.
In today extension, use CFNotificationCenterAddObserver to observer the CFNotification, upon receiving it, callback will be called, in which a NSNotification has to be posted due to callback is a C style function and "userInfo" cannot be passed in the CFNotification, after receiving this NSNotification object, it starts to read data from the file, which is used to update the today extension view in Notification center.
You can use this github code to implement force loading the today extension view. It works for me.
Here is a great post on this. http://www.atomicbird.com/blog/sharing-with-app-extensions
Another option is to use setHasContent function. When you schedule a local identifier, set has content to false to hide the view, in handleActionWithIdentifier set it to true to show the view. This way, when you stay in notification center, you will not see the view for a moment, but when you see it, it will be the updated data.
let widgetController = NCWidgetController.widgetController()
widgetController.setHasContent(false, forWidgetWithBundleIdentifier: "YourTodayWidgetBundleIdentifier")
But I think the whole problem is a rare case, which doesn't need to be fixed since you can get the updated data reloading the notification center or switch to notification tab and switch back to today tab.

Are Today Extension Ivars Reset Constantly?

I'm writing code to a new iOS 8 Today widget, but I noticed that each time that widgetPerformUpdateWithCompletionHandler: is called my ivars (created from #property) are reset. It's is like every time a new view controller is getting instantiated.
This makes it impossible to save data on memory between updates to the widget (while it is in the background, for example, and is called to update its content).
Is this normal behaviour, or a bug? Should I save my simple numbers to NSUserDefaults instead of relying on memory based data, which is being reset?
Your extension will not be running in between calls to widgetPerformUpdateWithCompletionHandler:. That method is called when iOS launches your extension in the background for you to fetch new data. The OS then captures an image of your extension (thats what the completion handler is for) to show as a sort of "launch screen" for your extension (when notification center is launched your extension isn't available immediately so it shows the image until it is). You likely want to use NSUserDefaults (or another method) to store cached data to load while waiting for updated data to come from a server.
In other words, the OS will launch your app periodically to let you fetch new data so that the user will always see updated data in notification center. You should cache this data in that method so that you can load your extension faster when it is launched for notification center. This is all discussed here.

Resources