SwiftUIKit
A growing collection of Flutter widgets, routes, and interaction primitives inspired by iOS 26–27.
Motion, depth, interactive gestures, adaptive corners, and presentation patterns that feel at home on Apple platforms while remaining Flutter-native.
SwiftUIKit is an independent project inspired by Apple interface patterns. It is not affiliated with or endorsed by Apple Inc.
Why SwiftUIKit
Flutter has excellent primitives, but reproducing the interaction details of modern iOS interfaces often requires coordinating routes, gestures, clipping, scroll positions, spring motion, and physical screen corners. SwiftUIKit packages those details into composable building blocks.
| Motion-first | Router-ready | Composable | Adaptive |
|---|---|---|---|
| Spring-like transitions and interruption-safe retargeting | Direct Navigator, Navigator 2.0 / go_router, and auto_route APIs |
Use complete routes or lower-level widgets independently | Physical screen corner radius and safe-area aware presentation |
Component catalog
Text and page motion
| Component | What it does | Status |
|---|---|---|
SwiftText |
Animates changed glyphs with vertical motion, blur, opacity, and automatic numeric direction | Stable |
SwiftPageViewAnimation |
Adds overlap, parallax, scale, and rounded overscroll to a horizontal PageView |
Experimental |
Navigation and presentations
| Component | What it does | Status |
|---|---|---|
SwiftPageRoute |
Full-screen page transition with interactive swipe-back, scale, and parallax | Stable |
SwiftZoomHero + SwiftZoomRoute |
Element-to-element zoom with a draggable whole-page dismissal and dynamic destinations | Stable |
SwiftSheetRoute |
Stack-aware iOS sheet with drag-to-dismiss and scroll handoff | Stable |
SwiftScrollSheetRoute |
Draggable sheet with fractional or fixed-height detents and programmatic control | Experimental |
SwiftModalRoute |
Content-sized modal with dimming, rounded corners, and drag-to-dismiss | Experimental |
Composition and scroll utilities
| Component | What it does | Status |
|---|---|---|
SwiftStepSheet |
Multi-step content container with animated height changes | Experimental |
SwiftModalScaffold |
Morphs a floating scroll sheet into a full-screen surface as it expands | Experimental |
SwiftScrollSheetDragTarget |
Turns a custom header or handle into a drag surface for a scroll sheet | Experimental |
SwiftSheetScrollProvider |
Exposes a sheet's effective ScrollController to descendants |
Stable |
SwiftSheetScrollBinding |
Binds the primary scroll controller, with a safe fallback | Stable |
ScrollOverlapListener |
Rebuilds from the current leading overscroll amount | Stable |
ScrollValueListener |
Rebuilds from the current scroll offset | Stable |
SnappingScrollPhysics |
Snaps a scroll position to configurable points using spring physics | Stable |
ScreenRadiusService |
Reads and caches the device's physical corner radius | Stable |
Installation
Install the current pub.dev release:
flutter pub add swiftuikit
Or add it manually:
dependencies:
swiftuikit: ^0.2.1
To follow the latest GitHub revision:
dependencies:
swiftuikit:
git:
url: https://github.com/exeshka/swiftuikit
Import the public library:
import 'package:swiftuikit/swiftuikit.dart';
Setup
Initialize ScreenRadiusService before runApp when using routes or widgets
that follow the physical corners of the device:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await ScreenRadiusService.instance.initialize();
runApp(const App());
}
Without initialization, corner-aware components safely fall back to
BorderRadius.zero.
Widget guide
SwiftText
SwiftText is a glyph-level counterpart to SwiftUI's numeric text content
transition. Unchanged glyphs stay sharp while changed glyphs move vertically,
fade, and blur. Numeric direction is detected automatically, rapid updates
retarget from the current visual state, and style-only changes do not restart
the animation.
99% native feel. The timing, roll direction, blur, opacity, and interruption behavior are tuned so the text transition feels almost indistinguishable from its native iOS counterpart.
SwiftText(
'Balance: $balance',
duration: const Duration(milliseconds: 450),
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.w700,
),
)
Use countsDown to override the detected roll direction, SwiftText.rich for
styled TextSpan content, and onEnd to observe completion. WidgetSpan is
not supported because inline render objects cannot move as individual glyphs.
SwiftText.rich(
TextSpan(
children: [
const TextSpan(text: 'Score '),
TextSpan(
text: '$score',
style: const TextStyle(fontWeight: FontWeight.w800),
),
],
),
)
SwiftPage
SwiftPageRoute provides a full-screen push transition with an interactive
swipe-back gesture. The incoming page overlaps the previous page while the
background scales and moves with the gesture.
Navigator.of(context).push(
SwiftPageRoute<void>(
settings: const RouteSettings(name: '/details'),
child: const DetailsScreen(),
canSwipe: true,
canOnlySwipeFromEdge: false,
minScale: 0.95,
),
);
Key controls include pageOverlapFraction, backGestureWidth,
clipWithScreenRadius, radius, borderRadius, and a custom transition
duration. Use SwiftPage with Navigator 2.0 / go_router, or
SwiftPageAutoRoute with auto_route.
SwiftZoom
SwiftZoomHero connects a source element to a complete destination page.
SwiftZoomRoute manages background dimming, fallback scale, and interactive
whole-page dismissal. The destination ID may change while the route is open,
so a PageView can dismiss into the source for its currently selected item.
Source:
SwiftZoomHero(
id: product.id,
borderRadius: BorderRadius.circular(24),
child: ProductCard(product: product),
)
Destination:
SwiftZoomHero(
id: products[currentIndex].id,
borderRadius: ScreenRadiusService.instance.radius,
child: ProductGallery(
products: products,
initialIndex: currentIndex,
),
)
Route:
Navigator.of(context).push(
SwiftZoomRoute<void>(
dismissDirection: SwiftZoomDismissDirection.horizontal,
builder: (_) => const ProductGalleryScreen(),
),
);
Choose SwiftZoomDismissDirection.any, .horizontal, or .downward to
coordinate dismissal with the destination's own scroll gestures. If the source
is no longer mounted, the route falls back to a complete-page scale and fade.
SwiftSheet
SwiftSheetRoute presents an iOS-style sheet that stacks correctly above
pages and other sheets. It coordinates drag-to-dismiss with the sheet's scroll
position and can animate the previous route into the background.
For direct Navigator usage, showSwiftSheet is the shortest entry point:
showSwiftSheet<void>(
context: context,
showDragHandle: true,
scrollableBuilder: (context, controller) {
return ListView(
controller: controller,
children: const [
ListTile(title: Text('Account')),
ListTile(title: Text('Notifications')),
],
);
},
);
The sheet supports custom radii, nested navigation, background animation,
top-safe-area preservation, drag thresholds, and fling thresholds. Use
SwiftSheetRoute.popSheet(context) to close the complete sheet from nested
content.
SwiftScrollSheet
SwiftScrollSheetRoute combines draggable sheet behavior with snap detents.
Detents may be fractions of the available height, fixed logical-pixel heights,
or the built-in medium and large values.
final sheetController = SwiftScrollSheetController(initialValue: 0.5);
Navigator.of(context).push(
SwiftScrollSheetRoute<void>(
settings: const RouteSettings(name: '/library'),
child: const LibrarySheet(),
detents: const [
SwiftSheetDetent.medium,
SwiftSheetDetent.large,
],
initialDetent: SwiftSheetDetent.medium,
sheetController: sheetController,
),
);
SwiftScrollSheetController exposes animateTo, jumpTo, expand,
collapse, snapToNearest, the current extent, and the resolved snap stops.
Inside the route, use SwiftScrollSheetRoute.controllerOf(context) or
SwiftScrollSheetRoute.extentOf(context) when direct access is more convenient.
SwiftScrollSheetDragTarget
Wrap a custom header or drag handle to forward vertical gestures to the
nearest SwiftScrollSheetRoute:
SwiftScrollSheetDragTarget(
child: const SheetHeader(title: 'Library'),
)
SwiftModal
SwiftModalRoute sizes itself to its child up to the available screen height.
It slides from the bottom, dims the background, supports a configurable
barrier, and hands drag gestures off from scrollable content before dismissing.
Navigator.of(context).push(
SwiftModalRoute<void>(
settings: const RouteSettings(name: '/filters'),
child: const FiltersPanel(),
barrierDismissible: true,
barrierOpacity: 0.3,
dismissThreshold: 0.3,
),
);
Use SwiftModalPage for Navigator 2.0 / go_router, or
SwiftModalAutoRoute for auto_route.
SwiftPageViewAnimation
SwiftPageViewAnimation adds iOS-inspired depth to horizontal paging: the
previous page can remain partially covered, pages scale with progress, and
edge overscroll reveals adaptive rounded corners.
SwiftPageViewAnimation.pageView(
controller: pageController,
itemCount: pages.length,
itemBuilder: (context, index) => pages[index],
minScale: 0.95,
pageOverlapFraction: 0.20,
onPageChanged: onPageChanged,
)
Use parallaxIndexes to restrict the effect to selected pages,
coverPreviousPage to disable overlap, and radius or borderRadius to
override the physical screen radius.
SwiftStepSheet
SwiftStepSheet switches between a list of steps and animates height changes
with AnimatedSize. Its state exposes navigation commands and the enclosing
modal route's open progress.
final stepKey = GlobalKey<SwiftStepSheetState>();
SwiftStepSheet(
key: stepKey,
steps: const [
ContactStep(),
AddressStep(),
ConfirmationStep(),
],
)
Call nextStep, previousStep, or goToStep through the state key or
SwiftStepSheet.of(context). Descendants can read the route transition with
SwiftStepSheet.openProgressOf(context).
SwiftModalScaffold
SwiftModalScaffold is designed for content inside a
SwiftScrollSheetRoute. As the sheet expands, it removes its floating margin
and shadow, flattens the lower corners, and aligns the upper corners with the
physical screen.
SwiftModalScaffold(
header: const SheetHeader(title: 'Collection'),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemTile(items[index]),
),
)
The optional header is automatically wrapped in
SwiftScrollSheetDragTarget.
Scroll coordination
These smaller widgets make scroll-driven effects reusable without moving controller logic into page state:
| Widget | Use it for |
|---|---|
SwiftSheetScrollProvider |
Providing a known ScrollController to a sheet subtree |
SwiftSheetScrollBinding |
Exposing the nearest PrimaryScrollController, with an owned fallback when none exists |
ScrollOverlapListener |
Building from leading overscroll, clamped by maxOverlap |
ScrollValueListener |
Building from the current offset of a single attached scroll position |
ScrollValueListener(
controller: controller,
builder: (context, offset) {
return Header(elevation: (offset / 24).clamp(0.0, 1.0));
},
)
SnappingScrollPhysics
SnappingScrollPhysics snaps a scroll position to a list of logical-pixel
points. Low-velocity movement uses snapThreshold; a fling chooses the next
point in its direction.
ListView(
controller: controller,
physics: const SnappingScrollPhysics(
snapPoints: [0, 240, 480],
springConfig: SnapSpringConfig.snappy,
),
children: children,
)
Choose SnapSpringConfig.smooth, .snappy, or .bouncy, or provide custom
mass, stiffness, and damping.
ScreenRadiusService
ScreenRadiusService is a ChangeNotifier singleton that reads and caches the
physical corner radius reported by the platform. Components use its
BorderRadius to keep page and sheet clipping concentric with the screen.
final borderRadius = ScreenRadiusService.instance.radius;
final radius = ScreenRadiusService.instance.radiusValue;
Call refresh() if the host window changes and the radius must be queried
again.
Routing adapters
The same transition core is exposed for three navigation styles:
| Experience | Direct Navigator | Navigator 2.0 / go_router |
auto_route |
|---|---|---|---|
| Page | SwiftPageRoute |
SwiftPage |
SwiftPageAutoRoute |
| Zoom | SwiftZoomRoute |
SwiftZoomPage |
SwiftZoomAutoRoute |
| Sheet | SwiftSheetRoute / showSwiftSheet |
SwiftSheetPage |
SwiftSheetAutoRoute |
| Scroll sheet | SwiftScrollSheetRoute |
SwiftScrollSheetPage |
SwiftScrollSheetAutoRoute |
| Content modal | SwiftModalRoute |
SwiftModalPage |
SwiftModalAutoRoute |
Navigator 2.0 / go_router
Use the Page adapters from a pageBuilder:
GoRoute(
path: '/details',
pageBuilder: (context, state) {
return SwiftPage<void>(
key: state.pageKey,
name: state.name,
child: const DetailsScreen(),
);
},
)
auto_route
Use the matching route definitions in RootStackRouter:
@AutoRouterConfig(replaceInRouteName: 'Screen|Page,Route')
class AppRouter extends RootStackRouter {
@override
List<AutoRoute> get routes => [
SwiftPageAutoRoute(page: HomeRoute.page, initial: true),
SwiftPageAutoRoute(page: DetailsRoute.page),
SwiftZoomAutoRoute(page: GalleryRoute.page),
SwiftSheetAutoRoute(
page: SettingsRoute.page,
showDragHandle: true,
),
];
}
Regenerate routes from the application package after changing route definitions:
dart run build_runner build --delete-conflicting-outputs
Low-level building blocks
Most applications should use the routes and adapters above. The package also exports these lower-level pieces for custom integrations:
| API | Purpose |
|---|---|
SwiftSheetTransition |
Builds the primary and delegated animations used by stacked sheets |
SwiftSheetScope |
Propagates the resolved sheet radius to nested page routes |
SwiftPageTransitions |
Central configuration and route builder for page and sheet motion |
SwiftTextSmoothCurve |
Critically damped curve used by the default SwiftText transition |
Example app
The example application contains the auto_route catalog,
interactive page and zoom flows, stacked sheets, and the responsive
SwiftText music player.
cd example
flutter run
Stability
APIs annotated with @experimental are usable but may change before the next
stable release. At the moment this includes scroll sheets, content-sized
modals, SwiftPageViewAnimation, SwiftStepSheet, and
SwiftModalScaffold.
Bug reports, focused examples, and pull requests are welcome.
Support SwiftUIKit
If SwiftUIKit saves you time or helps your Flutter app feel closer to native iOS, consider supporting its continued development.
License
SwiftUIKit is available under the MIT License.