I get the following error when performing a NSMetadataQuery in iCloud Drive:
[ERROR] cannot query iCloud Drive items: Error Domain=BRCloudDocsErrorDomain Code=16 "Sync is restricted for this client" UserInfo={NSDescription=Sync is restricted for this client}
This might be my problem
I have a free developer account. However I have not found anything about a requirement to use a paid developer account for using NSMetadataQuery in iCloud Drive.
Note that I found out that one has to use a paid developer account to use the iCloud Key-Value and Document Storage which is not the same as iCloud Drive.
What I tried
I used the following code to make the query (calling Test().getAllFilesIniCloud()):
class Test {
let metadataQuery = NSMetadataQuery()
func getAllFilesIniCloud() {
metadataQuery.searchScopes = [
NSMetadataQueryUbiquitousDocumentsScope,
NSMetadataQueryUbiquitousDataScope,
NSMetadataQueryAccessibleUbiquitousExternalDocumentsScope,
]
let predicate = NSPredicate { (any, dict) -> Bool in
print(any as Any, dict as Any)
return false
}
metadataQuery.predicate = predicate
NotificationCenter.default.addObserver(
self,
selector: #selector(self.queryDidFinishGathering(_:)),
name: .NSMetadataQueryDidFinishGathering,
object: metadataQuery)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.queryDidStart(_:)),
name: .NSMetadataQueryDidStartGathering,
object: metadataQuery)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.queryDidUpdate(_:)),
name: .NSMetadataQueryDidUpdate,
object: metadataQuery)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.queryGathering(_:)),
name: .NSMetadataQueryGatheringProgress,
object: metadataQuery)
metadataQuery.enableUpdates()
if metadataQuery.start() {
print("query did start")
}
}
#objc func queryGathering(_ notification: NSNotification) {
print("gathering")
self.getValuesOf(notification: notification)
}
#objc func queryDidStart(_ notification: NSNotification) {
print("didStart")
self.getValuesOf(notification: notification)
}
#objc func queryDidUpdate(_ notification: NSNotification) {
print("didUpdate")
self.getValuesOf(notification: notification)
}
#objc func queryDidFinishGathering(_ notification: NSNotification) {
print("didFinish gathering")
metadataQuery.disableUpdates()
metadataQuery.stop()
self.getValuesOf(notification: notification)
}
func getValuesOf(notification: NSNotification) {
let metadataQuery = notification.object as! NSMetadataQuery
metadataQuery.disableUpdates()
for item in metadataQuery.results as! [NSMetadataItem] {
let url = item.value(forAttribute: NSMetadataItemURLKey) as! URL
print(url)
}
metadataQuery.enableUpdates()
}
}
When starting the query it prints and nothing else:
didStart
... [default] [ERROR] cannot query iCloud Drive items: Error Domain=BRCloudDocsErrorDomain Code=16 "Sync is restricted for this client" UserInfo={NSDescription=Sync is restricted for this client}
query did start
You need to add iCloud to Capability in your Project and select key-value + iCloud Documents + add your container. I had have the same problem.
Related
Anyone know why a change in the contact store triggers more than one notification to a notification center observer?
I did a test project to see how many times CNContactStoreDidChange notification occurs when I make a simple change to contact store.
Here is my relevant code:
NotificationCenter.default.addObserver(self, selector: #selector(self.storeChanged(_:)), name: NSNotification.Name.CNContactStoreDidChange, object: nil)
#objc func storeChanged(_ notification: Notification) {
print("#", #function)
}
let newMutableGroup = CNMutableGroup()
newMutableGroup.name = ...
let saveRequest = CNSaveRequest()
saveRequest.add(newMutableGroup, toContainerWithIdentifier: contactStore.defaultContainerIdentifier())
do {
try contactStore.execute(saveRequest)
} catch {
print("error:\n\t\(error.localizedDescription)")
}
Debug window output:
# storeChanged(_:)
# storeChanged(_:)
# storeChanged(_:)
# storeChanged(_:)
I am trying to write an extension for NotificationCenter
I find the syntax a little meaty and "boilerplatey" and would like to provide a simple extension that simplifies posting and observing.
I can dispatch an event like so
NotificationCenter.dispatch(key: <#T##String#>, payload: <#T##[String : String]#>)
However I would like to observe an event in a similar fashion.
I am trying to create something like
NotificationCenter.observe(key: <#T##String#>, handler: <#T##() -> Void#>)
However this is not correct. I am unsure how I can handler passing in my selector function that should be triggered on an observation?
This is my attempt so far.
extension NotificationCenter {
static func dispatch(key: String, payload: [String: String] = [:]) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: key), object: nil, userInfo: payload)
}
static func observe(key: String, handler: ()->Void) {
NotificationCenter.default.addObserver(
self, selector: handler, name: NSNotification.Name(rawValue: key), object: nil
)
}
}
It sounds like you need something like
extension NotificationCenter {
static func dispatch(key: String, payload: [String: String] = [:]) {
self.default.post(name: NSNotification.Name(rawValue: key), object: nil, userInfo: payload)
}
static func observe(key: String, handler: #escaping (Notification) -> Void) {
self.default.addObserver(forName: NSNotification.Name(rawValue: key), object: nil, queue: .main, using: handler)
}
}
You can use something like this:
extension NotificationCenter {
class func observe(name: NSNotification.Name, handler: #escaping (Notification) -> Void) {
self.default.addObserver(forName: name, object: nil, queue: .main, using: handler)
}
}
Then you can use it like this:
NotificationCenter.observe(name: UIResponder.keyboardDidShowNotification) { notification in
// do something
}
You should definitely check out https://developer.apple.com/documentation/foundation/notificationcenter/1411723-addobserver regarding unregistering observations though.
NotificationCenter - a useful but bulky looking interface, feels a bit bloating in code, as someone coming from UNIX-like thread synchronization semantics... So, these extensions are based on stuff on S.O. & the 'Net, with a couple of twists.
extension Notification.Name {
static let patientlyWaiting = Notification.Name("patientlyWaiting")
// .
// . list your arbitrary waiter names here...
// .
}
extension NotificationCenter {
static func wait(_ name : Notification.Name) async {
for await _ in NotificationCenter.default.notifications(named: name) {
break;
}
}
static func post(_ name : Notification.Name) {
NotificationCenter.default.post(name: name, object: nil)
}
static func postProcessing(_ name: Notification.Name, using block: #escaping (Notification) -> Void) {
NotificationCenter.default.addObserver(forName: name, object: nil, queue: OperationQueue.main, using: block)
}
}
To use it, something like this:
Waiting for notification:
#IBAction func doSomethingAsychronouslyButtonPushed(_ sender: Any) {
doSomethingAsynchronously()
NotificationCenter.postProcessing(.patientlyWaiting, using: { _ in
print("doSomethingAsynchronouslyButton completion handler called")
})
}
Notifying:
func doSomethingAsynchronously() {
for n in ["Time", " ", "consuming", " - ", "whatever"] {
print("\(n)", terminator: "")
}
print("\n")
NotificationCenter.post(.patientlyWaiting)
}
Note: You could avoid using a closure and do an inline wait using Swift language's recently-added async/await support (see the 'wait()function in the extensions shown above), but I didn't provide an example of using that because I haven't been able to invokeasyncfunctions from selectors, such as from aUIButtonpress, directly or indirectly; because, any function that usesawaitneeds to be declaredasync. Specifically, #selector(afunc(_:)) doesn't match functions declared async, and I'm unaware of a function signature syntax that accounts for an asyncin function declaration (to pass to#selector()`). If someone knows, let me know in the comments and I'll update the answer.
I am using GCDAsyncUdpSocket for communication between my app and some smart-home hardware, and I have a problem with stopping a certain function. Logic goes something like this:
Send a command
If you didn't receive feedback from the hardware, it'll try to send it a few more times
When app receives feedback, notification DidReceiveDataForRepeatSendingHandler is posted (along with device information in userInfo)
For example, let's say I have a curtain that can react on 3 commands: Open, Close and Stop... and that curtain is currently closed.
I press Open (and don't receive feedback), and during the process I change my mind, so I press Stop. Now the app will send both commands simultaneously.
So without further ado, here's the code:
class RepeatSendingHandler: NSObject {
var byteArray: [UInt8]!
var gateway: Gateway!
var repeatCounter:Int = 1
var device:Device!
var appDel:AppDelegate!
var error:NSError? = nil
var sameDeviceKey: [NSManagedObjectID: NSNumber] = [:]
var didGetResponse:Bool = false
var didGetResponseTimer:Foundation.Timer!
//
// ================== Sending command for changing value of device ====================
//
init(byteArray:[UInt8], gateway: Gateway, device:Device, oldValue:Int) {
super.init()
appDel = UIApplication.shared.delegate as! AppDelegate
self.byteArray = byteArray
self.gateway = gateway
self.device = device
NotificationCenter.default.addObserver(self, selector: #selector(RepeatSendingHandler.didGetResponseNotification(_:)), name: NSNotification.Name(rawValue: NotificationKey.DidReceiveDataForRepeatSendingHandler), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(sameDevice(_:)), name: NSNotification.Name(rawValue: NotificationKey.SameDeviceDifferentCommand), object: nil)
sendCommand()
}
func updateRunnableList(deviceID: NSManagedObjectID) {
RunnableList.sharedInstance.removeDeviceFromRunnableList(device: deviceID)
}
// Did get response from gateway
func didGetResponseNotification (_ notification:Notification) {
if let info = (notification as NSNotification).userInfo! as? [String:Device] {
if let deviceInfo = info["deviceDidReceiveSignalFromGateway"] {
if device.objectID == deviceInfo.objectID {
didGetResponse = true
didGetResponseTimer = nil
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationKey.DidReceiveDataForRepeatSendingHandler), object: nil)
}
}
}
}
func sameDevice(_ notification: Notification) {
print("NOTIFICATION RECEIVED for device with ID: ", self.device.objectID, "\n")
if let info = notification.userInfo as? [NSManagedObjectID: NSNumber] {
sameDeviceKey = info
}
}
func sendCommand () {
if sameDeviceKey != [device.objectID: device.currentValue] { print("keys have DIFFERENT values") } else { print("keys have SAME values") }
if sameDeviceKey != [device.objectID: device.currentValue] {
if !didGetResponse {
if repeatCounter < 4 {
print("Sending command. Repeat counter: ", repeatCounter)
SendingHandler.sendCommand(byteArray: byteArray, gateway: gateway)
didGetResponseTimer = Foundation.Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(RepeatSendingHandler.sendCommand), userInfo: nil, repeats: false)
repeatCounter += 1
} else {
didGetResponseTimer = nil
updateRunnableList(deviceID: device.objectID)
CoreDataController.shahredInstance.saveChanges()
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationKey.DidReceiveDataForRepeatSendingHandler), object: nil)
NotificationCenter.default.post(name: Notification.Name(rawValue: NotificationKey.RefreshDevice), object: self)
}
}else{
didGetResponseTimer = nil
updateRunnableList(deviceID: device.objectID)
CoreDataController.shahredInstance.saveChanges()
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationKey.DidReceiveDataForRepeatSendingHandler), object: nil)
NotificationCenter.default.post(name: Notification.Name(rawValue: NotificationKey.RefreshDevice), object: self)
}
} else {
print("Command canceled")
didGetResponseTimer = nil
return
}
}
On the ViewController where I keep my devices, I call this like:
func openCurtain(_ gestureRecognizer:UITapGestureRecognizer){
let tag = gestureRecognizer.view!.tag
let address = [UInt8(Int(devices[tag].gateway.addressOne)),UInt8(Int(devices[tag].gateway.addressTwo)),UInt8(Int(devices[tag].address))]
if devices[tag].controlType == ControlType.Curtain {
let setDeviceValue:UInt8 = 0xFF
let deviceCurrentValue = Int(devices[tag].currentValue)
devices[tag].currentValue = 0xFF // We need to set this to 255 because we will always display Channel1 and 2 in devices. Not 3 or 4. And this channel needs to be ON for image to be displayed properly
let deviceGroupId = devices[tag].curtainGroupID.intValue
CoreDataController.shahredInstance.saveChanges()
DispatchQueue.main.async(execute: {
RunnableList.sharedInstance.checkForSameDevice(device: self.devices[tag].objectID, newCommand: NSNumber(value: setDeviceValue))
_ = RepeatSendingHandler(byteArray: OutgoingHandler.setCurtainStatus(address, value: setDeviceValue, groupId: UInt8(deviceGroupId)), gateway: self.devices[tag].gateway, device: self.devices[tag], oldValue: deviceCurrentValue)
})
}
}
What I did was I made a separate class where I have a dictionary that has Device's ManagedObjectID as a key, and the command we are sending is it's value. So whenever we are sending a command for a device that's already on the list, I post a notification SameDeviceDifferentCommand with userInfo containing device's ManagedObjectID and the old command. I use it on RepeatSendingHandler to populate sameDeviceKey dictionary. That's how I tried to distinguish which function should be stopped.
public class RunnableList {
open static let sharedInstance = RunnableList()
var runnableList: [NSManagedObjectID: NSNumber] = [:]
func checkForSameDevice(device: NSManagedObjectID, newCommand: NSNumber) {
if runnableList[device] != nil && runnableList[device] != newCommand {
let oldDataToSend = [device: runnableList[device]!]
NotificationCenter.default.post(name: Notification.Name(rawValue: NotificationKey.SameDeviceDifferentCommand), object: self, userInfo: oldDataToSend)
print("Notification sent for device with ID: ", device, "\n")
}
runnableList[device] = newCommand
print("Device with ID: ", device, "received a new command", newCommand, "\n")
}
func removeDeviceFromRunnableList(device: NSManagedObjectID) {
runnableList.removeValue(forKey: device)
print("Removed from list device with ID: ", device)
}
}
However, sometimes it does it's job as it should, and sometimes it doesn't. Using a bunch of prints I tried to see in which order everything happens, and it seems that sometimes even though sameDeviceKey gets it's value from the notification - it looks like it uses old (nil) value until repeatCounter maxes out. I do not understand why.
Could anyone explain what is happening, and/or advise a better solution than the one I provided?
(There is a bit of additional code which I removed as it's irrelevant to logic/question). Please bear in mind that I am a junior and that I'm relatively new to this.
I want to develop an app for iOS that have a Widget for notification center, but I don't know how I should send and receive data (pass data) between View Controller and And Today Extension.
I tried to use structs, but it doesn't work, and also I used app groups but I don't want to use this method.
let shared = NSUserDefaults(suiteName: "group.Demo.Share-Extension-Demo.mahdi")
shared?.setObject("Hello", forKey: "kkk")
Apart from NSUserDefaults, you can use the NSNotificationCenter to send or receive data anywhere.
You need to set the observer where you can receive the data like below:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "dataReceived:", name: "SpecialKey", object: nil)
}
Funciton to catch data:
func dataReceived(notification: NSNotification) {
//deal with notification.userInfo
println(notification.userInfo)
println("Received Data")
}
And you need to define NSNotificationCenter from where you need to send the data:
NSNotificationCenter.defaultCenter().postNotificationName("SpecialKey", object: nil, userInfo: ["MyData": "Demo"])
References:
The complete guide to NSNotificationCenter
Hope it helps!
http://moreindirection.blogspot.in/2014/08/nsnotificationcenter-swift-and-blocks.html
For the people who haven't found a way to implement calling function or button click from App Extension (Widget):
Note: This is using Swift
Note 2: Replace the names of NSNotification and methods with your implementations
First, create NotificationCenter post method (In Swift 2.0 - NSNotification Center)
Create methods in App Delegate class -
var scheme: String!
var host: String!
Then, add the following function on the bottom of the class (after the last one):
func application(_ app: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
scheme = url.scheme
host = url.host
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "NOTIFICATION_NAME"), object: nil)
return true
}
In your ViewController class, where you want to execute the function or the statement from clicking Widget, add the following in super.viewDidLoad():
NotificationCenter.default.addObserver(self,selector: #selector(self.YOUR_METHOD_NAME),
name: NSNotification.Name(rawValue: "NOTIFICATION_NAME"),
object: nil)
And the method you want to call:
func YOUR_METHOD_NAME(notification: NSNotification) {
let appDelegate =
UIApplication.shared.delegate as! AppDelegate
if appDelegate.scheme != nil {
startRecording()
}
}
I'm assuming that you have already created your widget target, and the view for it. Add this to your button in TodayViewController from which you want to handle the click:
#IBAction func openApp(_ sender: UIButton) {
openApp()
}
And the function to handle opening app by URl Scheme:
func openApp(){
let myAppUrl = NSURL(string: "YOUR_URL_SCHEME://YOUR_HOST_NAME")!
extensionContext?.open(myAppUrl as URL, completionHandler: { (success) in
if (!success) {
self.textView.text = "There was a problem opening app!"
}
})
}
For YOUR_URL_SCHEME, add your scheme that you have specified in Info.plist, if your don't, go to this link and follow instructions:
Add URL Scheme to Xcode
For YOUR_HOST_NAME, you can remove this, and only open app by URL Scheme.
Happy coding!
This is my current code
func applicationDidBecameActive(notification:NSNotification) {
println("Application is active")
NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleIdentityChange:", name: NSUbiquityIdentityDidChangeNotification, object: nil)
}
func applicationBecameInactive(notification:NSNotification) {
println("Application is inactive")
NSNotificationCenter.defaultCenter().removeObserver(self, name: NSUbiquityIdentityDidChangeNotification, object: nil)
}
func handleIdentityChange(notification:NSNotification) {
println("this is working")
let fileManager = NSFileManager()
if let token = fileManager.ubiquityIdentityToken {
println("New token is \(token)")
}
else {
println("User has logged out of iCloud")
}
}
"Application is active" & "Application is inactive" works properly. There is no problem there.
I could not get it fire "This is working". By logging into different iCloud account or logging out of iCloud account.
I tried on simulator & on actual device.
Please help me fix this or suggest alternative method to achieve same goal(change in iCloud account).
In Swift I have sometimes needed to add #objc in front of a func for NSNotificationCenter to find it.
So instead of
func handleIdentityChange(notification:NSNotification) {
I'd try
#objc func handleIdentityChange(notification:NSNotification) {
Have a look at my comment in this question. I never received a NSUbiquityIdentityDidChangeNotification either. In iOS your app gets killed when you change accounts. In tvOS you can use NSUbiquitousKeyValueStoreAccountChange.