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.
Related
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;
Is it possible to programatically find out name of all apps installed on my iOS device ?
Is there any API available for same ?
Thanks for the help
No, on iOS applications has no access to information of/about other applications due to sandboxed environment.
Yes it is possible to get list of all installed app
-(void) allInstalledApp
{
NSDictionary *cacheDict;
NSDictionary *user;
static NSString *const cacheFileName = #"com.apple.mobile.installation.plist";
NSString *relativeCachePath = [[#"Library" stringByAppendingPathComponent: #"Caches"] stringByAppendingPathComponent: cacheFileName];
NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent: #"../.."] stringByAppendingPathComponent: relativeCachePath];
cacheDict = [NSDictionary dictionaryWithContentsOfFile: path];
user = [cacheDict objectForKey: #"User"];
NSDictionary *systemApp=[cacheDict objectForKey:#"System"];
}
systemApp Dictionary contains the list of all system related app
and user Dictionary contains other app information.
Not from the device. However, from the desktop you could peek into the iTunes library.
There are ways to do this without a jailbroken device and not get your app rejected.
1. get a list of currently running processes see this SO answer. You will need to translate from process name to app name.
2. Check to see if any apps have registered a unique URL scheme with UIApplicationDelegate canOpenURL. There are a few sites cataloging known url schemes, this is the best one.
If an app is not currently running and does not register a custom url scheme then it will not be detected by these methods. I am interested in hearing a method that will be allowed in the app store that works better than this.
try this, it will work even with non-jailbroken devices:
#include <objc/runtime.h>
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
SEL selector=NSSelectorFromString(#"defaultWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector];
SEL selectorALL = NSSelectorFromString(#"allApplications");
NSLog(#"apps: %#", [workspace performSelector:selectorALL]);//will give you all **Bundle IDS** of user's all installed apps
You can do it by checking whether an application is installed or not by using canOpenURL method or by checking the background processes and matching them with the name of the app you are interested in.
You can use runtime objective c to get the list of all installed apps. It will give you an array of LSApplicationProxy objects.
Following is a code snippet that prints Name of all applications installed in your device.
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:NSSelectorFromString(#"defaultWorkspace")];
NSMutableArray *array = [workspace performSelector:NSSelectorFromString(#"allApplications")];
NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
for (id lsApplicationProxy in array) {
if(nil != [lsApplicationProxy performSelector:NSSelectorFromString(#"itemName")]){
[mutableArray addObject:[lsApplicationProxy performSelector:NSSelectorFromString(#"itemName")]];
}
}
NSLog(#"********* Applications List ************* : \n %#",mutableArray);
Don't forget to include <objc/runtime.h> .
I have read few articles but could not find what I was looking for so here's my query.
I am downloading few files from the server and there is a case where the user locks his screen, in this case the ios device loses its network connectivity and the file sync fails.
I have read few article on NSURLSession but it is available from iOS 7 onwards and the app I am working on supports from iOS 6 and later.
So is there a way where I can download 20 or 30 files in the background or when the user hits the lock screen in a generic fashion without having to worry about which OS version I am supporting.
As of now I have read that we have 30 seconds to perform network activity so is there a limitation to the number of server calls in these 30 seconds?
About my code, I am having a class named as DownloadFiles which calls a service and the service returns me an array of fileURL and using NSData I am fetching these files and saving them in the doc directory, so while implementing the background call thing do I need to pass the index of my array which will detect the current file which is downloading and then carry on from the next index.
for(NSDictionary *dict in filearray) {
NSString *fileURL = [[dict valueForKey:#"FileURL"]stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSData *fileData = [NSData dataWithContentsOfURL:theFileURL];
if (fileData.length==0 || fileData==nil || theFileURL==nil) {
NSLog(#"empty file URL = %#",theFileURL);
}
if (fileData.length!=0){
BOOL savefile = [fileData writeToFile:[HTML_SERVER_FILES stringByAppendingPathComponent:[dict valueForKey:#"FileName"]] atomically:YES];
if (savefile!=YES) {
NSLog(#"Not saved file = %#",theFileURL);
}else{
NSLog(#"file saved at path %#",HTML_SERVER_FILES);
}
fileData = nil;
}
}
Please let me know what needs to be done in this case.
I am a newbie iOS learner. Couldn't find answer to following question after searching for a while. Hence, here it is.
Building on a first app that displays recent pictures from a user's instagram feed, I am trying to display pictures from the follows of that users instead.
To call recent pictures from the Instagram feed, which worked well, I had created the following method "imageForPhoto"
+ (void)imageForPhoto:(NSDictionary *)photo size:(NSString *)size completion:(void(^)(UIImage *image))completion {
if (photo == nil || size == nil || completion == nil) {
return;
}
NSString *key = [[NSString alloc] initWithFormat:#"%#-%#", photo[#"id"], size];
NSURL *url = [[NSURL alloc] initWithString:photo[#"images"][size][#"url"]];
[self downloadURL:url key:key completion:completion];
}
Therefore, I first modified my code to get the data related to the "follows" in my PhotosViewController instead of recent media pictures ( v1/users/3/follows ):
NSURLSession *session = [NSURLSession sharedSession];
NSString *urlString = [[NSString alloc] initWithFormat:#"https://api.instagram.com/v1/users/3/follows?count=99&access_token=%#", self.accessToken];
Then, I created a new method that I called friendAvatarForPhoto to get the follows profile pictures from a photo NSDictionary that is passed in as the only method parameter. I placed this method in my PhotoController class:
+ (void)friendAvatarForPhoto:(NSDictionary *)photo completion:(void(^)(UIImage *image))completion {
if (photo == nil || completion == nil) {
return;
}
NSString *key = [[NSString alloc] initWithFormat:#"avatar-%#", photo[#"user"][#"id"]];
NSURL *url = [[NSURL alloc] initWithString:photo[#"profile_picture"]];
[self downloadURL:url key:key completion:completion];
}
It seems to work. I have manually checked that the pictures that are rendered on my UICollectionView are actually coming from the "profile_picture" key of the responseDictionary I get back from Instagram.
Here is the structure of this dictionary: https://www.dropbox.com/s/ldjqupadqg0j3nq/friends_response_dictionary.png
Specifically, as you can see, I modified both the key and the url:
NSString *key = [[NSString alloc] initWithFormat:#"avatar-%#", photo[#"user"][#"id"]];
NSURL *url = [[NSURL alloc] initWithString:photo[#"profile_picture"]];
but I am not really understanding what I need to initial the *key string with... I kept "avatar" for example but why should I? I am pretty sure it's wrong although the result seems to be fine from what is returned on my UICollectionView.
What should I initiate this string with instead?
What is the function of this key in the overall NSURLSession process?
I'd love to better understand the overall process and better connect the dots with Instagram's API so I can query the right key and be sure I am getting what I am looking for. In a reliable way, not an intuitive one as I have just done app-arently.
Any help from experienced developers in the community would be welcome, I've just started to explore iOS dev a few weeks ago based on rusty C skills from a long time back.
Thank you!
:) Arsene
I would like to get the file name and, if possible, album image from a streaming URL in a AVPlayerItem that I am playing with AVQueuePlayer but I don't know how to go about doing this.
Also if it turns out that my streaming URL doesn't have any metadata can I put metadata in my NSURL* before passing it to the AVPlayerItem?
Thanks.
Well I am surprised no one has answered this question.
In fact no one has answered any of my other questions.
Makes me wonder how much knowledge people in here truly have.
Anyways, I will go ahead and answer my own question.
I found out how to get the metadata by doing the following:
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url];
NSArray *metadataList = [playerItem.asset commonMetadata];
for (AVMetadataItem *metaItem in metadataList) {
NSLog(#"%#",[metaItem commonKey]);
}
Which gives me a list as follows:
title
creationDate
artwork
albumName
artist
With that list now I know how to access the metadata from my audio stream. Just simply go through the NSArray and look for an AVMetadataItem that has the commonKey that I want (for example, title). Then when I find the AVMetadataItem just get the value property from it.
Now, this works great but it may be possible that when you try to get the data it will take a while. You can load the data asynchronously by sending loadValuesAsynchronouslyForKeys:completionHandler: to the AVMetadataItem you just found.
Hope that helps to anyone who may find themselves with the same problem.
When retrieving a particular item I would use the Metadata common keys constant declared in AVMetadataFormat.h, i.e.: AVMetadataCommonKeyTitle.
NSUInteger titleIndex = [avItem.asset.commonMetadata indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
AVMutableMetadataItem *metaItem = (AVMutableMetadataItem *)obj;
if ([metaItem.commonKey isEqualToString:AVMetadataCommonKeyTitle]) {
return YES;
}
return NO;
}];
AVMutableMetadataItem *item = [avItem.asset.commonMetadata objectAtIndex:titleIndex];
NSString *title = (NSString *)item.value;