Recently some of our users were complaining about contacts not syncing to their iCloud account. It was working in iOS 8 and mysteriously stopped in one of the updates in iOS 9. With iOS 10 around the corner I thought it might be linked to deprecation of AddressBook.framework to Contacts.framework. However, even moving to new Contacts.framework it didn't fix the problem.
There are no error logs on console of the device and neither of the frameworks generate any errors when contacts are being created/updated on the device.
Contacts are visible and available on the device just not syncing to iCloud and other devices attached to the iCloud account.
After a lot of debugging what I was able to isolate the issue to imageData property. Contacts which had imageData populated were not synced and the few which had no images were synced. This lead me to look at the code for imageData. Turns out I had been using UIImagePNGRepresentation to convert UIImage to NSData for imageData. Moving to UIImageJPEGRepresentation fixed the issue. The day was saved and iCloud accounts are synced.
Thank you Apple for not documenting this change. (it might be the image size even that is not documented)
CNContact(s) created locally are not synced to Third party application.
I have crated contacts in iphone contacts page.Those contacts are not synching inside my application.I have used below method to fetch contacs from my contacts framework.
There are no error logs on console of the device and neither of the frameworks generate any errors when contacts are being created/updated on the device.
Contacts are visible and available on the device just not syncing to my app.I have given permission to my application to access my contacts.
#available(iOS 10.0, *)
func retrieveContacts(_ completion: (_ success: Bool, _ contacts: [ContactEntry]?) -> Void) {
var contacts = [ContactEntry]()
do {
let contactsFetchRequest = CNContactFetchRequest(keysToFetch: [CNContactGivenNameKey as CNKeyDescriptor, CNContactFamilyNameKey as CNKeyDescriptor, CNContactImageDataKey as CNKeyDescriptor, CNContactImageDataAvailableKey as CNKeyDescriptor, CNContactPhoneNumbersKey as CNKeyDescriptor, CNContactEmailAddressesKey as CNKeyDescriptor])
try contactStore.enumerateContacts(with: contactsFetchRequest, usingBlock: { (cnContact, error) in
if let contact = ContactEntry(cnContact: cnContact) {
contacts.append(contact)
print(contact,contacts.count)
}
})
completion(true, contacts)
} catch {
completion(false, nil)
}
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName),CNContactPhoneNumbersKey] as [Any]
let fetchRequest = CNContactFetchRequest( keysToFetch: keysToFetch as! [CNKeyDescriptor])
var contacts1 = [CNContact]()
if #available(iOS 10.0, *) {
fetchRequest.mutableObjects = false
} else {
// Fallback on earlier versions
}
fetchRequest.unifyResults = true
fetchRequest.sortOrder = .userDefault
let contactStoreID = CNContactStore().defaultContainerIdentifier()
print("\(contactStoreID)")
do {
try CNContactStore().enumerateContacts(with: fetchRequest) { (contact, stop) -> Void in
contacts1.append(contact)
print(contact)
}
} catch let e as NSError {
print(e.localizedDescription)
}
Related
So I have submitted an iOS app for review and the apple team has rejected it saying they cannot sign in on an iPad using the test number I provided. I know the test number works on my physical iPhone and it also works on an Xcode iPad simulator, however I do not have a physical iPad to test on.
Screenshot from them:
screenshot from apple review team
here is the phone number handling func:
private func startAuth(completion: #escaping (Bool) -> Void) {
let areaPhoneNum = "+\(self.getCountryCode() + self.phoneNum)"
print(areaPhoneNum)
PhoneAuthProvider.provider().verifyPhoneNumber(areaPhoneNum, uiDelegate: nil) { [weak self] verificationID, error in
if let verificationID = verificationID {
self?.verificationID = verificationID
completion(true)
} else if let error = error {
print(error)
self?.authPhoneErrorMess = error.localizedDescription
self?.isLoading = false
completion(false)
}
}
}
Why would an error message print on a valid test number?
I currently have an application that uses Core Data with Apple's NSPersistentCloudKitContainer and I've a 2 users mentioning they lost their data when updating the app - I'm using Lightweight migrations, and the only factor they have in common is: both had no iCloud Storage left.
After further inspection I've noticed that if I go to Settings > iCloud > Disable it for my app, whenever I open my app again all my data will be gone.
As anyone run into this issue? Is this expected? Any way around it?
For reference, here's my setup code:
self.container = NSPersistentCloudKitContainer(name: "DATABASE_NAME")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
Crashlytics.crashlytics().record(error: error)
}
self.container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
self.container.viewContext.automaticallyMergesChangesFromParent = true
})
You could add a check for availability of iCloud container:
func isICloudContainerAvailable()-> Bool {
if let _ = FileManager.default.ubiquityIdentityToken {
return true
} else {
return false
}
}
Depending on results of the check you could return NSPersistentCloudKitContainer or NSPersistentContainer. You should also turn on NSPersistentHistoryTrackingKey for NSPersistentContainer case:
lazy var persistentContainer: NSPersistentContainer = {
var container: NSPersistentContainer!
if isICloudContainerAvailable() {
container = NSPersistentCloudKitContainer(name: "DATABASE_NAME")
} else {
container = NSPersistentContainer(name: "DATABASE_NAME")
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
}
...
return container
}
This way if you go to Settings > iCloud > Disable it for the app, data still should be available in the app (it will use NSPersistentContainer). If you enable iCloud back, all changes in the app will also sync.
But in case user manually delete iCloud data for the app in Manage Account Storage, data in the app will be lost, as it said in the alert: "This will delete all data for this app stored on this iPhone and in iCloud". I can't find the way to save local data for such case.
I have the strangest problem. I’ve developed a SwiftUI app for iPhone, iPad and MacCatalyst using Core Data and CloudKit private database to sync the user’s data across all his/her devices.
The problem is that when I make an update on an iOS device (iPhone or iPad), the update syncs across all iOS devices, but not to the Macs. Similarly, updates I make on the Macs, sync across the Macs, but not to the iOS devices.
If I delete the app on the Mac and its associated sqllite database in the app’s associated ~/Library/Container/<myapp_container>/Data subfolder and reinstall the app, only the «Mac data» gets refilled from CloudKit. Likewise if I delete the app on an iOS device, only the «iOS data» arrives from CloudKit. In other words, it behaves as if the MacCatalyst data and the iOS data are stored separately in CloudKit.
Now, if I compile the app onto the Mac (rather than installing an Archive app), the sync from the iOS devices go through. But only once. Subsequent updates are not synced until I compile the app onto the Mac again. Likewise, updates made on the Mac only get synced (once) to the iOS devices when I compile the app on the Mac. Recompiling to the iOS devices does not make any difference.
The PersistentCloudkitContainer class:
public class PersistentCloudKitContainer {
// MARK: - Define Constants / Variables
public static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
// MARK: - Initializer
private init() {}
// MARK: - Core Data stack
public static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentCloudKitContainer(name: "MyModel")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
guard let description = container.persistentStoreDescriptions.first else {
fatalError("### PersistentCloudKitContainer->\(#function): Failed to retrieve persistant store description")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.cloudKitContainerOptions?.databaseScope = .private
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
// MARK: - Core Data Saving support
public static func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
The persistent container is called by the SwiftUI App struct:
struct MyApp: App {
let context = PersistentCloudKitContainer.persistentContainer.viewContext
var body: some Scene {
WindowGroup {
ContentView().environment(\.managedObjectContext, context)
}
}
}
In my iOS app I have a bunch of mp4 videos that I download at a certain time on the app using On Demand Resources. Using this tutorial:
https://www.raywenderlich.com/520-on-demand-resources-in-ios-tutorial
I download the resources like this at the start of the app, in a previous view controller:
func requestSceneWith(tag: String,
onSuccess: #escaping () -> Void,
onFailure: #escaping (NSError) -> Void) {
// 2
currentRequest = NSBundleResourceRequest(tags: [tag])
// 3
guard let request = currentRequest else { return }
request.beginAccessingResources { (error: Error?) in
// 4
if let error = error {
onFailure(error as NSError)
return
}
// 5
onSuccess()
}
The resource seem to download fine, and I know that they have been downloaded, by looking in the disk report in xcode.
However, when the videos are supposed to be played in the app, the app just shows a black screen. Here is my code to play the videos:
let videoURL = Bundle.main.url(forResource: "cow2", withExtension: "mp4", subdirectory: "Videos/Animals")
self.player = AVPlayer(url: videoURL!)
self.myPlayerController.player = self.player
self.myPlayerController.player?.play()
Now, when the resources are not tagged, and they come with the app and not downloaded later, they work fine. And the console prints the file name, like I did (print(videoURL.absoluteString). But after they are tagged and downloaded later, they dont work, and nothing prints in the console. Just a black screen appears in the app.
I've been stuck on this for ages, and help with really help.
Thanks
I think you didn't request the on-demand resources before accessing the video file. Maybe you can try to declare the NSBundleResourceRequest instance as a global variable in AppDelegate.
I've been working for a while on the login part of my app. I'm trying to use ASW Mobile Hub for this matter. I found a way to get it work with the different providers I need: my own user pool, FB and Google.
The problem is that I've been searching here and all over the AWS documentation trying to find the way to get user data (Username and some othe user data like picture, email and so on). I can get it if I'm using the FBSDK directly (usingFBSDKGraphRequest) but I don't know how to do it if the user choose to login in my cognito-user-pool. Also I cannot see what provider the user used once succeeded.
I can find some other ways to get that, but using the old SDK o directly Cognito calls and initially is not what I need. Here's the code I'm using to present the login window:
override func viewDidLoad() {
super.viewDidLoad()
if !AWSSignInManager.sharedInstance().isLoggedIn {
presentAuthUIViewController()
}
}
func presentAuthUIViewController() {
let config = AWSAuthUIConfiguration()
config.enableUserPoolsUI = true
config.addSignInButtonView(class: AWSFacebookSignInButton.self)
config.addSignInButtonView(class: AWSGoogleSignInButton.self)
AWSAuthUIViewController.presentViewController(
with: self.navigationController!,
configuration: config, completionHandler: { (provider:
AWSSignInProvider, error: Error?) in
if error == nil {
// SignIn succeeded.
} else {
// end user faced error while loggin in, take any
required action here.
}
})
}
So, the question is, how can I get the relevant user info, once the signin is succeeded?
If the user used cognito login, you can use the below code to get the username.
let identityManager = AWSIdentityManager.default()
let identityUserName = identityManager.identityProfile?.userName
For retrieving the provider once user succeeds, keep it in the session as below
func onLogin(signInProvider: AWSSignInProvider, result: Any?,
authState: AWSIdentityManagerAuthState, error: Error?) {
let defaults = UserDefaults.standard
defaults.set(signInProvider.identityProviderName, forKey:
"identityProviderName")
}
Hope this answer helps.
Updated Code to get Username:
let pool = AWSCognitoIdentityUserPool.init(forKey: "CognitoUserPools")
let username = pool.currentUser()?.username
I've been working on a workaround till I sort this out in a more elegant way. I guess that I need to go deeper in Cognito's understanding. But the fact is even the sample provided by Amazon doesen't show the User's Name...
Sample Amazon app screen
So, in the meantime, I modified the source code of the Cognito library AWSUserPoolsUIOperations to send me the data directly to my app, on a message:
#implementation AWSUserPoolsUIOperations
-(void)loginWithUserName:(NSString *)userName
password:(NSString *)password
navigationController:(UINavigationController *)navController
completionHandler:(nonnull void (^)(id _Nullable, NSError *
_Nullable))completionHandler {
self.userName = userName;
NSDictionary* userInfo = #{#"username": self.userName};
[[NSNotificationCenter defaultCenter]
postNotificationName:#"UsernameNotification"
object:nil userInfo:userInfo];
And then just getting the message in the app and storing the value.
#objc private func TestNotification(_ notification: NSNotification){
if let dict = notification.userInfo as NSDictionary? {
if let username = dict["username"] as? String {
appEstats.username = username
defaults.set(username, forKey: sUserName)
}
}
}
As I said is not the solution but in the meantime it works.