What makes a user discoverable to CKDiscoverAllUserIdentitiesOperation? - ios

I am trying to discover contacts for a user with the code below (the code is in an implementation of a UITableViewController. I put breakpoints in both code blocks, and I determined that the the userIdentityDiscoveredBlock is not called while the completionBlock is called. This indicates that the operation is being run as expected, it just isn't finding any contacts.
I am running on the simulator, but I verified that the simulator has synced all my iCloud contacts (opening the Contacts app on the simulator shows all my contacts).
override func viewDidLoad() {
super.viewDidLoad()
let op = CKDiscoverAllUserIdentitiesOperation()
op.discoverAllUserIdentitiesCompletionBlock = { error -> Void in
// reload my data table
}
op.userIdentityDiscoveredBlock = { user -> Void in
if user.hasiCloudAccount {
self.iCloudUsers.append(user)
} else {
self.nonICloudUsers.append(user)
}
}
CKContainer.default().add(op)
}
So my question is this - Is there something else that has to be done in order to discover contacts? Is this a simulator issue?
I searched the documentation and other questions but I can't seem to find information on this given the operation is new to iOS 10.

There's quite a bit that must be done before CKDiscoverAllUserIdentitiesOperation will return any results.
First, each user of your app must grant permission to be looked up by email. Your app makes this request using CKContainer requestApplicationPermission.
Each user of your app must also be logged into an iCloud account. iCloud Drive must also be enabled by the user.
And lastly, for CKDiscoverAllUserIdentitiesOperation to return any users, the person must have contacts with email addresses that match other users that completed all of the previous steps.

Related

firestore collection path giving bugs with constants value and String value

So my goal is to get rid of these bugs completely. I am in a dilemma where each decision leads to a bug.
The first thing I can do that eventually becomes an issue is use a String-interpolated collection path in all my query functions like so:
func getEventName() {
listener = db.collection("school_users/\(user?.uid)/events").order(by: "time_created", descending: true).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
if let error = error {
print("There was an error fetching the data: \(error)")
} else {
self.events = querySnapshot!.documents.map { document in
return EventName(eventName: (document.get("event_name") as! String))
}
self.tableView.reloadData()
}
}
}
The thing with this is, when I run the app on the simulator, I am restricted from pressing buttons and then sometimes I can press them and then sometimes they get restricted again. This bug is so confusing because it makes no sense where it springs from.
The other issue is I can use a Constants value in all the query functions in my collections path.
static let schoolCollectionName = "school_users/\(user?.uid)/events"
This is nested in a Firebase struct within the Constants struct. In order to keep Xcode from giving errors I create a let users = Auth.auth().currentUser variable outside the Constants struct. The issue with this value is that when I put that in all of my query functions collection paths, all the buttons are accessible and selectable all the time, but when a user logs out and I log in as a new user, the previous user's data shows up in the new user's tableview.
It would obviously make more sense to use the Constants value because you prevent typos in the future, but I can't figure out how to get rid of the bug where the old user's data shows up in the new user's tableview. Thanks in advance.
The user id should definitely not be a constant. What it sounds like is that right now, you have no reliable way to change users -- your setup probably depends on which user is logged in at app startup, since that's where your variable gets set.
I would do something more like this:
func getEventName() {
guard let user = Auth.auth().currentUser else {
//handle the fact that you don't have a user here -- don't go on to the next query
return
}
listener = db.collection("school_users/\(user.uid)/events").order(by: "time_created", descending: true).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
Note that now, user.uid in the interpolated path doesn't have the ? for optionally unwrapping it (which Xcode is giving you a warning for right now). It will also guarantee that the correct query is always made with the currently-logged-in user.
Regarding being able to press the buttons, that sounds like an unrelated issue. You could run your app in Instruments and check the Time Profiler to see if you have long-running tasks that are gumming up the main/UI thread.

App Clip - Support Multiple Businesses Locations

When I designed my App Clip Launch experience, I had in mind that the App can only be triggered via QR code, NFC or App Clip Code. That why I linked the App Launch to a specific location with specific Id.
When my App went live last week, and when I try to scan a NFC tag the App is launching as expected every time.
Now, if I tap the App Clip icon on the home screen, the App is launching with the last URL scanned I dig some googling and I found that the App Clip is caching the last URL scanned and simulating a universal link launch when icon tapped!
This is not working for me! So I am looking for a way to check if the App was launched via scan or tap? I tried to log the App launch but it's always running in the order either via Scan (NFC) or icon tap:
AppDelegate.didFinishLaunchingWithOptions()
SceneDelegate.willConnectTo() // It's here where I am handling the Universal Link
How can I check if the user launched the App via Tap or Scan? Knowing that the App is always simulating Universal launch Link when icon tapped!
Or how I can look for the saved URL? I tried to fetch all UserDefaults and Some Keychain data, but I found nothing!
I faced the same issue! And unfortunately there’s no way to:
Check how the App was launched, icon tap or NFC/QR scan
To retrieve cached data from either UserDefaults or Keychain
Apple says clearly on their Human Interface Guidelines that if you want support multiple businesses you should add the location services factor!
Consider multiple businesses. An App Clip may power many different
businesses or a business that has multiple locations. In both
scenarios, people may end up using the App Clip for more than one
business or location at a time. The App Clip must handle this use case
and update its user interface accordingly. For example, consider a way
to switch between recent businesses or locations within your App Clip,
and verify the user’s location when they launch it.
So, now your tags for specific location should be mapped to a coordinates [Longitude, Latitude]. Apple has introduced a new location verification API just for App Clips that allows you to do a one-time check to see if the App Clip code, NFC tag or QR code that the user scanned is where it says it is.
Enable Your App Clip to Verify the User’s Location
To enable your App Clip to verify the user’s location, modify your App Clip’s Info.plist file:
Open your App Clip’s Info.plist, add the NSAppClip key, and set its
type to Dictionary.
Add an entry to the dictionary with NSAppClipRequestLocationConfirmation as the key, select Boolean as
its type, and set its value to true.
But using App Clip Location services is different:
Parse the information on the URL that launches the App CLip
Send a request to your Database to fetch the location information for this business
Use activity.appClipActivationPayload to confirm if the location (in Step 2) is in region where the user is right now.
The Code bellow (Copied from Apple) explains how to do it.
import UIKit
import AppClip
import CoreLocation
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// Call the verifyUserLocation(_:) function in all applicable life-cycle callbacks.
func verifyUserLocation(_ activity: NSUserActivity?) {
// Guard against faulty data.
guard activity != nil else { return }
guard activity!.activityType == NSUserActivityTypeBrowsingWeb else { return }
guard let payload = activity!.appClipActivationPayload else { return }
guard let incomingURL = activity?.webpageURL else { return }
// Create a CLRegion object.
guard let region = location(from: incomingURL) else {
// Respond to parsing errors here.
return
}
// Verify that the invocation happened at the expected location.
payload.confirmAcquired(in: region) { (inRegion, error) in
guard let confirmationError = error as? APActivationPayloadError else {
if inRegion {
// The location of the NFC tag matches the user's location.
} else {
// The location of the NFC tag doesn't match the records;
// for example, if someone moved the NFC tag.
}
return
}
if confirmationError.code == .doesNotMatch {
// The scanned URL wasn't registered for the App Clip.
} else {
// The user denied location access, or the source of the
// App Clip’s invocation wasn’t an NFC tag or visual code.
}
}
}
func location(from url:URL) -> CLRegion? {
// You should retrieve the coordinates from your Database
let coordinates = CLLocationCoordinate2D(latitude: 37.334722,
longitude: 122.008889)
return CLCircularRegion(center: coordinates,
radius: 100,
identifier: "Apple Park")
}
}
And that’s it, this his how your support multiple businesses with App Clip

