skip to Main Content

I am trying to implement a simple 3-screen application on SwiftUI. The 1st screen allows the user to select a photo, the 2nd must show the loading screen while the photo is analyzed, and the 3rd shows analysis results. Currently, my application doesn’t show the loading screen at all and just jumps to the 3rd screen.

ContentView.swift:

struct ContentView: View {
@State var image: Image = Image("MyImageName")
@State var tiles: [Tile] = []
@State var isSelectionView: Bool = true
@State var isLoadingView: Bool = false
@State var isAdjustmentView: Bool = false

var body: some View {
    if (isSelectionView) {
        SelectionView(image: $image, isSelectionView: $isSelectionView, isLoadingView: $isLoadingView)
    }
    if (isLoadingView) {
        LoadingMLView(image: $image, tiles: $tiles, isLoadingView: $isLoadingView, isAdjustmentView: $isAdjustmentView)
    }
    if (isAdjustmentView) {
        AdjustmentView(tiles: $tiles)
    }
}}

SelectionView.swift:

struct SelectionView: View {
@State var showCaptureImageView = false
@State var showPopover = false
@State var sourceType: UIImagePickerController.SourceType = .camera
@State var imageSelected = false
@Binding var image: Image
@Binding var isSelectionView: Bool
@Binding var isLoadingView: Bool

var body: some View {
    VStack() {
        if (!showCaptureImageView && !imageSelected) {
            Spacer()
            Button(action: {
                showPopover.toggle()
            }) {
                Text("Choose photo")
                    .frame(width: 100, height: 100)
                    .foregroundColor(Color.white)
                    .background(Color.blue)
                    .clipShape(Circle())
            }
            .confirmationDialog("Select location", isPresented: $showPopover) {
                Button("Camera") {
                    sourceType = .camera
                    showPopover.toggle()
                    showCaptureImageView.toggle()
                }
                Button("Photos") {
                    sourceType = .photoLibrary
                    showPopover.toggle()
                    showCaptureImageView.toggle()
                }
            }
        } else if (showCaptureImageView) {
            CaptureImageView(isShown: $showCaptureImageView, image: $image, sourceType: $sourceType)
        } else {
            ActivityIndicator()
                .frame(width: 200, height: 200)
                .foregroundColor(.blue)
        }
    }
    .onChange(of: image) { image in
        showCaptureImageView.toggle()
        imageSelected.toggle()
        isSelectionView.toggle()
        isLoadingView.toggle()
    }
    
}}

LoadingMLView.swift:

struct LoadingMLView: View {
@Binding var image: Image
@Binding var tiles: [Tile]
@Binding var isLoadingView: Bool
@Binding var isAdjustmentView: Bool

var body: some View {
    ActivityIndicator()
        .frame(width: 200, height: 200)
        .foregroundColor(.blue)
        .onAppear {
            do {
                let model = try VNCoreMLModel(for: MyModel().model)
                let request = VNCoreMLRequest(model: model, completionHandler: myResultsMethod)
                let uiImage: UIImage = image.asUIImage()
                guard let ciImage = CIImage(image: uiImage) else {
                    fatalError("Unable to create (CIImage.self) from (image).")
                  }
                let handler = VNImageRequestHandler(ciImage: ciImage)
                
                do {
                    try handler.perform([request])
                } catch {
                    print("Something went wrong!")
                }
                func myResultsMethod(request: VNRequest, error: Error?) {
                    guard let results = request.results as? [VNRecognizedObjectObservation]
                    else { fatalError("error") }
                    for result in results {
                        let tile = Tile(name: result.labels[0].identifier)
                        tiles.append(tile)
                    }
                    isLoadingView.toggle()
                    isAdjustmentView.toggle()
                }

            } catch {
                print("Error:", error)
            }
        }
}}

No matter how I change the toggle between screens, it just doesn’t seem reactive and the views don’t switch momentarily. What am I doing wrong? Is there a proper way to show loading screen right after the image is selected?

2

Answers


  1. Chosen as BEST ANSWER

    Turns out, the main performance issue was caused by the image type conversion:

    let uiImage: UIImage = image.asUIImage()
    

    After passing the image as UIImage in the first place, the app started working fast, so there was no need to handle the loading in the first place.


  2. Seems like you’re missing an else to avoid rendering the screens you don’t want:

    var body: some View {
        if (isSelectionView) {
            SelectionView(image: $image, isSelectionView: $isSelectionView, isLoadingView: $isLoadingView)
        } else if (isLoadingView) {
            LoadingMLView(image: $image, tiles: $tiles, isLoadingView: $isLoadingView, isAdjustmentView: $isAdjustmentView)
        } else if (isAdjustmentView) {
            AdjustmentView(tiles: $tiles)
        }
    }}
    

    But, in general what you want is also called "state machine" – essentially your app can be in one of 3 possible states: selection, loading, adjustment, so you should have an enum @State to represent the current state, instead of the 3 bool values:

    struct ContentView: View {
      // ...
      enum AppState { case selection, loading, adjustment }
      @State private var state = AppState.selection
      
      var body: some View {
        switch state {
          case .selection:
            SelectionView(image: $image, state: $state)
          case .loading:
            LoadingMLView(image: $image, tiles: $tiles, state: $state)
          case .adjustment:
            AdjustmentView(tiles: $tiles)
        }
      }
    

    When you want to switch to the next screen, just change state value…

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search