如何处理 SwiftUI 中的加载屏幕?

How to handle loading screens in SwiftUI?

我正在尝试在 SwiftUI 上实现一个简单的三屏应用程序。第一个屏幕允许用户 select 一张照片,第二个屏幕必须在分析照片时显示加载屏幕,第三个显示分析结果。目前,我的应用程序根本不显示加载屏幕,只是跳转到第三个屏幕。

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)
            }
        }
}}

无论我如何更改屏幕之间的切换,它似乎都没有反应,并且视图不会立即切换。我究竟做错了什么?在图像 selected 之后,是否有正确的方法来显示加载屏幕?

似乎您缺少一个 else 来避免呈现您不想要的屏幕:

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)
    }
}}

但是,一般来说,您想要的也称为“状态机”——基本上您的应用可以处于 3 种可能状态之一:选择、加载、调整,因此您应该有一个枚举 @State表示当前状态,而不是 3 个布尔值:

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)
    }
  }

当您想切换到下一个屏幕时,只需更改state值...

原来,主要的性能问题是由图像类型转换引起的:

let uiImage: UIImage = image.asUIImage()

首先将图像作为 UIImage 传递后,应用程序开始快速运行,因此无需首先处理加载。