My app keep deny the access to contacts iOS - Swift

I have a view when it get loaded it will check first the permission to access the contacts if it's Authorized, NotDetermined or Denied .. the code at first time will go with NotDetermined so it will call function to ask for access .. the problem is here in this function :
private func requestContactsAccess() {
//store = CNContactStore()
store.requestAccessForEntityType(.Contacts) {granted, error in
if granted {
dispatch_async(dispatch_get_main_queue()) {
self.accessGrantedForContacts()
return
}
}
}
}
Now the app never ask for access to Contacts , I don't know why, it will select the permission as denied by its self .
I don't know if the problem with the code or the simulator because this work for other people .
Note : when I go to the settings of the sim , Setteings > Privacy > Contacts .. I find nothing ! just saying : 'Applications that have requested access to your contacts will appear here' . which mean the app never ask for it !
Any help ?
I'm not sure if this is your problem but if you're running your app on iOS 10 Simulator you need to provide a "usage description" for accessing private information like Contacts (camera, camera roll, etc.).
Open your Info.plist and add a new entry and you will see new "Privacy - ..." settings.
Select "Privacy - Contacts Usage Description" and type in a usage description. See if this helps.
When run you should see the grant access popup with your usage description.
First time when code executed, popup appears for permission of accessing contact list. Which button you tapped on that popup? (Allow or Don't Allow) ?
I think you have unintentionally tapped don't allow. Please go to Settings -> -> Allow to access Contacts -> Set switch to YES.

How to test if "Allow Full Access" permission is granted from containing app?

I'm working on a keyboard extension project. At some points of the application code I need to test if the user have granted the "Allow Full Access" permission for the keyboard extension. The deal is that I need to do those tests from the application side, and based on this let the user to access keyboard settings or alert him in case the permission wasn't granted.
The problem is that the methods that provided here like:
func isOpenAccessGranted() -> Bool {
return UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard)
}
or:
func isOpenAccessGranted() -> Bool {
let fm = NSFileManager.defaultManager()
let containerPath = fm.containerURLForSecurityApplicationGroupIdentifier(
"group.com.example")?.path
var error: NSError?
fm.contentsOfDirectoryAtPath(containerPath!, error: &error)
if (error != nil) {
NSLog("Full Access: Off")
return false
}
NSLog("Full Access: On");
return true
}
Working only from the keyboard side, as the keyboard is the only one that is affected from this permission. From the containing app side both of those methods always return true.
Does someone knows a reliable way to test this from the application side?
Consider using NSUSerdefaults in your app and keyboard. In order for an extension and app to be able to share the same NSUserdefaults, you can simply turn on App Groups. Select your main app target under your project in the project navigator and go to capabilities and enable App Groups by toggling it to on, adding your Developer profile and fixing the possibly arising issues.
Now create a new container. According to the help, it must start with “group.”, so give it a name like “group.com.mycompany.myapp”. Select your Today Extension target and repeat this process of switching on app groups. Don’t create a new one, rather select this newly created group to signify that the Today Extension is a part of the group.
This will now allow you to share the same NSUserdefaults as your container app.
All you have to do now is use the code you provided and add the saving method to the NSUserdefaults like so:
func isOpenAccessGranted() -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
let fm = NSFileManager.defaultManager()
let containerPath = fm.containerURLForSecurityApplicationGroupIdentifier(
"group.com.example")?.path
var error: NSError?
fm.contentsOfDirectoryAtPath(containerPath!, error: &error)
if (error != nil) {
NSLog("Full Access: Off")
defaults.setBool(false, forKey: "hasFullAccess")
return false
}
NSLog("Full Access: On");
defaults.setBool(true, forKey: "hasFullAccess")
return true
}
Do this in your keyboard extension. And then in your parent app, simply retrieve them and act upon their value.
let hasFullAccess : Bool = NSUserDefaults.standardUserDefaults().boolForKey("hasFullAccess")
if hasFullAccess{
//User granted full access
}
else{
//User didn't grant full access
}
If the user didn't grant full access the show an alert, else code away!
EDIT:
After contacting Apple's Technical Developer support they told me that this is not possible to achieve in any supported way as of right now. Their response is below:
Our engineers have reviewed your request and have concluded that there
is no supported way to achieve the desired functionality given the
currently shipping system configurations.
If you would like for Apple to consider adding support for such
features in the future, please submit an enhancement request via the
Bug Reporter tool at https://developer.apple.com/bug-reporting/.
Hope that helps, Julian
You can easily test whether the "Allow Full Access" permission is granted on iOS 11 and later.
To get the "Allow Full Access" permission, first subclass the
UIInputViewController class.
Add the code. The code returns a bool value.
Objective-C
[self hasFullAccess];
Swift
self.hasFullAccess
https://developer.apple.com/documentation/uikit/uiinputviewcontroller/2875763-hasfullaccess?changes=_2
Objective-C
[UIInputViewController new].hasFullAccess;

