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 #include "flutter/fml/memory/weak_ptr.h"
7 #include "flutter/fml/platform/darwin/message_loop_darwin.h"
8 
9 static constexpr CFTimeInterval kDistantFuture = 1.0e10;
10 
12 
13 /**
14  * The primary responders added by addPrimaryResponder.
15  */
16 @property(nonatomic, retain, readonly)
17  NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
18 
19 /**
20  * The secondary responders added by addSecondaryResponder.
21  */
22 @property(nonatomic, retain, readonly)
23  NSMutableArray<id<FlutterKeySecondaryResponder>>* secondaryResponders;
24 
25 - (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press
26  complete:(nonnull KeyEventCompleteCallback)callback
27  API_AVAILABLE(ios(13.4));
28 
29 @end
30 
31 @implementation FlutterKeyboardManager {
32  std::unique_ptr<fml::WeakPtrFactory<FlutterKeyboardManager>> _weakFactory;
33 }
34 
35 - (nonnull instancetype)init {
36  self = [super init];
37  if (self != nil) {
38  _primaryResponders = [[NSMutableArray alloc] init];
39  _secondaryResponders = [[NSMutableArray alloc] init];
40  _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterKeyboardManager>>(self);
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)dealloc {
54  // It will be destroyed and invalidate its weak pointers
55  // before any other members are destroyed.
56  _weakFactory.reset();
57 
58  [_primaryResponders removeAllObjects];
59  [_secondaryResponders removeAllObjects];
60  [_primaryResponders release];
61  [_secondaryResponders release];
62  _primaryResponders = nil;
63  _secondaryResponders = nil;
64  [super dealloc];
65 }
66 
67 - (fml::WeakPtr<FlutterKeyboardManager>)getWeakPtr {
68  return _weakFactory->GetWeakPtr();
69 }
70 
71 - (void)handlePress:(nonnull FlutterUIPressProxy*)press
72  nextAction:(nonnull void (^)())next API_AVAILABLE(ios(13.4)) {
73  if (@available(iOS 13.4, *)) {
74  // no-op
75  } else {
76  return;
77  }
78 
79  bool __block wasHandled = false;
80  KeyEventCompleteCallback completeCallback = ^void(bool handled, FlutterUIPressProxy* press) {
81  wasHandled = handled;
82  CFRunLoopStop(CFRunLoopGetCurrent());
83  };
84  switch (press.phase) {
85  case UIPressPhaseBegan:
86  case UIPressPhaseEnded: {
87  // Having no primary responders requires extra logic, but Flutter hard-codes
88  // all primary responders, so this is a situation that Flutter will never
89  // encounter.
90  NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added.");
91 
92  __block auto weakSelf = [self getWeakPtr];
93  __block NSUInteger unreplied = [self.primaryResponders count];
94  __block BOOL anyHandled = false;
95  FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
96  unreplied--;
97  NSAssert(unreplied >= 0, @"More primary responders replied than expected.");
98  anyHandled = anyHandled || handled;
99  if (unreplied == 0) {
100  if (!anyHandled && weakSelf) {
101  [weakSelf.get() dispatchToSecondaryResponders:press complete:completeCallback];
102  } else {
103  completeCallback(true, press);
104  }
105  }
106  };
107  for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
108  [responder handlePress:press callback:replyCallback];
109  }
110  // Create a nested run loop while we wait for a response from the
111  // framework. Once the completeCallback is called, this run loop will exit
112  // and the main one will resume. The completeCallback MUST be called, or
113  // the app will get stuck in this run loop indefinitely.
114  //
115  // We need to run in this mode so that UIKit doesn't give us new
116  // events until we are done processing this one.
117  CFRunLoopRunInMode(fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, kDistantFuture, NO);
118  break;
119  }
120  case UIPressPhaseChanged:
121  case UIPressPhaseCancelled:
122  case UIPressPhaseStationary:
123  break;
124  }
125  if (!wasHandled) {
126  next();
127  }
128 }
129 
130 #pragma mark - Private
131 
132 - (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press
133  complete:(nonnull KeyEventCompleteCallback)callback
134  API_AVAILABLE(ios(13.4)) {
135  if (@available(iOS 13.4, *)) {
136  // no-op
137  } else {
138  callback(false, press);
139  return;
140  }
141 
142  for (id<FlutterKeySecondaryResponder> responder in _secondaryResponders) {
143  if ([responder handlePress:press]) {
144  callback(true, press);
145  return;
146  }
147  }
148  callback(false, press);
149 }
150 
151 @end
FlutterKeyPrimaryResponder-p
Definition: FlutterKeyPrimaryResponder.h:19
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:18
FlutterKeyboardManager.h
_weakFactory
std::unique_ptr< fml::WeakPtrFactory< FlutterDartVMServicePublisher > > _weakFactory
Definition: FlutterDartVMServicePublisher.mm:161
FlutterUIPressProxy
Definition: FlutterUIPressProxy.h:17
kDistantFuture
static constexpr CFTimeInterval kDistantFuture
Definition: FlutterKeyboardManager.mm:9
FlutterKeyboardManager
Definition: FlutterKeyboardManager.h:54