1

I have a SwiftUI app with a .sheet() which includes in its View a ColorPicker plus a toolbar button that dismisses the sheet (either using dismiss() or setting binding to false).

On an iPhone everything works fine, but on an iPad things can be disrupted if the ColorPicker is displaying its palette and the user accidentally taps on the Dismiss button in the background while dismissing the ColorPicker. If this happens the Dismiss button triggers its action but the sheet does not dismiss. The ColorPicker is dismissed (by this tap in the background I guess) but the sheet remains visible.

Additionally, and more importantly, tapping the Dismiss button again, now that the ColorPicker has gone still does not dismiss the sheet which remains displayed. The only way now to dismiss the sheet is to tap outside the sheet bounds and then the iPad dismisses it. If it is a .fullScreenCover() sheet it becomes impossible to dismiss it as you cannot tap outside!!

It appears that once the dismiss() function has been called or the bound "isPresenting" Bool is set to false, any further attempts to dismiss cannot trigger the dismissal action. So you have one go to dismiss a sheet and if anything blocks that you are stuck. (As an aside if you use isPresented.toggle() instead of isPresented = false, you get another error as the iPad tries to re-present the already stuck sheet)

I can understand why the sheet would initially not dismiss while another view was superimposed, but then becoming unresponsive is highly undesirable. Ideally I would disable the Dismiss button when the ColorPicker is displayed but there is no way to detect the ColorPicker being activated. Rather than a bug this appears to be a defect in the very mechanism by which sheets are dismissed.

The basic problem is that if your dismiss action is triggered while the sheet is not in a position to dismiss, it will subsequently never dismiss on an iPad without a tap outside. Why this is not a problem with the iPhone I don't know. The exact same setup and steps to make the crash when done on an iPhone just allows the sheet to dismiss: you get the Dismiss button's action called on the tap while it is the background and then again when it is in the foreground and on an iPhone the second tap dismisses the sheet.

Below, I have created some really minimalistic demo code to illustrate the scenario. This uses the environment dismiss() action but it makes no difference if you set a binding Bool. Also the button can be anywhere, not just in the toolbar and it still fails. Still fails if the code in SomePickerView is inserted directly into the sheet on SomeParentView. In fact any combination of views and locations makes no difference!!

import SwiftUI

struct SomePickerView: View {

    @State var colourBackground: Color = .clear
    @Environment(\.dismiss) var dismiss

    var body: some View {
        ColorPicker("", selection: $colourBackground)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Dismiss") {
                        dismiss()
                    }
                }
            }
    }
}

struct SomeParentView: View {
    @State var showSomePickerView: Bool = false

    var body: some View {
        Button("Pick a colour") {
            showSomePickerView = true
        }
        .sheet(isPresented: $showSomePickerView, content: {
            NavigationStack {
                SomePickerView()
            }
        })
    }
}

Any suggestions how to get around this? Thanks

jnpdx
  • 45,847
  • 6
  • 64
  • 94
djmlewis
  • 273
  • 4
  • 7

0 Answers0