SwiftUI and "Simultaneous access" error

I'm using Xcode 13.4.1 and targeting iOS 15.0. I have aSwiftUI app that crashes as soon as you click a button that changes a variable from true to false:

Button("Close Side by Side Mode") {
   mainScreenRecs.isViewingSideBySide = false
}

This is the only place where the variable is changed; everything else is just SwiftUI reading the variable to determine whether to show views or not, like this:

var body: some View {
        VStack(spacing: 0) {
                HStack {
                    if mainScreenRecs.isViewingSideBySide {
                        Text(catalogModel.title)
                    }

When I look at the debug stack, I can see that the previous modification says LayoutComputer.EngineDelegate.childGeometries(at:origin:), which makes me wonder if it's related to SwiftUI:

I see this in the debug output, which has the additional note about AttributeGraph: cycle detected through attribute, another possible SwiftUI problem:

I tried wrapping this code in DispatchQuery.main.async, like this:

Button("Close Side by Side Mode") {
   DispatchQueue.main.async {
     mainScreenRecs.isViewingSideBySide = false
   }
}

but it didn't help. Is it possible this is a SwiftUI bug? I hate to think that because it leaves me stuck without a solution, but I can't figure out what else I could check or try.

Answered by southbayjt in 720298022

Finally solved this. The problem was that I wanted to maintain the state of the PDFKit views when switching between them (like what page you're on, how much you're zoomed in on a page, etc), so I maintain a copy of the PDFView objects after instantiating them, but I wasn't handling the makeUIView and updateUIView methods properly, which are called constantly in SwiftUI when switching between the different PDFViews, even though I wasn't unloading the views. There's not a lot of documentation about using PDFKit and SwiftUI, especially the way we're doing it with an app that needs to open multiple PDFs and display them in tabs, and even display two of them side-by-side at the same time. Here is what my core SwiftUI wrapper around PDFKit now looks like:

import PDFKit
import SwiftUI

struct PDFViewSwiftUIWrapper: UIViewRepresentable {
    var myViewerModel: PDFViewerModel
    func makeUIView(context: Context) -> PDFView {
        return myViewerModel.myPDFView
    }

    func updateUIView(_ pdfView: PDFView, context: Context) {
        pdfView.document = myViewerModel.myPDFView.document
    }
}

It's super-basic, but both functions need to do something; I thought "updateUIView" would only be called if you were displaying a different PDF in the same viewer, but it gets called every time the view displays.

The PDFViewerModel is the class that I instantiate and hold on to. I have one of these for each PDF tab that is opened in the app. By keeping a copy of this, which has the PDFView in it, I can maintain the state of PDFView, like the zoom level, the page #, etc:

// I have to set this as Indentifiable so I can use it with ForEach
class PDFViewerModel: ObservableObject, Identifiable {
    var myPDFView = PDFView()
    // There are other properties like these so we can have additional state for the viewer, like a word search textbox. This isn't part of PDFView; you have to add these UI features.
    var showBookmarkPopover = false
    var showSearchToolbar = false
    ...
}

When you click to open another PDF in a new tab, it instantiates it and adds it to the array like this:

let pdfViewerModel = PDFViewerModel()

if let unwrappedPdfDoc = PDFDocument(url: getMyURL()) {
      pdfViewerModel.myPDFView.document = unwrappedPdfDoc
}

currentTabs.append(pdfViewerModel)

There's a SwiftUI view that displays the PDFViewSwiftUIWrapper and some other helper controls, like the search toolbar, and each of these is displayed like this:

ForEach(currentTabs) { pdfViewerModel in
     PDFViewer(pdfViewerModel: pdfViewerModel)
}

This problem was a hybrid of the way SwiftUI gets called repeatedly, which I didn't realize, even if the state hadn't necessarily changed, along with the correct way to integrate PDFKit into SwiftUI. Hope this helps anyone with a similar issue!

Please show more code.

Where do you define and set: mainScreenRecs and isViewingSideBySide?

What happens if you comment out the change:

Button("Close Side by Side Mode") {
  print("isViewingSideBySide", mainScreenRecs.isViewingSideBySide)
  // mainScreenRecs.isViewingSideBySide = false
}

Hi Claude, thank you for responding on this. When I use the print statement, it prints

isViewingSideBySide true

There's no problem reading its value; it's just changing it that crashes. The var mainScreenRecs is defined as an ObservableObject,

class MainScreenRecs: ObservableObject {
   ...
    @Published var isViewingSideBySides = false
}

and is instantiated in the App struct as a StateObject,

struct EtimsEcpApp: App {
    ...
    @StateObject var myRecs = MainScreenRecs(recs: [],
                                             ...
                                             isViewingSideBySide: false
}

and is passed into MainListView,

var body: some Scene {
        WindowGroup {
            MainListView(myRecs: self.myRecs)
            ...
        }
}

where it's defined as an ObservedObject, and then to the child view PDFSidebar also as an ObservedObject:

struct MainListView: View {
    @ObservedObject var myRecs: MainScreenRecs
    ...
    var body: some View {
    ...
                PDFSidebar(mainScreenRecs: myRecs)

In the PDFSide, it's an ObservedObject:

struct PDFSidebar: View {
    @ObservedObject var mainScreenRecs: MainScreenRecs

That's where the "Close Side by Side" button is located.

I also noticed that the crash only occurs if you've tapped the face of the PDFKit viewer. When you tap the face of the PDFKit viewer, you get this blast of messages:

objc[40678]: Class _PathPoint is implemented in both /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore (0x11d77b658) and /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/TextInputUI.framework/TextInputUI (0x13fa5d690). One of the two will be used. Which one is undefined.


I stripped down the views controlled by the isViewingSideBySide variable to just "Hello World", and don't receive the error, so I'm going to build them back piece by piece until I hit on what's causing the "simultaneous access" error. @Claude31, thanks for the time and effort up to this point though.

Accepted Answer

Finally solved this. The problem was that I wanted to maintain the state of the PDFKit views when switching between them (like what page you're on, how much you're zoomed in on a page, etc), so I maintain a copy of the PDFView objects after instantiating them, but I wasn't handling the makeUIView and updateUIView methods properly, which are called constantly in SwiftUI when switching between the different PDFViews, even though I wasn't unloading the views. There's not a lot of documentation about using PDFKit and SwiftUI, especially the way we're doing it with an app that needs to open multiple PDFs and display them in tabs, and even display two of them side-by-side at the same time. Here is what my core SwiftUI wrapper around PDFKit now looks like:

import PDFKit
import SwiftUI

struct PDFViewSwiftUIWrapper: UIViewRepresentable {
    var myViewerModel: PDFViewerModel
    func makeUIView(context: Context) -> PDFView {
        return myViewerModel.myPDFView
    }

    func updateUIView(_ pdfView: PDFView, context: Context) {
        pdfView.document = myViewerModel.myPDFView.document
    }
}

It's super-basic, but both functions need to do something; I thought "updateUIView" would only be called if you were displaying a different PDF in the same viewer, but it gets called every time the view displays.

The PDFViewerModel is the class that I instantiate and hold on to. I have one of these for each PDF tab that is opened in the app. By keeping a copy of this, which has the PDFView in it, I can maintain the state of PDFView, like the zoom level, the page #, etc:

// I have to set this as Indentifiable so I can use it with ForEach
class PDFViewerModel: ObservableObject, Identifiable {
    var myPDFView = PDFView()
    // There are other properties like these so we can have additional state for the viewer, like a word search textbox. This isn't part of PDFView; you have to add these UI features.
    var showBookmarkPopover = false
    var showSearchToolbar = false
    ...
}

When you click to open another PDF in a new tab, it instantiates it and adds it to the array like this:

let pdfViewerModel = PDFViewerModel()

if let unwrappedPdfDoc = PDFDocument(url: getMyURL()) {
      pdfViewerModel.myPDFView.document = unwrappedPdfDoc
}

currentTabs.append(pdfViewerModel)

There's a SwiftUI view that displays the PDFViewSwiftUIWrapper and some other helper controls, like the search toolbar, and each of these is displayed like this:

ForEach(currentTabs) { pdfViewerModel in
     PDFViewer(pdfViewerModel: pdfViewerModel)
}

This problem was a hybrid of the way SwiftUI gets called repeatedly, which I didn't realize, even if the state hadn't necessarily changed, along with the correct way to integrate PDFKit into SwiftUI. Hope this helps anyone with a similar issue!

SwiftUI and "Simultaneous access" error
 
 
Q