Skip to content

Commit cbe662e

Browse files
authored
iOS: fix status-bar tap scroll-to-top on device (#3589) (#4857)
The hidden UIScrollView used to receive the iOS status-bar tap (scrollViewShouldScrollToTop:) was effectively invisible to the system on real devices for two reasons: its contentSize (1x2) was smaller than its full-screen frame so iOS treated it as not actually scrollable, and userInteractionEnabled was NO which excludes the view from scroll-to-top dispatch on recent iOS versions. Even when those were addressed, any on-screen WKWebView/UIWebView/UITextView (whose internal scroll views default to scrollsToTop=YES) created an ambiguity that caused iOS to deliver the message to no one. - Introduce a CN1StatusBarTapProxyView UIScrollView subclass that returns NO from pointInside: so taps still fall through to the GL view, and keeps contentSize one point taller than its bounds in layoutSubviews so the scroll view stays scrollable across rotations. - Recreate the proxy with userInteractionEnabled=YES and re-attach / bring it to the front from viewDidAppear: so peers added after viewDidLoad don't bury it. - Set scrollsToTop=NO on the editing CN1UITextView and on UIWebView / WKWebView's embedded scrollViews so they don't compete with the proxy.
1 parent a3effb5 commit cbe662e

2 files changed

Lines changed: 66 additions & 5 deletions

File tree

Ports/iOSPort/nativeSources/CodenameOne_GLViewController.m

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,31 @@ static CGSize cn1OrientationCorrectSize(UIView *view) {
195195
return size;
196196
}
197197
BOOL forceSlideUpField;
198-
static UIScrollView *cn1StatusBarTapProxy = nil;
198+
199+
// UIScrollView subclass used solely to receive the status-bar tap
200+
// (scrollViewShouldScrollToTop:) on iOS. Touches must pass through to the
201+
// underlying GL view, so we always return NO from pointInside:.
202+
// layoutSubviews keeps contentSize larger than the bounds so iOS considers
203+
// the scroll view actually scrollable, which is required for the system to
204+
// dispatch the scroll-to-top message.
205+
@interface CN1StatusBarTapProxyView : UIScrollView
206+
@end
207+
@implementation CN1StatusBarTapProxyView
208+
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
209+
return NO;
210+
}
211+
- (void)layoutSubviews {
212+
[super layoutSubviews];
213+
CGSize sz = self.bounds.size;
214+
if (sz.width < 1) sz.width = 1;
215+
self.contentSize = CGSizeMake(sz.width, sz.height + 1);
216+
if (self.contentOffset.y <= 0) {
217+
self.contentOffset = CGPointMake(0, 1);
218+
}
219+
}
220+
@end
221+
222+
static CN1StatusBarTapProxyView *cn1StatusBarTapProxy = nil;
199223

200224

