Gosh Darn SwiftUI

Gosh Darn SwiftUI is a curated list of questions and answers about SwiftUI.

All the answers you found here don't mean to be complete or detail, the purpose here is to act as a cheat sheet or a place that you can pick up keywords you can use to search for more detail.

FAQ

Frequently asked questions about SwiftUI.

Should I learn SwiftUI?

Yes.

Should I learn it now?

It depends since SwiftUI runs on iOS 13, macOS 10.15, tvOS 13, and watchOS 6. If you work on a new app that plans to target only the mentioned OS, I would say yes. If you plan to find a job or work on a client project in which you have no control over this OS version, you might want to wait a year or two before considering moving to SwiftUI. Because most client work would like to support as much as users as possible, that means you have to work on an app that supports iOS N-1, N-2, or worse N-3. So the best case would be a year until you can get a hand on this lovely SwiftUI.


iOS 14

A new WidgetKit framework in iOS 14 is exclusive to SwiftUI, so you might need to learn it this year if you want to support a new widget.

Should I learn UIKit?

Yes, UIKit would still be an important part of the iOS world for quite some time. At the current stage, SwiftUI is still missing many features, and I think even you start fresh with SwiftUI, you still need to come back to UIKit from time to time.


iOS 14

iOS 14 closing some gap, but the point remains. You need to come back to UIKit when you hit a roadblock.

Does SwiftUI replace UIKit?

Not right now, but I can see it might in the future. Apple just introduces SwiftUI, and it already looks great. I expect both to coexist for a long time, SwiftUI is very young and needs years to grow to be able to replace its ancestor.

If I can learn one thing today, what would it be UIKit or SwiftUI?

Too bad I don't have the answer to this question. Both UIKit and SwiftUI are different beasts with different ways of thinking. I suggest you try both of them and judge for yourself which paradigm you like the most. No matter what technologies you choose, one thing I can assure you is you can create a great app out of either one of them.

Where is a view controller in SwiftUI?

They are gone. Now views talk with others via the new reactive framework, Combine. This new approach work as a replacement for UIViewController, which is just a way of communication.

Minimum Requirements

  • Xcode 11 for SwiftUI and Xcode 12 beta for iOS 14 features (Download beta software from Apple)
  • iOS 13 / macOS 10.15 / tvOS 13 / watchOS 6
  • macOS Catalina in order to have SwiftUI render in the canvas.

UIKit equivalent in SwiftUI

View Controllers

UIKit SwiftUI Note
UIViewController View
UITableViewController List You can also use ScrollView with LazyHStack or LazyVStack
UICollectionViewController LazyVGrid and LazyHGrid Currently there is no SwiftUI view replacement for this, but you can simulate some layout with composing of List as in Composing Complex Interfaces's tutorial, In iOS 14, we now have LazyVGrid and LazyHGrid.
UISplitViewController NavigationView
UINavigationController NavigationView
UIPageViewController TabView A style of TabView in iOS 14
UITabBarController TabView
UISearchController -
UIImagePickerController -
UIVideoEditorController -
UIActivityViewController -
UIAlertController Alert

Views and Controls

UIKit SwiftUI Note
UILabel Text, Label
UITabBar TabView
UITabBarItem TabView .tabItem under TabView
UITextField TextField For password (isSecureTextEntry) use SecureField
UITextView TextEditor iOS 14
UITableView List also VStack and Form
UINavigationBar NavigationView Part of NavigationView
UINavigationItem ToolbarItem iOS 14
UIBarButtonItem NavigationView .navigationBarItems in NavigationView
UICollectionView LazyVGrid and LazyHGrid iOS 14
UIStackView HStack, LazyHStack .axis == .Horizontal
UIStackView VStack, LazyVStack .axis == .Vertical
UIScrollView ScrollView
UIActivityIndicatorView ProgressView with CircularProgressViewStyle iOS 14
UIImageView Image
UIPickerView Picker
UIButton Button, Link
UIDatePicker DatePicker
UIPageControl iOS 14. Auto add to TabView with PageTabViewStyle style. You can control its appearance by .indexViewStyle.
UIProgressView ProgressView iOS 14
UISegmentedControl Picker A style (SegmentedPickerStyle) of Picker
UISlider Slider
UIStepper Stepper
UISwitch Toggle
UIToolBar NavigationView with .toolbar iOS 14
MKMapView Map import MKMapView to use this view

