I'm trying to reload the icon cache after installing an application through LSApplicationWorkspace. The applications install, but I have to execute "uicache" in mobileterminal to reload the springboard cache. Is there a way I could do this programmatically? The application ISN'T running as root by the way.
At the moment, I have code as follows to reload the springboard icon cache but it doesn't work:
remove("/var/mobile/Library/Caches/com.apple.mobile.installation.plist");
remove("/var/mobile/Library/Caches/com.apple.springboard-imagecache-icons");
remove("/var/mobile/Library/Caches/com.apple.springboard-imagecache-icons.plist");
remove("/var/mobile/Library/Caches/com.apple.springboard-imagecache-smallicons");
remove("/var/mobile/Library/Caches/com.apple.springboard-imagecache-smallicons.plist");
remove("/var/mobile/Library/Caches/SpringBoardIconCache");
remove("/var/mobile/Library/Caches/SpringBoardIconCache-small");
remove("/var/mobile/Library/Caches/com.apple.IconsCache");
// find ios 8 version:
NSString *cache_path = #"/var/mobile/Library/Caches/";
NSArray *conts = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:cache_path error:nil];
for (NSString *filefolder in conts) {
if ([filefolder containsString:#"com.apple.LaunchServices"]) {
NSString *newpath = [cache_path stringByAppendingPathComponent:filefolder];
remove([newpath UTF8String]);
}
}
// build the args list
Class __LSApplicationWorkspace = objc_getClass("LSApplicationWorkspace");
[(LSApplicationWorkspace *)[__LSApplicationWorkspace defaultWorkspace] invalidateIconCache:nil];
[(LSApplicationWorkspace *)[__LSApplicationWorkspace defaultWorkspace] registerApplication:[NSURL fileURLWithPath:bundlePath]];
int didNotify = notify_post("com.apple.mobile.application_installed");
NSLog(#"Did Notify: %i",didNotify);
// remove temp.app:
if ([[NSFileManager defaultManager] fileExistsAtPath:bundlePath]) {
[[NSFileManager defaultManager] removeItemAtPath:bundlePath error:nil];
}
I found a temporary solution to my question if anyone is interested. I created an extra application (running as root), that was to be installed separately from the original app. I made this application invisible to the user by adding the following to the Info.plist:
<key>SBAppTags</key>
<array>
<string>hidden</string>
</array>
And used URL Schemes to call the "Reload" function.
Although another option could have been to create a LaunchDaemon or a MobileSubstrate extension. I couldn't find a way of reloading the cache as a "mobile" user though.
Related
In its documents, my application uses a lot of assets that are relative to the document path. So the document must be saved before assets can be added. How can I force-call a [NSDocument saveDocumentAs] ?
I managed to do parts of it : by creating my own document controller, and inside openUntitledDocumentAndDisplay: force a call like this :
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError **)outError
{
NSDocument * res = [super openUntitledDocumentAndDisplay:displayDocument error:outError];
[res saveDocumentAs:self];
return res;
}
This forces the save dialog to appear, but unfortunately I can not check whether the user pressed cancel : the saveDocumentAs call is asynchronous and continues immediately !
Is there a way to fix this ?
I had a similar problem. By using:
saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo
you can defer your processing (or not) until after the document save dialogue has completed. This means you can find out whether the user cancelled or not. You split your processing in two, do whatever preparation you need and put the rest (that depends upon a successful save) into another method. If you use something like:
[self saveDocumentWithDelegate:self didSaveSelector:#selector(actuallyDoIt: didSave: contextInfo:) contextInfo:nil];
The document will be saved but, critically, if it has not been saved before, the Save dialogue will appear so the user can input a file name. Once he/she has done that, or cancelled, your method actuallyDoIt: (or whatever) is invoked. The didSave: parameter tells you whether the save actually happened (essentially, did the user cancel) so you can either continue or offer an alert explaining politely to the user that nothing's going to happen until they save.
I have a similar thing in my application, in my case if the user tries to do something, I pull up a prompt to say 'This requires you to save the document first' with buttons to cancel or save.
If you want to absolutely force it, then instead of using saveDocumentAs, just display your own NSSavePanel. Run it modally, check the result, save the document with the result, and if this doesn;t go smoothly, call it again. You can check if the document is saved by looking for a valid value for it's file path.
NSSavePanel can run modally. Here is how it can/should look like.
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError *__autoreleasing *)outError
{
Document *document;
NSSavePanel *panel = [NSSavePanel savePanel];
panel.prompt = #"Create";
NSInteger modalCode = [panel runModal];
if (modalCode == NSModalResponseOK) {
NSURL *URL = [panel URL];
NSError *docError;
document = [[Document alloc] initWithType:nil & docError];
[document saveToURL:URL ofType:nil forSaveOperation:NSSaveOperation completionHandler:^(NSError *error){
if(error) {
return nil;
}
[self addDocument:document];
[document makeWindowControllers];
if (displayDocument) {
[document showWindows];
}
}];
}
return document;
}
To sum up for reference:
Create custom nsdocumentsubclass in XIB (no XIB -> app did finish launching)
override openUntitledDocumentAndDisplay
(NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError;
The related code below worked perfect when i was building for iOS 7, but it seems now in iOS 8, it's not working properly.
By properly, I mean in the sense where it's not actually sending the file or whatever to the chosen app.
Example: If I selected Mail, it would open the mail app with the image or zip I chose in the text field. Now it won't send and it takes forever to call/dismiss the UIDocumentInteractionController.
What am I doing wrong?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.tableView deselectRowAtIndexPath:indexPath animated:NO];
NSString *fileName = [directoryContents objectAtIndex:indexPath.row];
NSString *path;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Downloads"];
path = [path stringByAppendingPathComponent:fileName];
documentController = [[UIDocumentInteractionController alloc] init];
documentController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]];
[documentController setDelegate:self];
[documentController presentOptionsMenuFromRect:CGRectZero inView:self.view animated:YES];
[documentController retain];
}
I have been playing around with the UIDocumentInteractionController and Delegate trying to fix a similar problem, the controller opened-up alright but selecting an application caused it to close without doing anything, my delegate method documentInteractionControllerDidDismissOpenInMenu also run alright afterwards.
In the console i got the notification enabledRemoteNotificationTypes is not supported in iOS 8.0 and later.
It turns out that this problem will accrue when one of these delegate methods is called :
documentInteractionControllerDidDismissOpenInMenu
documentInteractionControllerDidDismissOptionsMenu
(and possibly others, i did not check all of them)
I did not find any comment in the IOS Development Library or the UIDocumentInteractionController.h about these methods not supported for IOS 8.1 but at this point i cant find any other explanation.
Solution :
i replaced documentInteractionControllerDidDismissOpenInMenu
with didEndSendingToApplication
and it solved the problem for me.
I have a very strange (& serious) problem.
My app uses a UIDocumentInteractionController to share a PDF document.
When the user selects the "Mail" option in the controller's pop-up the MailCompose window is opened.
But, neither the Send nor Cancel button in this window causes the MailCompose window to be dismissed, meaning the user gets stuck and has to kill the app. The mail does go out though.
Here's the catch:
This happens only in iOS8 (both versions released so far) and only on apps installed via the AppStore. That EXACT same version of the app, when running on my device via USB debugging works fine.
Here's some code:
-(void)sharePDF:(id)sender
{
#try
{
NSURL *fileURL = [NSURL fileURLWithPath:currentFileObject.LocalPath];
if(fileURL)
{
//UIDocumentInteractionController
NSString *newPath;
#try
{
//Create a copy of the file for sharing with a friendly name
if (currentFileObject.isSpecialReport)
{
newPath = [svc saveReport:[NSData dataWithContentsOfURL:fileURL] ToFile:[NSString stringWithFormat:#"%#.pdf", currentFileObject.ReportName]];
}
else
{
newPath = [svc saveReport:[NSData dataWithContentsOfURL:fileURL] ToFile:[NSString stringWithFormat:#"%#.pdf", currentFileObject.PatientFullName]];
}
}
#catch (NSException *exception) {
return;
}
NSURL *newURL = [NSURL fileURLWithPath:newPath];
self.docController = [UIDocumentInteractionController interactionControllerWithURL:newURL];
self.docController.delegate = self;
if (currentFileObject.isSpecialReport)
{
self.docController.name = [NSString stringWithFormat:#"Pathology - %#", currentFileObject.ReportName];
}
else
{
self.docController.name = [NSString stringWithFormat:#"Pathology - %#", currentFileObject.PatientFullName];
}
[self.docController presentOptionsMenuFromBarButtonItem:btnShare animated:YES];
}
}
#catch (NSException *exception) {
return;
}
}
I do not implement any of the delegate methods since non of them are required, I also do not make use of the preview functionality.
What's most puzzling to me is that the app from the AppStore behaves differently than my local one although the code is identical. My next step is to use the new beta developer tools (Test Flight) to re-publish the app, hoping that I can replicate the problem.
EDIT: I found a similar question on SO here: Cannot dismiss email sheet invoked from UIDocumentInteractionController in iOS 8
After reading that post I think it worth mentioning that I submitted the app to the AppStore via XCode 5 (the last version before XCode 6). Can that really be a factor here? Does Apple not use the same version on their side as the version in which the app was originally built?
I think this is a bug in iOS 8, and if it's still not working for you, I don't think Apple are likely to fix it. I'd upgrade to Xcode 6 and see if that fixes it for you. (It did for us, as you've discovered).
Here is my problem. After I archive my app and install it on my Iphone before I open my app I check the settings and see that the Settings Bundle is not updated. It just shows the default input (as seen in pic below).
I want it to show the updated settings (like below).
Here is my code in my AppDelegate.m
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// refresh upload queue
_uploadQueue = [[SCUploadQueue alloc] init];
[_uploadQueue refreshUpload];
return YES;
}
-(void) updateVersionInfo{
// Get Settings.bundle object
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// Get values from .plist file generated by build phase script
NSString *versionNumber = [[[NSBundle mainBundle] infoDictionary] valueForKey:#"CFBundleShortVersionString"];
// Dealing with the date
NSString *dateFromSettings = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBuildDate"];
// Create the version number
NSString *versionNumberInSettings = [NSString stringWithFormat:#"%#", versionNumber];
NSLog(#"Version: %#", versionNumberInSettings);
NSLog(#"Build date: %#", dateFromSettings);//buildDate);
// Set the build date and version number in the settings bundle reflected in app settings.
[defaults setObject:versionNumberInSettings forKey:#"version"];
[defaults setObject:dateFromSettings forKey:#"buildDate"];
}
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// refresh upload queue
_uploadQueue = [[SCUploadQueue alloc] init];
[_uploadQueue refreshUpload];
return YES;
}
-(void) updateVersionInfo{
// Get Settings.bundle object
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// Get values from .plist file generated by build phase script
NSString *versionNumber = [[[NSBundle mainBundle] infoDictionary] valueForKey:#"CFBundleShortVersionString"];
// Dealing with the date
NSString *dateFromSettings = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBuildDate"];
// Create the version number
NSString *versionNumberInSettings = [NSString stringWithFormat:#"%#", versionNumber];
NSLog(#"Version: %#", versionNumberInSettings);
NSLog(#"Build date: %#", dateFromSettings);//buildDate);
// Set the build date and version number in the settings bundle reflected in app settings.
[defaults setObject:versionNumberInSettings forKey:#"version"];
[defaults setObject:dateFromSettings forKey:#"buildDate"];
}
The settings works when I build it off xcode. It does not work with archiving however. Let me know if you need more info.
You can accomplish this with a build phase run script.
Add a run script:iOS Developer Doc
In the project editor, select the target to which you want to add a
run script build phase.
Click Build Phases at the top of the project editor.
Choose Editor > Add Build Phase > Add Run Script Build Phase.
Configure the script in the Run Script template. Right under the shell input box is where you you add you script.
Example scriptYou can use PlistBuddy in your script. Here is an example script that utilizes PlistBuddy to pull the CFBundleShortVersionString and the CFBundleVersion from the apps main Info.plist and adds them as the default values in the Settings bundle Root.plist.
Note: In this example script, the path to your projects Root.plist could be different. Also, the index of the PreferenceSpecifier needs to be changed to match the setup of your Root.plist file.
version=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${PROJECT_DIR}/${INFOPLIST_FILE}")
build=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${PROJECT_DIR}/${INFOPLIST_FILE}")
/usr/libexec/PlistBuddy "$SRCROOT/Settings.bundle/Root.plist" -c "set PreferenceSpecifiers:1:DefaultValue $version"
/usr/libexec/PlistBuddy "$SRCROOT/Settings.bundle/Root.plist" -c "set PreferenceSpecifiers:2:DefaultValue $build"
I try to make a PDF document manager for my own OSX by Xcode. Now I can get the URL of the document. With the code below:
NSOpenPanel *documentOpenPannel = [NSOpenPanel openPanel];
NSInteger situationInt = [documentOpenPannel runModal];
if (situationInt == NSOKButton) {
NSURL *documentPath = [[documentOpenPannel URLs] lastObject];
}
From the documentPath, I can get some properties already, like file size:
NSString *fileSize;
[documentPath getResourceValue:&fileSize forKey:NSURLFileSizeKey error:nil];
Now I want to get more attributes of the documents, e.g. version number, total pages.
Could you give me some suggestion?
You need to use CGPDFDocument. CGPDFDocumentGetVersion(), CGPDFDocumentGetNumberOfPages() solve your specific problem.