Implementing the ImageController

Implementing the ImageController 💡

For the user to edit his photos later, we need a place where we load, edit, and export the images. Therefore, we create a new Swift file called “ImageController”, import the SwiftUI framework, and declare an ObservableObject named “ImageController”.

import SwiftUI

class ImageController: ObservableObject {
    
}

Our ImageController should manage three different variables for holding two different image versions. We need to manage the original image (the one that the user has selected and not yet edited) and the image that is currently displayed in the app (possibly with a filter applied).

First, we declare a variable “unprocessedImage” holding an Optional UIImage.

class ImageController: ObservableObject {
    
    var unprocessedImage: UIImage?
    
}

Hint: You might wonder why we initialize the image as an UIImage and not as a regular SwiftUI Image. We do this because it is not possible to process SwiftUI Images using the CoreImage framework directly. You could convert a SwiftUI Image to an UIImage for editing purposes and output it as a SwiftUI Image again. However, it is not possible to convert SwiftUI Images to UIImages. Because we can only convert UIImages to SwiftUI Images, we initialize the image to be edited directly as an UIIimage and convert it to a SwiftUI Image after the editing process is finished.

Next, we create another variable displayedImage, also holding an UIImage as an Optional.

var displayedImage: UIImage?

In our Photo_EditorApp struct, we can now initialize the ImageController as a @StateObject. We inject it into the view hierarchy by passing it to the initial ContentView as a .environmentObject.

@main
struct Photo_EditorApp: App {
    
    @StateObject var imageController = ImageController()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(imageController)
        }
    }
}

Now, we just need to use the @EnvironmentObject property wrapper to access this imageController from our ContentView.

struct ContentView: View {
    
    @EnvironmentObject var imageController: ImageController
    
    var body: some View {
        //...
    }
}

We also provide our ContentView_Previews with a ImageController instance.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(ImageController())
    }
}

Updating our ContentView and ThumbnailView

Our ContentView should use the displayedImage of the imageController instead of our “testImage” asset. Since this is an Optional, it is possible that no UIImage is assigned to it or, in other words, that the displayedImage is nil. This is especially the case if the user has not yet selected an image from his gallery.

So we have to make sure that the displayedImage is not nil. Only then do we want to initialize the Image view and the ScrollView in our VStack with referring to the displayedImage of our imageController. Therefore, we write:

VStack {
    if let imageToDisplay = imageController.displayedImage {
        Image(uiImage: imageToDisplay)
            //...
        ScrollView(.horizontal, showsIndicators: false) {
            //...
        }
            //...
    }
}

If there is no displayedImage available, we want to show the user a Text with the hint that he has to select an image from his gallery first.

VStack {
    if let imageToDisplay = imageController.displayedImage {
        //...
    } else {
        Spacer()
        Text("Upload a photo to start editing.")
            .frame(width: geometry.size.width, height: geometry.size.height*0.25)
        Spacer()
    }
}

We want to use the displayedImage of the imageController also for our ThumbnailView. Therefore we add a property named “imageToDisplay” to it. 

struct ThumbnailView: View {
    
    let imageToDisplay: UIImage
    
    //..
    
    var body: some View {
        VStack {
            //...
            Image(uiImage: imageToDisplay)
                //...
        }
            //...
    }
}

Now we can use the imageToDisplay in our if-statement as we initialize the ThumbnailView in our ContentView.

HStack {
    ThumbnailView(imageToDisplay: imageToDisplay, width: geometry.size.width*(21/100), height: geometry.size.height*(15/100), filterName: "Original")
}

Next, we will learn how to use a PHPicker to retrieve an image from the user’s gallery.

Leave a Reply

Your email address will not be published. Required fields are marked *