Closure with typed throws stored as a SwiftUI View property crashes on iOS 17

If you're not going to defer the error handling, then you might as well just store body directly.

struct NondeferringErrorRecoveringView<
  Success: View, Error: Swift.Error, Recovery: View
>: NondeferringErrorRecoveringViewProtocol {
  init(
    @ViewBuilder success: () throws(Error) -> Success,
    @ViewBuilder recovery: (Error) -> Recovery
  ) {
    body = Self.either(success: success, recovery: recovery)
  }

  var body: Body
}

/// Necessary to be able to refer to the return type of `either`.
private protocol NondeferringErrorRecoveringViewProtocol: View where Body == Either {
  associatedtype Success: View
  associatedtype Error: Swift.Error
  associatedtype Recovery: View
  associatedtype Either: View

  static func either(
    success: () throws(Error) -> Success,
    recovery: (Error) -> Recovery
  ) -> Either
}

extension NondeferringErrorRecoveringViewProtocol {
  @ViewBuilder static func either(
    @ViewBuilder success: () throws(Error) -> Success,
    @ViewBuilder recovery: (Error) -> Recovery
  ) -> some View {
    switch Result(catching: success) {
    case .success(let success): success
    case .failure(let error): recovery(error)
    }
  }
}

You can still change the naming from the generic form if you think that's worth bothering with:

struct PermissionCheckedView<AllowedContent: View, DeniedContent: View>: View {
  var body: NondeferringErrorRecoveringView<AllowedContent, PermissionError, DeniedContent>

  init(
    @ViewBuilder protectedView: () throws(PermissionError) -> AllowedContent,
    @ViewBuilder deniedView: (PermissionError) -> DeniedContent
  ) {
    body = .init(success: protectedView, recovery: deniedView)
  }
}
1 Like