Access contents of Watchkit extension's host's mainBundle - objective-c

I'm writing a WatchKit extension and I'd like to read a file out of the host application's [NSBundle mainBundle]. I've tried [NSBundle bundleWithIdentifier:] but that just returns nil.
I have several potential workarounds, but nothing that would be as simple as "just read what you need from the host's mainBundle".
Is there a way of doing this?

The host app and your WatchKit extension can share files in only one of two ways, as far as I know:
Shared app group
Including a file in both targets
They run in separate processes and aren't accessible to each other outside of approved methods.

I ran into a similar problem like yours. The main host app has a particular pList that I needed to read, and I couldn't read from watch extension because they are isolated.
So in the watch I invoked the openParentApplication method
and in the main application my handler was something along the lines of
-(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
NSString *request = [userInfo objectForKey:#"request"];
if ([request isEqualToString:ReadFile])
{
//read the file. and then i like to put it into a NSDictionary
NSDictionary *responseDictionary = //whatever
reply(responseDictionary);
}
else{ reply(nil); }
}
And then the contents were returned to me in the callback closure on the watch of the openParentApplication. Seems to work. Though your situation could be different in which case this method might not be viable.

From the Apple WatchKit programming guide:
To share preferences data between apps, create an NSUserDefaults object using the identifier of the shared group. The initWithSuiteName: method of NSUserDefaults creates an object that allows access to the shared user defaults data. Both processes can access this data and write changes to it.
Your main app can write a NSDictionary/NSArray to the shared prefs, and then the watch kit can pull it out, without starting the main app - however, the main app will have to be run at least once to update the shared prefs.

Related

Fetch preferences / permissions for NSUserNotificationCenter

I'm currently trying to set up a notification service using the old NSUserNotification API on macOSX (not iOS) in objective c++.
The catch is that I would need to know the system preferences associated with notifications that the user set (and preferably during the rest of runtime too) to determine if my notifications are actually seen. The application is meant to get the users attention in some way to guarantee things like reminders don't go unnoticed, so the thing I want to avoid is a notification delivered that silently doesn't produce any sound nor image on the screen whatsoever without the application knowing. If the notification is not allowed the application should handle it in some other way.
I am working on a macOS 10.14 Mojave machine. My overall code with NSUserNotificationCenter works fine with a custom Delegate that implements shouldDeliver, didActivate etc. Its just that I am currently assuming the user didn't press the "frick off" button on my notifications.
Unfortunately, NSUserNotificationCenter and related classes don't appear to feature a function that determines the permissions the way the new API (UNUserNotificationCenter) does. I have been trying to resolve this for a few hours so I was wondering if anyone else has found a solution to this. I can't really use the new API since this is strictly for back-compatibility in favour of the possibly rather prevalent amount of people who don't use mojave yet.
Things I have tried to make it work so far:
Try and see if there is a .plist somewhere where these settings are stored, in the User-specific Library folder as well as the general Library folder.
Try and find said .plist using CFPreferencesCopyKeyList and related methods. Apparently I don't really know the right domain name.
This answer from 2012 which appears to be outdated since I cannot locate the database on my machine anymore. Might be due to me using a mojave machine, or the file has since moved somewhere else.
Try and determine whether we can retro-actively check our permissions by checking a NSUserNotification's "presented" property on didDeliverNotification, shouldPresentNotification in the delegate. Unfortunately as stated in NSUserNotificationCenter itself it still behaves the same whether the preferences allow notifications or not.
Some code I tried includes the following:
CFPreferencesCopyKeyList((CFStringRef) #"com.apple.systemPreferences.plist", kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
CFPreferencesCopyKeyList((CFStringRef) #"com.apple.systemPreferences.plist", kCFPreferencesAnyUser, kCFPreferencesAnyHost);
//Unfortunately returns null with any combination of com.apple.notificationcenter.plist, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost
CFPreferencesCopyKeyList((CFStringRef) #"com.apple.notificationcenter", kCFPreferencesAnyUser, kCFPreferencesAnyHost);
On my system (only macOS High Sierra), it looks like the file you're after is:
~/Library/Preferences/com.apple.ncprefs.plist
That would correspond to a combination of kCFPreferencesCurrentUser, and kCFPreferencesAnyHost. (For the record, kCFPreferencesCurrentUser + kCFPreferencesCurrentHost equates to pref files in the ~/Library/Preferences/ByHost/ folder).
I don't think any settings would be stored in the local domain (by that I mean in the root /Library/Preferences/ folder).
I was able to get a list of apps and settings using the following code:
[[NSUserDefaults standardUserDefaults] addSuiteNamed:#"com.apple.ncprefs"];
NSArray *apps = [[NSUserDefaults standardUserDefaults] objectForKey:#"apps"];
NSLog(#"[%# %#] apps == %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), apps);
NOTE: this will not work if your app is sanboxed, as access to other pref files like that will be denied.

Always appending file extensions in NSDocument

What would be the best way to always ensure the file saved to disk (or iCloud) contains the default file extension for our document format in an NSDocument based Cocoa app?
Background:
Users can load legacy files into the app that still use Type-Creator-Codes.
With auto-saving enabled in our app we need to make sure the file always has the file extension added as soon as it's written back to the disk (following any kind of changes) with our Cocoa app - or the app won't be able to open it (with now neither the type-creator-code nor the file extension).
If I got it right I'd overwrite NSDocumentController's open method
- (void)openDocumentWithContentsOfURL:(NSURL *)url
display:(BOOL)displayDocument
completionHandler:(void (^)(NSDocument *document,
BOOL documentWasAlreadyOpen,
NSError *error))completionHandler {
if(!url.pathExtension isEqualToString:#"XYZ")
url = url URLByAppendingPathExtension:#"XYZ"];
[super openDocumentWithContentsOfURL:url
display:displayDocument
completionHandler:completionHandler];
}
NOTE: written inline

Objective C reading array from file fails on different locations

I am developing a mac app and I want to put my settings (array of strings) into a file. If that file exists in the same folder as the app it is read and it overwrites default settings.
No problems writing the file but when reading with code:
NSArray* settingsInput = [[NSArray alloc] initWithContentsOfFile:#"./SettingsFile"];
something strange happens. When running the app from XCode (the settings file is in the build/debug folder next to the app) settings are read without a problem. When I archive the app and run it from the desktop, the file cannot be loaded. Even when I copy the app from the build folder to the desktop it does not work.
What could be the reason for this kind of behaviour? How can I read this file?
It may be a better Idea to use the normal prefence system. NSUserDefaults.
There a couple of ways you can do it.
But the idea is to give your app a set of default preference which are registered for you in the correct domain and always with a fresh app.
Using the registerDefaults: from NSUserDefaults.
See Apples documentation NSUserDefaults and its #registerDefaults
But the one I would use is :
Copy a plist file into the supporting files in you Xcode project.
Making sure "Copy files into destination group's folder" is checked. And the "Add to targets is check also"
The plist file should contain your array of strings.
(I created mine. By duplicating another plist in my user preferences. Renaming it. Copying it to the project. Selecting it and editing it to how I needed it. Making sure I use the file menu ->'Save' to save the changes. )
Declare a NSUserDefaults * prefs;
Now in the - (id)init method for the app. you register the contents of the file as your default preferences.
- (id)init
{
self = [super init];
if (self) {
prefs = [NSUserDefaults standardUserDefaults] ;
NSString *registerDefaultsPlistFile= [[NSBundle mainBundle] pathForResource:#"registerDefaults" ofType:#"plist"];
[prefs registerDefaults:[NSDictionary dictionaryWithContentsOfFile: registerDefaultsPlistFile]];
}
return self;
}
You can later make a call to read these preferences.
NSLog(#" arrayOfStrings = %#", [prefs objectForKey:#"arrayOfStrings" ]);
These default preferences are NOT written to file/out unless you make a change to them. By written to file I mean to the applications preference file. Once you do make a change to them then they will be written out into the users preferences and those are what will be used from then on.
You should not rely on the current directory of the app. You should either read from the app bundle (see NSBundle class for get the correct path) or the app's document directory (see NSSearchPathForDirectoriesInDomains(NSDocumentDirectory ...).
The UNIX concept of the current working directory is not commonly used in Mac desktop applications. When launching an app through the Finder it's usually set to the root directory of the boot volume.
You should find another way to determine locations for your settings files. Good spots would be ~/Library/Preferences or ~/Library/Application Support/MyApp. You can get the absolute path to these directories using:
NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomain, YES)[0];

Authenticating Dropbox in iOS

I am adding Dropbox support to my iOS application. Using the official Dropbox API and the tutorials online here I have gotten to the point where Dropbox needs to be authenticated. The code below is what is given to Authenticate when a button is pressed:
//MainViewController.m
....
#implementation CryptoMainViewController
.....
#pragma mark - Dropbox
- (void)didPressLink {
if (![[DBSession sharedSession] isLinked]) {
[[DBSession sharedSession] link];
}
}
But no matter how I change the code, where I put it or what button I link it to, nothing will happen. Using breakpoints I've found that the method does in-fact get triggered. I've even put it in an IBAction, but this gives the same result. What am I doing wrong? How can I get my app to authenticate the end-user?
And, once authenticated, How can I save an NSString to the user's Dropbox?
If this is just totally wrong, then where can I go to find resources on how to do this properly?
The whole tutorial, all of the documentation, api, etc. is available here.
I had the same problem; the reason was that I hadn't set the shared Dropbox session, e.g.
DBSession* dbSession = [[[DBSession alloc] initWithAppKey: #"your_app_key"
appSecret: #"your_app_secret"
root: kDBRootAppFolder] autorelease];
[DBSession setSharedSession: dbSession];
Once that was called the link worked fine.
this answer may be late but im guessing that you already linked your app before and want to do so again. The only way you can have the process of linking taking place again is if you run the following code:
[[DBSession sharedSession] unlinkAll];
You can place it in your viewDidLoad. When you then call didPressLink: the app should open up dropbox app(if available), safari or an in app window asking for your permission to access your dropbox. If this does not happen then the problem is somewhere else. Hope this helps
Does your view implement the <DBLoginControllerDelegate> ?
If so, link Dropbox like so:
DBLoginController* controller = [[DBLoginController new] autorelease];
controller.delegate = self;
[controller presentFromController:self];

cocoa open multiple files with associated app

I have an array of filenames that i would like to pass to an external application for opening. I'd like to do one of the following:
a) Somehow instruct OSX to open all these files with an associated application, but it must invoke the target app's openFiles NSApplication delegate method
b) Specify the application to open these files with (and also invoke openFiles)
Basically it doesn't matter which solution to realise, because these files will be associated with the target application anyway. How would i do one of these things?
To open a whole bunch of files at once, send the shared NSWorkspace object an openURLs:withAppBundleIdentifier:options:additionalEventParamDescriptor:launchIdentifiers: message, or call the LSOpenURLsWithRole function or the LSOpenFromURLSpec function. Either way, you'll pass an array of URLs to items to open.
Each one of these will let you identify a specific application to use. NSWorkspace lets you specify it by bundle identifier, while the two Launch Services functions let you provide the URL or FSRef to a specific application bundle.
… it must invoke the target app's openFiles NSApplication delegate method
That isn't possible to require, because (a) the application may be document-based, in which case it probably does not have an NSApplication delegate and, even if it does, such a delegate would probably not respond to application:openFiles:, and (b) the application may not be Cocoa-based, in which case it would handle the Open Documents Apple Event directly. None of this is your application's business, so don't worry about it.
First add your video files to Resources Folder.The code like follow:
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
NSString* videoPath = [bundlePath stringByAppendingPathComponent:#"/Contents/Resources/video.mov"];
[[NSWorkspace sharedWorkspace] openFile:videoPath];