CNContact for Mac OS X 10.11 - macos

from OS X 10.11 and iOS 9.0 Apple provided this framework Contacts:
https://developer.apple.com/library/prerelease/mac/documentation/Contacts/Reference/Contacts_Framework/index.html#//apple_ref/doc/uid/TP40015328
and I would like to access contacts from Mac, but I cannot do anything, because the app does not have access to contacts:
switch CNContactStore.authorizationStatusForEntityType(.Contacts) {
case .Authorized:
print("Authorized")
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "contactsAllowed")
case .Denied:
print("Denied")
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "contactsAllowed")
case .NotDetermined:
print("Not Determined")
case .Restricted:
print("Restricted")
}
The app never prompts to allow access to contacts, neither can I select it in System Preferences (it does not show up).
Has anyone any idea how to acccess them on Mac (on iOS it works well)?

Any call to instance of CNContactStore should trigger request. If not try
Reboot (actually this helped me; I was granted access)
Turn on sandboxing
Resetprivacy settings "sudo tccutil reset AddressBook"
Call it on main thread
Instance of CNContactStore call:
[[CNContactStore alloc] init];
Privacy
Users can grant or deny access to contact data on a per-application
basis. Any call to CNContactStore will block the application while the
user is being asked to grant or deny access. Note that the user is
prompted only the first time access is requested; any subsequent
CNContactStore calls use the existing permissions. To avoid having
your app’s UI main thread block for this access, you can use either
the asynchronous method [CNContactStore
requestAccessForEntityType:CNEntityTypeContacts completionHandler:] or
dispatch your CNContactStore usage to a background thread.

Are you trying this from the playground? This won't work.
Try this method in your project:
func getContactImage(name:String) -> NSImage?
{
let store = CNContactStore()
do
{
let contacts = try store.unifiedContactsMatchingPredicate(CNContact.predicateForContactsMatchingName(name), keysToFetch:[CNContactImageDataKey])
if contacts.count > 0
{
if let image = contact[0].imageData
{
return NSImage.init(data: image)
}
}
}
catch
{
}
return nil
}
Anyway your question led me on the path to find a way to get the contacts image, so thanks for that.

Related

Prevent Catalina from asking for app permissions (like microphone or keychain access) on each new Xcode build

It seems that Catalina will ask the user for permissions for an app every time anything changes in the Xcode build.
For example, I have a simple example that asks for permission to access the microphone. The first time the app is compiled and run, it asks for microphone access. I grant that access. If I build and run again without changing anything in the code, that access will be retained. However, if I change any code (even changing the Text label by one character), the permission dialog is displayed again.
import AVFoundation
import SwiftUI
struct ContentView : View {
#ObservedObject private var recorderManager = AudioRecorderManager()
var body: some View {
Text("Recording view")
}
}
class AudioRecorderManager: NSObject, ObservableObject {
override init() {
super.init()
AVCaptureDevice.requestAccess(for: .audio) { allowed in
DispatchQueue.main.async {
if allowed {
print("Allowed")
} else {
print("Not allowed")
}
}
}
}
}
Note that this also requires you to add Audio Input to the entitlements and NSMicrophoneUsageDescription to the Info.plist before it'll run.
I've also noticed the same phenomenon happening with accessing Keychain data.
This is dramatically slowing down my development time having to confirm access (or worse, enter my password for the Keychain data) every single time I build the app. Is there a solution to get Catalina/Xcode to give lasting permission to the app during development?

Why does Xcode simulator launch app with didEnterRegion call, while iPhone does not?

