velikanov / SwiftPasscodeLock

An iOS passcode lock with TouchID authentication written in Swift.
MIT License
199 stars 52 forks source link

Use of UIApplication.shared prevents usage within ios extensions #47

Open fuji7l opened 7 years ago

fuji7l commented 7 years ago

IOS extensions like share, document provider, etc do not allow access to UIAplication.shared.

This results in a compile time error due to

PasscodeLockPresenter.swift line 55.

ziogaschr commented 7 years ago

This is very interesting and I will probably run into the same bottleneck very soon. @fuji7l can you make a PR on this? If you also add a demo (or enhance the existing one), I will jump in, review and merge once you nudge me.

Thanks

fuji7l commented 7 years ago

I solved it, but it's probably not the ideal way, not even sure a pull request would work with this. It's not ideal, not fully tested, but I've made progress with this so far. I honestly think some documentation on this would be better but after it's tested further. Especially since with different targets for different classes, it's not exactly something I would suggest baking in the repo.

You could add the #ifdef to the repo, but then you'd need to document what macro/define I as the user would need to set in my target's build settings. I believe AFNetworking does this, maybe you can use the same define as they do.

Again, I haven't tested this further than seeing that it compiles and runs. There may be some functions/features I'm not aware of where this breaks/falls apart. Also, the keyboard hiding code you have will not execute on an extension, I'm not sure how to address that.

With that said, here's what I did:

I edited PasscodeLockPresenter.swift to make the toggle method overridable.

open func toggleKeyboardVisibility(hide: Bool) {
        NSLog("Not implemented");
    }

Then I override that method in my class that implements PasscodeLockPresenter and added some C compile flags -DTARGET_APP_EXTENSION and macro TARGET_APP_EXTENSION=1 under the build settings for my app extensions.

class PasscodeDisplay: PasscodeLockPresenter {
    .... 
    override func toggleKeyboardVisibility(hide: Bool) {
        #if !TARGET_APP_EXTENSION
        if let keyboardWindow = UIApplication.shared.windows.last,
            keyboardWindow.description.hasPrefix("<UIRemoteKeyboardWindow")
        {
            keyboardWindow.alpha = hide ? 0.0 : 1.0
        }
        #endif
    }
}

Then for repository which requires "group" capabilities:

UserDefaultsPasscodeRepository: PasscodeRepositoryType {
    fileprivate lazy var defaults: UserDefaults = {

        return UserDefaults(suiteName: "group.com.my.security");
    }()!
}

And a special display class for extensions since the window object cannot be retrieved.

class PasscodeExtensionDisplay {

    var mConfig: PasscodeLockConfigurationType;
    var mPasscodeLockVC: PasscodeLockViewController;
    var mPasscodePresented = false;
    fileprivate lazy var mPasscodeLockWindow: UIWindow = {

        let window = UIWindow(frame: UIScreen.main.bounds)

        window.windowLevel = 0
        window.makeKeyAndVisible()

        return window
    }()

    init(configuration: PasscodeLockConfigurationType) {
        mConfig = configuration;
        mPasscodeLockVC = PasscodeLockViewController(state: .enterPasscode, configuration: configuration);
    }

    func shouldPresentPasscodeLock() -> Bool {
        guard mConfig.repository.hasPasscode else { NSLog("hasPasscode = false"); return false }
        guard !mPasscodePresented else { NSLog("isPresented = true");  return false; }
        return true;

    }

    func presentPasscodeLock() {
        guard mConfig.repository.hasPasscode else { return }
        guard !mPasscodePresented else { return }

        mPasscodePresented = true;
        let userDismissCompletionCallback = mPasscodeLockVC.dismissCompletionCallback

        mPasscodeLockVC.dismissCompletionCallback = { [weak self] in

            userDismissCompletionCallback?()

            self?.dismissPasscodeLock()
        }

    }
    func dismissPasscodeLock(animated: Bool = true) {

        mPasscodePresented = false

        if animated {
            UIView.animate(
                withDuration: 0.5,
                delay: 0,
                usingSpringWithDamping: 1,
                initialSpringVelocity: 0,
                options: [.curveEaseInOut],
                animations: { [weak self] in

                    self?.mPasscodeLockWindow.alpha = 0
                },
                completion: { [weak self] _ in

                    self?.mPasscodeLockWindow.windowLevel = 0
                    self?.mPasscodeLockWindow.rootViewController = nil
                    self?.mPasscodeLockWindow.alpha = 1
                }
            )
        } else {
            mPasscodeLockWindow.windowLevel = 0
            mPasscodeLockWindow.rootViewController = nil
        }
    }
}

And finally, in my UIViewController for the extension (e.g., share):

    lazy var mPassCode: PasscodeExtensionDisplay = {
        let conf = PasscodeLockConfig();
        return PasscodeExtensionDisplay(configuration: conf);
    }();
    override func viewWillAppear() {
        super.viewWillAppear()
            if (mPassCode.shouldPresentPasscodeLock()) {
                self.present(mPassCode.mPasscodeLockVC, animated: true, completion: nil);
            } else {
                NSLog("ShareVC shouldPresent returned false")
            }
   }
}

It works for me in my limited testing with the Share extension using my own UIViewController (instead of SLComposeViewController or whatever the default is).

I'm not too sure how you could handle this and what a pull request would even look like with this. It's really just documentation and then supporting that documentation for app extensions like Share and Document Provider.

ziogaschr commented 7 years ago

Thanks for the shared info @fuji7l. Any suggestion, in code or documentation form, is welcomed. Feel free to make a PR. I wasn't able to test your code because of lack of time. If you can also add this in a demo, this will be great.

Otherwise, your advice above is also helpful, if somebody requests the same. I might have a look at some point.

fuji7l commented 7 years ago

I'll see what I can do. I only have access to a mac while at work, so I'll try to get something together - can't guarantee when though.

yishilin14 commented 7 years ago

I am running into this now. Looking forward to see a solution for app extension in the repository!