30 Days of SwiftUI - Day 22: SwiftUI Challenge Day: Data Filtering, Grids, and Navigation Mastery!


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 iExpense, grids, and navigation.

Practical Tests

Test 1: Enhanced iExpense

Question: Create a SwiftUI view that displays a list of numbers from 1 to 20. Add a Toggle that allows the user to filter the list to show only even numbers.

Test 2: Dynamic Grid with Image Loading

Question: Create a SwiftUI view with a LazyVGrid that displays images from an array of image URLs. Load the images asynchronously and display a placeholder while the images are loading.

Test 3: Navigation with Path Persistence

Question: Create a navigation stack that allows users to navigate to detail views based on strings and integers. Implement the ability to save and load the navigation path using UserDefaults and Codable.

Answers and Complete SwiftUI Code

Answer 1: Data Filtering

import SwiftUI

struct DataFilterTest: View {
    @State private var showEvenNumbers = false

    var body: some View {
        VStack {
            Toggle("Show Even Numbers", isOn: $showEvenNumbers)
                .padding()

            List {
                ForEach(1...20, id: \.self) { number in
                    if !showEvenNumbers || number.isMultiple(of: 2) {
                        Text("\(number)")
                    }
                }
            }
        }
    }
}

struct DataFilterTest_Previews: PreviewProvider {
    static var previews: some View {
        DataFilterTest()
    }
}
Answer 2: Dynamic Grid with Image Loading
import SwiftUI

struct ImageGrid: View {
    let imageURLs = [
        "https://source.unsplash.com/random/200x200",
        "https://source.unsplash.com/random/201x201",
        "https://source.unsplash.com/random/202x202",
        "https://source.unsplash.com/random/203x203"
    ]

    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible())
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 10) {
                ForEach(imageURLs, id: \.self) { urlString in
                    AsyncImage(url: URL(string: urlString)) { image in
                        image.resizable().scaledToFit()
                    } placeholder: {
                        ProgressView()
                    }
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 100)
                    .background(Color.gray.opacity(0.3))
                    .cornerRadius(8)
                }
            }
            .padding()
        }
    }
}

struct ImageGrid_Previews: PreviewProvider {
    static var previews: some View {
        ImageGrid()
    }
}

Answer 3: Navigation with Path Persistence

import SwiftUI

struct NavigationPathData: Codable {
    let path: [AnyCodable]
}

struct AnyCodable: Codable {
    let value: Any

    init<T: Codable>(_ value: T) {
        self.value = value
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let intValue = try? container.decode(Int.self) {
            self.value = intValue
        } else if let stringValue = try? container.decode(String.self) {
            self.value = stringValue
        } else {
            throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unsupported type"))
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        if let intValue = value as? Int {
            try container.encode(intValue)
        } else if let stringValue = value as? String {
            try container.encode(stringValue)
        }
    }
}

struct NavigationPersistenceTest: View {
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            VStack {
                Button("Push String") {
                    path.append("Hello")
                }
                Button("Push Number") {
                    path.append(42)
                }
                Button("Save Path") {
                    savePath()
                }
                Button("Load Path") {
                    loadPath()
                }
            }
            .navigationTitle("Main View")
            .navigationDestination(for: String.self) { text in
                Text("String: \(text)")
            }
            .navigationDestination(for: Int.self) { number in
                Text("Number: \(number)")
            }
        }
    }

    func savePath() {
        let codablePath = NavigationPathData(path: path.map { AnyCodable($0 as! Codable) })
        if let encoded = try? JSONEncoder().encode(codablePath) {
            UserDefaults.standard.set(encoded, forKey: "navigationPath")
        }
    }

    func loadPath() {
        if let data = UserDefaults.standard.data(forKey: "navigationPath") {
            if let decoded = try? JSONDecoder().decode(NavigationPathData.self, from: data) {
                path = NavigationPath(decoded.path.map { $0.value })
            }
        }
    }
}

struct NavigationPersistenceTest_Previews: PreviewProvider {
    static var previews: some View {
        NavigationPersistenceTest()
    }
}

Key Takeaways

  • iExpense Enhancement: We added filtering to the iExpense app, demonstrating practical data manipulation.
  • Dynamic Grids: We created a dynamic grid with asynchronous image loading, showcasing how to handle external data.
  • Navigation Persistence: We implemented navigation path persistence, reinforcing how to save and load 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