I was doing an app which had several "camera" buttons each one dedicated to taking/storing/reviewing/deleting an image associated with a variable URL but what should have been a simple no brainer turned out to be a programming nightmare.
To cut a long story short there is a bug in the sheet handling wherebye even tho you have separate instance for each button the camera/picker cylcles sequentially thru the stack of instances for any action finally always placing the image in the first URL. Working with myself debugging, all major AIs (Grok, Claude, Gemini and Perplexity) after 4 x 12hr+ days we finally managed to crack a solution. What follows is Groks interpretation (note it misses the earlier problem of instance cycling!!) ...
You can follow the discussion here: https://x.com/i/grok/share/KHeaUPladURmbFq5qy9W506er
but be warned its long a detailed but if you are having problems then read ...
**Bug Report: Race Conditions with UIImagePickerController in SwiftUI Sheet
**
Environment:
SwiftUI, iOS 17.7.5
Device: iPad Pro (12.9-inch, 2nd generation)
Xcode Version: [Insert your Xcode version]
Date: March 30, 2025
**Issue 1: Multiple Instances of UIImagePickerController Spawned After Dismissal
**
Description:
When using a UIImagePickerController wrapped in a UIViewControllerRepresentable and presented via a SwiftUI .sheet, selecting "Use Photo" resulted in multiple unintended instances of the picker being initialized and presented. The console logs showed repeated "Camera initialized" and "Camera sheet appeared" messages (e.g., multiple <UIImagePickerController: 0x...> instances) after the initial dismissal, despite the sheet being dismissed programmatically.
Reproduction Steps:
Create a SwiftUI view with a button that sets a @State variable showCamera to true.
Present a UIImagePickerController via .sheet(isPresented: $showCamera).
Update a @Binding variable (e.g., photoLocation: URL?) in imagePickerController(_:didFinishPickingMediaWithInfo:) after saving the image.
Dismiss the picker with picker.dismiss(animated: true) and presentationMode.wrappedValue.dismiss().
Observe that updating the @Binding variable triggers a view re-render, causing the .sheet to re-present multiple times before finally staying dismissed.
Root Cause:
A race condition occurred between the view update (triggered by changing photoLocation) and the dismissal of the picker. During the re-render, showCamera remained true momentarily, causing the .sheet modifier to re-evaluate and spawn new picker instances before the onDismiss closure could reset showCamera to false.
The fix involved delaying the @Binding update (photoLocation) until after the picker and sheet were fully dismissed, ensuring showCamera was reset to false before the view re-rendered:
Introduced an onPhotoPicked: (URL) -> Void closure to decouple the photoLocation update from the dismissal timing.
Modified the coordinator to call onPhotoPicked and reset showCamera before initiating dismissal:swift
Issue 2: Single Unintended Picker Reopen After Initial Fix
Description:
After addressing the multiple-instance issue, a single unintended reopen of the picker persisted. The logs showed one additional "Camera initialized" and "Camera sheet appeared" after "Use Photo," before the final dismissal.
Reproduction Steps:
Reproduction Steps:
Use the initial fix with onPhotoPicked and delayed photoLocation update.
Take a photo and select "Use Photo."
Observe one extra picker instance appearing briefly before dismissal completes.
Root Cause:
The @Binding update (photoLocation) was still occurring too early in the dismissal sequence. Although delayed until after picker.dismiss, the view re-render happened while showCamera was still true during the dismissal animation, causing the .sheet to re-present once before onDismiss reset showCamera.
Resolution:
The fix ensured showCamera was set to false before the picker dismissal animation began, preventing the .sheet from re-evaluating during the transition:
Moved the dismissCamera() call (which sets showCamera to false) into the onPhotoPicked callback, executed before picker.dismiss:
CameraView(
photoLocation: $photoLocation,
storeDirectory: storeDirectory,
onPhotoPicked: { url in
print("Photo picked callback for \(id), setting photoLocation: \(url)")
self.photoLocation = url
self.cameraState.dismissCamera() // Sets showCamera to false first
}
)
Kept the dismissal sequence in the coordinator:
DispatchQueue.main.async {
self.parent.onPhotoPicked(fileURL)
picker.dismiss(animated: true) {
self.parent.presentationMode.wrappedValue.dismiss()
}
}
This synchronized the state change with the dismissal, ensuring showCamera was false before the view re-rendered, eliminating the single reopen.
Request:
Could the SwiftUI team clarify if this behavior is expected, or consider improving the .sheet modifier to better handle state transitions during UIKit controller dismissal? A more robust bridge between SwiftUI’s declarative state and UIKit’s imperative lifecycle could prevent such race conditions.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hi , I am new to SwiftUI and Mac/iOS concepts so am on a very steep learning curve.
If this is not the correct forum for this please let me know and I will delete and post elsewhere ...
Basically I am doing a favor for a friend and writing a simple data collection app for his iPad in Swift... it consists of a quite long list list of items to select from, each of which will have several possible values. Clicking on a "Change" button next to each item will bring up a list of possible values to choose from. So ....
The code works except that the list is displayed as an "icon" and has to be clicked to display the items to choose from ...
**How do I get the list to display automatically when instanced without having to click on the List icon ? **
Code: I have a list view which is called as the result of a button press:
Button(action: {
isShowingList=true
When the View is refreshed (State variable) results in the Selectable List view as follows:
struct TechReportView: View {
@State private var isShowingList = false
var body: some View {
if isShowingList { // display a selectable list
SelectableListView(selectedItem: $selectedItem,
isShowingList: $isShowingList,
items: $copyList,
)
}
else
{ // show list of items and current values}
The List View is as follows
struct SelectableListView: View {
@Binding var selectedItem: String?
@Binding var isShowingList: Bool
@Binding var items : [String]
var key : String
@Binding var toChange : String
var body: some View {
VStack()
{
NavigationView {
List(items, id: \.self) { item in
HStack {
Text(item)
Spacer()
if item == selectedItem {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
.contentShape(Rectangle())
.onTapGesture {
selectedItem = item
toChange = item
isShowingList = false
setSKeyData(key, selectedItem!)
}
}
.navigationTitle("Select Item")
.navigationBarItems(trailing: Button("Cancel") {
isShowingList = false
})
}
}
}
}
Topic:
UI Frameworks
SubTopic:
SwiftUI
Hi , I am new to SwiftUI and Mac/iOS concepts so am on a very steep learning curve.
If this is not the correct forum for this please let me know and I will delete and post elsewhere ...
Basically I am doing a favor for a friend and writing a simple data collection app for his iPad in Swift... it consists of a quite long list list of items to select from, each of which will have several possible values. Clicking on a "Change" button next to each item will bring up a list of possible values to choose from. So ....
The code works except that the list is displayed as an "icon" and has to be clicked to display the items to choose from ...
**How do I get the list to display automatically when instanced without having to click on the List icon ?
**
Code:
I have a list view which is called as the result of a button press:
Button(action: {
isShowingList=true
When the View is refreshed (State variable) results in the Selectable List view as follows:
struct TechReportView: View {
@State private var isShowingList = false
var body: some View {
if isShowingList { // display a selectable list
SelectableListView(selectedItem: $selectedItem,
isShowingList: $isShowingList,
items: $copyList,
)
}
else
{ // show list of items and current values}
The List View is as follows
struct SelectableListView: View {
@Binding var selectedItem: String?
@Binding var isShowingList: Bool
@Binding var items : [String]
var key : String
@Binding var toChange : String
var body: some View {
VStack()
{
NavigationView {
List(items, id: \.self) { item in
HStack {
Text(item)
Spacer()
if item == selectedItem {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
.contentShape(Rectangle())
.onTapGesture {
selectedItem = item
toChange = item
isShowingList = false
setSKeyData(key, selectedItem!)
}
}
.navigationTitle("Select Item")
.navigationBarItems(trailing: Button("Cancel") {
isShowingList = false
})
}
}
}
}
Topic:
UI Frameworks
SubTopic:
SwiftUI