I'm troubleshooting a location-based app launch cycle that works fine on iOS simulator, but apparently does not launch the app on an actual device.
I've set up UNNotifications for both enter and exit events. Both the simulator and the device(s) register and display these notifications.
The next thing that is supposed to happen is that the app goes through a launch process, setting the app's state in such a way that I can tell without being connected to the debugger whether the app has launched or not when I open it from the home screen.
On simulator, the didEnterRegion code gets called and I can step through the subsequent launch code using the debugger - success.
However when I take the device out (for a walk) all I get are UNNotifications, and no app launch (which I can tell from the UI in the app on the real device)
I'm sure I need to improve my testing strategy (welcome to suggestions!), but at this point I should be able to expect that the app should behave the same on the simulator and the actual device - it is not.
Why is the expected outcome happening on the simulator but not on the device?
LocationService.swift
func handleRegionEnterEvent (for region: CLRegion) {
ApplicationController.sharedInstance.didEnterRegion(region)
}
func handleRegionExitEvent (for region: CLRegion) {
ApplicationController.sharedInstance.didExitRegion(region)
}
ApplicationController.swift
func didEnterRegion (_ region: CLRegion) {
// Present Alert
delegate?.handleUNEvent(note: "ENTER: \(region.identifier)")
// Start launch cycle
}
AppDelegate.swift
extension AppDelegate: AppControllerDelegate {
func handleUNEvent(note: String?) {
// Show an alert if application is active
if UIApplication.shared.applicationState == .active {
guard let message = note else { return }
Alert().showAlert(withMessage: "Application received UN while active: \(message)", title: "UNNotification")
} else {
// Otherwise present a local notification
guard let body = note else { return }
let notificationContent = UNMutableNotificationContent()
notificationContent.body = body
notificationContent.sound = UNNotificationSound.default
notificationContent.badge = UIApplication.shared.applicationIconBadgeNumber + 1 as NSNumber
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: "location_change",
content: notificationContent,
trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Error: \(error)")
}
}
}
}
}
The simulator sadly has a lot of differences from a device both in terms of data reported (you will not get speed for example) and also as you saw call flow may be different...
Something to check is to make sure you have the descriptions for the specific location permissions you are asking for in relation to location set up. For entering regions you probably want to make sure you have "always" access.
It may also be worth enabling wireless debugging, and walking around with laptop tethered to the device to verify with the debugger the didEnterRegion is truly not called, and there is a bug in the code that is supposed to display the UI you are expecting. At the very least perhaps you could get better logging as to the status of various location callbacks.
What happened: Code that executed on simulator was indeed crashing on the device and I was not reviewing crash logs from the device. This question arose from an issue that is neither ios, or core-location specific.
It has everything to do with choosing appropriate testing strategies and using all available information at one's disposal before coming to what may turn out to be premature conclusions.
However when I take the device out (for a walk) all I get are UNNotifications, and no app launch (which I can tell from the UI in the app on the real device)
I was mistaken to think that a) User Notifications that appear when the App is not running are presented by the system (as banners) and b) the App launches in another process. In fact my own code specifically executes the code that makes those notifications appear. Face-to-palm: Copying code from tutorials to achieve quick test results without even trying to understand what the code does can lead to that I suppose.
In Xcode, plug in your device and hit command-shift-2 to open Devices & Simulators window. Select your device on the left and hit "View Logs" button to bring up your crash log.

Is there a way to force a bot to always accept permission alerts under ui tests in xcode continuous integration?

I need to run continuous integration on xcode server using a lot of simulators. Is there a way to force it to always accept permission alerts like:
Allow "App" to access your photos
and so on...
In your setUp() method, create an interruption monitor and handle the alert by tapping the OK button. This means that whenever you try to interact with the app, a check will be made to see if the permissions view is in the way, and tap the OK button.
let permissionInterruptionMonitor = addUIInterruptionMonitor(withDescription: "Photos permission alert") { (alert) in
alert.buttons["OK"].tap()
return true // The interruption has been handled
}
If there are other alerts that may appear in your app with an OK button, but you don't want those to be automatically handled, you should make sure that the interruption monitor handler checks that it is the alert that you want to handle.
let permissionInterruptionMonitor = addUIInterruptionMonitor(withDescription: "Photos permission alert") { (alert) in
if alert.staticTexts["\"AppName\" Would Like To Access Your Photos"].exists {
alert.buttons["OK"].tap()
return true // The interruption has been handled
}
return false // The interruption has not been handled
}

How to give an app access to Accounts that was previously denied?

