@@ -203,6 +203,22 @@ class TextEditingElement {
203
203
/// See [TextEditingElement.persistent] to understand what persistent mode is.
204
204
TextEditingElement (this .owner);
205
205
206
+ /// Timer that times when to set the location of the input text.
207
+ ///
208
+ /// This is only used for IOS. In IOS, virtual keyboard shifts the screen.
209
+ /// There is no callback to know if the keyboard is up and how much the screen
210
+ /// has shifted. Therefore instead of listening to the shift and passing this
211
+ /// information to Flutter Framework, we are trying to stop the shift.
212
+ ///
213
+ /// In IOS, the virtual keyboard shifts the screen up if the focused input
214
+ /// element is under the keyboard or very close to the keyboard. Before the
215
+ /// focus is called we are positining it offscreen. The location of the input
216
+ /// in IOS is set to correct place, 100ms after focus. We use this timer for
217
+ /// timing this delay.
218
+ Timer _positionInputElementTimer;
219
+ static const Duration _delayBeforePositining =
220
+ const Duration (milliseconds: 100 );
221
+
206
222
final HybridTextEditing owner;
207
223
bool _enabled = false ;
208
224
@@ -222,20 +238,22 @@ class TextEditingElement {
222
238
/// On iOS, sets the location of the input element after focusing on it.
223
239
///
224
240
/// On iOS, keyboard causes scrolling in the UI. This scrolling does not
225
- /// trigger an event. In order to position the input element correctly , it is
241
+ /// trigger an event. In order not to trigger a shift on the page , it is
226
242
/// important we set it's final location after focusing on it (after keyboard
227
243
/// is up).
228
244
///
229
- /// This method is called in the end of the 'touchend' event, therefore it is
230
- /// called after the editing state is set .
245
+ /// This method is called after a delay.
246
+ /// See [_positionInputElementTimer] .
231
247
void configureInputElementForIOS () {
232
248
if (browserEngine != BrowserEngine .webkit ||
233
249
operatingSystem != OperatingSystem .iOs) {
234
- // Only relevant on Safari.
250
+ // Only relevant on Safari Mobile and Chrome on IOS .
235
251
return ;
236
252
}
253
+
237
254
if (domElement != null ) {
238
255
owner.setStyle (domElement);
256
+ owner.inputPositioned = true ;
239
257
}
240
258
}
241
259
@@ -274,6 +292,32 @@ class TextEditingElement {
274
292
}));
275
293
}
276
294
295
+ if (browserEngine == BrowserEngine .webkit &&
296
+ operatingSystem == OperatingSystem .iOs) {
297
+ /// Position the element outside of the page before focusing on it.
298
+ ///
299
+ /// See [_positionInputElementTimer] .
300
+ owner.setStyleOutsideOfScreen (domElement);
301
+
302
+ _subscriptions.add (domElement.onFocus.listen ((_) {
303
+ // Cancel previous timer if exists.
304
+ _positionInputElementTimer? .cancel ();
305
+ _positionInputElementTimer = Timer (_delayBeforePositining, () {
306
+ if (textEditing.inputElementNeedsToBePositioned) {
307
+ configureInputElementForIOS ();
308
+ }
309
+ });
310
+
311
+ /// When the virtual keyboard is closed on IOS, onBlur is triggered.
312
+ _subscriptions.add (domElement.onBlur.listen ((_) {
313
+ /// Cancel the timer since there is no need to set the location of the
314
+ /// input element anymore. It needs to be focused again to be editable
315
+ /// by the user.
316
+ _positionInputElementTimer? .cancel ();
317
+ }));
318
+ }));
319
+ }
320
+
277
321
domElement.focus ();
278
322
279
323
if (_lastEditingState != null ) {
@@ -299,6 +343,8 @@ class TextEditingElement {
299
343
_subscriptions[i].cancel ();
300
344
}
301
345
_subscriptions.clear ();
346
+ _positionInputElementTimer? .cancel ();
347
+ owner.inputPositioned = false ;
302
348
_removeDomElement ();
303
349
}
304
350
@@ -361,10 +407,6 @@ class TextEditingElement {
361
407
..addRange (_createRange (editingState));
362
408
break ;
363
409
}
364
-
365
- // Safari on iOS requires that we focus explicitly. Otherwise, the on-screen
366
- // keyboard won't show up.
367
- domElement.focus ();
368
410
}
369
411
370
412
/// Swap out the current DOM element and replace it with a new one of type
@@ -583,8 +625,19 @@ class HybridTextEditing {
583
625
/// Also used to define if a keyboard is needed.
584
626
bool _isEditing = false ;
585
627
586
- /// Flag indicating if the flutter framework requested a keyboard.
587
- bool get needsKeyboard => _isEditing;
628
+ /// Flag indication the input element needs to be positioned.
629
+ ///
630
+ /// See [TextEditingElement._delayBeforePositining] .
631
+ bool get inputElementNeedsToBePositioned =>
632
+ ! inputPositioned &&
633
+ _isEditing &&
634
+ browserEngine == BrowserEngine .webkit &&
635
+ operatingSystem == OperatingSystem .iOs;
636
+
637
+ /// Flag indication wheterher the input element's position is set.
638
+ ///
639
+ /// See [inputElementNeedsToBePositioned] .
640
+ bool inputPositioned = false ;
588
641
589
642
Map <String , dynamic > _configuration;
590
643
@@ -715,10 +768,16 @@ class HybridTextEditing {
715
768
///
716
769
/// They are changed depending on the messages coming from method calls:
717
770
/// "TextInput.setStyle", "TextInput.setEditableSizeAndTransform".
771
+ ///
772
+ /// In IOS, initial positionig of input element is not done in this method.
773
+ /// This method changes the location of the input element if it is already
774
+ /// positioned.
775
+ /// See [TextEditingElement._delayBeforePositining] .
718
776
void _setDynamicStyleAttributes (html.HtmlElement domElement) {
719
777
if (_editingLocationAndSize != null &&
720
- ! (browserEngine == BrowserEngine .webkit &&
721
- operatingSystem == OperatingSystem .iOs)) {
778
+ (inputPositioned ||
779
+ ! (browserEngine == BrowserEngine .webkit &&
780
+ operatingSystem == OperatingSystem .iOs))) {
722
781
setStyle (domElement);
723
782
}
724
783
}
@@ -741,6 +800,19 @@ class HybridTextEditing {
741
800
..transform = transformCss;
742
801
}
743
802
803
+ // TODO(flutter_web): When the browser closes and re-opens the virtual
804
+ // shifts the page in IOS. Call this method from visibility change listener
805
+ // attached to body.
806
+ /// Set the dom elements location somewhere outside of the screen.
807
+ ///
808
+ /// This is useful for not triggering a scroll when IOS virtual keyboard is
809
+ /// coming up.
810
+ ///
811
+ /// See [TextEditingElement._delayBeforePositining] .
812
+ void setStyleOutsideOfScreen (html.HtmlElement domElement) {
813
+ domElement.style.transform = 'translate(-9999px, -9999px)' ;
814
+ }
815
+
744
816
html.InputElement createInputElement () {
745
817
final html.InputElement input = html.InputElement ();
746
818
_setStaticStyleAttributes (input);
0 commit comments