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. Even though the site has an F word it doesn't mean I hate SwiftUI, in fact, I love it and I think it set a good direction for Apple ecosystem. The site naming is inspired by fuckingblocksyntax.com

FAQ #

Frequently asked questions about SwiftUI.

Should I learn SwiftUI?

Yes.

Should I learn it now?

Depends, since SwiftUI runs on iOS 13, macOS 10.15, tvOS 13, and watchOS 6. If you work on a new app which plans to target only mentioned OS I would say yes. But if you plan to find a job or work on a client project which you have no control over this OS version you might want to wait a year or two before you can even think of 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 OS 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.

Should I learn UIKit/AppKit/WatchKit?

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

Does SwiftUI replace UIKit/AppKit/WatchKit?

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 very long time, SwiftUI is very young and it needs years to grow to be able to replace its ancestor.

If I can learn one thing today what would it be UIKit/AppKit/WatchKit or SwiftUI?

UIKit. You can always rely on UIKit, it serves us well and still be serving. If you start with SwiftUI at this stage you might be missing some features you don't even know exists.

Where is 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.

Requirements #

UIKit equivalent in SwiftUI #

View Controllers

UIKit SwiftUI Note
UIViewController View
UITableViewController List
UICollectionViewController - 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
UISplitViewController NavigationView Partial support in beta 3, but still broken.
UINavigationController NavigationView
UIPageViewController -
UITabBarController TabbedView
UISearchController -
UIImagePickerController -
UIVideoEditorController -
UIActivityViewController -
UIAlertController Alert

Views and Controls

UIKit SwiftUI Note
UILabel Text
UITabBar TabbedView
UITabBarItem TabbedView .tabItem under TabbedView
UITextField TextField For password (isSecureTextEntry) use SecureField
UITableView List also VStack and Form
UINavigationBar NavigationView Part of NavigationView
UIBarButtonItem NavigationView .navigationBarItems in NavigationView
UICollectionView -
UIStackView HStack .axis == .Horizontal
UIStackView VStack .axis == .Vertical
UIScrollView ScrollView
UIActivityIndicatorView -
UIImageView Image
UIPickerView Picker
UIButton Button
UIDatePicker DatePicker
UIPageControl -
UISegmentedControl SegmentedControl
UISlider Slider
UIStepper Stepper
UISwitch Toggle
UIToolBar -

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

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 beahvior 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

TextField

A control that displays an editable text interface.
@State var name: String = "John"    
var body: some View {
TextField($name)
.textFieldStyle(.roundedBorder)
.padding()
}

Documentation

SecureField

A control that displays an editable text interface.
@State var password: String = "1234"    
var body: some View {
SecureField($password)
.textFieldStyle(.roundedBorder)
.padding()
}

Documentation

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)
mage(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

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 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

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

A control that presents content when triggered. This is a replacement for present

PresentationLink(destination: Text("Present"), label: { Text("Popup") })

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

PresentationLink("Popup", destination: Text("Present"))

Documentation

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

Picker

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


Picker style change based on its accestor, 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(.wheel)

Documentation

DatePicker

A control for selecting an absolute date.


Date Picker style also change based on its accestor, under Form or List it appear 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") }
)
}
}
}

Out side Form and List it show 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

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

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 a 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)") })

Documentation

SegmentedControl

A control for selecting from a set of options.
@State var mapChoioce = 0
var settings = ["Map", "Transit", "Satellite"]
SegmentedControl(selection: $mapChoioce) {
ForEach(0 ..< settings.count) { index in
Text(self.settings[index])
.tag(index)
}
}
Documentation

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

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

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

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(.grouped)

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(.grouped)

Documentation

ScrollView

scroll view.
ScrollView(alwaysBounceVertical: true) {
Image("foo")
Text("Hello World")
}

Documentation

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

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

Divider

A visual element that can be used to separate other content.
HStack {
Image(systemName: "clock")
Divider()
Text("Time")
}.fixedSize()

Documentation

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(.stack)

Documentation

TabbedView

A view which allows for switching between multiple child views using interactable user interface elements.
TabbedView {
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.

TabbedView {
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

TabbedView {
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)
}

Documentation

Alert

A container for an alert presentation.


We can show Alert based on boolean.

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

It 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

A storage type for a modal presentation.


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
})

It 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

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"), onTrigger: {

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

})
]
)
}

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

It 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"), onTrigger: {

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

})
]
)
}

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

let body: String
}

Documentation

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 conform 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 UIKit data binding (delegate, target/action) 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

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 conform 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

Framework Integration - SwiftUI in UIKit #

UIHostingController

A UIViewController that represent a SwiftUI view.

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

Documentation

CircleCI ⚡️Automate your development process quickly, safely, and at scale. ethical ad by CodeFund