Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS Share Extension flow

I have problems with creating share extension like share extension of the Pinterest app. When user is not logged to the containing app the share extension only presents alert with an option to log in and cancel.

Where in code decide which view controller to show in my shared extension. I see this like I need to check authorization status from shared container and if this status is not logged I need to present alert controller. If status is logged I need to show my main view controller ShareViewController which is a subclass of SLComposeServiceViewController

My question is not UI related but where to put this check code. I didn't find any method where app extension starts so I can select some initial view controller for extension based on some state.

In Pinterest extension I don't see their main view controller when user is logged out from their containing app. I see only alert with options.

Second question: How to programatically switch from share extension to containing app. How this Pinterest share extension is doing this when user need to authenticate?

I'm working on latest iOS SDK 10.2

like image 391
Marcin Kapusta Avatar asked Mar 17 '17 13:03

Marcin Kapusta


1 Answers

Since I didn't receive any feedback for this and I figure it out how to do this. Here is the answer.

To control which view load I used loadView method from UIViewController lifecycle. This method is fired when application first need to get view property from UIViewController. So this is lazy loading. This method is also fired when user invoke loadViewIfNeeded() from UIViewController api. In the body of this method You need to be very careful to not read view property because this will invoke loadView again and You will have recursive loop.

My implementation for this method is following. I need to tell if user is logged in or not in containing app and based on this choose which view to load.

override func loadView() {
    // check in shared Keychain if user is authenticated
    self.userAuthenticated = userService.isAuthenticated()

    if self.userAuthenticated {
        // load default view of the ShareViewController
        super.loadView()
    } else {
        // if user is not logged in show only alert view controller with transparent dummy view
        let view = UIView()
        self.view = view
    }
}

And if user is not logged in I show alert in

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    let context = self.extensionContext!
    if !self.userAuthenticated {
        let alert = UIAlertController(title: "Error", message: "User not logged in", preferredStyle: .alert)
        let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in
            context.completeRequest(returningItems: nil, completionHandler: nil)
        }
        let login = UIAlertAction(title: "Log In", style: .default, handler: { _ in
            let url = URL(string: "fashionapp://login")!
            // This is utility method written in Objective-C.
            // I don't know yet if it passes Apple Review process or not.
            // We will see ;)
            self.open(url, options: [:], completionHandler: nil)
            context.completeRequest(returningItems: nil, completionHandler: nil)
        })

        alert.addAction(cancel)
        alert.addAction(login)
        present(alert, animated: true, completion: nil)
    }
}

And here is the method for opening containing app from share extension. I hope it will be useful and Apple will review this without any problems. It is written in Objective-C because in Swift there is no NSInvocation class so you can only perform selectors with max two arguments.

#import <UIKit/UIKit.h>

@interface UIViewController (OpenURL)

- (void)openURL:(nonnull NSURL *)url
        options:(nonnull NSDictionary<NSString *, id> *)options
completionHandler:(void (^ __nullable)(BOOL success))completion;

@end

And implementation.

#import "UIViewController+OpenURL.h"

@implementation UIViewController (OpenURL)


- (void)openURL:(nonnull NSURL *)url
        options:(nonnull NSDictionary<NSString *, id> *)options
completionHandler:(void (^ __nullable)(BOOL success))completion {

    SEL selector = NSSelectorFromString(@"openURL:options:completionHandler:");

    UIResponder* responder = self;
    while ((responder = [responder nextResponder]) != nil) {
        if([responder respondsToSelector:selector] == true) {
            NSMethodSignature *methodSignature = [responder methodSignatureForSelector:selector];
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];

            [invocation setTarget: responder];
            [invocation setSelector: selector];
            [invocation setArgument: &url atIndex: 2];
            [invocation setArgument: &options atIndex:3];
            [invocation setArgument: &completion atIndex: 4];
            [invocation invoke];
            break;
        }
    }
}

@end
like image 54
Marcin Kapusta Avatar answered Oct 15 '22 12:10

Marcin Kapusta