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:
- Make your models
struct
s, which SwiftUI generally deals with much better when doing state comparisons - Don't store references to SwiftUI
Image
s -- instead, store a reference to a path or some other way to recreate that image. That will make the transition forLabeledImage
to astruct
easier anyway. So, your model might look like this:
struct LabeledImage : Identifiable {
var id = UUID()
let imageName: String
let marker: Double
var appeared = false
}
- Consider whether or not you need the
tag
andselection
parameters in yourNavigationLink
-- perhaps it's just not clear in the minimal example why they're used.