Framework Integration - UIKit in SwiftUI

Integrate SwiftUI views into existing apps, and embed UIKit views and controllers into SwiftUI view hierarchies.

UIKit SwiftUI Note
UIView UIViewRepresentable
UIViewController UIViewControllerRepresentable

Framework Integration - SwiftUI in UIKit

Integrate SwiftUI views into existing apps, and embed UIKit views and controllers into SwiftUI view hierarchies.

UIKit SwiftUI Note
UIView (UIHostingController) View There is no direct convert to UIView, but you can use container view to add view from UIViewController into view hierarchy
UIViewController (UIHostingController) View

Pure SwiftUI

iOS 14

In iOS 14, you can write the whole app without a need for UIKit. Checkout App essentials in SwiftUI session from WWDC2020.

UIKit SwiftUI Note
UIApplicationDelegate App
UIWindowSceneDelegate Scene

SwiftUI - Views and Controls

Text

A view that displays one or more lines of read-only text.

Text("Hello World")

Styling

Text("Hello World")
.bold()
.italic()
.underline()
.lineLimit(2)

String provided in Text also used as LocalizedStringKey, so you get NSLocalizedString's behavior for free.

Text("This text used as localized key")

To format text inside text view. Actually this is not SwiftUI feature, but Swift 5 String interpolation.

static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()

var now = Date()
var body: some View {
Text("What time is it?: \(now, formatter: Self.dateFormatter)")
}

You can also concatenate Text together with +.

Text("Hello ") + Text("World!").bold()

Text alignment.

Text("Hello\nWorld!").multilineTextAlignment(.center)

Documentation - Text

Label

iOS 14

Label is a convenient view that presents an image and text alongside each other. This is suitable for a menu item or your settings.

You can use your own image or SF Symbol.

Label("Swift", image: "swift")
Label("Website", systemImage: "globe")

Documentation - Label

TextField

A control that displays an editable text interface.

@State var name: String = "John"    
var body: some View {
TextField("Name's placeholder", text: $name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}

Documentation - TextField

TextEditor

iOS 14

A view that can display and edit long-form text.

@State private var fullText: String = "This is some editable text..."

var body: some View {
TextEditor(text: $fullText)
}

Documentation - TextEditor

SecureField

A control into which the user securely enters private text.

@State var password: String = "1234"    
var body: some View {
SecureField($password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}

Documentation - SecureField

Image

A view that displays an environment-dependent image.

Image("foo") //image name is foo

We can use new SF Symbols

Image(systemName: "clock.fill")

you can add style to system icon set to match font you use

Image(systemName: "cloud.heavyrain.fill")
.foregroundColor(.red)
.font(.title)
Image(systemName: "clock")
.foregroundColor(.red)
.font(Font.system(.largeTitle).bold())

Add style to Image

Image("foo")
.resizable() // it will sized so that it fills all the available space
.aspectRatio(contentMode: .fit)

Documentation - Image

Button

A control that performs an action when triggered.

Button(
action: {
// did tap
},
label: { Text("Click Me") }
)

If your Button's label is only Text you can initialize with this simpler signature.

Button("Click Me") {
// did tap
}

You can get a bit fancy with this button

Button(action: {

}, label: {
Image(systemName: "clock")
Text("Click Me")
Text("Subtitle")
})
.foregroundColor(Color.white)
.padding()
.background(Color.blue)
.cornerRadius(5)

Documentation - Button

iOS 14

Create a link-style Button that will open in the associated app, if possible, but otherwise in the user’s default web browser.

Link("View Our Terms of Service", destination: URL(string: "https://www.example.com/TOS.html")!)

Documentation - Link

A button that triggers a navigation presentation when pressed. This is a replacement for pushViewController

NavigationView {
NavigationLink(destination:
Text("Detail")
.navigationBarTitle(Text("Detail"))
) {
Text("Push")
}.navigationBarTitle(Text("Master"))
}

Or make it more readable by use group destination into it own view DetailView

NavigationView {
NavigationLink(destination: DetailView()) {
Text("Push")
}.navigationBarTitle(Text("Master"))
}

If your NavigationLink's label is only Text you can initialize with this simpler signature.

NavigationLink("Detail", destination: Text("Detail").navigationBarTitle(Text("Detail")))

Documentation - NavigationLink

ToolbarItem

iOS 14

A model that represents an item which can be placed in the toolbar or navigation bar. This represents most properties in UINavigationItem

Add titleView.

NavigationView {
Text("SwiftUI").padding()
.toolbar {
ToolbarItem(placement: .principal) {
VStack {
Text("Title")
Button("Clickable Subtitle") { print("principle") }
}
}
}
}

Add leftBarButtonItem or leftBarButtonItems.

NavigationView {
Text("SwiftUI").padding()
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {

} label: {
Image(systemName: "square.and.pencil")
}

}
}
}

