I need to rename my application's name.
Is it possible to store some metadata of its old name somewhere?
Users may try to search its old name in the iPhone's search option.
Is there any designated value in the plist file or elsewhere so that its bundle display name is the new name, while the old name is still searchable?
To achieve this your application has to be launched atleast once, and in didFinishLaunchingWithOptions: of your AppDelegate you can index your Core Spotlight searchable item like below:
CSSearchableItemAttributeSet* attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:#"myApp.image"];
attributeSet.title = #"My Old App";
attributeSet.contentDescription = #"This application help you in acheiving so & so";
CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:#"myApp" domainIdentifier:#"com.myapp" attributeSet:attributeSet];
// Index the item.
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler: ^(NSError * __nullable error) {
NSLog(#"Search item indexed");
}];
You can even add thumbnail image to be shown when the app with old name is searched; You need to add following code before creating the searchableItem
UIImage *image = [UIImage imageNamed:#"MercC"];
NSData *thumbNailData = UIImagePNGRepresentation(image);
attributeSet.thumbnailData = thumbNailData;
Related
I want to let the user choose the folder, where I'd save a new file.
To achieve that, I use document picker, and set the document type to public.folder and inMode UIDocumentPickerModeOpen.
After user opens the document picker and selects the desired folder, in didPickDocumentsAtURLs callback I get the NSUrl object, which has permissions to modify the file at that url (in this case, it's an url to a folder).
There is my issue. I have the url with access permission to a folder, however, to create a file I ussualy need to have the filename.extension in the url. If I were to modify the NSUrl object I've received from the document picker, or convert it to NSString, my guess is I lose the access permission and createFileAtPath method always fails.
What method do I need to use, or what configuration document picker do I need, in order to create a new file in the path that the user selected? I attach my current code:
- (void)openDocumentPicker:(NSString*)pickerType
{
//Find the current app window, and its view controller object
UIApplication* app = [UIApplication sharedApplication];
UIWindow* rootWindow = app.windows[0];
UIViewController* rootViewController = rootWindow.rootViewController;
//Initialize the document picker
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:#[pickerType] inMode:UIDocumentPickerModeOpen];
//Assigning the delegate, connects the document picker object with callbacks, defined in this object
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
//Call the document picker, to the view controller that we've found before
[rootViewController presentViewController:documentPicker animated:YES completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
//If we come here, user successfully picked a file/folder
[urls[0] startAccessingSecurityScopedResource]; //Let the os know we're going to use the file
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentsDirectory = urls[0].absoluteString;
NSString *newFilePath = [documentsDirectory stringByAppendingPathComponent:#"test.txt"];
NSError *error = nil;
if ([fileManager createFileAtPath:newFilePath contents:[#"new file test" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]){
NSLog(#"Create Sucess");
}
else{
NSLog(#"Create error: %#", error);
}
[urls[0] stopAccessingSecurityScopedResource]; //Let the os know we're done
}
Any leads would be kindly appreciated!
This is solution in swift, please try and let me knwo if any problem
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]){
var imgData: Data?
if let url = urls.first{
imgData = try? Data(contentsOf: url)
do{
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let destURLPath = documentDirectory.appendingPathComponent(url.lastPathComponent)
try imgData?.write(to: URL(fileURLWithPath: destURLPath))
print("FILES IS Writtern at DOcument Directory")
}catch{
}
}
}
To answer my own question, I'll leave a fully working code bellow.
My main issue was, that when you're using "public.folder" document type, you need to call startAccessingSecurityScopedResource with the url of the chosen folder, not with the modified link (file the user chose + NewFileName.extension)
- (void)openDocumentPicker
{
//This is needed, when using this code on QT!
//Find the current app window, and its view controller object
/*
UIApplication* app = [UIApplication sharedApplication];
UIWindow* rootWindow = app.windows[0];
UIViewController* rootViewController = rootWindow.rootViewController;
*/
//Initialize the document picker. Set appropriate document types
//When reading: use document type of the file, that you're going to read
//When writing into a new file: use #"public.folder" to select a folder, where your new file will be created
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:#[#"public.folder"] inMode:UIDocumentPickerModeOpen];
//Assigning the delegate, connects the document picker object with callbacks, defined in this object
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
//In this case we're using self. If using on QT, use the rootViewController we've found before
[self presentViewController:documentPicker animated:YES completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
//If we come here, user successfully picked a single file/folder
//When selecting a folder, we need to start accessing the folder itself, instead of the specific file we're going to create
if ( [urls[0] startAccessingSecurityScopedResource] ) //Let the os know we're going use this resource
{
//Write file case ---
//Construct the url, that we're going to be using: folder the user chose + add the new FileName.extension
NSURL *destURLPath = [urls[0] URLByAppendingPathComponent:#"Test.txt"];
NSString *dataToWrite = #"This text is going into the file!";
NSError *error = nil;
//Write the data, thus creating a new file. Save the new path if operation succeeds
if( ![dataToWrite writeToURL:destURLPath atomically:true encoding:NSUTF8StringEncoding error:&error] )
NSLog(#"%#",[error localizedDescription]);
//Read file case ---
NSData *fileData = [NSData dataWithContentsOfURL:destURLPath options:NSDataReadingUncached error:&error];
if( fileData == nil )
NSLog(#"%#",[error localizedDescription]);
[urls[0] stopAccessingSecurityScopedResource];
}
else
{
NSLog(#"startAccessingSecurityScopedResource failed");
}
}
This was also being discussed at apple forums:
Thread name: "iOS Creating a file in a public folder"
Thread link:
https://developer.apple.com/forums/thread/685170?answerId=682427022#682427022
I am Writing an app which has share extension to save selected photo to my app' local storage from iphone photo gallery.
NSData WriteToFile returns YES but I couldn't find the stored file into the directory in of which I gave path while writing.
So, in short NSData WriteToFile fails to save a photo at given path.
Below is my code.
- (IBAction)acceptButtonTapped:(id)sender
{
__block UIImage *photo;
for (NSExtensionItem *item in self.extensionContext.inputItems)
{
for (NSItemProvider *itemProvider in item.attachments)
{
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage])
{
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {
if(image)
{
dispatch_async(dispatch_get_main_queue(), ^{
photo = image;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy_MM_dd_hh_mm_ss"];
NSString *fileName;
fileName = [NSString stringWithFormat:#"%#.jpeg",[formatter stringFromDate:[NSDate date]]];
dataPath = [dataPath stringByAppendingPathComponent:fileName];
NSData * imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0)];
BOOL isdone = [imageData writeToFile:dataPath atomically:NO];
NSLog(#"%u", isdone);
});
}
}];
break;
}
}
}
[self.extensionContext completeRequestReturningItems:#[] completionHandler:nil];
}
Any Help would be much appreciable.
Thank you.
If you're trying to access the Document directory from the share extension, NO you can't do that. Share extension or other widgets are separate application from their containing app and therefore have their own sandbox. So you will need to use App Groups to share files.
Application groups are primarily targeted for extensions, more specifically, for widgets.
NSFileManager has a method on it containerURLForSecurityApplicationGroupIdentifier: where you can pass in the identifier you created when turning on App Groups for your apps
NSURL *containerURL = [[NSFileManager defaultManager]
containerURLForSecurityApplicationGroupIdentifier:#"group.com.company.app"];
You can save the files to this location, because you can access the shared application groups from both extension and host app.
You're modifying dataPath on each pass through the loop, appending another filename to it. That will create an ever-growing series of badly formed paths that contain all the filenames.
Don't do that. Create a new local variable filePath, and construct a filename into filePath using
filePath = [docsPath stringByAppendingPathComponent: filename];
Log your path and LOOK AT IT. When your program doesn't behave as expected, don't trust any of your assumptions, because one or more of them may be wrong.
Hi I'm trying to implement CoreSpotlight in my app.
When indexing do I need to run this every time or is it sufficient to run this once when app is installed for the first time?
If app is deleted do I need to index again?
Here's the code I'm using:
- (void)spotLightIndexing {
NSString *path = [[NSBundle mainBundle] pathForResource:
#"aDetailed" ofType:#"plist"];
NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray *plistArray = [plistDict allKeys];
for (id key in plistDict) {
CSSearchableItemAttributeSet* attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeImage];
// Set properties that describe attributes of the item such as title, description, and image.
attributeSet.title = key;
attributeSet.contentDescription = [plistDict objectForKey:key];
//*************************************
attributeSet.keywords = plistArray; // Another Q: do i need this????
//**************************************
// Create an attribute set for an item
UIImage *image = [UIImage imageNamed:#"icon.png"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attributeSet.thumbnailData = imageData;
// Create a searchable item, specifying its ID, associated domain, and the attribute set you created earlier.
CSSearchableItem *item;
NSString *identifier = [NSString stringWithFormat:#"%#",attributeSet.title];
item = [[CSSearchableItem alloc] initWithUniqueIdentifier:identifier domainIdentifier:#"com.example.apple_sample.theapp.search" attributeSet:attributeSet];
// Index the item.
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler: ^(NSError * __nullable error) {
if (!error)
NSLog(#"Search item indexed");
else {
NSLog(#"******************* E R R O R *********************");
}];
}
}
thank you
Its indexed as specified. So if you put your spotLightIndexing method in didFinishLaunchingWithOptions it will of naturally index items every launch, unless you set a bool of course. If the app is deleted it will re-index again as the NSUserDefault values will be zeroed out. That is why they offer you add/altering/updating indices via batch updates or other methods as annotated here
Since your populating it from a local plist as opposed to the web, you will have to do the updates yourself or create an index-maintenance app extension.
If you watch the WWDC video on this topic, you will see that's easy to update or delete domains by a 'group' using the domain identifier. Source It's a good watch.
As far as the keywords, there is no telling until the documents are fully supporting iOS9 APIs. But just by reading what Apple has publicly provided here is a note you should consider :
Important: Be sure to avoid over-indexing your app content or adding unrelated keywords and attributes in an attempt to improve the ranking of your results. Because iOS measures the level of user engagement with search results, items that users don’t find useful are quickly identified and can eventually stop showing up in results.
That is located after the new Search features summary. And it goes on to say why:
When you combine multiple Search APIs, items can get indexed from multiple places. To avoid giving users duplicate items in search results, you need to link item IDs appropriately. To ensure that item IDs are linked, you can use the same value in a searchable item’s uniqueIdentifier property and in the relatedUniqueIdentifier property within an NSUserActivity object’s contentAttributes property
So in other words, say you incorporate NSUserActivity as they intend you to because it can apply to all users of your app, not just the person doing the querying, it can populate multiple times in the same search. So, based on Apples suggestions, try not to use keywords unless your sure, especially based off your example, where the keyword already = uniqueIdentifier.
Personally, i've already implemented this into my app and love it, however, I use web mark-up which makes batch updates almost instantaneously, as opposed to your route, where you would have to actually push out a new update to re-update/delete the indices.
I need to get the reference URL for the stored image of a contact.
If I'm getting an image from a UIImagePickerController, I can get that simply by doing this.
NSURL *imageURL = [info objectForKey:UIImagePickerControllerReferenceURL];
But how do I get this NSURL for an image of a contact from the Address Book?
UIImage *contactImage = [UIImage imageWithData:CFBridgingRelease(ABPersonCopyImageData(ref))];
i.e. get the reference URL for contactImage.
The ref argument in your question is not an NSURL, but an ABRecordRef, i.e. the reference to an address book record. You had first to open the address book using code like
CFErrorRef error = nil;
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions (NULL, &error);
and then to find the required person using e.g.
ABRecordRef ref = ABAddressBookGetPersonWithRecordID (addressBookRef,recordID);
This gives you the required ref.
The current best solution:
https://stackoverflow.com/a/21888830/1786820
I am trying to do one better, by opening the Instagram app with a preselected video file from the PhotoRoll, and a preloaded caption. In the Flipagram app, they do just this. When you hit share on a video, they save it your camera roll, suggest a caption, then direct to the Instagram app photo selection screen, with the video preselected. Even if the video is not the latest media in the PhotoRoll, it correctly highlights the correct video, along the caption prepared in the Flipagram app.
Is this possibly an undocumented iPhone hook?
Any help is appreciated.
I came up with the idea to allow my app to accept the instagram:// URL schema. The hook from Flipagram opened up in my app as the following:
instagram://library?AssetPath=assets-library%3A%2F%2Fasset%2Fasset.mp4%3Fid%3D8864C466-A45C-4C48-B76F-E3C421711E9D%26ext%3Dmp4&InstagramCaption=Some%20Preloaded%20Caption
The undocumented iPhone hook that allows you to automatically select assets from the iPhones photo roll, and preload a caption for the video. This should give you the same user experience that Flipagrams app has with sharing a video to Instagram.
NSURL *videoFilePath = ...; // Your local path to the video
NSString *caption = #"Some Preloaded Caption";
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:[NSURL URLWithString:videoFilePath] completionBlock:^(NSURL *assetURL, NSError *error) {
NSURL *instagramURL = [NSURL URLWithString:[NSString stringWithFormat:#"instagram://library?AssetPath=%#&InstagramCaption=%#",[assetURL absoluteString].percentEscape,caption.percentEscape]];
if ([[UIApplication sharedApplication] canOpenURL:instagramURL]) {
[[UIApplication sharedApplication] openURL:instagramURL];
}
}];
Works great!
Update:
Instagram has removed the ability to pass the caption to their app. The best solution now it to just copy the desired caption to the paste board.
The answer is that it is not pulling the video from the camera roll at all, it might just look like it is.
Documentation here: http://instagram.com/developer/mobile-sharing/iphone-hooks/
The relevant bit is the bottom section "Document Interaction".
You would do this by doing something like this:
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"instagram.igo"];
NSData *data = // set this yourself
NSError *error = nil;
if (! [data writeToFile:filePath options:NSDataWritingAtomic error:&error])
{
// error here
}
self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
self.documentInteractionController.delegate = self;
self.documentInteractionController.UTI = #"com.instagram.exclusivegram";
self.documentInteractionController.annotation = #{ #"InstagramCaption" : #"caption text here" };
const BOOL couldOpen = [self.documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:myView animated:YES];
Set the data, the caption, and the view to present from yourself. Notice the UIDocumentInteractionController is also a property. It should be retained somewhere and not just a local variable in a method because it needs to exist outside of that scope when the method completes.