SwiftUI¶
Components¶
As explained by Kelvin Vins in his SwiftUI component overview.
Segmented Control¶
@State private var selectedTag = 0
Picker("", selection: $selectedTag) {
Image(systemName: "paperplane").tag(0)
Image(systemName: "tray").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.padding()
Slider¶
Slider(value: $number, in: 1...100).padding()
Text("You've selected \(Int(number))")
VStack() {
Slider(value: $number, in: 1...100)
HStack {
Image(systemName: "speaker.fill")
Spacer()
Image(systemName: "speaker.2.fill")
Spacer()
Image(systemName: "speaker.3.fill")
}
.padding(.top, 8)
.foregroundColor(.accentColor)
}
.padding()
TabView¶
TabView() {
Text("First Tab").tabItem {
Image(systemName: (selected == 0 ? "house.fill" : "house"))
Text("Home")
}
Text("Second Tab").tabItem {
Image(systemName: (selected == 1 ? "plus.circle.fill" : "plus.circle"))
Text("Add")
}
Text("Third Tab").tabItem {
Image(systemName: (selected == 2 ? "heart.fill" : "heart"))
Text("Favorite")
}
Text("Fourth Tab").tabItem {
Image(systemName: (selected == 3 ? "person.fill" : "person"))
Text("Profile")
}
}
Textfield¶
TextField("User Name", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
HStack {
Image(systemName: "person").foregroundColor(.gray)
TextField("Enter your firstName", text: $firstName)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding()
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
SecureField¶
SecureField("Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
List¶
var food = ["Spaghetti", "Cheese Burger", "Pizza", "Fried Rice"]
struct ContentView: View {
var body: some View {
List(food, id: \.self) { data in
Text(data)
}
}
}
Deletion¶
struct ContentView: View {
@State var food = ["Spaghetti", "Cheese Burger", "Pizza", "Fried Rice"]
var body: some View {
List {
Section(header: Text("List of food")) {
ForEach(food, id: \.self) { data in
HStack {
Image(systemName: "smiley")
Text(data)
}
}
.onDelete(perform: delete)
}
}
.listStyle(GroupedListStyle())
}
}
[...]
func delete(index: IndexSet) {
if let first = index.first {
food.remove(at: first)
}
}
Edit¶
struct ContentView: View {
@State var food = ["Spaghetti", "Cheese Burger", "Pizza", "Fried Rice"]
var body: some View {
NavigationView {
List {
ForEach(food, id: \.self) { data in
HStack {
Image(systemName: "smiley")
Text(data)
}
}
.onDelete(perform: delete)
.onMove(perform: moveRow)
}
.navigationBarItems(trailing: EditButton())
}
.listStyle(GroupedListStyle())
}
}
[...]
func moveRow(index: IndexSet, destination: Int) {
if let first = index.first {
food.insert(food.remove(at: first), at: destination > first ? destination - 1 : destination)
}
}
Datepicker¶
struct ContentView: View {
@State private var currentDate = Date()
var body: some View {
VStack {
DatePicker("", selection: $currentDate, displayedComponents: .date)
}
}
}
Timepicker¶
struct ContentView: View {
@State private var currentDate = Date()
var body: some View {
VStack {
DatePicker("", selection: $currentDate, displayedComponents: .hourAndMinute)
.labelsHidden()
}
}
}
Datetimepicker¶
struct ContentView: View {
@State private var currentDate = Date()
var body: some View {
VStack {
DatePicker("", selection: $currentDate, displayedComponents: [.date, .hourAndMinute])
.labelsHidden()
}
}
}
Text¶
struct ContentView: View {
var body: some View {
VStack(spacing: 50) {
Text("This is suppose to be a really long text that can go on to multiple lines. By default, it could go more than one lines.")
Text("This is only one line regardless of how long the sentence is")
.lineLimit(1)
}
}
}
Font Types
.largetitle
.title
.headline
.subheadline
.body
.callout
.footnote
Font Weight
.ultraLight
.thin
.regular
.medium
.semibold
.bold
.heavy
.black
Font Design
.default
.monospaced
.rounded
.serif
Attributes
.bold()
.italic()
.strikethrough()
.strikethrough(true, color: .blue)
.foregroundColor(.yellow)
.underline()
.underline(true, color: .red)
.multilineTextAlignment(.center)
Truncation
Text("This really long text is meant to have some space in between the texts to make it nicer. This is cool!")
.truncationMode(.middle)
.lineLimit(1)
Combining Modified Text
Text("What about a combination of cool texts such as") +
Text(" bold ").bold() +
Text("Yellow Text").foregroundColor(.yellow) +
Text(" or ") +
Text(" a design font ").font(.title).fontWeight(.medium)
Action Sheet¶
@State private var actionSheetShown = false
[...]
Button("Action Sheet") {
self.actionSheetShown = true
}
[...]
.actionSheet(isPresented: $actionSheetShown) { () -> ActionSheet in
ActionSheet(title: Text("Menu"), message: Text("Select your options"),
buttons: [
.default(Text("Ok"), action: {
print("Ok selected")
}),
.destructive(Text("Cancel"), action: {
print("Cancel selected")
})
])
}
Alert¶
@State private var alertShown = false
[...]
Button("Show Alert") {
self.alertShown = true
}.alert(isPresented: $alertShown) { () -> Alert in
Alert(title: Text("Alert Title"), message: Text("Alert Message"), dismissButton: .default(Text("Ok")))
}
Alert(title: Text("Alert Title"), message: Text("Alert Message"), primaryButton: .default(Text("Ok")), secondaryButton: .default(Text("Cancel")))
Form¶
For setting page
Form {
Section(header: Text("General")){
Toggle(isOn: $enableDarkMode) {
Text("Dark Mode")
}
HStack {
Image(systemName: "wifi").foregroundColor(Color.blue)
Text("Wifi")
Spacer()
Text("WifiHouse")
Image(systemName: "chevron.right")
}
HStack {
Text("Mobile Data")
Spacer()
Image(systemName: "chevron.right")
}
}
Section(header: Text("Phone Setting")) {
HStack {
Text("Name")
Spacer()
Text("Kelvin's iPhone")
}
HStack {
Text("Software Version")
Spacer()
Text("13.3.1")
}
HStack {
Text("Model Name")
Spacer()
Text("iPhone 8 Plus")
}
}
}
ScrollView¶
Horizontal
var colors = [Color.green, Color.yellow, Color.orange, Color.blue]
[...]
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(self.colors, id: \.self) { color in
RoundedRectangle(cornerRadius: 4)
.fill(color)
.frame(width: 250, height: 200)
}
}
}
.padding(.horizontal)
Stepper¶
HStack {
Image(systemName: "heart.circle")
Text("Cup of water drank: \(stepperValue)")
Spacer()
Stepper("", value: $stepperValue)
.frame(width: 100, height: 35)
.offset(x: -4)
.background(Color.blue)
.cornerRadius(8)
}
.padding()
VStack¶
VStack(alignment: .leading, spacing: 10) {
Text("First Subview").background(Color.blue)
Text("Second Subview").background(Color.yellow)
Text("Third Subview").background(Color.red)
Text("Fourth Subview").background(Color.orange)
VStack {
Text("Fourth Subview's subview").background(Color.orange)
Text("Fourth Subview's subview").background(Color.orange)
Text("Fourth Subview's subview").background(Color.orange)
Text("Fourth Subview's subview").background(Color.orange)
Text("Fourth Subview's subview").background(Color.orange)
Text("Fourth Subview's subview").background(Color.orange)
}
Text("Fifth Subview").background(Color.green)
Text("Sixth Subview").background(Color.pink)
Text("Seventh Subview").background(Color.purple)
Text("Eighth Subview").background(Color.blue)
Text("Nineth Subview").background(Color.yellow)
}
Attributes
spacing: int
alignment: .leading|.trailing
HStack¶
HStack(spacing: 20 ) {
Image(systemName: "1.circle")
Image(systemName: "2.circle")
Image(systemName: "3.circle")
Image(systemName: "4.circle")
Image(systemName: "5.circle")
Image(systemName: "6.circle")
Image(systemName: "7.circle")
}
Attributes
spacing: int
alignment: .bottom|.top|.center
ZStack¶
ZStack {
// 1
Rectangle()
.fill(Color.gray)
.frame(width: 300, height: 300)
// 2
Image("background")
.resizable()
.frame(width: 210, height: 210)
.edgesIgnoringSafeArea(.all)
// 3
Text("Text on top of image")
.foregroundColor(.white)
}
Spacer¶
HStack {
Image(systemName: "message.fill")
Text("Inbox")
Spacer()
.frame(height: 10)
.background(Color.blue)
Image(systemName: "chevron.right")
}
.padding(.horizontal)
Divider¶
HStack {
Text("First Text")
Divider()
Text("Second Text")
Divider()
Text("Third text")
}
Sheet¶
struct DetailInfo: Identifiable {
var id = UUID()
let text: String
}
struct ContentView: View {
@State private var details: DetailInfo? = nil
var body: some View {
VStack {
Button("Show Sheet") {
self.details = DetailInfo(text: "Hello, this is the sheet screen")
}
.sheet(item: $details) { detail in
DetailSheet(details: detail)
}
}
.padding()
}
}
struct DetailSheet: View {
@Environment(\.presentationMode) var presentation
let details: DetailInfo
var body: some View {
VStack {
Text(details.text)
.font(.largeTitle)
Spacer()
Button("Dismiss") { self.presentation.wrappedValue.dismiss() }
}
.padding()
}
}
Geometries¶
Circle()
Rectangle()
Button(action: {} ){
Image(systemName: "airplane")
.foregroundColor(.white)
.font(.largeTitle)
.padding()
}
.background(Circle())
.foregroundColor(.blue)
Rectangle(cornerRadius: 4.0)
.fill(Color.green)
.frame(width: 300, height: 100)
Passing Data in SwiftUI¶
As explained by Brian Advent .
@State
Simple properties (Int, String)
Belongs to specific view
Never used outside view
@ObservedObject
More complex properties (Composed classes)
Class must adhear
ObservableObject
View refreshed, when properties marked
@Published
@EnvironmentObject
See
@ObservedObject
but available to all viewsSet in
SceneDelegate
.
Bindings explained by Paul Hudson
Bindings for shared objects via @State
:
import SwiftUI
struct User {
var username = "xys"
var password = "as929asp1.;a"
var emailAddress = "test@example.com"
}
struct contentView : View {
@State var user = User()
var body: some View{
VStack{
TextField($user.username)
TextField($user.password)
TextField($user.emailAddress)
}
}
}
⚠️ With XCode 11.5 the BindableObject
and ObjectBinding
are deprecated and are replaced by ObservableObject
.
The old example howto, use bindings for shared objects with @ObjectBinding
:
import SwiftUI
import Combine
class User: BindableObject {
var didChange = PassthroughObject<Void, Never>()
var username = "xys" { didSet {didChange.send() } }
var password = "as929asp1.;a" { didSet {didChange.send() } }
var emailAddress = "test@example.com" { didSet {didChange.send() } }
}
struct contentView : View {
@OBjectBinding var user = User()
var body: some View{
VStack{
TextField($user.username)
TextField($user.password)
TextField($user.emailAddress)
TextField($user.username)
}
}
}
This deprecated source
final class FooData: BindableObject {
let willChange = PassthroughSubject<Void, Never>()
var show = false { willSet { willChange.send() } }
}
struct BindingExample: View {
@ObjectBinding var foo: FooData
var body: some View {
Text("Hello \(foo.show.description)")
}
}
becomes
final class FooData: ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
var show = false { willSet { objectWillChange.send() } }
}
struct BindingExample: View {
@ObservedObject var foo: FooData
var body: some View {
Text("Hello \(foo.show.description)")
}
}
Bindings for shared objects with @EnvironmentObject
:
import SwiftUI
import Combine
class User: BindableObject {
var didChange = PassthroughObject<Void, Never>()
var username = "xys" { didSet { didChange.send() } }
var password = "as929asp1.;a" { didSet { didChange.send() } }
var emailAddress = "test@example.com" { didSet { didChange.send() } }
}
struct contentView : View {
@EnvironmentObject var user: User
var body: some View{
VStack{
TextField($user.username)
TextField($user.password)
TextField($user.emailAddress)
TextField($user.username)
}
}
}
let userData = User()
struct ContentView_Preview : PreviewProvider{
static var previews: some View{
ContentView().environmentObject(userData)
}
}
In the SceneDelegate
[...]
var userData = User()
window.rootViewController = UIHostingController(rootView: ContentView()).environmentObject(userData)
[...]