Skip to content

Let iOS keyboard dismissal synchronize with scroll #57609

Open

Description

@xster

Since #21814, keyboards will dismiss during scroll. For higher fidelity, rather than one single brisk dismissal event, the keyboard dismissal should ideally be done gradually along with the scroll gesture.

Android counterpart: #62876

Activity

  1. added
    a: text inputEntering text in a text field or keyboard related problems
    platform-iosiOS applications specifically
    frameworkflutter/packages/flutter repository. See also f: labels.
    engineflutter/engine related. See also e: labels.
    a: fidelityMatching the OEM platforms better
    f: scrollingViewports, list views, slivers, etc.
    on May 19, 2020
  2. xster commented on Aug 27, 2020

    @xster
    MemberAuthor

    The upstream visual symptom to fix is #19279

  3. added
    P3Issues that are less important to the Flutter project
    on Dec 10, 2020
  4. mleonhard commented on Sep 5, 2021

    @mleonhard
  5. furkansarihan commented on Sep 6, 2021

    @furkansarihan
  6. mleonhard commented on Sep 20, 2021

    @mleonhard
    Contributor

    Dear Google Flutter Team,
    I am preparing an app for release on Android & iOS. It has a small chat function. I am struggling to get the UX to match expectations for iOS users.

    A common use-case for chat functions is to scroll up and consult a previous message while composing a new message. Users expect this on all platforms. Flutter currently makes this very hard to implement with acceptable UX on iOS:

    • A Flutter app using the onDrag mode will hide the keyboard as soon as the user begins scrolling. This is jarring to iOS users who expect the keyboard to stay put.
      • If the app displays messages with a ListView with reverse: true, when the keyboard hides, the list jumps down. This is jarring, too. The user scrolls the desired information into view and taps the text box. The keyboard re-appears and the list jumps upward again, hiding the information they wanted to see. The user must learn to scroll the information to the bottom edge of the screen before tapping the text box. If they don't use the app frequently, they will forgot to do this and feel frustrated every time they use the Flutter app.
      • If the app displays messages with a non-reversed ListView, the app must scroll the list up to the proper position when the keyboard appears. Figuring out how to do this is incredibly involved and may be impossible. Also, when the user begins scrolling and the keyboard disappears, they are left with the list scrolled half-way up the screen. They must move their finger down half the screen to continue scrolling. Additionally, when the user is scrolling and a new message arrives, the app tries to scroll to the bottom. This cancels their current scroll gesture. This is jarring. Therefore the non-reversed ListView requires large amounts of code to work around Flutter's poor behavior when scrolling and showing/hiding the keyboard.
    • A Flutter app could configure the keyboard with a done button by passing TextInputAction. This means the app cannot use the newline or send buttons which are much more useful.
    • The third-party module flutter_interactive_keyboard provides the function requested in this issue. It is at v0.2.0. It has no tests. Every team desiring to use this in their production apps will need to review the code thoroughly.
    • The third-party module keyboard_actions provides buttons that appear above the keyboard to dismiss it. Some iOS apps use this mode. The row takes up vertical space on the screen. This makes the app harder to use on small phones and for folks who can't see very well and have their device zoomed or set to display large fonts. The module has no tests. Every team desiring to use this in their production apps will need to review the code thoroughly.

    Would you please implement this now?

    Best Wishes,
    Michael

    CC: Flutter PM Tim Sneath @timsneath

  7. CavalcanteLeo commented on Nov 1, 2021

    @CavalcanteLeo
  8. Tom3652 commented on Dec 3, 2021

    @Tom3652

    i am in the same situation as you @mleonhard, and as an iOS user myself i can tell that this is simply required as UX experience... Hopefully the upvotes should give some weight to the request as it is impacted many people :/

  9. 59 remaining items

  10. matthew-carroll commented on Nov 21, 2024

    @matthew-carroll
    Contributor

    Thanks @ky1vstar - I noticed a property was added to ListView in Flutter (I think it was ListView) for this purpose. Of course, any scrollable might want that same behavior - not just a ListView and not just Flutter framework scrollables. Is there a composable widget where we can hand over a ScrollController or something like that and have that scrollable automatically interface with the interactive dismissal you referenced?

  11. bbishdotdev commented on Nov 24, 2024

    @bbishdotdev
  12. gnprice commented on Nov 24, 2024

    @gnprice
    Member

    I noticed a property was added to ListView in Flutter (I think it was ListView) for this purpose.

    You may be thinking of the keyboardDismissBehavior property. It's on the ScrollView base class, so indeed you can use it on a CustomScrollView or any other subclass.

    It doesn't solve this issue, though, only mitigate it. The ScrollViewKeyboardDismissBehavior enum has two values:

    • manual, the default, means don't do anything;
    • onDrag means that when the user starts dragging — even if they move only a small distance, and not near the keyboard — the keyboard gets dismissed. It doesn't sync with the user's finger; the drag kicks off an animation that then runs to completion, where the keyboard moves off the screen.

    Once a solution to this issue is built, a likely API for it would be to add another value to that enum. The hard part is building the solution, though.

  13. gnprice commented on Nov 24, 2024

    @gnprice
    Member

    What you can do is to make iOS think that there is a UIKit UIScrollView which has interactive dismissal enabled and let system do its stuff. Probably, you can look trough my plugin

    Interesting, thanks @ky1vstar!

    From a quick look at the code, it looks like the basic design is:

    That seems like a pretty plausible approach, though definitely a hack.


    Two questions: first, what's the function of the KeyboardManager class? How well would the plugin function without it? I ask because these lines look like potentially the sort of thing that would become not an option if Apple happened to notice it, and therefore isn't really an option for including in a widely-used framework like Flutter.

    Second question: I notice the UIScrollView subclass sets a fixed content size:

        contentSize = CGSize(width: 10, height: 10000)

    Are those numbers significant — what happens if the actual content is taller and the user scrolls farther than that? Would the UIScrollView logic decide at some point that the user has reached the far end of the content, and change its behavior?

  14. ky1vstar commented on Nov 24, 2024

    @ky1vstar

    Hi, @gnprice

    This customized ScrollPhysics tends to fix the situation when interactive dismissal is used together with reversed ScrollView (like in chat-like UI). Otherwise, ScrollView will be scrolled with doubled velocity, when keyboard is being interactively dismissed.

    what's the function of the KeyboardManager class? How well would the plugin function without it? I ask because these lines look like potentially the sort of thing that would become not an option if Apple happened to notice it, and therefore isn't really an option for including in a widely-used framework like Flutter.

    Short answer: the plugin would not function without KeyboardManager class.
    Long answer: it is possible to make this plugin work without KeyboardManager class, but it would require significant effort.

    1. Actual keyboard position is not reported by public system notifications (such as UIKeyboardDidChangeFrame) when it is being interactively dismissed.
    2. In native iOS, when keyboard appears, you don't resize the whole UIVIewController or it's root UIView, but you just add bottom contentInset to UIScrollView (basically, just padding from the bottom).
    3. On other hand, on Flutter visible keyboard height is passed to framework as bottom viewInsets which, in most of the cases, will resize the whole Scaffold's body, including any ScollView in it.
    4. This difference means that on native iOS we don't need to know the actual position of keyboard when it is being interactively dismissed, because UIScrollView will just scroll/overscroll as usual.
    5. On Flutter, it leads to ScrollView being clipped from the bottom to old keyboard size, before it started to be dismissed, and user will see the gap between the keyboard and ScrollView's content.

    So, actually, you can disable resizeToAvoidBottomInset in Scaffold, then add bottom viewInsets as the padding inside ScrollView and you can get rid of KeyboardManager. But it will not work with some Widget attached to the top of the keyboard (such as message input in chat-like UI), since KeyboardMessenger is used to find out the actual keyboard position, which is not reported by the system by default.

    Not sure what to say about private API usage and Apple restrictions, but I believe I've seen some Apple's private API usage in Flutter already.

    Hope, I've made this clearer 🙃

    Are those numbers significant — what happens if the actual content is taller and the user scrolls farther than that? Would the UIScrollView logic decide at some point that the user has reached the far end of the content, and change its behavior?

    It doesn't matter, because the only requirement for interactive dismissal to happen is contentSize.height to be bigger than view port's height. If user reach the bottom of this scrollview, interactive dismissal is still gonna work.

  15. itsjasur commented on Jan 17, 2025

    @itsjasur
  16. bbishdotdev commented on Jan 17, 2025

    @bbishdotdev

    I gave up and just simplified a clean, you touch anywhere outside the keyboard it closes. This also isn't the default, had to add a gesture detect area and handle some stuff in the chat specifically.

    Any other solution I've tried at this point just creates a janky buggy experience which is way worse.

    I pivoted when I realized ChatGPT and Grok close the keyboard once you send the message. This combined with tap anywhere outside to close is the best UX at the moment IMO...

  17. abdalmonem commented on Jan 19, 2025

    @abdalmonem
    Contributor

    @btbishop93 @CavalcanteLeo
    This issue is really complicated , and its need a lot of work, till we can find the best optimal solution you can use this package:
    https://pub.dev/packages/cupertino_interactive_keyboard
    Its work for the most scenario cases, also there is a public Google document explain the issue and the suggested solutions and they cons and pros , (i can't find the link) but hope someone share the link here, it may helps to give you an overview of what we are dealing with it.

  18. markymark2001 commented on Feb 23, 2026

    @markymark2001
  19. CavalcanteLeo commented on Feb 23, 2026

    @CavalcanteLeo
  20. markymark2001 commented on Feb 23, 2026

    @markymark2001
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projecta: fidelityMatching the OEM platforms bettera: text inputEntering text in a text field or keyboard related problemsc: new featureNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to Fluttercustomer: crowdAffects or could affect many people, though not necessarily a specific customer.engineflutter/engine related. See also e: labels.f: scrollingViewports, list views, slivers, etc.frameworkflutter/packages/flutter repository. See also f: labels.platform-iosiOS applications specificallyteam-iosOwned by iOS platform teamtriaged-iosTriaged by iOS platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions