Home > Net >  Xcode error: The compiler is unable to type-check this expression in reasonable time; try breaking u
Xcode error: The compiler is unable to type-check this expression in reasonable time; try breaking u

Time:03-03

When using Xcode 13.2.1 and SwiftUI to implement a simple slideshow, I am hitting a compile-time error in which Xcode takes about 5 minutes on my M1 to decide it cannot parse my code, and eventually gives me the error:

The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions

I narrowed it down to the NavigationLink line near the bottom. If I comment that out, it compiles quickly with only a warning.

The following is my Minimal, Reproducible Example:

import SwiftUI
import Foundation

enum MarkerType: Double {
    case unlabeled = -99
    case end = -4
    case start  = -3
    case stop = -2
    case blank = -1
    case image = 1
}

class LabeledImage {
    let image: Image
    let marker: Double
    var appeared = false
    
    init(image: Image, marker: Double) {
        self.image = image
        self.marker = marker
    }
}

struct SlideShow {
    private let maxImages: Int = 10000
    var images = [LabeledImage]()
    var labels = [String]()
    var totalImages: Int { return self.images.count }
    private var fromFolder: URL
    
    init(fromURL: URL = Bundle.main.bundleURL.appendingPathComponent("Contents/Resources/DefaultImages")) {
        self.fromFolder = fromURL
    }
}

class AppState: ObservableObject {
    static var docDir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    @Published var isMainMenuActive = false
    @Published var loadFolder: URL = Bundle.main.bundleURL.appendingPathComponent("Contents/Resources/DefaultImages")
    @Published var intervalSeconds: Double = 0.6

    var saveFolder = URL(fileURLWithPath: "BCILab", relativeTo: docDir)
    var labels = [String]()
    var totalImages: Int = 0
    var saveIndex: Int = 0
}


struct minsample: View {
    @StateObject private var appState = AppState()
    @State private var slideshow = SlideShow()
    @State private var selection: Int = 0
    
    private func insertAppears(_ marker: Double) {
        let nmarker = marker   100.0
    }
    
    var body: some View {
        NavigationView {
            ForEach(0..<slideshow.images.count-1, id: \.self) { i in
                let thisImage = slideshow.images[i].image
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .onAppear(perform: { insertAppears(slideshow.images[i].marker) })
                let nextImage = slideshow.images[i 1].image
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .onAppear(perform: { insertAppears(slideshow.images[i 1].marker) })

                NavigationLink(destination: nextImage, tag: i, selection: self.$selection) { thisImage }
            }
        }
    }
}

CodePudding user response:

Generally, using an index-based solution in ForEach is a bad idea. It breaks SwiftUI's system for diffing the views and tends to lead to compile-time issues as well.

I'd start by making LabeledImage Identifiable:

class LabeledImage : Identifiable {
    var id = UUID()
    let image: Image
    let marker: Double
    var appeared = false
    
    init(image: Image, marker: Double) {
        self.image = image
        self.marker = marker
    }
}

(I'd also make it a struct -- more on that later)

Then, since you do need indexes to achieve your nextImage functionality, you can use .enumerated on the collection:

struct MinSample: View {
    @StateObject private var appState = AppState()
    @State private var slideshow = SlideShow()
    @State private var selection: Int? = 0
    
    private func insertAppears(_ marker: Double) {
        let nmarker = marker   100.0
    }
    
    var body: some View {
        NavigationView {
            ForEach(Array(slideshow.images.enumerated()), id: \.1.id) { (index,imageModel) in
                if index < slideshow.images.count - 1 {
                    let thisImage = imageModel.image
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .onAppear(perform: { insertAppears(imageModel.marker) })
                    let nextImage = slideshow.images[index   1].image
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .onAppear(perform: { insertAppears(slideshow.images[index 1].marker) })

                    NavigationLink(destination: nextImage, tag: index, selection: self.$selection) { thisImage }
                } else {
                    EmptyView()
                }
            }
        }
    }
}

The above compiles quickly on my M1 with no issues.


Now, not directly-related to your issue, but there are some other things I would change:

  1. Make your models structs, which SwiftUI generally deals with much better when doing state comparisons
  2. Don't store references to SwiftUI Images -- instead, store a reference to a path or some other way to recreate that image. That will make the transition for LabeledImage to a struct easier anyway. So, your model might look like this:
struct LabeledImage : Identifiable {
    var id = UUID()
    let imageName: String
    let marker: Double
    var appeared = false
}
  1. Consider whether or not you need the tag and selection parameters in your NavigationLink -- perhaps it's just not clear in the minimal example why they're used.
  • Related