30 Days of SwiftUI - Day 26: SwiftUI Challenge Day: Networking, SwiftData, and More!


Hello Swift learners! Today, we're putting our knowledge to the test with some practical questions based on what we've learned in the past few days. Let's sharpen our skills and solidify our understanding of networking, SwiftData, and other advanced techniques.

Practical Tests

Test 1: Network Request with Form Validation

Question: Create a SwiftUI view that fetches user data from the JSONPlaceholder API. Include a form with username and email fields. Validate the form, and only enable the submit button when both fields are valid. When the submit button is pressed, print a message with the fetched user data.

Test 2: SwiftData Movie Editor with Filtering 

Question: Enhance the SwiftData movie editor from Day 24 by adding a filter that allows users to view movies based on their release year. Include a Stepper to dynamically change the filter year and update the @Query results.

Test 3: SwiftData Movie Relationships and Deletion

Question: Extend the SwiftData movie app to include an Actor model. Create a relationship between Movie and Actor. Implement a feature to delete a movie along with its associated actors.

Answers and Complete SwiftUI Code

Answer 1: Network Request with Form Validation

import SwiftUI

struct User: Codable, Identifiable {
    let id: Int
    let name: String
    let username: String
    let email: String
}

struct NetworkFormTest: View {
    @State private var users = [User]()
    @State private var username = ""
    @State private var email = ""

    var isFormValid: Bool {
        !username.isEmpty && email.contains("@")
    }

    var body: some View {
        VStack {
            List(users) { user in
                VStack(alignment: .leading) {
                    Text(user.name).font(.headline)
                    Text(user.email)
                }
            }

            Form {
                TextField("Username", text: $username)
                TextField("Email", text: $email)
                Button("Submit") {
                    if let user = users.first(where: {$0.username == username && $0.email == email}){
                        print("User data: \(user)")
                    } else {
                        print("User not found.")
                    }
                }
                .disabled(!isFormValid)
            }
        }
        .onAppear(perform: loadData)
    }

    func loadData() {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else {
            print("Invalid URL")
            return
        }

        URLSession.shared.dataTask(with: url) { data, response, error in
            if let data = data {
                if let decodedUsers = try? JSONDecoder().decode([User].self, from: data) {
                    DispatchQueue.main.async {
                        self.users = decodedUsers
                    }
                    return
                }
            }
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
        }.resume()
    }
}

struct NetworkFormTest_Previews: PreviewProvider {
    static var previews: some View {
        NetworkFormTest()
    }
}

Answer 2: SwiftData Movie Editor with Filtering

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 FilteredMovieEditor: View {
    @Environment(\.modelContext) var modelContext
    @State private var filterYear = 2000
    @Query var movies: [Movie]

    var filteredMovies: [Movie] {
        movies.filter { $0.year > filterYear }
    }

    var body: some View {
        VStack {
            Stepper("Filter Year: \(filterYear)", value: $filterYear)
            List(filteredMovies) { movie in
                NavigationLink(destination: EditableMovieDetail(movie: movie)) {
                    Text("\(movie.title) (\(movie.year))")
                }
            }
        }
    }
}

struct EditableMovieDetail: View {
    @Bindable var movie: Movie

    var body: some View {
        Form {
            TextField("Title", text: $movie.title)
            TextField("Director", text: $movie.director)
            Stepper("Year: \(movie.year)", value: $movie.year)
        }
        .padding()
    }
}

struct FilteredMovieEditor_Previews: PreviewProvider {
    static var previews: some View {
        FilteredMovieEditor()
            .modelContainer(for: Movie.self)
    }
}

Answer 3: SwiftData Movie Relationships and Deletion

import SwiftUI
import SwiftData

@Model
class Movie {
    var title: String
    @Relationship(deleteRule: .cascade) var actors: [Actor]? = []
}

@Model
class Actor {
    var name: String
    var age: Int
}

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

    var body: some View {
        List {
            ForEach(movies) { movie in
                VStack(alignment: .leading) {
                    Text(movie.title).font(.headline)
                    if let actors = movie.actors, !actors.isEmpty {
                        Text("Actors: \(actors.map { $0.name }.joined(separator: ", "))")
                    }
                }
            }
            .onDelete(perform: deleteMovies)
        }
    }

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

struct MovieActorDeletion_Previews: PreviewProvider {
    static var previews: some View {
        MovieActorDeletion()
            .modelContainer(for: Movie.self, configurations: ModelConfiguration())
    }
}

Key Takeaways

  • Networking and Forms: We combined network requests with form validation, demonstrating how to handle external data and user input.
  • SwiftData Filtering: We enhanced the SwiftData movie editor with dynamic filtering, showcasing how to manipulate data based on user input.
  • SwiftData Relationships: We implemented movie relationships and deletion, reinforcing how to manage complex data structures.

By working through these practical tests, you've strengthened your understanding of key SwiftUI concepts. Keep practicing, and you'll be building more complex and engaging apps in no time!


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