RxKeyboard
RxKeyboard copied to clipboard
Consider App Extension environment
Another workaround for #22, which does not make subspec.
UIApplication.shared is unavailable on AppExtension environment. Use Selector to get the first window of UIApplication's shared instance. There will be no window if the application is running on AppExtension.
Hm. My main concern here would be not passing review. I just tested it and in an app extension it does respond to sharedApplication, returns an application object, and then returns the first window, but the windows appear to not be the extension's windows.
From RxKeyboard.applicationWindow:
(lldb) po application!
<UIApplication: 0x7f94a1201540>
(lldb) po application!.windows
▿ 3 elements
- 0 : <UITextEffectsWindowHosted: 0x7f94a1212f40; frame = (0 0; 375 667); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x61000002e140>>
- 1 : <UITextEffectsWindowHosted: 0x7f949e407430; frame = (0 0; 375 667); hidden = YES; layer = <UIWindowLayer: 0x600000031600>>
- 2 : <UIRemoteKeyboardWindowHosted: 0x7f949e50b410; frame = (0 0; 375 667); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x60800002c020>>
From the extension's view controller:
(lldb) po self.view!.window!
<_UIHostedWindow: 0x7f949e506a20; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x608000250290>; layer = <UIWindowLayer: 0x60800002fb00>>
It's unclear to me what that UIApplication object actually is.
Possibly we can add an initializer to RxKeyboard that accepts a window, and make the current initializer a convenience initializer
init(window: UIWindow?) {
...
}
override convenience init() {
init(window: RxKeyboard.applicationWindow())
}
Then extensions can initialize their own RxKeyboard instance instead of using the shared instance. instance would need to be lazily loaded to prevent the implicit access of the shared application, but I think static variables are already lazily loaded in swift.
Did a bit more testing and it seems like the interactive frame changes don't work from a share extension, so there's definitely something weird with those windows.
@ianyh, oh I just tested with Today extension and there was no window. 🤔 I think the best option is not to include RxKeyboard to extension target but I'll dig into this more.
I expect today extensions are hosted differently than share extensions are. Share extensions are hosted by an application's process, so there would be different dynamics. Just not including the library doesn't really make sense. It's the correct behavior for what I want to be doing, so if I don't include the library I'm just going to end up writing something myself that's going to turn out the same. Seems silly.
@ianyh, I've added some new commits! Now RxKeyboard has gestureView as a public property. Set this value with the App Extension's root view controller's view to use interactive keyboard dismiss mode.
// ShareViewController.swift
RxKeyboard.instance.gestureView = self.view // add this line
RxKeyboard.instance.visibleHeight.drive(...)
I also added a ShareExtension example. Check this out for working example :)
This still triggers the UIApplication getter. It's let so it's not lazy loaded. Even if you make it lazy loaded, though, the startWith combined with the nil coalescing means that the getter gets triggered anyway when the instance is initialized, which happens before you can set the gesture's view on it. So this is the correct feature behavior, but still might not pass muster because of accessing the UIApplication.
@ianyh, which are you worrying about? (1) using "sharedApplication" selector or (2) accessing UIApplication instance. For (1), we can use responder chain instead to get current application instance.
I'm worried about both, but accessing it is probably worse.
@ianyh, Hmm, I think this is not a private API so apple won't reject it but I'm not sure 🤔
@ianyh, MBProgressHUD uses this method too and there's no any open issue about the apple review.