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 #
- Xcode 11 Beta (Download from Apple)
- iOS 13 / macOS 10.15 / tvOS 13 / watchOS 6
- macOS Catalina in order to have SwiftUI render in the canvas (Download from Apple)
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)
TextField
A control that displays an editable text interface.@State var name: String = "John"
var body: some View {
TextField($name)
.textFieldStyle(.roundedBorder)
.padding()
}
SecureField
A control that displays an editable text interface.@State var password: String = "1234"
var body: some View {
SecureField($password)
.textFieldStyle(.roundedBorder)
.padding()
}
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)
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)
NavigationLink
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")))
PresentationLink
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"))
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)
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)
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])
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()
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)") })
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")
}
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")
}
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)
}
}
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)
ScrollView
scroll view.ScrollView(alwaysBounceVertical: true) {
Image("foo")
Text("Hello World")
}
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)
})
}
}
}
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")
}
Divider
A visual element that can be used to separate other content.HStack {
Image(systemName: "clock")
Divider()
Text("Time")
}.fixedSize()
NavigationView
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)
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)
}
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
}
Modal
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
}
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
}
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
}
}
}
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)
}
}
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())