Add rightBarButtonItem or rightBarButtonItems.

NavigationView {
Text("SwiftUI").padding()
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {

} label: {
Image(systemName: "square.and.pencil")
}

}
ToolbarItem(placement: .navigationBarTrailing) {
Button {

} label: {
Image(systemName: "square.and.pencil")
}

}
}
}

Documentation - ToolbarItem

Toggle

A control that toggles between on and off states.

@State var isShowing = true // toggle state

Toggle(isOn: $isShowing) {
Text("Hello World")
}

If your Toggle's label is only Text you can initialize with this simpler signature.

Toggle("Hello World", isOn: $isShowing)

Documentation - Toggle

Map

iOS 14

A view that displays an embedded map interface.

Show map with a specified region

import MapKit

@State var region = MKCoordinateRegion(center: .init(latitude: 37.334722, longitude: -122.008889), latitudinalMeters: 300, longitudinalMeters: 300)

Map(coordinateRegion: $region)


You can control a map interaction by specify interactionModes (Use [] to disable all interactions).

struct PinItem: Identifiable {
let id = UUID()
let coordinate: CLLocationCoordinate2D
}

Map(coordinateRegion: $region,
interactionModes: [],
showsUserLocation: true,
userTrackingMode: nil,
annotationItems: [PinItem(coordinate: .init(latitude: 37.334722, longitude: -122.008889))]) { item in
MapMarker(coordinate: item.coordinate)
}

Documentation - Map

Picker

A control for selecting from a set of mutually exclusive values.


Picker style change based on its ancestor, under Form or List it appear as a single list row that you can tap to bring in a new screen showing all possible options.

NavigationView {
Form {
Section {
Picker(selection: $selection, label:
Text("Picker Name")
, content: {
Text("Value 1").tag(0)
Text("Value 2").tag(1)
Text("Value 3").tag(2)
Text("Value 4").tag(3)
})
}
}
}

You can override style with .pickerStyle(WheelPickerStyle()).

In SwiftUI, UISegmentedControl is just another style of Picker.

@State var mapChoioce = 0
var settings = ["Map", "Transit", "Satellite"]
Picker("Options", selection: $mapChoioce) {
ForEach(0 ..< settings.count) { index in
Text(self.settings[index])
.tag(index)
}

}.pickerStyle(SegmentedPickerStyle())

Documentation - Picker

DatePicker

A control for selecting an absolute date.


Date Picker style also changes based on its ancestor. Under Form or List, it appears as a single list row that you can tap to expand to date picker (just like calendar app).

@State var selectedDate = Date()

var dateClosedRange: ClosedRange<Date> {
let min = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
let max = Calendar.current.date(byAdding: .day, value: 1, to: Date())!
return min...max
}

NavigationView {
Form {
Section {
DatePicker(
selection: $selectedDate,
in: dateClosedRange,
displayedComponents: .date,
label: { Text("Due Date") }
)
}
}
}

Outside Form and List, it shows as normal wheel picker

@State var selectedDate = Date()

var dateClosedRange: ClosedRange<Date> {
let min = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
let max = Calendar.current.date(byAdding: .day, value: 1, to: Date())!
return min...max
}

DatePicker(
selection: $selectedDate,
in: dateClosedRange,
displayedComponents: [.hourAndMinute, .date],
label: { Text("Due Date") }
)

If your DatePicker's label is only plain Text you can initialize with this simpler signature.

DatePicker("Due Date",
selection: $selectedDate,
in: dateClosedRange,
displayedComponents: [.hourAndMinute, .date])

