Flutter iOS Embedder
FlutterKeyboardManager.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include "flutter/fml/platform/darwin/message_loop_darwin.h"
9 
11 
12 static constexpr CFTimeInterval kDistantFuture = 1.0e10;
13 
15 
16 /**
17  * The primary responders added by addPrimaryResponder.
18  */
19 @property(nonatomic, copy, readonly)
20  NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
21 
22 /**
23  * The secondary responders added by addSecondaryResponder.
24  */
25 @property(nonatomic, copy, readonly)
26  NSMutableArray<id<FlutterKeySecondaryResponder>>* secondaryResponders;
27 
28 - (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press
29  complete:(nonnull KeyEventCompleteCallback)callback
30  API_AVAILABLE(ios(13.4));
31 
32 @end
33 
34 @implementation FlutterKeyboardManager
35 
36 - (nonnull instancetype)init {
37  self = [super init];
38  if (self != nil) {
39  _primaryResponders = [[NSMutableArray alloc] init];
40  _secondaryResponders = [[NSMutableArray alloc] init];
41  }
42  return self;
43 }
44 
45 - (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder {
46  [_primaryResponders addObject:responder];
47 }
48 
49 - (void)addSecondaryResponder:(nonnull id<FlutterKeySecondaryResponder>)responder {
50  [_secondaryResponders addObject:responder];
51 }
52 
53 - (void)handlePress:(nonnull FlutterUIPressProxy*)press
54  nextAction:(nonnull void (^)())next API_AVAILABLE(ios(13.4)) {
55  if (@available(iOS 13.4, *)) {
56  // no-op
57  } else {
58  return;
59  }
60 
61  bool __block wasHandled = false;
62  KeyEventCompleteCallback completeCallback = ^void(bool handled, FlutterUIPressProxy* press) {
63  wasHandled = handled;
64  CFRunLoopStop(CFRunLoopGetCurrent());
65  };
66  switch (press.phase) {
67  case UIPressPhaseBegan:
68  case UIPressPhaseEnded: {
69  // Having no primary responders requires extra logic, but Flutter hard-codes
70  // all primary responders, so this is a situation that Flutter will never
71  // encounter.
72  NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added.");
73 
74  __block __weak __typeof(self) weakSelf = self;
75  __block NSUInteger unreplied = [self.primaryResponders count];
76  __block BOOL anyHandled = false;
77  FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
78  unreplied--;
79  NSAssert(unreplied >= 0, @"More primary responders replied than expected.");
80  anyHandled = anyHandled || handled;
81  if (unreplied == 0) {
82  if (!anyHandled && weakSelf) {
83  [weakSelf dispatchToSecondaryResponders:press complete:completeCallback];
84  } else {
85  completeCallback(true, press);
86  }
87  }
88  };
89  for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
90  [responder handlePress:press callback:replyCallback];
91  }
92  // Create a nested run loop while we wait for a response from the
93  // framework. Once the completeCallback is called, this run loop will exit
94  // and the main one will resume. The completeCallback MUST be called, or
95  // the app will get stuck in this run loop indefinitely.
96  //
97  // We need to run in this mode so that UIKit doesn't give us new
98  // events until we are done processing this one.
99  CFRunLoopRunInMode(fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, kDistantFuture, NO);
100  break;
101  }
102  case UIPressPhaseChanged:
103  case UIPressPhaseCancelled:
104  case UIPressPhaseStationary:
105  break;
106  }
107  if (!wasHandled) {
108  next();
109  }
110 }
111 
112 #pragma mark - Private
113 
114 - (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press
115  complete:(nonnull KeyEventCompleteCallback)callback
116  API_AVAILABLE(ios(13.4)) {
117  if (@available(iOS 13.4, *)) {
118  // no-op
119  } else {
120  callback(false, press);
121  return;
122  }
123 
124  for (id<FlutterKeySecondaryResponder> responder in _secondaryResponders) {
125  if ([responder handlePress:press]) {
126  callback(true, press);
127  return;
128  }
129  }
130  callback(false, press);
131 }
132 
133 @end
FlutterKeyPrimaryResponder-p
Definition: FlutterKeyPrimaryResponder.h:19
kDistantFuture
static constexpr FLUTTER_ASSERT_ARC CFTimeInterval kDistantFuture
Definition: FlutterKeyboardManager.mm:12
FlutterMacros.h
FlutterKeySecondaryResponder-p
Definition: FlutterKeySecondaryResponder.h:17
FlutterAsyncKeyCallback
void(^ FlutterAsyncKeyCallback)(BOOL handled)
Definition: FlutterKeyPrimaryResponder.h:10
KeyEventCompleteCallback
void(^ KeyEventCompleteCallback)(bool, FlutterUIPressProxy *_Nonnull) API_AVAILABLE(ios(13.4))
Definition: FlutterKeyboardManager.h:17
FlutterKeyboardManager.h
FlutterUIPressProxy
Definition: FlutterUIPressProxy.h:17
FlutterKeyboardManager
Definition: FlutterKeyboardManager.h:53
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13