30 Days of SwiftUI - Day 27: Power Wrappers, Image Magic, and User Engagement: Advanced SwiftUI Techniques


Hello Swift innovators! Today, we're diving into some advanced SwiftUI topics, including property wrappers, image processing, and user engagement. Let's get started!

The Magic Behind: How Property Wrappers Become Structs

Property wrappers are a powerful feature in Swift that allow you to add extra functionality to properties. They're essentially structs that wrap a property and provide custom behavior.

Explanation:

  • Property wrappers provide a layer of abstraction between the property and its underlying storage.
  • They can perform tasks like validation, observation, and data transformation.
  • SwiftUI uses property wrappers extensively for state management (@State, @Binding, @Environment, etc.).
  • Under the hood, property wrappers are structs that conform to the propertyWrapper protocol and have a wrappedValue property.

Example (Conceptual):

@propertyWrapper
struct Clamped<Value: Comparable> {
    var value: Value
    let range: ClosedRange<Value>

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }

    var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }
}

struct ClampedValueDemo {
    @Clamped(0...100) var number = 50
}

Reactive Updates: Responding to State Changes Using onChange()

onChange() allows you to perform actions when a state variable changes.

Example: Responding to Text Field Changes

import SwiftUI

struct ChangeResponder: View {
    @State private var text = ""

    var body: some View {
        TextField("Enter text", text: $text)
            .onChange(of: text) { oldValue, newValue in
                print("Text changed from \(oldValue) to \(newValue)")
            }
            .padding()
    }
}

struct ChangeResponder_Previews: PreviewProvider {
    static var previews: some View {
        ChangeResponder()
    }
}

Visual Representation:

  • A text field that prints changes to the console.

User Choice: Showing Multiple Options with confirmationDialog()

confirmationDialog() allows you to present multiple options to the user in a dialog.

Example: Confirmation Dialog

import SwiftUI

struct ConfirmationDialogDemo: View {
    @State private var showDialog = false
    @State private var selectedOption = "None"

    var body: some View {
        Button("Show Dialog") {
            showDialog = true
        }
        .confirmationDialog("Select an option", isPresented: $showDialog) {
            Button("Option 1") { selectedOption = "Option 1" }
            Button("Option 2") { selectedOption = "Option 2" }
            Button("Cancel", role: .cancel) { }
        } message: {
            Text("Please select an option.")
        }
        Text("Selected: \(selectedOption)")
    }
}

struct ConfirmationDialogDemo_Previews: PreviewProvider {
    static var previews: some View {
        ConfirmationDialogDemo()
    }
}

Visual Representation:

  • A button that shows a confirmation dialog with options.

Image Processing: Integrating Core Image with SwiftUI

Core Image allows you to apply filters and effects to images.

Example: Core Image Filter

import SwiftUI
import CoreImage
import CoreImage.CIFilterBuiltins

struct CoreImageDemo: View {
    @State private var inputImage: UIImage?
    @State private var processedImage: Image?

    var body: some View {
        VStack {
            if let image = processedImage {
                image.resizable().scaledToFit().frame(width: 200, height: 200)
            }
            Button("Load Image") {
                inputImage = UIImage(named: "ExampleImage") // Replace with your image
                applyFilter()
            }
        }
    }

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

        let filter = CIFilter.sepiaTone()
        filter.setValue(ciImage, forKey: kCIInputImageKey)
        filter.setValue(0.5, forKey: kCIInputIntensityKey)

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

struct CoreImageDemo_Previews: PreviewProvider {
    static var previews: some View {
        CoreImageDemo()
    }
}

Visual Representation:

  • An image with a sepia tone filter applied.

Empty State Handling: Showing Empty States with ContentUnavailableView

ContentUnavailableView provides a standard way to display empty states in your app.

Example: Empty State View

import SwiftUI

struct EmptyStateDemo: View {
    @State private var items: [String] = []

    var body: some View {
        if items.isEmpty {
            ContentUnavailableView("No Items", systemImage: "tray.fill", description: Text("Add items to see them here."))
        } else {
            List(items, id: \.self) { item in
                Text(item)
            }
        }
    }
}

struct EmptyStateDemo_Previews: PreviewProvider {
    static var previews: some View {
        EmptyStateDemo()
    }
}

Visual Representation:

