5 ms·
Just to be clear, UIWebBrowserView is a private class, not the public UIWebView people should use, so it's not like Apple started rejecting random apps for no g
by Sephiroth87 11y ago
Just to be clear, UIWebBrowserView is a private class, not the public UIWebView people should use, so it's not like Apple started rejecting random apps for no good reasons...
- Sujan 11y agoThanks for the info. Is your obj-c good enough to explain what Ionic is actually doing in the file that triggers the problem? https://github.com/driftyco/ionic-plugin-keyboard/blob/master/src/ios/UIWebViewExtension.m https://github.com/driftyco/ionic-plugin-keyboard/blob/maste... Are they really doing it in a 'bad' way?
- Sephiroth87 11y agoThey are accessing an internal subview of UIWebView to hide the inputAccessoryView from the keyboard. When you use a normal view that uses the keyboard (UITextView, etc.), you can set it to any custom view you want, but because UIWebView is supposed to be used to display websites and not full apps, that property is not accessible, so they access the internal hierarchy of the webView, find the actual view that triggers the keyboard and modify it's property.
- masklinn 11y ago> They are accessing an internal subview An internal undocumented subview which is part of a private framework, aka they're digging into UIWebView's private implementation details[0] in order to do whatever they want to. Which may or may not be possible without that, but sadly that's besides the point. It's interesting to see that below the now-offending bits are even hackier bits which are currently commented "until [they] know it's app store safe" [0] which can't be formally private due to the architecture of Cocoa and objective-c I guess
- Sujan 11y agoThanks for the explanatin to you both! So removing this code would mean that all Ionic apps would display this thing here on all keyboards: https://raw.githubusercontent.com/clrung/PrevNextInputAccessoryView/master/README%20images/nativePrevNext.png https://raw.githubusercontent.com/clrung/PrevNextInputAccess... Did I get this right?
- Sephiroth87 11y agoYes, that's the one
- nogbit 11y agoI just wasted 10 minutes of my life to get to this which should be upvoted.
- afoxtron 11y agoJust wanted to chime in and say that's actually really bad code. Accessing a views subviews like that and looking for a specific instance of a class is just begging for a crash.
- EugeneOZ 11y agoAfter reading lines 5 and 6 it does not surprising me.
- kennywinker 11y agoCounterpoint: they have all the nil checks needed for this to fail silently if no UIWebBrowserView is found. It's a fragile implementation in that it might stop working if Apple changes the internal implementation of UIWebView, but it doesn't look like it'll crash because of it. That said, clearly it's not good for Apple to have large numbers of apps relying on private implementation details. It hamstrings them when it comes time to improve the OS. That translates to a harm for users. I'm not advocating for this hack. I wonder if this could be avoided by switching to WKWebView?
- masklinn 11y ago> I wonder if this could be avoided by switching to WKWebView? Nope, the keyboard UI is pretty much the same and the API does not provide any more support for disabling the accessory bar.
- Benjammer 11y ago- (UIView *)hackishlyFoundBrowserView { UIScrollView *scrollView = self.scrollView; UIView *browserView = nil; for (UIView *subview in scrollView.subviews) { if ([NSStringFromClass([subview class]) hasPrefix:@"UIWebBrowserView"]) { browserView = subview; break; } } return browserView; } This iterates through the view hierarchy until it finds a view with class name starting with "UIWebBrowserView". - (void)ensureHackishSubclassExistsOfBrowserViewClass:(Class)browserViewClass { if (!hackishFixClass) { Class newClass = objc_allocateClassPair(browserViewClass, hackishFixClassName, 0); IMP nilImp = [self methodForSelector:@selector(methodReturningNil)]; class_addMethod(newClass, @selector(inputAccessoryView), nilImp, "@@:"); objc_registerClassPair(newClass); hackishFixClass = newClass; } } This creates a new class at runtime that is a subclass of UIWebBrowserView (not accessible at compile time), but with the getter method for the inputAccessoryView overridden to return nil instead of the accessory view. - (void) setHackishlyHidesInputAccessoryView:(BOOL)value { UIView *browserView = [self hackishlyFoundBrowserView]; if (browserView == nil) { return; } [self ensureHackishSubclassExistsOfBrowserViewClass:[browserView class]]; if (value) { object_setClass(browserView, hackishFixClass); } else { Class normalClass = objc_getClass("UIWebBrowserView"); object_setClass(browserView, normalClass); } [browserView reloadInputViews]; } This takes the UIWebBrowserView object and overrides it's class at runtime with the aforementioned subclass with the nil accessory view getter. (Side note, this could also maybe have been done by swizzling out the method on the internal class instead of subclassing at runtime)
- ryanbertrand 11y agoI agree with the swizzling preference. I wonder if they can go around the Apple issue by using something like https://github.com/UrbanApps/UAObfuscatedString https://github.com/UrbanApps/UAObfuscatedString to generate the "UIWebBrowserView" string. static NSString browserViewClassName = Obfuscate.U.I.W.e.b.B.r.o.w.s.e.r.V.i.e.w; - (UIView *)hackishlyFoundBrowserView { UIScrollView *scrollView = self.scrollView; UIView *browserView = nil; for (UIView *subview in scrollView.subviews) { if ([NSStringFromClass([subview class]) hasPrefix:browserViewClassName]) { browserView = subview; break; } } return browserView; } This generates the "UIWebBrowserView" string at runtime which may get around Apple's checks.
- masklinn 11y agoYeah the view in question is a private[0] sub-sub view of UIWebView (UIWebView > UIScrollView > UIWebBrowserView) which does the actual rendering [0] there is no direct access to it, though because of Cocoa's architecture you can access it by iterating the scrollview's subviews and look for one with the expected class name
- soperj 11y agoThat sounds really screwed up. You can access private methods?
- draw_down 11y agoWelcome to Objective-C!
- gurkendoktor 11y agoYes, which is useful because it lets us work around bugs in Apple's own frameworks. (There is a trend with Swift to make everything more safe and enterprisey, as in JVM languages, though.) But this isn't about calling private methods, it's about navigating the view hierarchy, which is possible in every GUI framework I've used so far.
- potatolicious 11y agoYeah, but it's pretty much impossible to accidentally do so (as demonstrated in this case). Objective-C has a lot of reflection built in - you can determine an object's class, methods, and even swap out the implementations of methods at runtime. So it's pretty trivial to do something like: if ([someObject respondsToSelector:@selector(_privateMethodName)]) { [someObject performSelector:@selector(_privateMethodName)]; } Apple scans for the easy to detect uses of this (i.e., string literals of private methods in the binary), but of course anyone sufficiently determined to get around it, can. In this case though it's not so much accessing a private method but relying on undocumented and non-guaranteed behavior - walking a view hierarchy looking for a particular view that has no exposed interface. This sort of hackery is highly fragile (it can and does change between releases of iOS), and IMO should not be allowed in a shipping codebase.
- sdgsfsfgfg 11y agoAh thanks, I was thinking they were banning all cordova style apps!