30 Days of SwiftUI - Day 29: Architecting, Accessing, and Interacting: MVVM, Accessibility, Notifications, and More!


Hello Swift architects! Today, we're diving into some advanced SwiftUI topics, including MVVM architecture, accessibility, notifications, gestures, and layout. Let's get started!

Architecting for Success: Introducing MVVM into Your SwiftUI Project

MVVM (Model-View-ViewModel) is a popular architectural pattern that promotes separation of concerns, making your code more maintainable and testable.

Detailed Explanation:

  • Model:
    • Represents the data and business logic of your application.
    • It's responsible for fetching, storing, and manipulating data.
    • In SwiftUI, models are often plain Swift structs or classes conforming to Codable.
  • View:
    • Represents the user interface.
    • It's responsible for displaying data and handling user interactions.
    • In SwiftUI, views are structs conforming to the View protocol.
    • Views should be as "dumb" as possible, relying on the ViewModel for data and logic.
  • ViewModel:
    • Acts as a bridge between the Model and the View.
    • It transforms data from the Model into a format suitable for the View.
    • It handles user input from the View and updates the Model.
    • In SwiftUI, ViewModels are often @Observable classes, using @Published properties to notify the View of changes.
    • The ViewModel should contain all of the logic required to display and manipulate the data.

Example: MVVM with a Simple Counter

import SwiftUI

// Model
struct CounterModel {
    var count = 0
}

// ViewModel
@Observable
class CounterViewModel {
    private var model = CounterModel()

    var count: Int {
        model.count
    }

    func increment() {
        model.count += 1
    }
}

// View
struct CounterView: View {
    @State private var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            Text("Count: \(viewModel.count)")
                .font(.largeTitle)
            Button("Increment") {
                viewModel.increment()
            }
        }
    }
}

struct CounterView_Previews: PreviewProvider {
    static var previews: some View {
        CounterView()
    }
}

Visual Representation:

  • A text view displaying the count, and a button to increment it.

Inclusive Design: Accessibility Introduction

Accessibility in SwiftUI: Making Apps for Everyone

Accessibility is about designing and developing applications that are usable by people with a wide range of abilities, including those with visual, auditory, motor, and cognitive impairments. In the context of SwiftUI, this means providing alternative ways for users to interact with and understand your app's content.

SwiftUI Code Examples:

Accessibility Labels:

  • Provide descriptive labels for images, buttons, and other UI elements.
import SwiftUI

struct AccessibilityLabelExample: View {
    var body: some View {
        Image(systemName: "speaker.wave.3.fill")
            .resizable()
            .scaledToFit()
            .frame(width: 50, height: 50)
            .accessibilityLabel("Volume Speaker")

        Button(action: {
            // Action
        }) {
            Text("Play Audio")
        }
        .accessibilityLabel("Play Audio Button")
    }
}

struct AccessibilityLabelExample_Previews: PreviewProvider {
    static var previews: some View {
        AccessibilityLabelExample()
    }
}
  • In this example, the image and button have descriptive labels that VoiceOver will read aloud.

Clear Labels: Identifying Views with Useful Labels

We'll learn how to add labels to views for accessibility.

Example: Accessibility Label

import SwiftUI

struct AccessibilityLabel: View {
    var body: some View {
        Image(systemName: "gear")
            .accessibilityLabel("Settings")
    }
}

struct AccessibilityLabel_Previews: PreviewProvider {
    static var previews: some View {
        AccessibilityLabel()
    }
}

Accessibility Control: Hiding and Grouping Accessibility Data

We'll learn how to hide and group accessibility data.

Example: Accessibility Hidden and Grouped

import SwiftUI

struct AccessibilityControl: View {
    var body: some View {
        VStack {
            Text("Hidden Text")
                .accessibilityHidden(true)
            HStack {
                Text("Grouped")
                Text("Elements")
            }
            .accessibilityElement(children: .combine)
            .accessibilityLabel("Grouped Elements")
        }
    }
}

Voice Over Data: Reading the Value of Controls

We'll learn how to provide custom values for VoiceOver.

Example: Accessibility Value

import SwiftUI

struct AccessibilityValue: View {
    @State private var volume = 50.0

    var body: some View {
        Slider(value: $volume, in: 0...100)
            .accessibilityValue("\(Int(volume)) percent")
    }
}

Custom Actions: Adding Custom Row Swipe Actions to a List

We'll learn how to add custom swipe actions to list rows.

Example: List Swipe Actions

import SwiftUI

