Post NSUserDefaults changes between iOS Keyboard Extension and Containing app? - ios

I thought I have tried everything and read every possible articles in this forum. But nothing seems to work. Here is some code snippet and some settings:
On Extension side:
let thisDefaults = NSUserDefaults(suiteName: "group.request.com")
thisDefaults?.setObject("Hello", forKey: "prevWord")
thisDefaults?.setObject("World", forKey: "word")
let success = thisDefaults?.Synchronize()
NSNotificationCenter.defaultCenter().postNotificationName("ExtensionRequestChanges", object: nil)
On Containingg app side:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("DidReceiveRequest:", name: "ExtensionRequestChanges",
object: nil)
func DidReceiveRequest(notification: NSNotification) {
// Read defaults
let thisDefaults = NSUserDefaults(suiteName: "group.request.com")
let word = thisDefaults?.stringForKey("word")
let prevWord = thisDefaults?.stringForKey("prevWord")
...
}
On the project settings:
. registered "group.request.com" app group to both the extension and containing app
. set "RequestOpenAccess" in NSExtensionAttributes to YES
But my DidReceiveRequest function never seemed to get called! Why???
Please help.

NSNotificationCenter only works in the process it's running in. Your keyboard extension and container app are separate processes. To post interprocess notifications, you should investigate the CFNotificationCenterGetDarwinNotifyCenterAPI call, which returns a Core Foundation notification center that can post notifications between processes, or other forms of interprocess communication.

Related

AudioKit handling of AVAudioSessionInterruption

After receiving a phone call or just having the phone ring our background play enabled AudioKit app goes silent for good and I am not sure how to handle that. The only way to restart sound output is to kill and restart the app. Other interruptions like enabling and using Siri work without a hitch and the app's sound is ducked during the event.
Typically an app can register itself to receive notifications (e.g. NSNotification.Name.AVAudioSessionInterruption) to detect an AVAudioSession interruption, but how does one retrieve the AVSession object that is normally passed into the notification?
NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.sessionInterrupted(_:)),
name: NSNotification.Name.AVAudioSessionInterruption,
object: MISSING_AK_AVAUDIOSESSION_REF)
Furthermore, if one were able to successfully implement audio interrupt notifications, what happens with AudioKit? It is not designed to be "restarted" or put on hold. Any help would be much appreciated.
This is going to depend on your app how you handle this. At very least, you'll want to do Audiokit.stop() and then Audiokit.start() when the interruption is finished.
You'll want to register for the notification with something like this:
NotificationCenter.default.addObserver(self,
selector: #selector(handleInterruption),
name: .AVAudioSessionInterruption,
object: nil)
Then handle it with something like this:
#objc internal func handleInterruption(_ notification: Notification) {
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
return
}
//...handle each type here
}

Detect changes in NSUserDefaults with suiteName

