30 Days of SwiftUI - Day 30: SwiftUI Challenge Day: Image Magic, Maps, and Local Notification!


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 image processing, maps, and advanced techniques.

Practical Tests

Test 1: Image Filter with Customization

Question: Create a SwiftUI view that loads an image from the user's photo library using PhotosPicker. Apply a CIGaussianBlur filter to the image. Allow the user to adjust the blur radius using a Slider within a confirmationDialog.

Test 2: Map with Editable Annotations

Question: Create a SwiftUI view that displays a map with annotations. Allow the user to select an annotation. When an annotation is selected, display a sheet with text fields to edit the annotation's latitude and longitude. Update the annotation's position on the map when the user saves the changes.

Test 3: MVVM with Local Notifications

Question: Create a simple task list app using MVVM. The ViewModel should manage the list of tasks and handle adding and deleting tasks. Add a button that schedules a local notification to remind the user of an upcoming task.

Answers and Complete SwiftUI Code

Answer 1: Image Filter with Customization

import SwiftUI
import PhotosUI
import CoreImage
import CoreImage.CIFilterBuiltins

struct ImageFilterTest: View {
    @State private var selectedImage: UIImage?
    @State private var selectedItem: PhotosPickerItem?
    @State private var processedImage: Image?
    @State private var showFilterOptions = false
    @State private var blurRadius: Double = 5.0

    var body: some View {
        VStack {
            if let image = processedImage {
                image.resizable().scaledToFit().frame(width: 200, height: 200)
            }
            PhotosPicker("Select Photo", selection: $selectedItem, matching: .images)
                .onChange(of: selectedItem) { _, newValue in
                    Task {
                        if let data = try? await newValue?.loadTransferable(type: Data.self), let uiImage = UIImage(data: data) {
                            selectedImage = uiImage
                            applyFilter()
                        }
                    }
                }
            Button("Filter Options") {
                showFilterOptions = true
            }
        }
        .confirmationDialog("Filter Options", isPresented: $showFilterOptions) {
            Slider(value: $blurRadius, in: 0...20) {
                Text("Blur Radius")
            }
            Button("Apply") {
                applyFilter()
            }
            Button("Cancel", role: .cancel) {}
        } message: {
            Text("Adjust blur radius")
        }
    }

    func applyFilter() {
        guard let inputImage = selectedImage, let ciImage = CIImage(image: inputImage) else { return }

        let filter = CIFilter.gaussianBlur()
        filter.setValue(ciImage, forKey: kCIInputImageKey)
        filter.setValue(blurRadius, forKey: kCIInputRadiusKey)

        if let outputCIImage = filter.outputImage, let cgImage = CIContext().createCGImage(outputCIImage, from: outputCIImage.extent) {
            processedImage = Image(cgImage, scale: inputImage.scale, orientation: .up)
        }
    }
}

struct ImageFilterTest_Previews: PreviewProvider {
    static var previews: some View {
        ImageFilterTest()
    }
}

Answer 2: Map with Editable Annotations

import SwiftUI
import MapKit

struct EditableAnnotation: Identifiable {
    let id = UUID()
    var coordinate: CLLocationCoordinate2D
}

struct MapAnnotationEditTest: View {
    @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194), span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
    @State private var annotations = [
        EditableAnnotation(coordinate: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)),
        EditableAnnotation(coordinate: CLLocationCoordinate2D(latitude: 37.7858, longitude: -122.4065))
    ]
    @State private var selectedAnnotation: EditableAnnotation?
    @State private var editLatitude = ""
    @State private var editLongitude = ""

    var body: some View {
        Map(coordinateRegion: $region, annotationItems: annotations, selection: $selectedAnnotation) { annotation in
            MapMarker(coordinate: annotation.coordinate)
        }
        .sheet(item: $selectedAnnotation) { annotation in
            VStack {
                TextField("Latitude", text: $editLatitude)
                TextField("Longitude", text: $editLongitude)
                Button("Save Changes") {
                    if let latitude = Double(editLatitude), let longitude = Double(editLongitude) {
                        if let index = annotations.firstIndex(where: {$0.id == annotation.id}) {
                            annotations[index].coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
                        }
                        selectedAnnotation = nil
                    }
                }
                .padding()
            }
            .onAppear {
                editLatitude = "\(annotation.coordinate.latitude)"
                editLongitude = "\(annotation.coordinate.longitude)"
            }
        }
    }
}

struct MapAnnotationEditTest_Previews: PreviewProvider {
    static var previews: some View {
        MapAnnotationEditTest()
    }
}

Answer 3: MVVM with Local Notifications

import SwiftUI
import UserNotifications

struct Task: Identifiable {
    let id = UUID()
    var title: String
}

@Observable
class TaskViewModel {
    var tasks = [Task]()

    func addTask(title: String) {
        tasks.append(Task(title: title))
    }

    func deleteTask(at offsets: IndexSet) {
        tasks.remove(atOffsets: offsets)
    }

    func scheduleNotification(taskTitle: String) {
        let content = UNMutableNotificationContent()
        content.title = "Task Reminder"
        content.body = "Don't forget: \(taskTitle)"
        content.sound = UNNotificationSound.default

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)

        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

        UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
                print("Error scheduling notification: \(error.localizedDescription)")
            } else {
                print("Notification scheduled")
            }
        }
    }
}

struct TaskListView: View {
    @State private var viewModel = TaskViewModel()
    @State private var newTaskTitle = ""

    var body: some View {
        VStack {
            TextField("New Task", text: $newTaskTitle)
            Button("Add Task") {
                viewModel.addTask(title: newTaskTitle)
                viewModel.scheduleNotification(taskTitle: newTaskTitle)
                newTaskTitle = ""
            }
            List {
                ForEach(viewModel.tasks) { task in
                    Text(task.title)
                }
                .onDelete(perform: viewModel.deleteTask)
            }
        }
        .onAppear(perform: requestAuthorization)
    }

    func requestAuthorization() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
            if success {
                print("Authorization granted!")
            } else if let error = error {
                print(error.localizedDescription)
            }
        }
    }
}

struct TaskListView_Previews: PreviewProvider {
    static var previews: some View {
        TaskListView()
    }
}

Key Takeaways

  • Image Processing: We combined photo library access with Core Image filtering, allowing for user customization.
  • Map Annotations: We implemented editable map annotations, demonstrating how to interact with map data.
  • MVVM and Notifications: We built a task list app using MVVM and added local notifications, showcasing how to combine architectural patterns with system features.

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!


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