NSUserActivity handoff not working for custom data - ios

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.]

Related

Sinch App-to-Phone not working

I'm trying to implement app to phone calling from Sinch for IOS. I have successfully implemented app to app calling which means that the SDK is properly set up in my app. However the app to phone for some reason is not working:
In my MainViewController.m, I have the following:
- (id<SINClient>)client {
return [(JSQAppDelegate *)[[UIApplication sharedApplication] delegate] client];
}
Then, in an alertView:
if ([self.client isStarted]) {
id<SINCall> call = [self.client.callClient callPhoneNumber:#"+46000000000"]; //Testing the phone number as advised by Sinch!
[self showCallController:call appToAppCall:NO];
}
Which call the following method:
-(void)showCallController: (id<SINCall>)call appToAppCall: (BOOL)appToapp
{
MMLCallingViewController *callView = [self.storyboard instantiateViewControllerWithIdentifier:#"CallingView"];
callView.call = call;
callView.callReceiverName = _theRecipient;
callView.calleeID = _theRecipientId;
if (!appToapp) {
callView.typeOfCall = #"phone";
callView.callStateLbl.text = #"Phone";
}
else {
callView.typeOfCall = #"app";
}
[self presentViewController:callView animated:YES completion:nil];
}
Now each time, I tried to test the call - I have the following problem:
The call view show, then stop suddenly with the below message showing in the console:
[appDelegate client:logMessage:area:severity:timestamp:] [Line 590] virtual void rebrtc::SetSDPObserver::OnFailure(const std::string &)Failed to set remote answer sdp: Offer and answer descriptions m-lines are not matching. Rejecting answer.
[appDelegate client:logMessage:area:severity:timestamp:] [Line 590] Failed to set remote answer sdp: Offer and answer descriptions m-lines are not matching. Rejecting answer.
My Sinch SDK version is: 3.7.1-0313526 - with Xcode 6.3 - IOS SDK 8.3
My Parse SDK version is 1.7.4
I have tried to look everywhere and read all the tutorials on Sinch! website about calling, and cannot find any clue for help.
Any help will be greatly appreciate.
Thanks in advance.

How to implement SportLight search?

I am working with spotlight search, I have implemented all the required thinks in my functions but still not getting item in search, please suggest me.
-(void)functionForSportlightsearch{
NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:sportlightSearchID];
activity.title = #"Item test";
activity.keywords = [NSSet setWithArray:self.arrUnits];
activity.eligibleForSearch = YES;
[activity becomeCurrent];
}
What think still missing to enable this, I was calling this in viewDidLoad(RootviewController).
With iOS 9, Apple upgrades Handoff, associating it with Spotlight.
For this, the application simply needs to register the launched activities (with NSUserActivity), as well as the content it presents (with Core Spotlight); then the system handles that information, indexing it either locally or on Apple servers.
Apple stipulates that keywords should not be overused, with the
following warning for developers: irrelevant keywords will be detected
and the activity will not be searchable through them.
To handle a search result , a specific method in the appDelegate has to be implemented:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray *restorableObjects))restorationHandler
This method should recreate the view associated to the activity and present it to the user.
For more info : http://applidium.com/en/news/ios9_search_api/
Also add the following line to the above code..
self.userActivity = activity;
userActivity is the UIResponder class property.

iOS 9 - NSUserActivity userinfo property showing null

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.

Make App Activities and States Searchable by using NSUserActivity

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.

iOS kontakt.io beacons search

I have two kontakt.io Beacons. I'm able to find it using the default Kontakt.io app available in the AppStore. But when I use the SDK and try to find it in my custom app, the app requests Bluetooth, which means it does something with it, but no beacons are found.
According to the documentation I must only create an object of KTKBeaconManager class, assign a KTKBeaconManagerDelegate and call startFindingDevices method. After that the delegate should receive callbacks whenever devices in range changes. I extended the KTKBeaconManager with a class called BeaconManager. Here's its code (Yes I have imported everything and code compiles. I didn't put it here to save space.).
BeaconManager.h
#interface BeaconManager : KTKBeaconManager <KTKBluetoothManagerDelegate>
#end
BeaconManager.m
#implementation BeaconManager
- (instancetype)init
{
self = [super init];
if (self) {
//Setting the delegate to self
self.delegate = self;
}
return self;
}
- (void)bluetoothManager:(KTKBluetoothManager *)bluetoothManager didChangeDevices:(NSSet *)devices {
NSLog(#"Entered didChangeDevices. Devices size: %d", devices.count);
}
#end
Starting the search.
BeaconManager *beaconManager = [BeaconManager new];
[beaconManager startFindingDevices];
[beaconManager reloadDevices]; //Tells the manager to forget all devices and start searching again.
This is actually a sample code from the documentation, but it's not working. Anybody's going through something similar and has got a clue what to do?
Your beaconManager is most probably deallocated just after it's created. You have to move it to an instance variable.
It's not written that directly but you should know what's the scope of life for object - it should be a property if you want to have it working all the time etc.

Resources