Disable confirmation on delete request in PHPhotoLibrary

What I am trying to do is to save videos to PHPhotoLibrary, and then remove them when upload to clients remote server in the application completes (basically, photo library serves as temporary storage to add additional layer of security in case anything at all fails (I already save my vides it in the applications directory).
Problem:
The problem is for that to work, everything has to work without input from the user. You can write video to photos library like this:
func storeVideoToLibraryForUpload(upload : SMUpload) {
if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.Authorized {
// Don't write to library since this is disallowed by user
return
}
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
// Write asset
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(NSURL(fileURLWithPath: upload.nonsecureFilePath!)!)
let assetPlaceholder = assetRequest.placeholderForCreatedAsset
let localIdentifier = assetPlaceholder.localIdentifier
// Store local identifier for later use
upload.localAssetIdentifier = localIdentifier
}, completionHandler: { (success, error) -> Void in
....
})
}
And that works flawlessly, I get local identifier, I store it for later use.. Unicorns and rainbows.
Now when I want to remove that video immediately after upload finishes, I call following:
func removeVideoFromLibraryForUpload(upload : SMUpload) {
// Only proceed if there is asset identifier (video previously stored)
if let assetIdentifier = upload.localAssetIdentifier {
// Find asset that we previously stored
let assets = PHAsset.fetchAssetsWithLocalIdentifiers([assetIdentifier], options: PHFetchOptions())
// Fetch asset, if found, delete it
if let fetchedAssets = assets.firstObject as? PHAsset {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
// Delete asset
PHAssetChangeRequest.deleteAssets([fetchedAssets])
}, completionHandler: { (success, error) -> Void in
...
})
}
}
}
Which successfully deletes the video, BUT user have to confirm deletion first. That is a problem as that backing up won't work.
I obviously know why there is confirmation (so you don't clear entire user library for example, but the thing is, My app made the video - and so I thought there will be way around it, since as an "owner" I should not be doing that, or at least have option to disable confirmation.
Thanks in advance!
TLDR: How can I disable confirmation on delete request, if my application created that content? (I don't want to delete anything else).
Note: Somebody can probably say this is rather strange thing to do but the application is distributed internally and there is good reason to do it like this (the video content is too valuable to be lost, even if user deletes the application for some reason, or there is anything at all that goes wrong, we need to be able to preserve the videos), so please don't question that and just focus your attention on the question :)
I cannot see a way to avoid the delete confirmation. It is an implementation detail of the Photos framework, similar to the way you cannot prevent the device from asking the user's permission to use the microphone when your app tries to use it, and is a matter of security & trust. Once you have saved an asset to the device photo library your app is no longer the owner of that asset, so as you noted in your question the device must of course ensure the app has the user's permission before it goes about deleting such data.
You can never entirely safeguard your users' data against their own unpredictable behaviour - if they decide to remove your app, or delete a particular asset from within Photos, it is up to them. I think your best option is to either put up with the built-in delete confirmation, or to provide a guide to your users that makes it clear that they should be careful to protect this important data by backing up their device, and not deleting the app!
If you did decide to stick to this approach, perhaps the best thing you could do is to prepare the user for the fact that their device may ask them for confirmation to delete a file that is being uploaded to your own servers. For example, put up your own modal alert just before trying to delete the asset. I wouldn't normally suggest that kind of approach for a public shipping app, but since you're only distributing internally it may be acceptable for your team.

Resources