SwiftUI
up:: Swift
-
Data
-
Second app: 00:27min
-
alessiorubicini/SFSymbolsPickerForSwiftUI: A SwiftUI view for selecting SF symbols in your app
-
Choosing the Right SwiftUI Property Wrapper for You | by Alessio Rubicini | Medium
SwiftUI fundamentals course
Course: https://youtu.be/b1oC7sLIgpI?feature=shared&t=18755
- Stacks
- Horizontal/Vertical:
HStack
,VStack
- Background/Foreground:
ZStack
- Horizontal/Vertical:
- Modifiers wrap a View in another View. So the order of modifiers matters.
- Like wrapping an element in
div
s
- Like wrapping an element in
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.
NavigationStack
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
()
)
- 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 (struct
s) 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 struct
s. 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