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.
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)
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")
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()
}
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)
}
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()
}
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)
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)
Link
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")!)
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")))
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")
}
}
}
}
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)
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)
}
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())
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])
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())
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 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)")
}
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")
}
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)")
}
}
}
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")
}
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)")
}
}
}
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(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())
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)
}
}
}
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)
}
}
}
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(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")
}
}
}
}
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())
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
}
Modal
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
})
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
}
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
}
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)
}
}
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()
}
}
}
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!")
}
}
}
Resources
- API Documentation
- Official Tutorials
- WWDC 2019
- WWDC 2020
- Introduction to SwiftUI
- What's new in SwiftUI
- Build complications in SwiftUI
- App essentials in SwiftUI
- Visually edit SwiftUI views
- Stacks, Grids, and Outlines in SwiftUI
- Build a SwiftUI view in Swift Playgrounds
- Build document-based apps in SwiftUI
- Data Essentials in SwiftUI
- Build SwiftUI views for widgets
- WWDC 2021
- What's new in SwiftUI
- SwiftUI on the Mac: The finishing touches
- SwiftUI on the Mac: Build the fundamentals
- SwiftUI Accessibility: Beyond the basics
- SF Symbols in SwiftUI
- Localize your SwiftUI app
- Discover concurrency in SwiftUI
- Direct and reflect focus in SwiftUI
- Demystify SwiftUI
- Craft search experiences in SwiftUI
- Bring Core Data concurrency to Swift and SwiftUI
- Add rich graphics to your SwiftUI app