idea
Refer to training[1]
Components
| Element | Description | Example |
|---|---|---|
| Text | Simple text | |
| Label | Text + icon | Label("title", systemImage: "person.3") |
| Spacer | Expends to push other items to borders | |
| List | Menu-like list of options, used for navigation | |
| Section | Creates a separator in a list with a title | |
| H/VStack | Arrange elements horizontally/vertically | |
| Button | A button | |
| Form | Edit data | |
| Picker | Pick one option |
Navigation
Done with NavigationStack and NavigationLinks.
The title to each step of the NavigationStack is set by add a .navigationTitle on elements of the stack.
Example:
List {
// [...]
}
.navigationTitle(nonsense.excuse)
State
@State wrapper makes a property mutable in the view it's defined in. Allows SwiftUI to update the values of that property.
@Binding wrapper identifies a property as mutable in a parent (It makes it into a Binding<T>)
Bindings (only to edit) are then passed through $ to components for edition. Eg: TextField("Some variable", text: $variableName)
Adding fields is done with an animation in the Button's action
action: {
withAnimation {
myList.add(something)
something = ""
}
}
Removing is done by adding a ondelete to the ForEach.
.onDelete { indices in
torture.victims.remove(atOffsets: indices)
}
To pass a constant as binding, wrap it in .constant(value). If you pass as a constant, then the binding will be constant in the entire hierarchy. To pass the binding from the app:
@State private var data = DailyMadness.sampleData
var body: some Scene {
WindowGroup {
SuppliceListView(supplices: $data)
}
}
@StateObject creates a singleton instance of an object, marks as state for the lifecycle of the view, and makes it observable.
Add style as leading dot syntax
Declare a style.
Declare an extension for the super-type, with the following syntax: (example for LabelStyle):
extension LabelStyle where Self == TrailingIconLabelStyle {
static var trailingIcon: Self { Self() }
}
Modals
Modals are done for short, out of flow tasks, such as editing an object. They're triggered with sheet on a view.
.sheet(isPresented: $isPresentingEditView) {
TortureEditView()
}
Scenes
See App Essentials[4]
Async & concurrency
Async functions are declared with
func fetchParticipants() async -> [Participant] { return [] }
Call with await. Can only be called from an async context. An async context is created with
Task {
await fetchParticipants()
}
Lifetime of a task matches that of the view. When the view disappears, tasks are cancelled[5].
@State and @Binding properties (and UI) can only be mutated from the main thread. To interact with main thread, use annotation @MainActor on the class, and @Published on the variable that need to be mutated with the main thread.
Async tasks can be invoked from views using .task {}
Custom shapes
Create a custom shape by implementing :Shape.
func path(in rect: CGRect) -> Path {
let radius = (min(rect.size.height, rect.size.width) - CGFloat(diameter)) / 2.0
let center = CGPoint(x: rect.midX, y: rect.midY)
return Path { path in
path.addArc(center: center, radius: radius, startAngle: Angle(), endAngle: currentAngle, clockwise: false)
}
}
ref
[1]: Creating a card view — iOS App Dev Tutorials | Apple Developer Documentation
[2]: Modality | Apple Developer Documentation
[3]: Creating the edit view — iOS App Dev Tutorials | Apple Developer Documentation
[4]: App essentials in SwiftUI - WWDC20 - Videos - Apple Developer
[5]: Adopting Swift concurrency — iOS App Dev Tutorials | Apple Developer Documentation