Skip to main content

Flutter for SwiftUI Developers

Learn how to apply SwiftUI developer knowledge when building Flutter apps.

SwiftUI developers who want to write mobile apps using Flutter should review this guide. It explains how to apply existing SwiftUI knowledge to Flutter.

Flutter is a framework for building cross-platform applications that uses the Dart programming language. To understand some differences between programming with Dart and programming with Swift, see Learning Dart as a Swift Developer and Flutter concurrency for Swift developers.

Your SwiftUI knowledge and experience are highly valuable when building with Flutter.

Flutter also makes a number of adaptations to app behavior when running on iOS and macOS. To learn how, see Platform adaptations.

This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs. This guide embeds sample code. By using the "Open in DartPad" button that appears on hover or focus, you can open and run some of the examples on DartPad.

Overview

#

As an introduction, watch the following video. It outlines how Flutter works on iOS and how to use Flutter to build iOS apps.

Watch on YouTube in a new tab: "Flutter for iOS developers"

Flutter and SwiftUI code describes how the UI looks and works. Developers call this type of code a declarative framework.

Views vs. Widgets

#

SwiftUI represents UI components as views. You configure views using modifiers.

swift
Text("Hello, World!") // <-- This is a View
  .padding(10)        // <-- This is a modifier of that View

Flutter represents UI components as widgets.

Both views and widgets only exist until they need to be changed. These languages call this property immutability. SwiftUI represents a UI component property as a View modifier. By contrast, Flutter uses widgets for both UI components and their properties.

dart
Padding(                         // <-- This is a Widget
  padding: EdgeInsets.all(10.0), // <-- So is this
  child: Text("Hello, World!"),  // <-- This, too
)

To compose layouts, both SwiftUI and Flutter nest UI components within one another. SwiftUI nests Views while Flutter nests Widgets.

Layout process

#

SwiftUI lays out views using the following process:

  1. The parent view proposes a size to its child view.
  2. All subsequent child views:
    • propose a size to their child's view
    • ask that child what size it wants
  3. Each parent view renders its child view at the returned size.

Flutter differs somewhat with its process:

  1. The parent widget passes constraints down to its children. Constraints include minimum and maximum values for height and width.

  2. The child tries to decide its size. It repeats the same process with its own list of children:

    • It informs its child of the child's constraints.
    • It asks its child what size it wishes to be.
  3. The parent lays out the child.

    • If the requested size fits in the constraints, the parent uses that size.
    • If the requested size doesn't fit in the constraints, the parent limits the height, width, or both to fit in its constraints.

Flutter differs from SwiftUI because the parent component can override the child's desired size. The widget cannot have any size it wants. It also cannot know or decide its position on screen as its parent makes that decision.

To force a child widget to render at a specific size, the parent must set tight constraints. A constraint becomes tight when its constraint's minimum size value equals its maximum size value.

In SwiftUI, views might expand to the available space or limit their size to that of its content. Flutter widgets behave in similar manner.

However, in Flutter parent widgets can offer unbounded constraints. Unbounded constraints set their maximum values to infinity.

dart
UnboundedBox(
  child: Container(
      width: double.infinity, height: double.infinity, color: red),
)

If the child expands and it has unbounded constraints, Flutter returns an overflow warning:

dart
UnconstrainedBox(
  child: Container(color: red, width: 4000, height: 50),
)
When parents pass unbounded constraints to children, and the children are expanding, then there is an overflow warning.

To learn how constraints work in Flutter, see Understanding constraints.

Design system

#

Because Flutter targets multiple platforms, your app doesn't need to conform to any design system. Though this guide features Material widgets, your Flutter app can use many different design systems:

  • Custom Material widgets
  • Community built widgets
  • Your own custom widgets
  • Cupertino widgets that follow Apple's Human Interface Guidelines

Watch on YouTube in a new tab: "Flutter's cupertino library for iOS developers"

If you're looking for a great reference app that features a custom design system, check out Wonderous.

UI Basics

#

This section covers the basics of UI development in Flutter and how it compares to SwiftUI. This includes how to start developing your app, display static text, create buttons, react to on-press events, display lists, grids, and more.

Getting started

#

In SwiftUI, you use App to start your app.

swift
@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup {
      HomePage()
    }
  }
}

Another common SwiftUI practice places the app body within a struct that conforms to the View protocol as follows:

swift
struct HomePage: View {
  var body: some View {
    Text("Hello, World!")
  }
}

To start your Flutter app, pass in an instance of your app to the runApp function.

dart
void main() {
  runApp(const MyApp());
}

App is a widget. The build method describes the part of the user interface it represents. It's common to begin your app with a WidgetApp class, like CupertinoApp.

dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // Returns a CupertinoApp that, by default,
    // has the look and feel of an iOS app.
    return const CupertinoApp(home: HomePage());
  }
}

The widget used in HomePage might begin with the Scaffold class. Scaffold implements a basic layout structure for an app.

dart
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(body: Center(child: Text('Hello, World!')));
  }
}

Note how Flutter uses the Center widget. SwiftUI renders a view's contents in its center by default. That's not always the case with Flutter. Scaffold doesn't render its body widget at the center of the screen. To center the text, wrap it in a Center widget. To learn about different widgets and their default behaviors, check out the Widget catalog.

Adding Buttons

#

In SwiftUI, you use the Button struct to create a button.

swift
Button("Do something") {
  // this closure gets called when your
  // button is tapped
}

To achieve the same result in Flutter, use the CupertinoButton class:

dart
CupertinoButton(
  onPressed: () {
    // This closure is called when your button is tapped.
  },
  const Text('Do something')