PresentationLink broken in Xcode 11 beta 3

PresentationLink (in SwiftUI) seems to be broken in Xcode 11 beta 3.

It succeeds in presenting a modal view controller the first time, but stops working thereafter.

As noted in this StackOverflow post, this seems to occur when embedding a PresentationLink within a NavigationView, with the following warning printed to console:

[WindowServer] display_timer_callback: unexpected state (now:1abc3d3ccc7 < expected:1abc3d91a0f)

This bug has existed since Xcode 11 beta 1 (back when PresentationLink was called PresentationButton), which is quite alarming given the fact that we’re on beta 3 now.

I’ve written a quick and dirty workaround for this:

private enum SetPresentedViewKey: EnvironmentKey {
    static var defaultValue: (AnyView?) -> () {
        fatalError()
    }
}

private extension EnvironmentValues {
    var setPresentedView: (AnyView?) -> () {
        get {
            self[SetPresentedViewKey.self]
        } set {
            self[SetPresentedViewKey.self] = newValue
        }
    }
}

/// A replacement for the buggy (as of Xcode 11 b3) `PresentationLink`.
public struct PresentationLink2<Destination: View, Label: View>: View {
    public let destination: Destination
    public let label: Label

    @Environment(\.setPresentedView) private var setPresentedView
    @State private var presentedView: AnyView? = nil

    public init(destination: Destination, @ViewBuilder _ label: () -> Label) {
        self.destination = destination
        self.label = label()
    }

    private struct _Body<Destination: View, Label: View>: View {
        @Environment(\.setPresentedView) private var setPresentedView

        let destination: Destination
        let label: Label

        init(destination: Destination, label: Label) {
            self.destination = destination
            self.label = label
        }

        var body: some View {
            Button(action: present, label: { label })
        }

        func present() {
            setPresentedView(AnyView(destination))
        }
    }

    public var body: some View {
        _Body(destination: destination, label: label)
            .environment(\.setPresentedView, { self.presentedView = $0 })
            .presentation(presentedView.map {
                Modal($0, onDismiss: { self.presentedView = nil })
            })
    }
}

Simply swap out PresentationLink for PresentationLink2, and your modal view presentations should start working consistently.

 
3
Kudos
 
3
Kudos

Now read this

Data Flow Through SwiftUI

This article may be considered a condensed version of the (highly-recommended) WWDC19 talk, “Data Flow Through SwiftUI”, along with a few thoughts and insights of my own. It represents my own, current understanding of data flow in... Continue →