Following is the code that I am trying to implement to make app activities and states searchable but not able to show on iOS search
NSUserActivity *userActivity = [[NSUserActivity alloc]initWithActivityType:#"com.mycompany.activity-type"];
userActivity.title = #"Hello world from in app search";
userActivity.keywords = [NSSet setWithArray:#[#"Hello",#"Welcome", #"search"]];
userActivity.userInfo = #{#"id":#"com.example.state"};
userActivity.eligibleForSearch = YES;
[userActivity becomeCurrent];
Link to make my question more clear.
From the Apple Forums:
One thing that has bitten a few people (myself included) is that the
activity must not be deallocated. If your code is only working with
NSUserActivities (i.e. not using CoreSpotlight in addition) then make
sure your activities aren't being deallocated immediately.
In my
case, I had code that was allocating the NSUA, setting some properties
on it, calling becomeCurrent, but then the object would go out of
scope and deallocated. If you're doing this, try tossing the activity
into a strong property to see if you can then see the results when you
search.
https://forums.developer.apple.com/message/13640#13640
What I have found is you have to assign the NSUserActivity instance you have created to your currently visible UIViewControllers's userActivity property before calling -becomeCurrent. It has fixed it for me and the items immediately appeared both for handoff on other devices and in spotlight search on the same device.
I was experiencing the same issue, and I read on the dev forums that in seed 1 it only works on the device. I was able to make it work on the device.
It might be that this, as with handoff, will only work on the device sadly.
I couldn't get it to work with beta 2 either.
Using a CSSearchableItemAttributeSet with
CSSearchableItemAttributeSet* attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString*)kUTTypeImage];
attributeSet.title = myobject.title;
attributeSet.keywords = [myobject.desc componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
attributeSet.contentDescription = myobject.desc;
if (myobject.images.count > 0) {
attributeSet.thumbnailData = myobject.myimagedata;
}
attributeSet.rating = #(myobject.rating.integerValue / 2);
CSSearchableItem* item;
item = [[CSSearchableItem alloc] initWithUniqueIdentifier:#"..." domainIdentifier:#"..." attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler: ^(NSError * __nullable error) {
NSLog(#"Search item indexed");
}];
works, though, even with images.
What I couldn't get to work was the rating to show up anywhere.
Related
To understand this question, return with me now through the WWDC time machine to the distant past, 2014, when Action extensions were introduced and explained in this video:
https://developer.apple.com/videos/play/wwdc2014/217/
About halfway through, in slide 71, about minute 23:30, the presenter gives instructions for returning a value back to the calling app (the app where the user tapped our Action extension's icon in an activity view):
- (IBAction)done:(id)sender {
NSData *data = self.contents;
NSItemProvider *itemProvider =
[[NSItemProvider alloc] initWithItem:data typeIdentifier:MyDocumentUTI];
NSExtensionItem *item = [[NSExtensionItem alloc] init];
item.attachments = #[itemProvider];
}
A moment later, slide 75, about minute 26, we see how the app that put up the activity view controller is supposed to unwrap that envelope to retrieve the result data:
- (void)setupActivityViewController {
UIActivityViewController *controller;
controller.completionWithItemsHandler =
^(NSString *activityType, BOOL completed,
NSArray *returnedItems, NSError *error) {
if (completed && (returnedItems.count > 0)) {
// process the result items
}
}];
}
So my question is: is that for real? Has anyone within the sound of my voice ever done either of those things? Namely:
Does your app have an Action extension that returns a value to the caller?
Does your app put up an activity view controller that receives the result of some arbitrary unknown Action extension and does something with the value?
I ask because (1) I have never seen (on my iPhone) an Action extension that actually returns a value, and (2) the code elided in "process the result items" seems to me to be complete hand-waving, because how would my app even know what kind of data to expect?
I have come to believe that this code is an aspirational pipe dream with no corresponding reality. But I would be delighted to be told I'm wrong.
I am working on implementing support for a CarPlay audio app, and am attempting to display listings in the simulator. I have implemented MPPlayableContentDataSource, but find that it is called inconsistently. It is called the first time the app is launched on a simulator, and if CarPlay is open on launch, I can make the first item display by scrolling up an otherwise empty listing to trigger a redraw.
CarPlay does not seem able to call the data source, however, and on a subsequent launch I see an empty screen or a spinner followed by the message Unable to connect to "AppName". I have tried different things but the main points are as follows:
In application: didFinishLaunchingWithOptions:
self.contentDataSource = [[MYContentDataSource alloc] init];
self.contentDelegate = [[MYContentDelegate alloc] init];
MPPlayableContentManager *contentManager = [MPPlayableContentManager sharedContentManager];
contentManager.dataSource = self.contentDataSource;
contentManager.delegate = self.contentDelegate;
[contentManager beginUpdates];
[contentManager endUpdates];
I've played around with beginUpdates endUpdates and reloadData methods of the content manager, but none of these result in the content datasource actually being called.
I've implemented numberOfChildItemsAtIndexPath and contentItemAtIndexPath in the datasource, which appear to be called correctly, although only on the first launch of the app on a fresh simulator.
The main points:
- (NSInteger)numberOfChildItemsAtIndexPath:(NSIndexPath *)indexPath {
return 3;
}
- (MPContentItem *)contentItemAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger categoryId = [indexPath indexAtPosition:0];
MPContentItem *contentItem = [[MPContentItem alloc] initWithIdentifier:[NSString stringWithFormat:#"CAT-%lu", (unsigned long)categoryId]];
contentItem.title = [NSString stringWithFormat:#"Category %lu", (unsigned long)categoryId];
contentItem.subtitle = #"Subtitle";
contentItem.playable = NO;
contentItem.container = YES;
}
I've also tried retaining (or not) the reference to the MPPlayableContentManager.
I have the same behavior on an actual head unit. Any help would be appreciated.
After banging my head against the wall for quite a while, I got the following answer from Apple. Turns out that MPRemoteCommandCenter and MPNowPlayingInfoCenter are needed for CarPlay to work.
1. Start responding to MPRemoteCommandCenter events at app launch
2. Set the MPNowPlayingInfoCenter dictionary at app launch
These are required for MPPlayableContentDataSource to function correctly.
They are mentioned in the doc, but it isn't clear that they are needed for the catalog display to work. That solved the problem.
I had a quick question about NSUserActivity's userInfo property.
NSString *activity = #"com.test.activity";
NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:activity];
userActivity.title = #"Test";
userActivity.keywords = [NSSet setWithArray:#[#"Test"];
userActivity.userInfo = #{#"location": location};
userActivity.eligibleForSearch = YES;
self.userActivity = userActivity;
[self.userActivity becomeCurrent];
I have the above snippet implemented in one of view controllers viewDidLoad(). When my item appears within spotlight search it calls the continueUserActivity delegate method.
I'm trying to access the userActivity.userInfo property but it's returning null even though it's been set above.
Here's the continueUserActivity snippet:
-(BOOL)application:(nonnull UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * __nullable))restorationHandler {
NSString *locationName = [[userActivity.userInfo objectForKey:#"location"] valueForKey: #"name"];
// Do stuff with locationName
return NO;
}
EDIT: I changed the location object to return as a primitive type but I'm still getting null on the delegate method.
Is anyone else having this problem in iOS 9 beta 3?
I'm working in iOS 9 beta 5, and another way that userInfo may be nil within the application:continueUserActivity:restorationHandler: is if you have also set NSUserActivity's webPageURL property. If you set the webPageURL property and would like userInfo to not be nil, then you will need to include the appropriate keys in NSUserActivity's requiredUserInfoKeys property. This took me forever to figure out, but shame on me for missing this in the docs.
NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:activityType];
activity.title = #"title";
activity.webpageURL = [NSURL URLWithString:webLinkUrlString];
activity.userInfo = #{#"id": webLinkUrlString, #"deep_link": deepLinkUrl};
// set requiredUserInfoKeys if you're also setting webPageURL !!
// otherwise, userInfo will be nil !!
activity.requiredUserInfoKeys = [NSSet setWithArrays:#[#"id", #"deep_link"]];
activity.eligibleForSearch = YES;
activity.eligibleForPublicIndexing = YES;
activity.contentAttributeSet = attributeSet;
activity.keywords = [NSSet setWithArray:keywordsArray];
self.activity = activity;
[self.activity becomeCurrent];
Hi could you check in your continueUserActivity snippet:
if ([userActivity.activityType isEqualToString:#"com.test.activity"])
{
NSString *locationName = [[userActivity.userInfo objectForKey:#"location"] valueForKey: #"name"];
}
If it doesn't go there I assume that you have Core Spotlight integrated in your app and you get in Spotlight exactly by these elements from Core Spotlight.
Hope it was helpful.
P.S. At the moment(Xcode 7 beta 3) many developers can't get Search API through NSUserActivity to work, including me.
It was a problem with beta 3 that was causing this error. Everything is working fine as of beta 5.
See this question:
NSUserActivity handoff not working for custom data
userInfo is non-null if you follow this guideline from Apple:
To update the activity object’s userInfo dictionary efficiently, configure its delegate and set its needsSave property to YES whenever the userInfo needs updating. At appropriate times, Handoff invokes the delegate’s userActivityWillSave: callback, and the delegate can update the activity state.
The doc does say that setting webpageURL requires that requiredUserInfoKeys also be set. However (a) handoff works fine without requiredUserInfoKeys, as long as the 'userActivityWillSave' set userInfo, and (b) webURL is an iOS 8 parameter, whereas requiredUserInfoKeys is iOS 9. So the delegate method allows handoff to be supported in iOS 8 / Xcode 6.
I noticed (in the simulator) that you can set either:
webpage URL
userInfo and requiredUserInfoKeys
But not both! If you set both an webpageURL and userInfo with requiredUserInfoKeys, your activity won't even show up in spotlight on iOS.
I think this is because Apple assumes if you set the webpage URL that you support universal links and thus don't need any additional user info.
It is, however, OK to set both the webpage and just userInfo, but in your app delegate you won't receive the userInfo.
I had the same problem that UserInfo was nil (on iOS 9.2). The problem was that the search result wasn't the latest NSUserActivity which I have tapped (older one with no UserInfo). So I renamed the title to clearly identify the correct search result.
Maybe this helps someone, too.
For me on iOS 14 the issue was fixed by changing NSNumber values inside userInfo to String. I reproduced this in a simple test project. Maybe this happens only in Swift code, I didn't check Objective-C.
I'm trying to test out the iOS 8.1 handoff feature with NSUserActivity between my iPhone and my iPad. For this, I tried both implementing my own solution, and to use Apple's PhotoHandoff project. However, it's not working.
If I provide a webpageURL, the handover works fine, but when I try to use userData or addUserInfoEntriesFromDictionary nothing works, and I can't for the life of me figure out what the catch is to make the data work.
Sample code:
NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:#"com.company.MyTestApp.activity"];
activity.title = #"My Activity";
activity.userInfo = # {};
// activity.webpageURL = [NSURL URLWithString:#"http://google.com"];
self.userActivity = activity;
[self.userActivity becomeCurrent];
[self.userActivity addUserInfoEntriesFromDictionary:# { #"nanananan": #[ #"totoro", #"monsters" ] }];
(I'm also unable to make it work with a Mac app with a corresponding activity type)
I hope you found the solution already, but in case somebody stumbles upon this problem too, here is a solution. (Actually not very different from the previous answer)
Create user activity without userInfo, it will be ignored:
NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:#"..."];
activity.title = #"Test activity";
activity.delegate = self;
activity.needsSave = YES;
self.userActivity = activity;
[self.userActivity becomeCurrent];
Implement the delegate to react to needSave events:
- (void)userActivityWillSave:(NSUserActivity *)userActivity {
userActivity.userInfo = #{ #"KEY" : #"VALUE" };
}
When needsSave is set to YES this method will be called and userActivity will be updated.
Hope this helps.
To update the activity object’s userInfo dictionary, you need to configure its delegate and set its needsSave property to YES whenever the userInfo needs updating.
This process is described in the best practices section of the Adopting Handoff guide.
For example, with a simple UITextView, you need to specify the activity type ("com.company.app.edit") identifier in the Info.plist property list file in the NSUserActivityTypes array, then:
- (NSUserActivity *)customUserActivity
{
if (!_customUserActivity) {
_customUserActivity = [[NSUserActivity alloc] initWithActivityType:#"com.company.app.edit"];
_customUserActivity.title = #"Editing in app";
_customUserActivity.delegate = self;
}
return _customUserActivity;
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
[self.customUserActivity becomeCurrent];
}
- (void)textViewDidChange:(UITextView *)textView
{
self.customUserActivity.needsSave = YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
[self.customUserActivity invalidate];
return YES;
}
- (void)userActivityWillSave:(NSUserActivity *)userActivity
{
[userActivity addUserInfoEntriesFromDictionary:#{ #"editText" : self.textView.text }];
}
FWIW, I was having this issue. I was lucky that one of my Activity types worked and the other didn't:
Activity: Walking
(UserInfo x1,y1)
(UserInfo x2,y2)
(UserInfo x3,y3)
Activity: Standing
(UserInfo x4,y4)
Activity: Walking
etc.
I got userInfo if the handoff occured when standing but not walking. I got other properties such as webpageURL in all cases; just userInfo came through null.
The fix for me was to invalidate & recreate the NSUserActivity object every time (e.g. when Walking to x2/y2 from x1/y1), instead of only when Activity type changed (e.g. from walking to standing). This is very much not the way the doc is written, but fixed the issue on iOS 9.
UPDATE: This workaround doesn't work on iOS 8. You need to implement this via the userActivityWillSave delegate, as gregoryM specified. Per Apple's doc:
To update the activity object’s userInfo dictionary efficiently,
configure its delegate and set its needsSave property to YES whenever
the userInfo needs updating. At appropriate times, Handoff invokes the
delegate’s userActivityWillSave: callback, and the delegate can update
the activity state.
This isn't a "best practice", it is required!
[Note: issue occurred on iOS 9 devices running code built on Xcode 6.x. Haven't tested Xcode 7 yet, and issue may not occur on iOS 8.]
In iPhone can we set the lock screen, wallpaper and ringtone programmatically?
If Yes, then please let me know how to set them?
This can all be done easily, but will be rejected by Apple.
The ringtone can be changed by altering com.apple.SpringBoard.plist, specifically the ringtone key.
The following code can be used to read the actual ringtone title of custom ringtones (synced by iTunes).
NSMutableDictionary *custDict = [[NSMutableDictionary alloc] initWithContentsOfFile:#"/private/var/mobile/Media/iTunes_Control/iTunes/Ringtones.plist"];
NSMutableDictionary *dictionary = [custDict objectForKey:#"Ringtones"];
NSArray *keys = [dictionary allKeys];
id key = [keys objectAtIndex:indexPath.row];
NSMutableDictionary *customRingtone = [dictionary objectForKey:key];
NSString *name = [customRingtone objectForKey:#"Name"];
cell.textLabel.text = name;
The Wallpapers can be overwritten at:
NSString *homePath1 = #"/private/var/mobile/Library/SpringBoard/HomeBackground.jpg";
NSString *homePath2 = #"/private/var/mobile/Library/SpringBoard/HomeBackgroundPortrait.jpg";
NSString *lockPath1 = #"/private/var/mobile/Library/SpringBoard/LockBackground.jpg";
NSString *lockPath2 = #"/private/var/mobile/Library/SpringBoard/LockBackgroundPortrait.jpg";
These examples were used in one of my Cydia apps. Theres not really much more to them, but these should get you going in the right direction.
The answer by WrightsCS stopped working at some point due to a change in iOS. Unfortunately, this is something you have to live with if you wish to use undocumented features.
If you still need to do this, for non-App Store apps only, this code works in iOS 9.3. It could stop working in any future iOS release, though. (see comment below: no longer working in iOS 10)
#import "SBSUIWallpaperPreviewViewController.h"
#import <dlfcn.h>
// open the private framework dynamically
void *handle = dlopen("/System/Library/PrivateFrameworks/SpringBoardUIServices.framework/SpringBoardUIServices", RTLD_NOW);
UIImage *wallpaper = [UIImage imageNamed: #"background.jpg"];
Class sbClass = NSClassFromString(#"SBSUIWallpaperPreviewViewController");
// we create a view controller, but don't display it.
// just use it to load image and set wallpaper
SBSUIWallpaperPreviewViewController *controller = (SBSUIWallpaperPreviewViewController*)[[sbClass alloc] initWithImage: wallpaper];
[controller setWallpaperForLocations: 3]; // 3 -> set both for lock screen and home screen
dlclose(handle);
You'll need to add the private API header to your project. You can usually find these online with a little searching, for example, here.
In the example above, [SBSUIWallpaperPreviewViewController setWallpaperForLocations:] is called with an argument of 3: 3 indicates the image should be used for both lock and home screens. 1 indicates Lock screen only. 2 indicates Home screen only.
For an explanation of why I open this framework up dynamically, see my related answer here.
I don't have an answer regarding ringtones. This really should be a separate question: completely different APIs at work.
use private api if you can
check PLStaticWallpaperImageViewController