201225
// 1 for portrait lock, and 2 for landscape lock
@@ -572,6 +596,11 @@ void cn1_setStyleDoneButton(CN1_THREAD_STATE_MULTI_ARG UIBarButtonItem* btn) {
572596
utv.blockPaste = CN1_blockPaste || blockCopyPaste;
573597
utv.blockCopy = CN1_blockCopy || blockCopyPaste;
574598
utv.blockCut = CN1_blockCut || blockCopyPaste;
599+
// Avoid competing with the CN1 status-bar tap proxy: iOS only fires
600+
// scrollViewShouldScrollToTop: when exactly one scroll view has
601+
// scrollsToTop=YES, and UITextView's internal scroll view defaults
602+
// to YES.
603+
utv.scrollsToTop = NO;
575604
[utv setBackgroundColor:[UIColor clearColor]];
576605
[utv.layer setBorderColor:[[UIColor clearColor] CGColor]];
577606
[utv.layer setBorderWidth:0];
@@ -1946,17 +1975,37 @@ - (void)viewDidLoad {
19461975

19471976
- (void)cn1InstallStatusBarTapProxy {
19481977
if (cn1StatusBarTapProxy != nil) {
1978+
// Re-attach if it was detached and ensure it sits on top so it isn't
1979+
// hidden by peer components added later in the view hierarchy.
1980+
if (cn1StatusBarTapProxy.superview != self.view) {
1981+
[cn1StatusBarTapProxy removeFromSuperview];
1982+
[self.view addSubview:cn1StatusBarTapProxy];
1983+
} else {
1984+
[self.view bringSubviewToFront:cn1StatusBarTapProxy];
1985+
}
19491986
return;
19501987
}
1951-
cn1StatusBarTapProxy = [[UIScrollView alloc] initWithFrame:self.view.bounds];
1988+
cn1StatusBarTapProxy = [[CN1StatusBarTapProxyView alloc] initWithFrame:self.view.bounds];
19521989
cn1StatusBarTapProxy.delegate = self;
19531990
cn1StatusBarTapProxy.backgroundColor = [UIColor clearColor];
1954-
cn1StatusBarTapProxy.contentSize = CGSizeMake(1, 2);
1955-
cn1StatusBarTapProxy.contentOffset = CGPointMake(0, 1);
19561991
cn1StatusBarTapProxy.scrollsToTop = YES;
1957-
cn1StatusBarTapProxy.userInteractionEnabled = NO;
1992+
// userInteractionEnabled must remain YES; some iOS versions skip the
1993+
// scrollViewShouldScrollToTop: dispatch for views that have it disabled.
1994+
// pointInside: in the subclass ensures touches still pass through.
1995+
cn1StatusBarTapProxy.userInteractionEnabled = YES;
1996+
cn1StatusBarTapProxy.scrollEnabled = YES;
1997+
cn1StatusBarTapProxy.showsVerticalScrollIndicator = NO;
1998+
cn1StatusBarTapProxy.showsHorizontalScrollIndicator = NO;
1999+
cn1StatusBarTapProxy.bounces = NO;
19582000
cn1StatusBarTapProxy.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
19592001
cn1StatusBarTapProxy.alpha = 0.0f;
2002+
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
2003+
if (@available(iOS 11.0, *)) {
2004+
cn1StatusBarTapProxy.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
2005+
}
2006+
#endif
2007+
cn1StatusBarTapProxy.contentSize = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height + 1);
2008+
cn1StatusBarTapProxy.contentOffset = CGPointMake(0, 1);
19602009
[self.view addSubview:cn1StatusBarTapProxy];
19612010
}
19622011

@@ -2032,6 +2081,10 @@ - (void)viewDidAppear:(BOOL)animated {
20322081
[super viewDidAppear:animated];
20332082
[self becomeFirstResponder];
20342083
[self updateCanvas:animated];
2084+
// Re-install / bring the status-bar tap proxy to the front. Native peers
2085+
// (browsers, video, etc.) added after viewDidLoad can obscure it or push
2086+
// sibling scroll views into the hierarchy.
2087+
[self cn1InstallStatusBarTapProxy];
20352088
//replaceViewDidAppear
20362089
}
20372090

Ports/iOSPort/nativeSources/IOSNative.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2605,6 +2605,10 @@ JAVA_LONG com_codename1_impl_ios_IOSNative_createBrowserComponent___java_lang_Ob
26052605
com_codename1_impl_ios_IOSNative_createBrowserComponent.backgroundColor = [UIColor clearColor];
26062606
com_codename1_impl_ios_IOSNative_createBrowserComponent.opaque = NO;
26072607
com_codename1_impl_ios_IOSNative_createBrowserComponent.autoresizesSubviews = YES;
2608+
// Disable scrollsToTop on the embedded scroll view so it doesn't compete
2609+
// with the CN1 status-bar tap proxy. iOS only delivers the tap when
2610+
// exactly one scroll view on screen has scrollsToTop=YES.
2611+
com_codename1_impl_ios_IOSNative_createBrowserComponent.scrollView.scrollsToTop = NO;
26082612
UIWebViewEventDelegate *del = [[UIWebViewEventDelegate alloc] initWithCallback:obj];
26092613
com_codename1_impl_ios_IOSNative_createBrowserComponent.delegate = del;
26102614
com_codename1_impl_ios_IOSNative_createBrowserComponent.autoresizingMask=(UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
@@ -2653,6 +2657,10 @@ JAVA_LONG com_codename1_impl_ios_IOSNative_createWKBrowserComponent___java_lang_
26532657
com_codename1_impl_ios_IOSNative_createWKBrowserComponent.backgroundColor = [UIColor clearColor];
26542658
com_codename1_impl_ios_IOSNative_createWKBrowserComponent.opaque = NO;
26552659
com_codename1_impl_ios_IOSNative_createWKBrowserComponent.autoresizesSubviews = YES;
2660+
// Disable scrollsToTop on the embedded scroll view so it doesn't compete
2661+
// with the CN1 status-bar tap proxy. iOS only delivers the tap when
2662+
// exactly one scroll view on screen has scrollsToTop=YES.
2663+
com_codename1_impl_ios_IOSNative_createWKBrowserComponent.scrollView.scrollsToTop = NO;
26562664
cn1_setBrowserFollowTargetBlank(com_codename1_impl_ios_IOSNative_createWKBrowserComponent, YES);
26572665

26582666
if (getBooleanClientProperty(CN1_THREAD_GET_STATE_PASS_ARG obj, @"BrowserComponent.ios.debug")) {

0 commit comments

Comments
 (0)