SwiftUI

Components

As explained by Kelvin Vins in his SwiftUI component overview.

Button

Button(action: {}) {
  HStack{
    Text("This is a button")
    .padding(.horizontal)
    Image(systemName: "person.crop.circle.fill") 
  }
  .padding()
}
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(8)

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

ContextMenu

HStack {
    Text("Contact Me")
    Spacer()
    Image(systemName: "phone.fill")
        .contextMenu {
            Button(action: {}) {
                Text("Email")
                Image(systemName: "envelope.fill")
            }
 
            Button(action: {}) {
                Text("Linkedin")
            }
    }
}
.padding()

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 views

  • Set in SceneDelegate.

_images/swiftui_property_wrapper_overview.png

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)
[...]