Skip to content

Swift UI for Fluter Developers

Matt Carroll edited this page Oct 18, 2023 · 3 revisions

Swift UI is a declarative UI toolkit. Therefore, Swift UI shares many characteristics with Flutter. However, Swift UI also includes a number of details that will be foreign to a Flutter developer. Such differences include syntax, paradigm, and available resources.

This guide provides a rapid introduction to Swift UI details that may not be immediately evident to a Flutter developer.

View structs instead of Widgets

Swift UI doesn't use Widget subclasses to declare pieces of a UI. Instead, Swift UI uses View subclasses to declare pieces of a UI. And technically they aren't "subclasses" - they're structs that conform to the View protocol. But you can think of Swift UI Views as the equivalent of Flutter's Widgets.

struct MyView: View {
  // TODO: implement the View, AKA widget.
}

body property instead of build() method

Instead of defining a build() method within a Widget, Swift UI developers define a body property within a View.

struct MyView: View {
  var body: some View {
    // TODO: build the body of this View, AKA the build() of the widget.
  }
}

Closures {} instead of child properties

In Flutter, Widgets are typically composed within other Widgets by using a property called child or children.

ContextMenu(
  children: [
    Text("Cut"),
    Text("Copy"),
    Text("Paste"),
    if (isSymbol)
      Text("Jump to Definition"),
  ],
);

In Swift UI, a special language feature is used to make it seem like no property is used at all to compose Views within Views. Swift UI uses trailing closures, which are expected to produce one or more Views. These closures are called ViewBuilders.

ContextMenu() {
    Text("Cut")
    Text("Copy")
    Text("Paste")
    if isSymbol {
        Text("Jump to Definition")
    }
}

In the above example, we're providing children to the ContextMenu. The closure {}, which follows ContextMenu(), is actually an argument that's passed into ContextMenu().

The ability to pass a closure as an argument, while declaring the closure AFTER the invocation, is a Swift language feature. It's called Escaping Closures

Composition with "modifier methods" instead of widgets

Sometimes Swift UI code composes Views within Views in a manner that's very similar to Flutter.

ContextMenu() {
    Text("Cut")
    Text("Copy")
    Text("Paste")
    if isSymbol {
        Text("Jump to Definition")
    }
}

However, most of the time, Swift UI code uses a different approach to compose Views, as well as configuration settings. This other approach is called "modifier methods". Modifier methods are methods called on a View.

content
  .font(.caption2)
  .padding(10)
  .overlay(
    RoundedRectangle(cornerRadius: 15)
      .stroke(lineWidth: 1)
  )
  .foregroundColor(Color.blue)

For the most part, modifier methods are little more than a stylistic paradigm choice. They accomplish the same or similar results as View composition, but with a different order of operations.

Let's demonstrate the interchangeable nature of View composition and modifier methods borrowing an example

Consider a situation where we'd like to implement the concept of a "featured label".

Here's a featured label implemented with View composition:

struct FeaturedLabel: View {
    var text: String

    var body: some View {
        HStack {
            Image(systemName: "star")
            Text(text)
        }
        .foregroundColor(.orange)
        .font(.headline)
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            // View-based version:
            FeaturedLabel(text: "Hello, world!")
        }
    }
}

Here's a featured label implemented as a modifier method:

extension View {
    func featured() -> some View {
        HStack {
            Image(systemName: "star")
            self
        }
        .foregroundColor(.orange)
        .font(.headline)
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            // Modifier-based version:
            Text("Hello, world!").featured()
        }
    }
}

As you can see, the same result is achieved in both cases.

There may be some use-cases which are easier to implement with modifier methods, but in general these two approaches differ only in stylistic preference.

System Icons

Many Swift UI examples include string references to system icons, e.g., "tray.and.arrow.down.fill". You might wonder where you can find a list of all such icons.

Swift UI calls these icons "symbols" and there is no simple web directory of available symbols. Instead, you must download the SF Symbols app for Mac, which browses all available symbols.

Clone this wiki locally