The order matters, imagine that SwiftUI renders your view after every single modifier.
.frame(maxWidth: .infinity, maxHeight: .infinity) //Sets the size of the stack
.background(.color) //background color
.background(.ultraThinMaterial) //Vibrancy
.foregroundStyle(useRedText ? .red : .blue) //Text Color with condition
VStack {
}
.font(.title) //Changes the font size
Allow spacing and allignment inside the stack
var body: some View {
Stack {
}
}
var body: some View {
VStack {
}
}
var body: some View {
HStack {
}
}
var body: some View {
ZStack {
}
}
Forms are scrolling lists of static controls like text and images, but can also include user interactive controls like text fields, toggle switches, buttons, and more.
var body: some View {
Form {
}
}
List are lists of controls like text and images, but can also include user interactive controls like text fields, toggle switches, buttons, and more. Other then forms they can include dynamic content.
var body: some View {
List {
}
List(0..<5) {
Text("Dynamic row \($0)")
}
}
Creating List content from an array:
let people = ["Finn", "Leia", "Luke", "Rey"]
var body: some View {
List(people, id: \.self) {
Text($0)
}
List {
Text("Static Row")
ForEach(people, id: \.self) {
Text($0)
}
Text("Static Row")
}
}
To group related items visually
Section ("Title it if you want") {
}
Section {
}
To Aaoid the form scrolling into the Dynamic Island use a navigation bar on top. Without a title there is no difference to see, but it bahaves different.
var body: some View {
NavigationStack {
Form {
}
.navigationTitle("SwiftUI")
.navigationBarTitleDisplayMode(.inline)
}
}
Text("Hello, world!")
struct ContentView: View {
@State private var name = ""
var body: some View {
Form {
TextField("Enter your name", text: $name)
Text("Hello, world!")
}
}
}
text:
for Strings
value:
for numeric (Int and Double)
struct ContentView: View {
let students = ["Harry", "Hermione", "Ron"]
@State private var selectedStudent = "Harry"
var body: some View {
NavigationStack {
Form {
Picker("Select your student", selection: $selectedStudent) {
ForEach(students, id: \.self) {
Text($0)
}
}
}
}
}
}
Button("Button Name", role: .destructive, action: functionName)
.buttonStyle(.bordered) //Background color
.buttonStyle(.borderedProminent) //Text color white, background color = role
alternative
Button("Button Name") {
print("Now deleting…")
}
Button {
print("Edit button was tapped")
} label: {
Label("Edit", systemImage: "pencil")
.padding()
.foregroundStyle(.white)
.background(.red)
}
@State private var sleepAmount = 8.0
Stepper("\(sleepAmount.formatted()) hours", value: $sleepAmount, in: 4...12, step: 0.25)
@State private var wakeUp = Date.now
DatePicker("Please enter a date", selection: $wakeUp, displayedComponents: .hourAndMinute, in: Date.now... )
.labelsHidden()
Color.colorName
.frame(minWidth: 200, maxWidth: .infinity, maxHeight: 200)
alternative:
Color(red: 1, green: 0.8, blue: 0)
.frame(minWidth: 200, maxWidth: .infinity, maxHeight: 200)
Creates a view for each time it goes through the loop
Form {
ForEach(0 ..< 100) {
Text("Row \($0)")
}
}
struct ContentView: View {
var body: some View {
LinearGradient(stops: [
.init(color: .white, location: 0.05),
.init(color: .black, location: 0.95),
], startPoint: .top, endPoint: .bottom)
}
}
struct ContentView: View {
var body: some View {
RadialGradient(colors: [.blue, .black], center: .center, startRadius: 20, endRadius: 200)
}
}
struct ContentView: View {
var body: some View {
AngularGradient(colors: [.red, .yellow, .green, .blue, .purple, .red], center: .center)
}
}
Text("Your content")
.background(.red.gradient)
struct ContentView: View {
@State private var showingAlert = false
var body: some View {
Button("Show Alert") {
showingAlert = true
}
.alert("Important message", isPresented: $showingAlert) {
Button("OK") { }
}
}
}
Alternatives:
.alert("Important message", isPresented: $showingAlert) {
Button("Delete", role: .destructive) { }
Button("Cancel", role: .cancel) { }
}
.alert("Important message", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
} message: {
Text("Please read this.")
}
struct ContentView: View {
var body: some View {
VStack(spacing: 10) {
Text("First")
.font(.largeTitle)
.padding()
.foregroundStyle(.white)
.background(.blue)
.clipShape(.capsule)
Text("Second")
.font(.largeTitle)
.padding()
.foregroundStyle(.white)
.background(.blue)
.clipShape(.capsule)
}
}
}
can be changed to:
struct CapsuleText: View {
var text: String
var body: some View {
Text(text)
.font(.largeTitle)
.padding()
.foregroundStyle(.white)
.background(.blue)
.clipShape(.capsule)
}
}
struct ContentView: View {
var body: some View {
VStack(spacing: 10) {
CapsuleText(text: "First")
CapsuleText(text: "Second")
}
}
}
struct Title: ViewModifier {
func body(content: Content) -> some View {
content
.font(.largeTitle)
.foregroundStyle(.white)
.padding()
.background(.blue)
.clipShape(.rect(cornerRadius: 10))
}
}
When working with custom modifiers, it’s usually a smart idea to create extensions on View that make them easier to use.
extension View {
func titleStyle() -> some View {
modifier(Title())
}
}
Used then:
Text("Hello World")
.titleStyle()
Opens a Sheet that gets opened from the Bottom and overlays the "ContentView".
@Environment(\.dismiss)
makes the dismiss animtion.
@Environment(\.dismiss) var dismiss
struct SecondView: View {
var body: some View {
Text("Second View")
Button("Dismiss") {
dismiss()
}
}
}
@State private var showingSheet = false
struct ContentView: View {
var body: some View {
Button("Show Sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SecondView()
}
}
}