Skip to content

Add modal introspection #268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Jun 26, 2023
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Changelog

## master

- Added: `.sheet` introspection (#268)
- Added: `.fullScreenCover` introspection (#268)
- Added: `.popover` introspection (#268)
- Added: `VideoPlayer` introspection (#264)
- Added: `SignInWithAppleButton` introspection (#265)
- Added: `View` introspection on macOS (#266)
Expand Down
61 changes: 59 additions & 2 deletions Examples/Showcase/Showcase/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ struct ContentView: View {
NavigationShowcase()
.tabItem { Text("Navigation") }
.tag(2)
PresentationShowcase()
.tabItem { Text("Presentation") }
.tag(3)
#endif
GenericViewShowcase()
.tabItem { Text("Generic View") }
.tag(3)
.tag(4)
SimpleElementsShowcase()
.tabItem { Text("Simple elements") }
.tag(4)
.tag(5)
}
#if os(iOS) || os(tvOS)
.introspect(.tabView, on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17)) { tabBarController in
Expand Down Expand Up @@ -196,6 +199,60 @@ struct NavigationShowcase: View {
}
}

#if !os(macOS)
struct PresentationShowcase: View {
@State var isSheetPresented = false
@State var isFullScreenPresented = false
@State var isPopoverPresented = false

var body: some View {
VStack(spacing: 20) {
Button("Sheet", action: { isSheetPresented = true })
.sheet(isPresented: $isSheetPresented) {
Button("Dismiss", action: { isSheetPresented = false })
#if os(iOS) || os(tvOS)
.introspect(
.sheet,
on: .iOS(.v13, .v14, .v15, .v16, .v17), .tvOS(.v13, .v14, .v15, .v16, .v17)
) { presentationController in
presentationController.containerView?.backgroundColor = .red.withAlphaComponent(0.75)
}
#endif
}

if #available(iOS 14, tvOS 14, *) {
Button("Full Screen Cover", action: { isFullScreenPresented = true })
.fullScreenCover(isPresented: $isFullScreenPresented) {
Button("Dismiss", action: { isFullScreenPresented = false })
#if os(iOS) || os(tvOS)
.introspect(
.fullScreenCover,
on: .iOS(.v14, .v15, .v16, .v17), .tvOS(.v14, .v15, .v16, .v17)
) { presentationController in
presentationController.containerView?.backgroundColor = .red.withAlphaComponent(0.75)
}
#endif
}
}

#if os(iOS)
Button("Popover", action: { isPopoverPresented = true })
.popover(isPresented: $isPopoverPresented) {
Button("Dismiss", action: { isPopoverPresented = false })
.padding()
.introspect(
.popover,
on: .iOS(.v13, .v14, .v15, .v16, .v17)
) { presentationController in
presentationController.containerView?.backgroundColor = .red.withAlphaComponent(0.75)
}
}
#endif
}
}
}
#endif

struct GenericViewShowcase: View {
var body: some View {
VStack(spacing: 10) {
Expand Down
13 changes: 13 additions & 0 deletions Sources/Introspect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,16 @@ extension PlatformViewController: PlatformEntity {
self.ancestors.contains(other)
}
}

#if canImport(UIKit)
extension UIPresentationController: PlatformEntity {
@_spi(Internals)
public var ancestor: UIPresentationController? { nil }

@_spi(Internals)
public var descendants: [UIPresentationController] { [] }

@_spi(Internals)
public func isDescendant(of other: UIPresentationController) -> Bool { false }
}
#endif
81 changes: 81 additions & 0 deletions Sources/ViewTypes/FullScreenCover.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import SwiftUI

/// An abstract representation of `.fullScreenCover` in SwiftUI.
///
/// ### iOS
///
/// ```swift
/// public struct ContentView: View {
/// @State var isPresented = false
///
/// public var body: some View {
/// Button("Present", action: { isPresented = true })
/// .fullScreenCover(isPresented: $isPresented) {
/// Button("Dismiss", action: { isPresented = false })
/// .introspect(.fullScreenCover, on: .iOS(.v14, .v15, .v16, .v17)) {
/// print(type(of: $0)) // UIPresentationController
/// }
/// }
/// }
/// }
/// ```
///
/// ### tvOS
///
/// ```swift
/// public struct ContentView: View {
/// @State var isPresented = false
///
/// public var body: some View {
/// Button("Present", action: { isPresented = true })
/// .fullScreenCover(isPresented: $isPresented) {
/// Button("Dismiss", action: { isPresented = false })
/// .introspect(.fullScreenCover, on: .tvOS(.v14, .v15, .v16, .v17)) {
/// print(type(of: $0)) // UIPresentationController
/// }
/// }
/// }
/// }
/// ```
///
/// ### macOS
///
/// Not available.
///
public struct FullScreenCoverType: IntrospectableViewType {
public var scope: IntrospectionScope { .ancestor }
}

#if os(iOS) || os(tvOS)
extension IntrospectableViewType where Self == FullScreenCoverType {
public static var fullScreenCover: Self { .init() }
}

#if canImport(UIKit)
extension iOSViewVersion<FullScreenCoverType, UIPresentationController> {
@available(*, unavailable, message: ".fullScreenCover isn't available on iOS 13")
public static let v13 = Self.unavailable()
public static let v14 = Self(for: .v14, selector: selector)
public static let v15 = Self(for: .v15, selector: selector)
public static let v16 = Self(for: .v16, selector: selector)
public static let v17 = Self(for: .v17, selector: selector)

private static var selector: IntrospectionSelector<UIPresentationController> {
.from(UIViewController.self, selector: \.presentationController)
}
}

extension tvOSViewVersion<FullScreenCoverType, UIPresentationController> {
@available(*, unavailable, message: ".fullScreenCover isn't available on tvOS 13")
public static let v13 = Self.unavailable()
public static let v14 = Self(for: .v14, selector: selector)
public static let v15 = Self(for: .v15, selector: selector)
public static let v16 = Self(for: .v16, selector: selector)
public static let v17 = Self(for: .v17, selector: selector)

private static var selector: IntrospectionSelector<UIPresentationController> {
.from(UIViewController.self, selector: \.presentationController)
}
}
#endif
#endif
53 changes: 53 additions & 0 deletions Sources/ViewTypes/Popover.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import SwiftUI

/// An abstract representation of `.popover` in SwiftUI.
///
/// ### iOS
///
/// ```swift
/// public struct ContentView: View {
/// @State var isPresented = false
///
/// public var body: some View {
/// Button("Present", action: { isPresented = true })
/// .popover(isPresented: $isPresented) {
/// Button("Dismiss", action: { isPresented = false })
/// .introspect(.popover, on: .iOS(.v13, .v14, .v15, .v16, .v17)) {
/// print(type(of: $0)) // UIPopoverPresentationController
/// }
/// }
/// }
/// }
/// ```
///
/// ### tvOS
///
/// Not available.
///
/// ### macOS
///
/// Not available.
///
public struct PopoverType: IntrospectableViewType {
public var scope: IntrospectionScope { .ancestor }
}

#if os(iOS)
extension IntrospectableViewType where Self == PopoverType {
public static var popover: Self { .init() }
}

#if canImport(UIKit)
extension iOSViewVersion<PopoverType, UIPopoverPresentationController> {
public static let v13 = Self(for: .v13, selector: selector)
public static let v14 = Self(for: .v14, selector: selector)
public static let v15 = Self(for: .v15, selector: selector)
public static let v16 = Self(for: .v16, selector: selector)
public static let v17 = Self(for: .v17, selector: selector)

private static var selector: IntrospectionSelector<UIPopoverPresentationController> {
.from(UIViewController.self, selector: \.popoverPresentationController)
}
}
#endif
#endif
95 changes: 95 additions & 0 deletions Sources/ViewTypes/Sheet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import SwiftUI

/// An abstract representation of `.sheet` in SwiftUI.
///
/// ### iOS
///
/// ```swift
/// public struct ContentView: View {
/// @State var isPresented = false
///
/// public var body: some View {
/// Button("Present", action: { isPresented = true })
/// .sheet(isPresented: $isPresented) {
/// Button("Dismiss", action: { isPresented = false })
/// .introspect(.sheet, on: .iOS(.v13, .v14, .v15, .v16, .v17)) {
/// print(type(of: $0)) // UIPresentationController
/// }
/// }
/// }
/// }
/// ```
///
/// ### tvOS
///
/// ```swift
/// public struct ContentView: View {
/// @State var isPresented = false
///
/// public var body: some View {
/// Button("Present", action: { isPresented = true })
/// .sheet(isPresented: $isPresented) {
/// Button("Dismiss", action: { isPresented = false })
/// .introspect(.sheet, on: .tvOS(.v13, .v14, .v15, .v16, .v17)) {
/// print(type(of: $0)) // UIPresentationController
/// }
/// }
/// }
/// }
/// ```
///
/// ### macOS
///
/// Not available.
///
public struct SheetType: IntrospectableViewType {
public var scope: IntrospectionScope { .ancestor }
}

#if os(iOS) || os(tvOS)
extension IntrospectableViewType where Self == SheetType {
public static var sheet: Self { .init() }
}

#if canImport(UIKit)
extension iOSViewVersion<SheetType, UIPresentationController> {
public static let v13 = Self(for: .v13, selector: selector)
public static let v14 = Self(for: .v14, selector: selector)
public static let v15 = Self(for: .v15, selector: selector)
public static let v16 = Self(for: .v16, selector: selector)
public static let v17 = Self(for: .v17, selector: selector)

private static var selector: IntrospectionSelector<UIPresentationController> {
.from(UIViewController.self, selector: \.presentationController)
}
}

#if !os(tvOS)
@available(iOS 15, *)
extension iOSViewVersion<SheetType, UISheetPresentationController> {
@_disfavoredOverload
public static let v15 = Self(for: .v15, selector: selector)
@_disfavoredOverload
public static let v16 = Self(for: .v16, selector: selector)
@_disfavoredOverload
public static let v17 = Self(for: .v17, selector: selector)

private static var selector: IntrospectionSelector<UISheetPresentationController> {
.from(UIViewController.self, selector: \.sheetPresentationController)
}
}
#endif

extension tvOSViewVersion<SheetType, UIPresentationController> {
public static let v13 = Self(for: .v13, selector: selector)
public static let v14 = Self(for: .v14, selector: selector)
public static let v15 = Self(for: .v15, selector: selector)
public static let v16 = Self(for: .v16, selector: selector)
public static let v17 = Self(for: .v17, selector: selector)

private static var selector: IntrospectionSelector<UIPresentationController> {
.from(UIViewController.self, selector: \.presentationController)
}
}
#endif
#endif
Loading