  • An empty state view with a message and icon when the list is empty.

Photo Access: Loading Photos from the User's Photo Library

We'll learn how to access the user's photo library using PhotosPicker.

Example: Photos Picker

import SwiftUI
import PhotosUI

struct PhotoPickerDemo: View {
    @State private var selectedImage: UIImage?
    @State private var selectedItem: PhotosPickerItem?

    var body: some View {
        VStack {
            if let image = selectedImage {
                Image(uiImage: image).resizable().scaledToFit().frame(width: 200, height: 200)
            }
            PhotosPicker("Select Photo", selection: $selectedItem, matching: .images)
            .onChange(of: selectedItem) { oldValue, newValue in
                Task {
                    if let data = try? await newValue?.loadTransferable(type: Data.self), let uiImage = UIImage(data: data) {
                        selectedImage = uiImage
                    }
                }
            }
        }
    }
}

struct PhotoPickerDemo_Previews: PreviewProvider {
    static var previews: some View {
        PhotoPickerDemo()
    }
}

Visual Representation:

  • A button that opens the photo picker, and an image view that displays the selected photo.

Content Sharing: How to Let the User Share Content with ShareLink

ShareLink allows users to easily share content from your app.

Example: Share Link

import SwiftUI

struct ShareLinkDemo: View {
    let textToShare = "Check out this awesome app!"

    var body: some View {
        ShareLink(item: textToShare) {
            Label("Share", systemImage: "square.and.arrow.up")
        }
    }
}

struct ShareLinkDemo_Previews: PreviewProvider {
    static var previews: some View {
        ShareLinkDemo()
    }
}

Visual Representation:

  • A share button that opens the share sheet.

App Store Reviews: How to Ask the User to Leave an App Store Review

You can prompt users to leave a review using SKStoreReviewController.

Conceptual Example:

import StoreKit

func requestReview() {
    if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
        SKStoreReviewController.requestReview(in: scene)
    }
}

Filter Customization: Customizing Our Filter Using confirmationDialog()

We'll learn how to allow users to customize Core Image filters using confirmationDialog().

Example: Customizable Filter

import SwiftUI
import CoreImage
import CoreImage.CIFilterBuiltins

struct CustomizableFilter: View {
    @State private var inputImage: UIImage?
    @State private var processedImage: Image?
    @State private var showFilterOptions = false
    @State private var filterIntensity: Double = 0.5

    var body: some View {
        VStack {
            if let image = processedImage {
                image.resizable().scaledToFit().frame(width: 200, height: 200)
            }
            Button("Load Image") {
                inputImage = UIImage(named: "ExampleImage") // Replace with your image
                applyFilter()
            }
            Button("Filter Options") {
                showFilterOptions = true
            }
        }
        .confirmationDialog("Filter Options", isPresented: $showFilterOptions) {
            Slider(value: $filterIntensity, in: 0...1) {
                Text("Intensity")
            }
            Button("Apply") {
                applyFilter()
            }
            Button("Cancel", role: .cancel) {}
        } message: {
            Text("Adjust filter intensity")
        }
    }

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

        let filter = CIFilter.sepiaTone()
        filter.setValue(ciImage, forKey: kCIInputImageKey)
        filter.setValue(filterIntensity, forKey: kCIInputIntensityKey)

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

struct CustomizableFilter_Previews: PreviewProvider {
    static var previews: some View {
        CustomizableFilter()
    }
}

Visual Representation:

  • An image with a filter that can be customized using a slider in a confirmation dialog.

Image Sharing: Sharing an Image Using ShareLink

We'll learn how to share an image using ShareLink.

Example: Sharing an Image

import SwiftUI

struct ImageShareLink: View {
    @State private var image: Image?

    var body: some View {
        VStack {
            if let image = image {
                image.resizable().scaledToFit().frame(width: 200, height: 200)
                ShareLink(item: image, preview: SharePreview("Image", image: image))
            } else {
                Text("Load an image to share")
            }
            Button("Load Example Image"){
                image = Image("ExampleImage")
            }
        }
    }
}

struct ImageShareLink_Previews: PreviewProvider {
    static var previews: some View {
        ImageShareLink()
    }
}

Visual Representation:

  • An image view with a share button that opens the share sheet.

🔥 Conclusion

Day 27 has been a deep dive into advanced SwiftUI techniques. We've explored property wrappers, image processing with Core Image, user interaction with confirmationDialog(), photo library access, and content sharing with ShareLink. These skills will empower you to build more sophisticated and engaging 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