swiftuikit 0.1.5 copy "swiftuikit: ^0.1.5" to clipboard
swiftuikit: ^0.1.5 copied to clipboard

iOS-style page, zoom, sheet, scroll-sheet, and modal transitions for Flutter, with go_router and auto_route adapters.

swiftuikit #

iOS-style page transitions and sheet routes for Flutter.

Provides two routing adapters: go_router / Navigator 2.0 and auto_route.

Quick start #

pub.dev:

dependencies:
  swiftuikit: ^latest

GitHub (latest):

dependencies:
  swiftuikit:
    git:
      url: https://github.com/exeshka/swiftuikit

Setup #

Call ScreenRadiusService.instance.initialize() before runApp() to enable device-aware corner radius clipping:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await ScreenRadiusService.instance.initialize();
  runApp(MyApp());
}

Route types #

Route Description
SwiftPage / SwiftPageAutoRoute Full-screen page with iOS swipe-back gesture and parallax/scale transition
SwiftZoomPage / SwiftZoomAutoRoute Element-to-element zoom whose destination Hero can change while the page is open
SwiftSheetPage / SwiftSheetAutoRoute Modal bottom sheet with drag-to-dismiss

Usage with go_router #

Use the page classes in a pageBuilder:

GoRoute(
  path: '/detail',
  pageBuilder: (context, state) {
    return SwiftPage<void>(
      key: state.pageKey,
      name: state.name,
      child: DetailScreen(),
    );
  },
);

GoRoute(
  path: '/compose',
  pageBuilder: (context, state) {
    return SwiftSheetPage<void>(
      key: state.pageKey,
      child: ComposeScreen(),
    );
  },
);

Usage with auto_route #

Use the auto route classes in your @AutoRouterConfig:

@AutoRouterConfig(replaceInRouteName: 'Screen|Page,Route')
class AppRouter extends RootStackRouter {
  @override
  List<AutoRoute> get routes => [
    SwiftPageAutoRoute(page: HomeRoute.page, initial: true),
    SwiftPageAutoRoute(page: DetailRoute.page),
    SwiftSheetAutoRoute(
      page: SheetRoute.page,
      showDragHandle: true,
      animateBackground: true,
    ),
  ];
}

Run codegen from the example/ directory:

cd example && dart run build_runner build --delete-conflicting-outputs

SwiftPage / SwiftPageAutoRoute #

Full-screen page transition with iOS-style swipe-back gesture.

SwiftPage<void>(
  child: MyScreen(),
  canSwipe: true,              // enable swipe-back gesture (default: true)
  canOnlySwipeFromEdge: false, // restrict swipe to screen edge (default: false)
  minScale: 0.95,              // scale of the outgoing page during push (default: 0.95)
  transitionDuration: Duration(milliseconds: 500),
)
Property Type Default Description
canSwipe bool true Enable swipe-back gesture
canOnlySwipeFromEdge bool false Restrict swipe detection to the screen edge
minScale double 0.95 Scale of the outgoing page during a push transition
pageOverlapFraction double 0.40 How much the incoming page overlaps the outgoing page
clipWithScreenRadius bool true Clip with physical device screen corners
radius double? Custom corner radius
borderRadius BorderRadius? Custom border radius geometry
transitionDuration Duration 500ms Transition animation duration

SwiftZoomHero / SwiftZoomRoute #

SwiftZoomHero is an element-to-element transition independent from the route. Put matching IDs around the source element and the complete destination page. The route only handles the background, whole-page drag, and the fallback animation.

SwiftZoomHero(
  id: product.id,
  borderRadius: BorderRadius.circular(24),
  child: ProductCard(
    product: product,
    onTap: () => Navigator.of(context).push(
      SwiftZoomRoute<void>(
        dismissDirection: SwiftZoomDismissDirection.horizontal,
        builder: (_) => ProductGallery(
          products: products,
          initialIndex: index,
        ),
      ),
    ),
  ),
)

Wrap the whole destination page and update its ID when the selected item changes:

SwiftZoomHero(
  id: products[currentIndex].id,
  borderRadius: ScreenRadiusService.instance.radius,
  child: PageView.builder(
    controller: pageController,
    scrollDirection: Axis.vertical,
    onPageChanged: (index) => setState(() => currentIndex = index),
    itemCount: products.length,
    itemBuilder: (_, index) => ProductScreen(
      product: products[index],
    ),
  ),
)

If the current source is not mounted when the route closes, the complete page uses a scale-and-fade fallback instead. The route accepts drags in every direction by default. Set SwiftZoomDismissDirection.horizontal only when a screen should reserve vertical gesture starts for its scrollable content. Once the horizontal dismiss wins, the page still follows the finger in both axes.

For go_router / Navigator 2.0 use SwiftZoomPage. For auto_route, declare the route without a source ID:

SwiftZoomAutoRoute(
  page: ProductGalleryRoute.page,
  dismissDirection: SwiftZoomDismissDirection.horizontal,
)

SwiftSheetPage / SwiftSheetAutoRoute #

Modal bottom sheet with drag-to-dismiss.

SwiftSheetPage<void>(
  child: ComposeScreen(),
  showDragHandle: true,     // show drag indicator at the top (default: false)
  enableDrag: true,         // allow drag-to-dismiss (default: true)
  dismissThreshold: 0.32,   // dismiss after dragging 32% of the sheet
  minFlingVelocity: 1.0,    // sheet heights per second
  animateBackground: true,  // animate the previous page (default: true)
  preserveTopSafeArea: true, // keep system top inset and open at full height
  sheetRadius: 38.0,        // corner radius
)
Property Type Default Description
showDragHandle bool false Show a drag handle indicator at the top of the sheet
enableDrag bool true Allow drag-to-dismiss gesture
dismissThreshold double 0.32 Fraction of sheet height required to dismiss
minFlingVelocity double 1.0 Downward fling velocity required to dismiss, in sheet heights per second
animateBackground bool true Animate (scale, slide, round corners of) the previous page when the sheet appears
preserveTopSafeArea bool false Keep the system top inset and open the sheet at 100% screen height
sheetRadius double? Corner radius of the sheet
sheetBorderRadius BorderRadius? Custom border radius geometry for the sheet
transitionDuration Duration 500ms Transition animation duration

When preserveTopSafeArea is enabled, the current sheet and the route behind it use the physical screen radius from ScreenRadiusService by default. An explicit sheetBorderRadius or sheetRadius takes priority and is applied to both routes.

Roadmap #

What's planned for future releases:

  • Sheets & modals from SwiftUI 26 — pull-down menus, confirmation sheets, and other presentation styles currently available in native SwiftUI. Note: Liquid Glass implementations in the Flutter community are not production-ready for these use cases. We're waiting for the Flutter team to provide proper support.

  • SwiftUI components without Liquid Glass — Header, Bottom Navigation Bar, and other UI elements that can be reliably implemented today.

We're hoping for community contributions to help close the gap between what Flutter offers natively and what SwiftUI provides out of the box.

License #

See LICENSE for details.

2
likes
0
points
215
downloads

Publisher

unverified uploader

Weekly Downloads

iOS-style page, zoom, sheet, scroll-sheet, and modal transitions for Flutter, with go_router and auto_route adapters.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

auto_route, equatable, flutter, flutter_physics, meta, screen_corner_radius

More

Packages that depend on swiftuikit