I've been playing with Accounts framework and during my tests I've denied the app access to Accounts. Now the following code returns Denied each time I run it.
[[self accountStore] requestAccessToAccountsWithType:twitterType options:nil completion:^(BOOL granted, NSError *error) {
if (granted) {
accounts = [[self accountStore] accountsWithAccountType:twitterType];
} else if (error != nil) {
NSLog(#"Failed to list Twitter accounts %#", error);
} else {
NSLog(#"Access denied");
}
}];
What sucks is I have no clue where to reset it and googling doesn't help. I've seen how to fix it on iOS but I'm running a Mac app and the solution doesn't apply.
The Account Framework API documentation is not helpful at all.
Palm face.
Figured it out, I've found a file with my app id called "~/Library//Application Support/com.apple.TCC/TCC.db"
It's a sqlite3 file, so run sqlite3 "~/Library//Application Support/com.apple.TCC/TCC.db" then search for your app in "access" table, delete the row and should you have access again.
If that doesn't help there's also a file called ~/Library/Accounts/Accounts3.sqlite, there ZAUTHORIZATION table which includes your app.
The proposed solution no longer works, because of high-profile hacks made by big companies and others. This solution has never been supported and is an invitation to hackers. The right way to do this is using the tool provided by Apple: tccutil.
Thus, do the following:
Open Terminal.
Run tccutil reset <service> [bundle_id], in which <service> can be AddressBook or Calendar or All with the optional ID of the bundle you want to reset.
Example:
tccutil reset All com.apple.Terminal

How do you write to the OSX System keychain?

As part of the process of creating a VPN connection programatically in OSX, using Cocoa, I need to store the PPP password in the System keychain. When I try to do this using the keychain API, I get the following error as a result of calling SecKeychainAddGenericPassword:
"Could not write to the file. It may have been opened with insufficient access privileges."
Here is the code I am using:
- (void)storePasswordInKeychain
{
SecKeychainRef keychain = nil;
err = SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem, &keychain);
if (err != errSecSuccess) {
NSLog(#"Error getting system keychain: %#", SecCopyErrorMessageString(err, NULL));
} else {
NSLog(#"Succeeded opening keychain: %#", SecCopyErrorMessageString(err, NULL));
SecKeychainItemRef item = nil;
err = SecKeychainUnlock(keychain, 0, NULL, FALSE);
NSLog(#"Keychain unlocked: %#", SecCopyErrorMessageString(err, NULL));
err = SecKeychainAddGenericPassword (keychain,
3, "VPN",
8, "username",
8, "password",
&item);
NSLog(#"Result of storing password: %#", SecCopyErrorMessageString(err, NULL));
}
}
The discussion How to write to the System.keychain? makes it seem like I need to make a command line call to /usr/bin/security from within my program, but the point of the Keychain API seems to be to avoid that kind of hackery.
Can anybody point me in the right direction for storing a new password in the System keychain? Thanks.
You need root privilege when you write something to system keychain.
For xcode debugging, you just "EditScheme"(From Menu by "Product->EditScheme...->Run->Info->Debug process as->root"). Well,my xcode version is 6.1, maybe there is some difference in different xcode version.
Or just use command line by sudo your app.
Hope this helps.
It is true that the credentials need to go into the system keychain and not the user keychain. You will not need the SMJobBless to do that.
After you unlocked the keychain, create an SecAccessRef like so:
SecAccessRef access = nil;
status = SecAccessCreate(CFSTR("Some VPN Test"), (__bridge CFArrayRef)(self.trustedApps), &access);
Then build your keychain item
SecKeychainAttribute attrs[] = {
{kSecLabelItemAttr, (int)strlen(labelUTF8), (char *)labelUTF8},
{kSecAccountItemAttr, (int)strlen(accountUTF8), (char *)accountUTF8},
{kSecServiceItemAttr, (int)strlen(serviceUTF8), (char *)serviceUTF8},
{kSecDescriptionItemAttr, (int)strlen(descriptionUTF8), (char *)descriptionUTF8},
};
And finally store it to the keychain:
SecKeychainAttributeList attributes = {sizeof(attrs) / sizeof(attrs[0]), attrs};
status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attributes, (int)strlen(passwordUTF8), passwordUTF8, keychain, access, &item);
There is a project on Github which does just that. Have a look at the VPNKeychain.m file to see the whole implementation. https://github.com/halo/macosvpn
Surely a VPN username & password belong to a particular user, do you really mean the system keychain and not the user keychain?
Try dropping the calls to SecKeychainCopyDomainDefault & SecKeychainUnlock and just pass NULL as the first argument to SecKeychainAddGenericPassword - this should add the item to the default keychain.
Apple's current recommendation for privileged access is to use the Service Management API. They have a sample project: SMJobBless. I'm not sure if the sandbox will allow system keychain access if you are targeting the Mac App Store.
The other question you point to seems to be recommending the Authorization Services API which Service Management replaced (in 10.6) and which is explicitly stated as not being allowed at all when sandboxed.

Resources