SwiftPasscodeLock icon indicating copy to clipboard operation
SwiftPasscodeLock copied to clipboard

Use of UIApplication.shared prevents usage within ios extensions

Open fuji7l opened this issue 7 years ago • 5 comments

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.

fuji7l avatar May 05 '17 22:05 fuji7l

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

ziogaschr avatar May 25 '17 08:05 ziogaschr

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.

fuji7l avatar Jun 05 '17 22:06 fuji7l

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.

ziogaschr avatar Jun 06 '17 08:06 ziogaschr

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.

fuji7l avatar Jun 06 '17 17:06 fuji7l

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

yishilin14 avatar Jun 14 '17 03:06 yishilin14