30 Days of SwiftUI - Day 24: Crafting, Editing, and Storing: Building Custom Components, Mastering SwiftData, and More!


Hello Swift creators! Today, we're diving deep into some exciting areas of SwiftUI development. We'll build custom components, handle multi-line text input, explore SwiftData, and even create a custom star rating component. Let's get started!

Dynamic Duo: Creating a Custom Component with @Binding

We'll learn how to create reusable components that can modify parent view state using @Binding.

Example: Custom Toggle Component

import SwiftUI

struct CustomToggle: View {
    @Binding var isOn: Bool
    let label: String

    var body: some View {
        HStack {
            Text(label)
            Spacer()
            Toggle("", isOn: $isOn)
        }
        .padding()
    }
}

struct CustomToggleDemo: View {
    @State private var isEnabled = false

    var body: some View {
        VStack {
            CustomToggle(isOn: $isEnabled, label: "Enable Feature")
            Text("Feature is \(isEnabled ? "enabled" : "disabled")")
        }
    }
}

struct CustomToggleDemo_Previews: PreviewProvider {
    static var previews: some View {
        CustomToggleDemo()
    }
}

Visual Representation:

  • A toggle with a label, and a text view that shows the state.

Unleashing Words: Accepting Multi-Line Text Input with TextEditor

We'll learn how to handle multi-line text input using TextEditor.

Example: Multi-Line Text Input

import SwiftUI

struct MultiLineText: View {
    @State private var text = "Enter your text here..."

    var body: some View {
        TextEditor(text: $text)
            .padding()
            .border(Color.gray, width: 1)
            .frame(height: 200)
    }
}

struct MultiLineText_Previews: PreviewProvider {
    static var previews: some View {
        MultiLineText()
    }
}

Visual Representation:

  • A text editor with a border, allowing multi-line text input.

Data Persistence Made Easy: Introduction to SwiftData and SwiftUI

SwiftData is a powerful data persistence framework that integrates seamlessly with SwiftUI.

Conceptual Overview:

  • SwiftData uses the @Model macro to define data models.
  • It provides a ModelContainer for managing the data store.
  • @Query is used to fetch data from the store.

Movie Magic: Creating Movies with SwiftData

Let's create a simple movie tracking app using SwiftData.

Example: SwiftData Movie Model

import SwiftUI
import SwiftData

@Model
class Movie {
    var title: String
    var director: String
    var year: Int

    init(title: String, director: String, year: Int) {
        self.title = title
        self.director = director
        self.year = year
    }
}

struct MovieList: View {
    @Environment(\.modelContext) var modelContext
    @Query var movies: [Movie]

    var body: some View {
        List(movies) { movie in
            Text(movie.title)
        }
        Button("Add Movie"){
            let movie = Movie(title: "New Movie", director: "New Director", year: 2024)
            modelContext.insert(movie)
        }
    }
}

Visual Representation:

  • A list of movies fetched from SwiftData, and a button to add new movies.

Star Power: Adding a Custom Star Rating Component

We'll create a custom star rating component using SwiftUI.

Example: Star Rating Component

import SwiftUI

struct StarRating: View {
    @Binding var rating: Int
    let maxRating = 5

    var body: some View {
        HStack {
            ForEach(1...maxRating, id: \.self) { index in
                Image(systemName: index <= rating ? "star.fill" : "star")
                    .foregroundColor(.yellow)
                    .onTapGesture {
                        rating = index
                    }
            }
        }
    }
}

struct StarRatingDemo: View {
    @State private var rating = 3

    var body: some View {
        StarRating(rating: $rating)
        Text("Rating: \(rating)")
    }
}

struct StarRatingDemo_Previews: PreviewProvider {
    static var previews: some View {
        StarRatingDemo()
    }
}

Visual Representation:

  • A row of stars that can be tapped to set the rating.

Data Fetching: Building a List with @Query

We'll use @Query to fetch Movie objects from the SwiftData store and display them in a List.

Example: Querying and Displaying Movies

import SwiftUI
import SwiftData

@Model
class Movie {
    var title: String
    var director: String
    var year: Int

    init(title: String, director: String, year: Int) {
        self.title = title
        self.director = director
        self.year = year
    }
}

