Support scroll to top on iOS statusbar touches (flutter/engine#3375)

On iOS, when a tap is detected in the status bar, provide a means to
pass that touch event through to one or more FlutterViewControllers to
trigger a scroll to top. In iOS apps, scroll to top should occur under
the following conditions:

1. There is one and only one UIScrollView visible with
   scrollsToTop == YES.
2. The status-bar is in standard height mode, not in double-height mode.
   In double-height mode, the expected behaviour is to trigger a switch
   to the application associated with the double-height status bar.
3. A tap or a drag gesture occurs that is entirely constrained to the
   status bar frame. (We currently only handle the tap scenario).

Unfortunately, AppDelegates only get touchesBegan events for status bar
taps, though get get touchesBegan and touchesEnded events for drags
within the status bar frame. As such, we currently synthesise the
touchesEnded event for taps.
This commit is contained in:
Chris Bracken
2017-01-31 13:42:58 -08:00
committed by GitHub
parent ea73356841
commit 21f38d7a48
2 changed files with 35 additions and 0 deletions

View File

@@ -37,6 +37,8 @@ FLUTTER_EXPORT
- (void)removeAsyncMessageListener:
(NSObject<FlutterAsyncMessageListener>*)listener;
- (void)handleStatusBarTouches:(UIEvent *)event;
@end
__BEGIN_DECLS

View File

@@ -271,6 +271,11 @@ static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(
}
- (void)dispatchTouches:(NSSet*)touches phase:(UITouchPhase)phase {
// Note: we cannot rely on touch.phase, since in some cases, e.g.,
// handleStatusBarTouches, we synthesize touches from existing events.
//
// TODO(cbracken) consider creating out own class with the touch fields we
// need.
auto eventTypePhase = PointerChangePhaseFromUITouchPhase(phase);
const CGFloat scale = [UIScreen mainScreen].scale;
auto packet = std::make_unique<blink::PointerDataPacket>(touches.count);
@@ -498,6 +503,34 @@ static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(
[super dealloc];
}
#pragma mark - Status Bar touch event handling
// Standard iOS status bar height in pixels.
constexpr CGFloat kStandardStatusBarHeight = 20.0;
- (void)handleStatusBarTouches:(UIEvent *)event {
// If the status bar is double-height, don't handle status bar taps. iOS
// should open the app associated with the status bar.
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
if (statusBarFrame.size.height != kStandardStatusBarHeight) {
return;
}
// If we detect a touch in the status bar, synthesize a fake touch begin/end.
for (UITouch *touch in event.allTouches) {
if (touch.phase == UITouchPhaseBegan && touch.tapCount > 0) {
CGPoint windowLoc = [touch locationInView:nil];
CGPoint screenLoc = [touch.window convertPoint:windowLoc toWindow:nil];
if (CGRectContainsPoint(statusBarFrame, screenLoc)) {
NSSet *statusbarTouches = [NSSet setWithObject:touch];
[self dispatchTouches:statusbarTouches phase:UITouchPhaseBegan];
[self dispatchTouches:statusbarTouches phase:UITouchPhaseEnded];
return;
}
}
}
}
#pragma mark - Status bar style
- (UIStatusBarStyle)preferredStatusBarStyle {