struct ListSwipeActions: View {
    @State private var items = ["Item 1", "Item 2", "Item 3"]

    var body: some View {
        List {
            ForEach(items, id: \.self) { item in
                Text(item)
                    .swipeActions(edge: .trailing) {
                        Button(role: .destructive) {
                            items.removeAll { $0 == item }
                        } label: {
                            Label("Delete", systemImage: "trash")
                        }
                    }
            }
        }
    }
}

struct ListSwipeActions_Previews: PreviewProvider {
    static var previews: some View {
        ListSwipeActions()
    }
}

Local Alerts: Scheduling Local Notifications

Local notifications allow your app to display alerts, play sounds, and badge your app's icon, even when the app is in the background or closed. This is particularly useful for reminders, alerts, and other time-sensitive information.

Steps to Schedule Local Notifications:

  1. Import the UserNotifications Framework:

    • This framework provides the necessary classes and methods for working with notifications.
  2. Request Authorization:

    • Before scheduling notifications, your app must request permission from the user.
  3. Create a Notification Content Object:

    • This object contains the notification's title, body, sound, and other properties.
  4. Create a Notification Trigger Object:

    • This object specifies when the notification should be delivered (e.g., after a certain time interval, at a specific date, or based on a location).
  5. Create a Notification Request Object:

    • This object combines the content and trigger objects, and it also includes a unique identifier for the notification.
  6. Schedule the Notification:

    • Use the UNUserNotificationCenter to schedule the notification request.

SwiftUI Code Example:

import SwiftUI
import UserNotifications

struct LocalNotificationsView: View {
    var body: some View {
        VStack {
            Button("Schedule Notification") {
                scheduleNotification()
            }
        }
        .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)
            }
        }
    }

    func scheduleNotification() {
        let content = UNMutableNotificationContent()
        content.title = "Reminder"
        content.body = "Don't forget to complete your task!"
        content.sound = UNNotificationSound.default

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) // Deliver in 5 seconds

        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 LocalNotificationsView_Previews: PreviewProvider {
    static var previews: some View {
        LocalNotificationsView()
    }
}

9. Lock Screen Notifications: Posting Notifications to the Lock Screen

We'll learn how to post notifications to the lock screen.

import SwiftUI
import UserNotifications

struct LockScreenNotificationsView: View {
    var body: some View {
        VStack {
            Button("Schedule Lock Screen Notification") {
                scheduleLockScreenNotification()
            }
        }
        .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)
            }
        }
    }

    func scheduleLockScreenNotification() {
        let content = UNMutableNotificationContent()
        content.title = "Important Reminder"
        content.body = "This notification will appear on the lock screen."
        content.sound = UNNotificationSound.default

        // Configure the notification to show on the lock screen
        // By default, notifications will appear on the lock screen if allowed by the user.
        // You can further customize this with interruption level and relevance score (iOS 15+)
        if #available(iOS 15.0, *) {
            content.interruptionLevel = .active // Or .passive, .timeSensitive, .critical
            //content.relevanceScore = 1.0 // Higher score = more important (0.0 to 1.0)
        }

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) // Deliver in 5 seconds

        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("Lock screen notification scheduled")
            }
        }
    }
}

struct LockScreenNotificationsView_Previews: PreviewProvider {
    static var previews: some View {
        LockScreenNotificationsView()
    }
}

User Interaction: How to Use Gestures in SwiftUI

We'll learn how to use gestures in SwiftUI.

Example: Tap Gesture

import SwiftUI

struct TapGestureDemo: View {
    @State private var tapCount = 0

    var body: some View {
        Text("Tap Count: \(tapCount)")
            .onTapGesture {
                tapCount += 1
            }
    }
}

struct TapGestureDemo_Previews: PreviewProvider {
    static var previews: some View {
        TapGestureDemo()
    }
}

Precise Placement: Absolute Positioning for SwiftUI Views

We'll learn how to use absolute positioning for views.

Example: Absolute Positioning

import SwiftUI

struct AbsolutePositioning: View {
    var body: some View {
        ZStack(alignment: .topLeading) {
            Color.blue.frame(width: 200, height: 200)
            Text("Top Left")
                .position(x: 50, y: 50)
        }
    }
}

struct AbsolutePositioning_Previews: PreviewProvider {
    static var previews: some View {
        AbsolutePositioning()
    }
}

🔥 Conclusion

Day 29 has been a deep dive into advanced SwiftUI topics. We've explored MVVM architecture, accessibility, notifications, gestures, and layout. These skills will empower you to build more robust and user-friendly apps. Keep experimenting and building!


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