11

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)

Daniel Galasko
  • 23,617
  • 8
  • 77
  • 97
Claus Jørgensen
  • 25,882
  • 9
  • 87
  • 150
  • Crazy question, but have you tried it without the nested NSArray? Or, could you try serializing it into NSData? And, have you tried setting userInfo before you call `self.userActivity = activity` and `becomeCurrent`? – brandonscript Nov 12 '14 at 23:07
  • I have the same problem here, nothing special is sent. Everything is configured conforming to Apple dev guides. Title and type are correct, but userInfo is missing. – GregoryM Apr 16 '15 at 14:26

3 Answers3

4

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.

GregoryM
  • 547
  • 5
  • 23
  • I confirm this works on iOS 8. On iOS 9 they already fixed it, so just set `.userInfo` directly and `.needsSave` to YES. – Tricertops Aug 31 '15 at 17:31
  • @Tricertops I wasn't even able to make it work in iOS 9 as you describe. The only way I could successfully capture the data was by implementing `userActivityWillSave:` and setting `userInfo` there. – aapierce Jun 29 '16 at 17:00
2

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 }];
}
HiDeoo
  • 10,353
  • 8
  • 47
  • 47
  • Except that's not working. Even with Apple's own sample. – Claus Jørgensen Nov 07 '14 at 08:25
  • Then it should be something on the receiving end of the other app, the example above is working correctly for me. I assume both devices are in physical proximity and signed into the same iCloud account. Do you implement application:willContinueUserActivityWithType: ? Is application:continueUserActivity:restorationHandler: called ? The activity object including the userInfo dictionary is not available before this last call. – HiDeoo Nov 07 '14 at 12:43
2

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

Richard
  • 1,249
  • 16
  • 25