In my project i use app group to transfer data to apple watch!
This look like this
let sharedDefaults = NSUserDefaults(suiteName: "group.com.myappname.defaults")
sharedDefaults?.setObject(MyData, forKey: "DataKey")
sharedDefaults?.synchronize()
In WKInterfaceController i getting my data with this code:
let sharedDefaults = NSUserDefaults(suiteName: "group.com.myappname.defaults")
let MyData = sharedDefaults?.objectForKey("DataKey") as! [[AnyObject]]
All work fine!
Now i try to detect if data in sharedDefaults?.objectForKey("DataKey") did changed. I try to use addObserver method:
override func willActivate() {
NSUserDefaults(suiteName: "group.com.myappname.defaults")!.addObserver(self, forKeyPath: "DataKey", options: NSKeyValueObservingOptions.New, context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>){
print("Data Changed")
}
But "override func observeValueForKeyPath" calling only when WKInterfaceController will Activate and didn't calling when i change Data in NSUserDefaults(suiteName: "group.com.myappname.defaults")
Also i try to use NSNotificationCenter:
override func willActivate() {
let sharedDefaults = NSUserDefaults(suiteName: "group.com.myappname.defaults")
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(userDefaultsDidChangeNotificationMethod(_:)), name: NSUserDefaultsDidChangeNotification, object: nil)
}
func userDefaultsDidChangeNotificationMethod(notification: NSNotification){
print("Data Changed")
}
It doesn't work(
What i doing wrong? How to detect if data changed?
I know that this is a bad way, but i didn't find any others...
My way is endless cycle:
var NeedCheking = Bool()
override func willActivate() {
super.willActivate()
NeedChecking = true
CheckDefaults()
}
override func didDeactivate() {
NeedChecking = false
super.didDeactivate()
}
func CheckDefaults(){
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
repeat {
let sharedDefaults = NSUserDefaults(suiteName: "group.com.myappname.defaults")
let NewData = sharedDefaults?.objectForKey("DataKey") as! [[AnyObject]]
if NewData != self.MyData {
dispatch_async(dispatch_get_main_queue()) {
self.MyData = NewData
//Here do update actions!
}
}
sleep(2)
}while self.NeedChecking
}
}
If anybody know other way, please post your solution as answer to this question!
Relationship between the Watch app interface, the WatchKit extension, and the iOS app
Base the relationship, i think KVO doesn't work between watchos and ios, if you want to get the latest data from ios app, there are two ways.
Check the latest data in willActivate
Background Tasks
Background tasks are a way for you to keep your app’s interfaces up-to-date. Receiving a background task object from the system is your signal to perform specific types of operations. The task object defines the type of task to perform and contains any data needed to complete the task. The system delivers background task objects to your app by calling the handleBackgroundTasks: method of your app’s extension delegate.
watchOS supports the following types of background tasks:
Background App Refresh Tasks. Use a WKApplicationRefreshBackgroundTask object to handle general updates to your app’s state. For example, you might use this type of task to check in with your company’s server or begin downloading new content. You schedule this type of background task explicitly from your your app’s WKExtension object.
Background Snapshot Refresh Tasks. Use a WKSnapshotRefreshBackgroundTask object to update your app’s interface in preparation of having its snapshot taken. The system automatically takes the snapshot when this task completes. The system schedules background snapshot refresh tasks periodically to update your snapshot. You can also schedule a task of this type explicitly from your app’s WKExtension object when your interface changes.
Background Watch Connectivity Tasks. Use a WKWatchConnectivityRefreshBackgroundTask object to receive data sent by your iOS app using the Watch Connectivity framework. The system automatically creates this type of task when your Watch app receives data from the its corresponding iOS app running on the paired iPhone. You do not schedule tasks of this type yourself.
Background NSURLSession Tasks. Use a WKURLSessionRefreshBackgroundTask object to receive data you previously requested using an NSURLSession object. This task is triggered when a background transfer requires authorization or when a background transfer completes (successfully or unsuccessfully). You do not schedule tasks of this type yourself.
Remember that background transfers may not be delivered immediately. Files and contextual data are delivered as quickly as possible, but transfers are not instantaneous. Data files involving large files or large amounts of data also take a commensurately long time to complete.

Contact Framework equivalent to ABAddressBook.ABAddressBookRegisterExternalChangeCallback

