SwiftUI

up:: Swift

SwiftUI fundamentals course

Course: https://youtu.be/b1oC7sLIgpI?feature=shared&t=18755

  • Stacks
    • Horizontal/Vertical: HStack, VStack
    • Background/Foreground: ZStack
  • Modifiers wrap a View in another View. So the order of modifiers matters.
    • Like wrapping an element in divs

Scenes

Are like windows

Animate

withAnimation {
	// action
}
 
// on component:
.contentTransition(.numericText(value: rating))

@State

When you mark a variable with @State, SwiftUI handles its storage and provides it back to the View to read and write.

@Binding

A Binding creates a two-way reference to the State of some other view.

You can bind a variable with @Binding and $varName. Whenever the variable in the child changes, it sets that variable on the parents.

This is similar to v-model in Vue.

Iteration

Interable needs to conform to Hashable. The Fundation types like string, int etc are all hashable

struct Framework: Hashable, Identifiable {
    let id = UUID()
    
    let name: String
    let imageName: String
    let urlString: String
    let description: String
}
            ForEach(MockData.frameworks) { framework in
                FrameworkTitleView(name: framework.name, imageName: framework.imageName)
            }

Basic button

            Button(action: {
                
            }, label: {
                Text("Learn more")
                    .font(.title2)
                    .fontWeight(.semibold)
                    .frame(width: 280, height: 50)
                    .background(Color.red)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            })
 

Use Apple’s defaults

            Button(action: {
                isShowingSafariView = true
            }, label: {
                AFButton(title: "Learn more")
            }).buttonStyle(.borderedProminent).controlSize(.large).tint(Color.red)
 

Create a big struct with all error messages

struct AlertItem {
    let title: String
    let message: String
    let dismissButtonText: String
}
 
struct AlertContext {
    static let invalidDeviceInput = AlertItem(title: "my error", message: "Hello there", dismissButtonText: "Close")
}

You create one alert and then pass in different AlertItem content.

Data-driven navigation. Based on data type, you show different screens.

    var body: some View {
        NavigationStack {
            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach(MockData.frameworks) { framework in
                        NavigationLink(value: framework) {
                            FrameworkTitleView(framework: framework)
                        }
                    }
                }
            }
                .navigationTitle(" Frameworks")
                .navigationDestination(for: Framework.self) { framework in
                    FrameworkDetailView(framework: framework)
                }
        }
    }

MVVM

  • Model
  • View
  • ViewModel

Model

That’s the data, eg. the framework model

View

That’s the display. There should be no logic here. They should be as dump as possible. For example, ternary operators.

ViewModel

This is where the logic sits. Only required if it’s dynamic. If there is a change in view logic.

ObservableObject means this class can “broadcast” data

  • In ObservableObject, you need to use @Published to broadcast the data
  • In the View, you need to use @StateObject to get those changes
    • There you also need to create a new one (with ())
  • Passing one in: observeObject

In the ViewModel:

import SwiftUI
 
final class FrameworkGridViewModel: ObservableObject {
    @Published var selectedFramework: Framework?
}

In the View:

@StateObject var viewModel = FrameworkGridViewModel()

@StateObject

Objects (structs) get destroyed and rerendered all the time. @StateObject signifies that this data should be kept during re-renders.

This is going to stay alive, while the parent View will be destroyed and created.

Listening to state changes

final class FrameworkGridViewModel: ObservableObject {
    var selectedFramework: Framework? {
        didSet {
            isShowingDetailView = true
        }
    }
 
    @Published var isShowingDetailView = false
}

Each component into their own file

Move each component into their own file. That way, you can preview just that component and debug it.

Modifiers

Every time you add a modifier, you wrap all existing modifiers in a new View. So the order matters.

Like in CSS, children inherit modifieres from parents. These are called “Environment modifiers”.

Viewbuilder

For example, a ZStack or a VStack. There are only specific things you can write within a Viewbuilder.

View performance

Views are structs. They don’t inherit, so creating and destroying them is fast.

The Views are broken out into a View tree. The diff-ing algorithm only changes what is bound reactively.

Basic NavigationView

    var body: some View {
        NavigationView {
            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach(MockData.frameworks) { framework in
                        FrameworkTitleView(framework: framework)
                    }
                }
            }.navigationTitle(" Frameworks")
        }
        
    }

Directory structure

/UIKit Components
/Views
	Button/
	View/
/Model
	FrameworkData.swift
/Screens
	FrameworkDetailView/
		FrameworkDetailView.swift
	FrameworkGridView/
		FrameworkGridView.swift
		FrameworkGridViewModel.swift