How to call main container app method from extension keyboard? - ios

Is there any way to call container app method from extension keyboard?
I tried by using following NSNotificationCenter, But still its not working.
I have added NSNotificationCenter and method into my main container app.
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(method),
name: "methodClicked",
object: nil)
func method(){
//doing stuff
}
and in my app extension keyboard I'm checking if container app is currently visible then post that notification NSNotificationCenter.defaultCenter().postNotificationName("methodClicked", object: nil)
Please post answer in Objective-c or Swift if anyone know proper way to do it. Thanks in advance.

I tried to do the same thing and realized (for now) that the only way to make an extension call a function on it's containing app is via remote server.
that means when an event on the extension occur you can make a call to your remote server that will notify your app (using notification).
Probably an overkill but it should work.

Related

Determine when user returns from deep-linked iOS Settings app?

I use deep-linking to let my users navigate to my app's page in the iOS Settings app, where I allow the user to set how many Core Data backups they want to store.
Settings is kind enough to provide a link back to the app, which is awesome, but I'd like to know when the user comes back specifically from Settings so that I can then prune the Core Data backups.
Is there a notification I can observe, or some other way I can tell when the app comes to the foreground specifically after leaving Settings?
I'm programming in Swift 4.2. Thanks!
As suggested by #mschmidt, the answer was simple; I just needed to register an observer for UserDefaults.didChangeNotification. Something like the following:
NotificationCenter.default.addObserver(
self,
selector: #selector(userDefaultsDidChange),
name: UserDefaults.didChangeNotification,
object: nil
)
#objc private func userDefaultsDidChange() {
coreData.pruneBackups()
}

Managing local notifications for iOS framework with beacons

I have been working on iOS framework (in Swift) which contains beacon functionality. I made it work except that I'm not sure how to handle scenario where I'm in foreground and I encounter multiple beacons in short duration.
If I want didReceive delegate method to show Alert for beacon while in foreground, and if I encounter many beacons it will not work nicely (alerts will display one over another). Is there some solution to queue notifications somehow?
Also I would like to know, if there is a way to make all that logic for receiving local notifications inside my framework?
I have to be able to support iOS-8.0 so I can't use Notification Center which is available from iOS-10.0
Can I create some class which would act like appdelegate (probably some class which would implement UIApplicationDelegate inside framework), is something like that possible?
I want to put as much code as I can inside framework itself so that it won't be too messy job for someone to include that framework with all functionality.
After some time I figured out a way to make this. I'm beginner in iOS with few months experience so I can't say if this solution is the best but it works for me.
I found a way to implement all push and local notification related delegate methods from framework. Basically if main application wants framework to take care of notifications without having to implement anything yourself, on runtime framework will dynamically implement certain UIApplicationDelegate methods for AppDelegate.swift class (or whatever is your AppDelegate class called).
I used object_getClass(UIApplication.shared.delegate!) to get the main class.
Then I used func class_addMethod(_ cls: AnyClass!, _ name: Selector!, _ imp: IMP!, _ types: UnsafePointer!) -> Bool
to implement delegate methods for push and local notifications from inside framework so now it comes down to write one or two lines to use framework entirely with working notifications and beacon location services instead of having to write a lots of code outside framework.
As for handling notifications in foreground mode I made that work by adding them to queue so that if more than one notification comes, and wants to be displayed in foreground regime, only one will be displayed by UIAlertController and the rest will be put in queue and sent again but with some small delay (I set fire date to be some value which I thought was appropriate in my case) after user makes an action regarding that first notification which was the only one presented.
These are just my ideas for the problems I had, if someone shows interest for these solutions I will write more details if needed. I will also gladly accept any criticism.

EAAccessoryDidConnectNotification doesn't fire