I am migrating an application from the deprecated Address Book Framework to the new Contacts Framework. The application utilizes ABAddressBookRegisterExternalChangeCallback to be notified when another application changes a contact.
I am unable to find equivalent functionality in the Contacts Framework. Apple documentation says to use the default notification center with the CNContactStoreDidChangeNotification notification:
The notification posted when changes occur in another CNContactStore.
Taking Apple's advice, my code looks like this:
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "contactsChanged:",
name: CNContactStoreDidChangeNotification,
object: nil)
However, I have found two problems with this approach:
I am notified for all changes, including those made by my own application.
Notifications are spurious - I receive many notifications for a single change.
If I log the debug description of the notification when the change was made within my app, I get something like this:
NSConcreteNotification 0x7d3370e0 {name = CNContactStoreDidChangeNotification; userInfo = {
CNNotificationOriginationExternally = 1;
CNNotificationSourcesKey = (
);
}}
And if the changes are made externally:
NSConcreteNotification 0x7bf7a690 {name = CNContactStoreDidChangeNotification; userInfo = {
CNNotificationOriginationExternally = 1;
CNNotificationSourcesKey = (
);
}}
As you can see, nothing obvious with which to distinguish them.
Can anyone tell me how to get the same behavior from the Contacts Framework as one can get from ABAddressBookRegisterExternalChangeCallback?
First, I'd recommend filing a bug with Apple about the lack of a way to identify internal vs external changes in the API.
As a possible workaround, you could see if unregistering your observer before making a change and re-registering immediately afterward ensures that you miss all of your change notifications and still get all the external ones:
class ContactsThingy {
var observer: NSObjectProtocol?
let contacts = CNContactStore()
func contactStoreDidChange(notification: NSNotification) {
NSLog("%#", notification)
}
func registerObserver() {
let center = NSNotificationCenter.defaultCenter()
observer = center.addObserverForName(CNContactStoreDidChangeNotification, object: nil, queue: NSOperationQueue.currentQueue(), usingBlock: contactStoreDidChange)
}
func unregisterObserver() {
guard let myObserver = observer else { return }
let center = NSNotificationCenter.defaultCenter()
center.removeObserver(myObserver)
}
func changeContacts(request: CNSaveRequest) {
unregisterObserver() // stop watching for changes
defer { registerObserver() } // start watching again after this change even if error
try! contacts.executeSaveRequest(request)
}
}

Can't get iCloud metadataQuery to work

I have an issue with the iCloud metadataQuery.
I am trying to fetch some documents from iCloud but when I call the NSPredicate function I get the following error in the console of Xcode:
warning: could not load any Objective-C class information from the
dyld shared cache. This will significantly reduce the quality of type
information available.
Note: I am using Swift version 2.
This is my function:
var _iCloudQuery:NSMetadataQuery!
func iCloudQuery()->NSMetadataQuery?{
if (_iCloudQuery == nil) {
_iCloudQuery = NSMetadataQuery.init()
_iCloudQuery.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
_iCloudQuery.predicate = NSPredicate(format: "(%K = '*')", NSMetadataItemURLKey)//
// Set observer here
NSNotificationCenter.defaultCenter().addObserver(self, selector: "processCloudQueryResults:", name: NSMetadataQueryDidFinishGatheringNotification, object: _iCloudQuery)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "processCloudQueryResults:", name: NSMetadataQueryDidUpdateNotification, object: _iCloudQuery)
}
return _iCloudQuery
}
I already figured out that processCloudQueryResults never gets fired because there are no items in icloudQuery but still there are documents in my iCloud container so there should.
This is a related topic with no solutions:
Searching for files using NSMetadataQuery does simply nothing

Getting the currently active keyboard language as NSLocale

I already found a lot of approaches for this but no working solution. Here's what I tried and didn't work.
(1) Simply calling primaryLanguage()
UITextInputMode().primaryLanguage
→ always returns nil :-/
(2) Subscribing to UITextInputCurrentInputModeDidChangeNotification notifications
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "changeInputMode:", name: UITextInputCurrentInputModeDidChangeNotification, object: nil)
}
func changeInputMode(sender : NSNotification) {
...?!
}
The notification is getting triggered but it is unclear how I can extract the current language information from the notification.
(3) Using activeInputModes()
let localeIdentifier = UITextInputMode.activeInputModes().first as? UITextInputMode
var locale:NSLocale = NSLocale(localeIdentifier: localeIdentifier.primaryLanguage!)
println("Input Mode Language \(localeIdentifier.primaryLanguage!)")
This always provides the same array of all available keyboards, no information on the actually active one.
How do I get the NSLocale of the currently active keyboard?
You can access the primaryLanguage from every textfield by accessing the textfields textInputMode like that:
var language = textfield.textInputMode?.primaryLanguage

Resources