I once sat next to an iOS designer who scrolled a Flutter list for ten seconds and said, "this is not iOS." She could not point at a pixel. She just knew. That conversation is the one every Flutter team has eventually, and the one every engineering lead deflects with a benchmark.
You cannot win that argument with numbers. You can only close the gap with deliberate work. This post is the catalogue of where Flutter on iOS still feels off in early 2025, what is causing each issue, and the smallest patch that gets you closer to UIKit. I am not going to tell you Flutter is bad. I am going to tell you where it is honest about being a different rendering pipeline and where you have to do extra work to hide that.
Context: why this gap exists at all
Flutter renders into a single FlutterView (a UIView host) and paints widgets itself. UIKit components — UIScrollView, UITextField, UIMenuController — are not in the tree. Anything that feels like UIKit inside a Flutter app has been reimplemented in Dart, sometimes faithfully, sometimes approximately. Apple does not publish the exact constants for UIScrollView deceleration or the magnifier's animation curve. Flutter's Cupertino library is a best-effort reproduction.
Knowing this changes the question. The question is not "why is Flutter not native?" The question is "which reproductions are close enough, and which are not?"
Scroll physics
This is the most-noticed gap. BouncingScrollPhysics simulates UIScrollView's behaviour, but the deceleration constant and bounce return are not identical to what UIKit applies on a 120 Hz iPhone.
To get closer, scope the platform check explicitly and prefer AlwaysScrollableScrollPhysics composed with the platform default. Flutter's ScrollBehavior already does this through MaterialScrollBehavior and CupertinoScrollBehavior, but a custom override gives you control over edge behaviour:
The UIKit reference:
What you cannot fix from Dart is the curve itself when scrolling at exactly the velocity that triggers a different animation segment. If users complain, your only real lever is hiding the difference behind a content design that does not stress that range.
Text selection and the magnifier
UIKit's text selection on iOS 17+ uses a circular magnifier that follows your finger and a bar with cut, copy, paste, and contextual options. Flutter ships CupertinoTextField with selectionControls: cupertinoTextSelectionControls, which renders a Cupertino-styled toolbar but does not exactly reproduce the iOS magnifier behaviour during caret drag in all configurations.
UIKit equivalent for reference:
There are real edge cases. Selecting across line wraps with one continuous drag does not always feel as smooth as UIKit. Long-press to position the caret with the magnifier is closer than it used to be but is not identical. If your app has heavy text input — a notes app, an editor — a UITextView wrapped as a UiKitView is a defensible compromise even though it costs you a platform view.
Keyboard behaviour
Flutter's MediaQuery.of(context).viewInsets.bottom reports the keyboard height. This works. What is harder is matching iOS's default keyboard-dismiss-on-scroll behaviour and the inline prediction bar's animation timing.
That gets you the right behaviour but the dismiss animation does not have the exact iOS damping. If you wrap an entire screen in a Scaffold with resizeToAvoidBottomInset: true, the layout reflow is a half-frame slower than UIKit equivalent on older devices. The fix is to animate your own bottom padding using the inset value rather than relying on layout to recompute:
Font rendering
San Francisco is not bundled with Flutter. On iOS, Flutter falls back to the system font when you specify CupertinoTheme defaults, which in practice means SF for Latin text. The kerning, line height, and dynamic type behaviour are close but not identical because Flutter does its own text layout via the engine, not UIKit's Text Kit. The difference shows up most in long-form reading interfaces where the line gap is a few pixels off.
The letterSpacing: -0.41 value matches the body text tracking Apple specifies for the SF Pro Text size 17 in their Typography guidance. You will need different values for other text styles; do not copy this number to headlines.
Navigation and pop gestures
CupertinoPageRoute provides interactive pop with an edge swipe. UIKit allows pop swipe from any horizontal position when configured. Flutter's gesture only initiates from the screen edge, which is the iOS default but feels constrained when your app's UIKit equivalent unlocked full-width swipe.
Caption: gesture origin requirements for the back-swipe. Flutter restricts to the edge by default; UIKit is configurable.
What I would do differently
- I would not have promised the iOS team that the Cupertino library was a drop-in replacement. It is a starting point.
- I would have built a "feel parity" checklist on day one with one engineer and one designer reviewing on a real iPhone every Friday, not a quarterly audit.
- I would have isolated text-heavy screens behind native
UiKitViews earlier, instead of fighting the magnifier for three months. - I would have measured perceived latency, not just frame time. A 16ms frame that lands one frame late at the wrong moment is worse than a steady 24ms.
- I would have stopped trying to fix the deceleration curve. After two weeks I had to admit it was a constant I could not access cleanly.
Closing opinion
Flutter on iOS is shippable. It is not invisible. If your product can absorb the perception tax, ship it. If your product is judged on iOS feel — financial, premium consumer, anything competing with first-party Apple apps — write Swift for those screens. The honest verdict for the broader stack comparison sits in I have shipped apps in Swift, Kotlin, and Flutter, and the gesture-system half of this story is in Flutter gesture system vs UIGestureRecognizer.
Written by the author of Flutterstacks
A developer who shipped production apps in Swift, Kotlin, and Dart — with a genuine native reference point that most Flutter writers simply don't have.
More articles →