struct MovieQueryList: View {
    @Environment(\.modelContext) var modelContext
    @Query var movies: [Movie] // Fetch movies using @Query

    var body: some View {
        NavigationStack {
            List(movies) { movie in
                NavigationLink(destination: MovieDetailView(movie: movie)) {
                    VStack(alignment: .leading) {
                        Text(movie.title)
                            .font(.headline)
                        Text("Director: \(movie.director)")
                            .font(.subheadline)
                    }
                }
            }
            .navigationTitle("Movies")
            .toolbar {
                Button("Add Movie") {
                    let newMovie = Movie(title: "New Movie", director: "New Director", year: 2024)
                    modelContext.insert(newMovie)
                }
            }
        }
    }
}

struct MovieDetailView: View {
    let movie: Movie

    var body: some View {
        VStack(alignment: .leading) {
            Text(movie.title).font(.title)
            Text("Director: \(movie.director)")
            Text("Year: \(movie.year)")
        }
        .padding()
    }
}

struct MovieQueryList_Previews: PreviewProvider {
    static var previews: some View {
        MovieQueryList()
            .modelContainer(for: Movie.self) // provide a model container for previews
    }
}

Movie Details: Showing Movie Details

We'll create a view to display detailed information about a movie.

Example: Movie Detail View

import SwiftUI

struct MovieDetailView: View {
    let movie: Movie

    var body: some View {
        VStack(alignment: .leading) {
            Text(movie.title).font(.title)
            Text("Director: \(movie.director)")
            Text("Year: \(movie.year)")
        }
        .padding()
    }
}

struct MovieDetailViewWrapper: View {
    let movie: Movie

    var body: some View {
        NavigationStack {
            MovieDetailView(movie: movie)
        }
    }
}

Ordered Data: Sorting SwiftData Queries Using SortDescriptor

We'll learn how to sort SwiftData queries using SortDescriptor.

Example: Sorting Movies by Year

import SwiftUI
import SwiftData

struct SortedMovieList: View {
    @Environment(\.modelContext) var modelContext
    @Query(sort: \Movie.year) var movies: [Movie]

    var body: some View {
        List(movies) { movie in
            Text("\(movie.title) (\(movie.year))")
        }
    }
}

Data Removal: Deleting from a SwiftData Query

We'll learn how to delete items from a SwiftData query.

Example: Deleting Movies

import SwiftUI
import SwiftData

struct DeletableMovieList: View {
    @Environment(\.modelContext) var modelContext
    @Query var movies: [Movie]

    var body: some View {
        List {
            ForEach(movies) { movie in
                Text(movie.title)
            }
            .onDelete(perform: deleteMovies)
        }
    }

    func deleteMovies(at offsets: IndexSet) {
        for index in offsets {
            modelContext.delete(movies[index])
        }
    }
}

Navigational Control: Using an Alert to Pop a NavigationLink Programmatically

We'll use an alert to programmatically pop a NavigationLink.

Example: Alert-Driven Navigation Pop

import SwiftUI

struct NavigationPop: View {
    @State private var showAlert = false
    @Environment(\.dismiss) var dismiss

    var body: some View {
        VStack {
            Button("Show Alert") {
                showAlert = true
            }
        }
        .alert(isPresented: $showAlert) {
            Alert(title: Text("Confirm"), message: Text("Pop View?"), primaryButton: .default(Text("Yes")) {
                dismiss()
            }, secondaryButton: .cancel())
        }
    }
}

struct NavLinkWrapper: View {
    var body: some View {
        NavigationStack{
            NavigationLink("Go to Next", destination: NavigationPop())
        }
    }
}

🔥 Conclusion

Day 24 has been a deep dive into advanced SwiftUI development. We've covered custom components, multi-line text input, SwiftData, and more. Keep exploring, and you'll be building more complex and feature-rich apps!


Follow me on Linkedin: igatitech 🚀🚀🚀

Comments

Popular posts from this blog

30 Days of SwiftUI — Day 1: Getting Started with SwiftUI

30 Days of SwiftUI Learning Journey: From Zero to Hero!

30 Days of SwiftUI - Day 11: Building Interactive SwiftUI Apps