minimumDate and maximumDate can be set using ClosedRange, PartialRangeThrough, and PartialRangeFrom.

DatePicker("Minimum Date",
selection: $selectedDate,
in: Date()...,
displayedComponents: [.date])
DatePicker("Maximum Date",
selection: $selectedDate,
in: ...Date(),
displayedComponents: [.date])

Documentation - DatePicker

ProgressView

iOS 14

A view that shows the progress towards completion of a task.

@State private var progress = 0.5

VStack {
ProgressView(value: progress)
Button("More", action: { progress += 0.05 })
}

Use can use this as UIActivityIndicatorView by apply CircularProgressViewStyle.

ProgressView(value: progress)
.progressViewStyle(CircularProgressViewStyle())

Documentation - ProgressView

Slider

A control for selecting a value from a bounded linear range of values.

@State var progress: Float = 0

Slider(value: $progress, from: 0.0, through: 100.0, by: 5.0)

Slider lack of minimumValueImage and maximumValueImage, but we can replicate that easily by `HStack

@State var progress: Float = 0
HStack {
Image(systemName: "sun.min")
Slider(value: $progress, from: 0.0, through: 100.0, by: 5.0)
Image(systemName: "sun.max.fill")
}.padding()

Documentation - Slider

Stepper

A control used to perform semantic increment and decrement actions.

@State var quantity: Int = 0
Stepper(value: $quantity, in: 0...10, label: { Text("Quantity \(quantity)")})

If your Stepper's label is only Text you can initialize with this simpler signature.

Stepper("Quantity \(quantity)", value: $quantity, in: 0...10)

If you want full control, they offer bare bone Stepper where you manage your own data source.

@State var quantity: Int = 0
Stepper(onIncrement: {
self.quantity += 1
}, onDecrement: {
self.quantity -= 1
}, label: { Text("Quantity \(quantity)") })

If you also specify an amount of value for each step with initializers with step.

Stepper(value: $quantity, in: 0...10, step: 2) {
Text("Quantity \(quantity)")
}

Documentation - Stepper

SwiftUI - View Layout and Presentation

HStack

A view that arranges its children in a horizontal line.


To create static scrollable List

HStack (alignment: .center, spacing: 20){
Text("Hello")
Divider()
Text("World")
}

Documentation - HStack

LazyHStack

iOS 14

A view that arranges its children in a line that grows horizontally, creating items only as needed.

ScrollView(.horizontal) {
LazyHStack(alignment: .center, spacing: 20) {
ForEach(1...100, id: \.self) {
Text("Column \($0)")
}
}
}

Documentation - LazayHStack

VStack

A view that arranges its children in a vertical line.


To create static scrollable List

VStack (alignment: .center, spacing: 20){
Text("Hello")
Divider()
Text("World")
}

Documentation - VStack

LazyVStack

iOS 14

A view that arranges its children in a line that grows vertically, creating items only as needed.

ScrollView {
LazyVStack(alignment: .leading) {
ForEach(1...100, id: \.self) {
Text("Row \($0)")
}
}
}

Documentation - LazyVStack

ZStack

A view that overlays its children, aligning them in both axes.

ZStack {
Text("Hello")
.padding(10)
.background(Color.red)
.opacity(0.8)
Text("World")
.padding(20)
.background(Color.red)
.offset(x: 0, y: 40)
}
}

Documentation - ZStack

List

A container that presents rows of data arranged in a single column.


To create static scrollable List

List {
Text("Hello world")
Text("Hello world")
Text("Hello world")
}

Cell can be mixed

List {
Text("Hello world")
Image(systemName: "clock")
}

To create dynamic List

let names = ["John", "Apple", "Seed"]
List(names) { name in
Text(name)
}

To add section

List {
Section(header: Text("UIKit"), footer: Text("We will miss you")) {
Text("UITableView")
}

Section(header: Text("SwiftUI"), footer: Text("A lot to learn")) {
Text("List")
}
}

To make it grouped add .listStyle(GroupedListStyle())

List {
Section(header: Text("UIKit"), footer: Text("We will miss you")) {
Text("UITableView")
}

Section(header: Text("SwiftUI"), footer: Text("A lot to learn")) {
Text("List")
}
}.listStyle(GroupedListStyle())

To make it inset grouped (.insetGrouped), add .listStyle(GroupedListStyle()) and force regular horizontal size class .environment(\.horizontalSizeClass, .regular).

List {
Section(header: Text("UIKit"), footer: Text("We will miss you")) {
Text("UITableView")
}

Section(header: Text("SwiftUI"), footer: Text("A lot to learn")) {
Text("List")
}
}.listStyle(GroupedListStyle())
.environment(\.horizontalSizeClass, .regular)

iOS 14

In iOS 14, we have a dedicated style for this.

.listStyle(InsetGroupedListStyle())

Documentation - List

ScrollView

A view that allows the scrolling of its contained views.

ScrollView {
Image("foo")
Text("Hello World")
}

More detail about ScrollView →

LazyHGrid

iOS 14

A container view that arranges its child views in a grid that grows horizontally, creating items only as needed.

var rows: [GridItem] =
Array(repeating: .init(.fixed(20)), count: 2)

ScrollView(.horizontal) {
LazyHGrid(rows: rows, alignment: .top) {
ForEach((0...100), id: \.self) {
Text("\($0)").background(Color.pink)
}
}
}

Documentation - LazyHGrid

LazyVGrid

iOS 14

A container view that arranges its child views in a grid that grows vertically, creating items only as needed.

var columns: [GridItem] =
Array(repeating: .init(.fixed(20)), count: 5)

ScrollView {
LazyVGrid(columns: columns) {
ForEach((0...100), id: \.self) {
Text("\($0)").background(Color.pink)
}
}
}

Documentation - LazyVGrid

Form

A container for grouping controls used for data entry, such as in settings or inspectors.


You can put almost anything into this Form and it will render appropriate style for a form.

NavigationView {
Form {
Section {
Text("Plain Text")
Stepper(value: $quantity, in: 0...10, label: { Text("Quantity") })
}
Section {
DatePicker($date, label: { Text("Due Date") })
Picker(selection: $selection, label:
Text("Picker Name")
, content: {
Text("Value 1").tag(0)
Text("Value 2").tag(1)
Text("Value 3").tag(2)
Text("Value 4").tag(3)
})
}
}
}

Documentation - Form

Spacer

A flexible space that expands along the major axis of its containing stack layout, or on both axes if not contained in a stack.

HStack {
Image(systemName: "clock")
Spacer()
Text("Time")
}

Documentation - Spacer

Divider

A visual element that can be used to separate other content.

HStack {
Image(systemName: "clock")
Divider()
Text("Time")
}.fixedSize()

Documentation - Divider

A view for presenting a stack of views representing a visible path in a navigation hierarchy.

NavigationView {            
List {
Text("Hello World")
}
.navigationBarTitle(Text("Navigation Title")) // Default to large title style
}

For old style title

NavigationView {            
List {
Text("Hello World")
}
.navigationBarTitle(Text("Navigation Title"), displayMode: .inline)
}

Add UIBarButtonItem

NavigationView {
List {
Text("Hello World")
}
.navigationBarItems(trailing:
Button(action: {
// Add action
}, label: {
Text("Add")
})
)
.navigationBarTitle(Text("Navigation Title"))
}

Add show/push with NavigationLink


Use as UISplitViewController.

NavigationView {
List {
NavigationLink("Go to detail", destination: Text("New Detail"))
}.navigationBarTitle("Master")
Text("Placeholder for Detail")
}

You can style a NavigationView using two new style properties: stack and doubleColumn. By default, navigation views on iPhone and Apple TV visually reflect a navigation stack, while on iPad and Mac, a split-view styled navigation view displays.

You can override this with .navigationViewStyle.

NavigationView {
MyMasterView()
MyDetailView()
}
.navigationViewStyle(StackNavigationViewStyle())

iOS 14

In iOS 14, there is a new sidebar style for UISplitViewController. You can also do that by putting three views under NavigationView.

NavigationView {
Text("Sidebar")
Text("Primary")
Text("Detail")
}

Documentation - NavigationView

iOS 14

Add UIToolbar like toolbarItems in UIViewController.

NavigationView {
Text("SwiftUI").padding()
.toolbar {
ToolbarItem(placement: .bottomBar) {
Button {

} label: {
Image(systemName: "archivebox")
}
}

ToolbarItem(placement: .bottomBar) {
Spacer()
}

ToolbarItem(placement: .bottomBar) {
Button {

} label: {
Image(systemName: "square.and.pencil")
}
}
}
}

Documentation - ToolbarItem

TabView

A view that allows for switching between multiple child views using interactable user interface elements.

TabView {
Text("First View")
.font(.title)
.tabItem({ Text("First") })
.tag(0)
Text("Second View")
.font(.title)
.tabItem({ Text("Second") })
.tag(1)
}

Image and Text together. You can use SF Symbol here.

TabView {
Text("First View")
.font(.title)
.tabItem({
Image(systemName: "circle")
Text("First")
})
.tag(0)
Text("Second View")
.font(.title)
.tabItem(VStack {
Image("second")
Text("Second")
})
.tag(1)
}

Or you can omit VStack

TabView {
Text("First View")
.font(.title)
.tabItem({
Image(systemName: "circle")
Text("First")
})
.tag(0)
Text("Second View")
.font(.title)
.tabItem({
Image("second")
Text("Second")
})
.tag(1)
}

UIPageViewController

UIPageViewController become a style of TabView. To use page view style, use .tabViewStyle(PageTabViewStyle()) modifier on your tab view.

TabView {
Text("1")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.pink)
Text("2")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
Text("3")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
Text("4")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
}.tabViewStyle(PageTabViewStyle())

This PageTabViewStyle will include UIPageControl at the bottom just like UIPageViewController. To remove it, pass indexDisplayMode to PageTabViewStyle.

.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))

There is a new style for page control which will render background around the indicator. To enforce this new style, add .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always)) modifier to your tab view.

TabView {
Text("1")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.pink)
Text("2")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
Text("3")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
Text("4")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
}
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.tabViewStyle(PageTabViewStyle())

Documentation - TabView

Alert

A container for an alert presentation.


We can show Alert based on boolean.

@State var isError: Bool = false

Button("Alert") {
self.isError = true
}.alert(isPresented: $isError, content: {
Alert(title: Text("Error"), message: Text("Error Reason"), dismissButton: .default(Text("OK")))
})

It is also bindable with Identifiable item.

@State var error: AlertError?

var body: some View {
Button("Alert Error") {
self.error = AlertError(reason: "Reason")
}.alert(item: $error, content: { error in
alert(reason: error.reason)
})
}

func alert(reason: String) -> Alert {
Alert(title: Text("Error"),
message: Text(reason),
dismissButton: .default(Text("OK"))
)
}

struct AlertError: Identifiable {
var id: String {
return reason
}

let reason: String
}

Documentation - Alert

A modal transition.


We can show Modal based on boolean.

@State var isModal: Bool = false

var modal: some View {
Text("Modal")
}

Button("Modal") {
self.isModal = true
}.sheet(isPresented: $isModal, content: {
self.modal
})

Documentation - Sheet

It is also bindable with Identifiable item.

@State var detail: ModalDetail?

var body: some View {
Button("Modal") {
self.detail = ModalDetail(body: "Detail")
}.sheet(item: $detail, content: { detail in
self.modal(detail: detail.body)
})
}

func modal(detail: String) -> some View {
Text(detail)
}

struct ModalDetail: Identifiable {
var id: String {
return body
}

let body: String
}

Documentation - Sheet

iOS 14

If you want the old style of modal presentation where a modal view is presented full screen, you can use .fullScreenCover instead of .sheet.

Since the full-screen cover style doesn't allow a user to use gesture to dismiss the modal, you have to add a way to dismiss the presented view manually. In the following example, we add a button to dismiss the presented view by set isModal to false.

@State var isModal: Bool = false

var modal: some View {
Text("Modal")
Button("Dismiss") {
self.isModal = false
}
}

Button("Fullscreen") {
self.isModal = true
}.fullScreenCover(isPresented: $isFullscreen, content: {
self.modal
})

If you use a custom view as a modal, you can dismiss the presented view using the presentationMode environmental key.

struct Modal: View {
@Environment(\.presentationMode) var presentationMode

var body: some View {
Text("Modal")
Button("Dismiss Modal") {
presentationMode.wrappedValue.dismiss()
}
}
}

struct ContentView: View {
@State private var isModal = false

var body: some View {
Button("Fullscreen") {
isModal = true
}
.fullScreenCover(isPresented: $isFullscreen, content: {
Modal()
})
}

Documentation - fullScreenCover

ActionSheet

A storage type for an action sheet presentation.


We can show ActionSheet based on boolean.

@State var isSheet: Bool = false

var actionSheet: ActionSheet {
ActionSheet(title: Text("Action"),
message: Text("Description"),
buttons: [
.default(Text("OK"), action: {

}),
.destructive(Text("Delete"), action: {

})
]
)
}

Button("Action Sheet") {
self.isSheet = true
}.actionSheet(isPresented: $isSheet, content: {
self.actionSheet
})

It is also bindable with Identifiable item.

@State var sheetDetail: SheetDetail?

var body: some View {
Button("Action Sheet") {
self.sheetDetail = ModSheetDetail(body: "Detail")
}.actionSheet(item: $sheetDetail, content: { detail in
self.sheet(detail: detail.body)
})
}

func sheet(detail: String) -> ActionSheet {
ActionSheet(title: Text("Action"),
message: Text(detail),
buttons: [
.default(Text("OK"), action: {

}),
.destructive(Text("Delete"), action: {

})
]
)
}

struct SheetDetail: Identifiable {
var id: String {
return body
}

let body: String
}

Documentation - ActionSheet

Framework Integration - UIKit in SwiftUI

UIViewRepresentable

A view that represents a UIKit view. Use this when you want to use UIView inside SwiftUI.


To make any UIView usable in SwiftUI, create a wrapper view that conforms UIViewRepresentable.

import UIKit
import SwiftUI

struct ActivityIndicator: UIViewRepresentable {
@Binding var isAnimating: Bool

func makeUIView(context: Context) -> UIActivityIndicatorView {
let v = UIActivityIndicatorView()

return v
}

func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {
if isAnimating {
uiView.startAnimating()
} else {
uiView.stopAnimating()
}
}
}

If you want to bridge between UIKit data binding (delegate, target/action) and SwiftUI, use Coordinator. Detail can be found in SwiftUI tutorials

import SwiftUI
import UIKit

struct PageControl: UIViewRepresentable {
var numberOfPages: Int
@Binding var currentPage: Int

func makeUIView(context: Context) -> UIPageControl {
let control = UIPageControl()
control.numberOfPages = numberOfPages
control.addTarget(
context.coordinator,
action: #selector(Coordinator.updateCurrentPage(sender:)),
for: .valueChanged)

return control
}

func updateUIView(_ uiView: UIPageControl, context: Context) {
uiView.currentPage = currentPage
}

func makeCoordinator() -> Coordinator {
Coordinator(self)
}

// This is where old paradigm located
class Coordinator: NSObject {
var control: PageControl

init(_ control: PageControl) {
self.control = control
}

@objc func updateCurrentPage(sender: UIPageControl) {
control.currentPage = sender.currentPage
}
}
}

Documentation - UIViewRepresentable

UIViewControllerRepresentable

A view that represents a UIKit view controller. Use this when you want to use UIViewController inside SwiftUI.


To make any UIViewController usable in SwiftUI, create a wrapper view that conforms UIViewControllerRepresentable. Detail can be found in SwiftUI tutorials


import SwiftUI
import UIKit

struct PageViewController: UIViewControllerRepresentable {
var controllers: [UIViewController]

func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal)

return pageViewController
}

func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[controllers[0]], direction: .forward, animated: true)
}
}

Documentation - UIViewControllerRepresentable

Framework Integration - SwiftUI in UIKit

UIHostingController

A UIViewController that represents a SwiftUI view.

let vc = UIHostingController(rootView: Text("Hello World"))
let vc = UIHostingController(rootView: ContentView())

Documentation - UIHostingController

App Structure and Behavior

App

iOS 14

A type that represents the structure and behavior of an app.

import SwiftUI

@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
Text("Hello, world!")
}
}
}

You can also create custom scenes. To include more than one scene in an app, add the @SceneBuilder attribute to the body.

@main
struct Mail: App {
@SceneBuilder var body: some Scene {
WindowGroup {
MailViewer()
}
Settings {
SettingsView()
}
}
}

Documentation - App

Scene

iOS 14

A part of an app’s user interface with a life cycle managed by the system.

@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
Text("Hello, world!")
}
}
}

Documentation - Scene