How to Properly Present Data from Core Spotlight a

2019-09-06 07:38发布

问题:

I'm trying to use spotlight results to guide the user to the appropriate section within my app.

Here is my structure, hopefully it does a good job illustrating it.

Navigation Controller
|
|-- Main View
    |-- About View
    |
    |-- SplitView Controller (Presented Modally;StoryboardID: PhoneDirectory)
        |-- Master View Controller
        |-- Detail View Controller
    |--Another View

When a search result brings the user to my app the first time all works well. I am able to present the SplitViewController and then send the user to the appropriate DetailView.

However, if the user leaves the app at it's current state and goes back to the search and the taps on another result, the user is taken back to the app with the previous view loaded.

I'm not sure how to go about it at this point.

  • Do I pop the previous view? If so, how?
  • Do I reload the data in the detail view? If so, how?
  • How can I identify that I'm already presenting a view that I need to present with different data?

With the code below I get the following warning, on the 2nd search result selection:

Warning: Attempt to present <PhoneDirectorySplitViewController: 0x7fdf398ab600> on <UINavigationController: 0x7fdf3c831600> whose view is not in the window hierarchy!

Here's the code I have so far (condensed)

AppDelegate.m

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
    NSString *uid = nil;
    if ([[userActivity activityType] isEqualToString:CSSearchableItemActionType] || [[userActivity activityType] isEqualToString:@"com.myapp.activity"]) {
        uid = [userActivity userInfo][CSSearchableItemActivityIdentifier];
    }

    if (uid != nil) {
        NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
        f.numberStyle = NSNumberFormatterDecimalStyle;
        NSNumber *idNum = [f numberFromString:uid];

        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
        UINavigationController *navController = nil;
        navController = [storyboard instantiateViewControllerWithIdentifier:@"PhoneDirectory"];

        if (navController != nil) {
            [[[self window] rootViewController] presentViewController:navController animated:YES completion:nil];
            PhoneDirectoryMasterViewController *master = [[[[navController viewControllers] firstObject] childViewControllers] firstObject];
            [master selectRowWithId:idNum];

            return YES;
        }
    }

    return NO;
}

MasterView.h

- (void)selectRowWithId:(NSNumber *)uid {
    [[self displayedPersons] enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(id == %@)", uid];
        NSArray *filteredPersonsArray = [obj filteredArrayUsingPredicate:predicate];
        if ([filteredPersonsArray count] > 0) {
            NSIndexPath *targetIndexPath = [NSIndexPath indexPathForRow:[[[self displayedPersons] objectForKey:key] indexOfObject:filteredPersonsArray[0]] inSection:[[self personIndexTitles] indexOfObject:key]];

            [[self tableView] selectRowAtIndexPath:targetIndexPath animated:NO scrollPosition:UITableViewScrollPositionMiddle];
            [self performSegueWithIdentifier:@"showPhoneDirectoryDetail" sender:self];
        }
    }];
}

If you need more info, just let me know.

回答1:

You need to do some validations checks at proper places. Add this declaration to your AppDelegate.m

UISplitViewController *splitViewController;

You might need to keep a reference to the splitViewController as you need to access it the second time and you must not create a new controller instance every time you need the same. Currently you are creating a new splitViewController instance every time you come back to app from outside.

Check #1

if(splitViewController == nil)
{
    // Only create a new splitViewController if it's not have been created already
    splitViewController = [storyboard instantiateViewControllerWithIdentifier:@"PhoneDirectory"];
}

Check #2

// presentingViewController points to the base viewController that presented it
    if(splitViewController.presentingViewController == nil)
    {
        // Only present splitViewController is it's not been presented already
        [[[self window] rootViewController] presentViewController:splitViewController animated:YES completion:nil];
    }

Check #3 (Inside your MasterViewController.m 'selectRowWithId:')

// Finally before making the push with new instance, just pop the old one out of the navigationStack
    [self.navigationController popViewControllerAnimated:YES];

    [self performSegueWithIdentifier:@"showPhoneDirectoryDetail" sender:self];

That'd be all you need. Hope this helps.



回答2:

So after looking through many SO questions/answers here's what I've come up with. It might not be universally, but it works. If someone can make it universal please feel free to submit a new answer.

AppDelegate.m

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
    NSString *uid = nil;
    if ([[userActivity activityType] isEqualToString:CSSearchableItemActionType] || [[userActivity activityType] isEqualToString:@"com.myapp.activity"]) {
        uid = [userActivity userInfo][CSSearchableItemActivityIdentifier];
    }

    if (uid != nil) {
        NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
        f.numberStyle = NSNumberFormatterDecimalStyle;
        NSNumber *idNum = [f numberFromString:uid];

        UIViewController *vc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] presentedViewController];

        if ([vc class] == [PhoneDirectorySplitViewController class]) {
            if ([vc respondsToSelector:@selector(dismissViewControllerAnimated:completion:)]) {
                [vc dismissViewControllerAnimated:NO completion:^{
                    [self showUserActivity:idNum];
                }];
            }
        } else if ([vc class] == [UINavigationController class]) {
            if ([vc respondsToSelector:@selector(visibleViewController)]) {
                if ([[(UINavigationController *)vc visibleViewController] respondsToSelector:@selector(dismissViewControllerAnimated:completion:)]) {
                    [[(UINavigationController *)vc visibleViewController] dismissViewControllerAnimated:NO completion:^{
                        [self showUserActivity:idNum];
                    }];
                }
            }
        } else {
            [self showUserActivity:idNum];
        }

        return YES;
    }

    return NO;
}

- (void)showUserActivity:(NSNumber *)idNum {
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    UINavigationController *navController = nil;
    navController = [storyboard instantiateViewControllerWithIdentifier:@"PhoneDirectory"];

    if (navController != nil) {
        [[[self window] rootViewController] presentViewController:navController animated:YES completion:nil];
        PhoneDirectoryMasterViewController *master = [[[[navController viewControllers] firstObject] childViewControllers] firstObject];
        [master selectRowWithId:idNum];
    }
}