I'm trying to learn about CoreBluetooth and External Accessories on iOS.
First, I tried to see a list of devices connect to my phone via Bluetooth via print(EAAccessoryManager.sharedAccessoryManager().connectedAccessories) ... despite having 3 devices connected (according to the Settings app), I'm given an empty array.
Next, I tried registering for connect / disconnect notifications:
import UIKit
import ExternalAccessory
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "somethingConnected:",
name: EAAccessoryDidConnectNotification,
object: nil)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "somethingDisconnected:",
name: EAAccessoryDidDisconnectNotification,
object: nil)
EAAccessoryManager.sharedAccessoryManager().registerForLocalNotifications()
}
func somethingConnected(name: EAAccessory) {
print("here")
}
func somethingDisconnected(name: EAAccessory) {
print("there")
}
}
... I receive nothing when I turn off/on (and thus disconnect/connect) a simple Bluetooth speaker I have.
I am seeing this issue (notifications not delivering until after the completion block of showBluetoothAccessoryPickerWithNameFilter() executes), but, generally, it seems like either:
A) Something with iOS isn't working correctly
B) I'm doing something wrong (the more likely of the two).
Do I need to have a special MFI certificate installed to see a list of connected Accessories? Why aren't notifications delivering?
Any recommendations / code examples are greatly appreciated.
Update
Most importantly: Still don't know why connectedAccessories doesn't work, so advice on this piece is greatly desired.
That said, re-reading the Apple Developer documentation, I don't believe that it's correct / possible to use NSNotificationCenter.defaultCenter().addObserver with these types of notifications.
Specifically, the documentation states that EA notifications will not be delivered until showBluetoothAccessoryPickerWithNameFilter() is called -- e.g. the EAAccessoryDidConnectNotification and EAAccessoryDidDisconnectNotification are meant to inform the app about what the User did with the picker dialogue. It doesn't seem that they are system-level notifications that can be picked up by NSNotificationCenter.
Please correct me if this is an incorrect reading.
you should change
selector: "somethingConnected:"
into
selector: #selector(somethingConnected:)
,than it will fire.
For more details, please see the following website:
Why does EAAccessoryDidConnectNotification occur twice?

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.

How to communicate between iOS App Containing Extension and Extension (not Host App)

TLDR: Is it possible to send realtime messages or notifications between iOS App and it's Extension?
I'm writing an iOS App with an extension that are part of the same App Group and share the same CoreData (SQLite database). I can read and write to the database using CoreData from the App and from the extension, they both share the same content.
My Question is: Is it possible to send messages or notifications between the App and the extension to notify the other to update if necessary?
I tried sending notifications through NSNotificationCenter but that does not go "out" of the App/Extension, same issue if I try to write to the group shared NSUserDefaults and listen to NSUserDefaultsDidChangeNotification. This works inside the App but the extension does not receive anything (when I know that it is launched and it share the same NSUserDefaults). Any idea how to keep things in sync?
TLDR: No, but there's a hack
There's no true interprocess communication for iOS apps, with or without extensions. NSDistributedNotification still hasn't made the trip from OS X to iOS, and probably won't.
With some extension types you can open URLs via NSExtensionContext and use them to pass data to an app that handles the URL. This brings the app to the foreground, which doesn't sound like what you want.
There is a hack that might get you what you need, though.
Instead of writing to user defaults, write to a file in your app group directory.
Don't just write the file directly-- use NSFileCoordinator to do coordinated writes to the file.
Implement NSFilePresenter on an object that wants to know about changes to the file, and make sure to call [NSFileCoordinator addFilePresenter:someObject]
Implement the optional presentedItemDidChange method on your file presenter.
If you do all of this right, you can write to this file from either the app or the extension, and then have presentedItemDidChange be automatically called in the other one. As a bonus you can of course read the contents of that file, so you can pass arbitrary data back and forth.
There is a hack that you can use to communicate between any two apps in iOS or app and extension. The only thing - it doesn't work with NetworkExtension since Apple is blocking any I/O in it.
You can post notification to the DarwinNotificationCenter this way:
let notificationName = CFNotificationName("com.notification.name" as CFString)
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, false)
In your app add observer:
let notificationName = "com.notification.name" as CFString
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
CFNotificationCenterAddObserver(notificationCenter,
nil,
{ (
center: CFNotificationCenter?,
observer: UnsafeMutableRawPointer?,
name: CFNotificationName?,
object: UnsafeRawPointer?,
userInfo: CFDictionary?
) in
print("Notification name: \(name)")
},
notificationName,
nil,
CFNotificationSuspensionBehavior.deliverImmediately)
Some links:
https://github.com/choefele/CCHDarwinNotificationCenter
https://developer.apple.com/documentation/corefoundation/1542572-cfnotificationcentergetdarwinnot
https://developer.apple.com/library/content/documentation/Darwin/Conceptual/MacOSXNotifcationOv/DarwinNotificationConcepts/DarwinNotificationConcepts.html
For an alternative means of doing general-purpose bidirectional communication between host app and app extension, try MMWormhole:
http://www.mutualmobile.com/posts/mmwormhole
https://github.com/mutualmobile/MMWormhole
It’s a fairly lightweight wrapper around CFNotificationCenter, and uses “Darwin” notifications to do interprocess communication (IPC).
It passes payloads back & forth using the apps’ shared container, and encapsulates even having to create the file(s) themselves.
The class (and the sample app in the repo) seem to work well, and are quite responsive.
I hope this also helps.
I've been struggling with the same issue and haven't found a clean solution either. Another hacky way to solve this is to simply run a timer in the extension and check the values in the shared container preferences/database periodically and then update if required. Not elegant, but it seems to work.

Resources