220 lines
7.5 KiB
Swift
220 lines
7.5 KiB
Swift
import SwiftUI
|
|
|
|
#if os(macOS)
|
|
public typealias PlatformView = NSView
|
|
#endif
|
|
#if os(iOS) || os(tvOS)
|
|
public typealias PlatformView = UIView
|
|
#endif
|
|
|
|
#if os(macOS)
|
|
public typealias PlatformViewController = NSViewController
|
|
#endif
|
|
#if os(iOS) || os(tvOS)
|
|
public typealias PlatformViewController = UIViewController
|
|
#endif
|
|
|
|
/// Utility methods to inspect the UIKit view hierarchy.
|
|
public enum Introspect {
|
|
|
|
/// Finds a subview of the specified type.
|
|
/// This method will recursively look for this view.
|
|
/// Returns nil if it can't find a view of the specified type.
|
|
public static func findChild<AnyViewType: PlatformView>(
|
|
ofType type: AnyViewType.Type,
|
|
in root: PlatformView
|
|
) -> AnyViewType? {
|
|
for subview in root.subviews {
|
|
if let typed = subview as? AnyViewType {
|
|
return typed
|
|
} else if let typed = findChild(ofType: type, in: subview) {
|
|
return typed
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/// Finds a child view controller of the specified type.
|
|
/// This method will recursively look for this child.
|
|
/// Returns nil if it can't find a view of the specified type.
|
|
public static func findChild<AnyViewControllerType: PlatformViewController>(
|
|
ofType type: AnyViewControllerType.Type,
|
|
in root: PlatformViewController
|
|
) -> AnyViewControllerType? {
|
|
for child in root.children {
|
|
if let typed = child as? AnyViewControllerType {
|
|
return typed
|
|
} else if let typed = findChild(ofType: type, in: child) {
|
|
return typed
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/// Finds a previous sibling that contains a view of the specified type.
|
|
/// This method inspects siblings recursively.
|
|
/// Returns nil if no sibling contains the specified type.
|
|
public static func previousSibling<AnyViewType: PlatformView>(
|
|
containing type: AnyViewType.Type,
|
|
from entry: PlatformView
|
|
) -> AnyViewType? {
|
|
|
|
guard let superview = entry.superview,
|
|
let entryIndex = superview.subviews.firstIndex(of: entry),
|
|
entryIndex > 0
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
for subview in superview.subviews[0..<entryIndex].reversed() {
|
|
if let typed = findChild(ofType: type, in: subview) {
|
|
return typed
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/// Finds a previous sibling that contains a view controller of the specified type.
|
|
/// This method inspects siblings recursively.
|
|
/// Returns nil if no sibling contains the specified type.
|
|
@available(macOS, unavailable)
|
|
public static func previousSibling<AnyViewControllerType: PlatformViewController>(
|
|
containing type: AnyViewControllerType.Type,
|
|
from entry: PlatformViewController
|
|
) -> AnyViewControllerType? {
|
|
|
|
guard let parent = entry.parent,
|
|
let entryIndex = parent.children.firstIndex(of: entry),
|
|
entryIndex > 0
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
for child in parent.children[0..<entryIndex].reversed() {
|
|
if let typed = findChild(ofType: type, in: child) {
|
|
return typed
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/// Finds a previous sibling that is a view controller of the specified type.
|
|
/// This method does not inspect siblings recursively.
|
|
/// Returns nil if no sibling is of the specified type.
|
|
public static func previousSibling<AnyViewControllerType: PlatformViewController>(
|
|
ofType type: AnyViewControllerType.Type,
|
|
from entry: PlatformViewController
|
|
) -> AnyViewControllerType? {
|
|
|
|
guard let parent = entry.parent,
|
|
let entryIndex = parent.children.firstIndex(of: entry),
|
|
entryIndex > 0
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
for child in parent.children[0..<entryIndex].reversed() {
|
|
if let typed = child as? AnyViewControllerType {
|
|
return typed
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/// Finds a next sibling that contains a view of the specified type.
|
|
/// This method inspects siblings recursively.
|
|
/// Returns nil if no sibling contains the specified type.
|
|
public static func nextSibling<AnyViewType: PlatformView>(
|
|
containing type: AnyViewType.Type,
|
|
from entry: PlatformView
|
|
) -> AnyViewType? {
|
|
|
|
guard let superview = entry.superview,
|
|
let entryIndex = superview.subviews.firstIndex(of: entry)
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
for subview in superview.subviews[entryIndex..<superview.subviews.endIndex] {
|
|
if let typed = findChild(ofType: type, in: subview) {
|
|
return typed
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/// Finds an ancestor of the specified type.
|
|
/// If it reaches the top of the view without finding the specified view type, it returns nil.
|
|
public static func findAncestor<AnyViewType: PlatformView>(ofType type: AnyViewType.Type, from entry: PlatformView) -> AnyViewType? {
|
|
var superview = entry.superview
|
|
while let s = superview {
|
|
if let typed = s as? AnyViewType {
|
|
return typed
|
|
}
|
|
superview = s.superview
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/// Finds the hosting view of a specific subview.
|
|
/// Hosting views generally contain subviews for one specific SwiftUI element.
|
|
/// For instance, if there are multiple text fields in a VStack, the hosting view will contain those text fields (and their host views, see below).
|
|
/// Returns nil if it couldn't find a hosting view. This should never happen when called with an IntrospectionView.
|
|
public static func findHostingView(from entry: PlatformView) -> PlatformView? {
|
|
var superview = entry.superview
|
|
while let s = superview {
|
|
if NSStringFromClass(type(of: s)).contains("HostingView") {
|
|
return s
|
|
}
|
|
superview = s.superview
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/// Finds the view host of a specific view.
|
|
/// SwiftUI wraps each UIView within a ViewHost, then within a HostingView.
|
|
/// Returns nil if it couldn't find a view host. This should never happen when called with an IntrospectionView.
|
|
public static func findViewHost(from entry: PlatformView) -> PlatformView? {
|
|
var superview = entry.superview
|
|
while let s = superview {
|
|
if NSStringFromClass(type(of: s)).contains("ViewHost") {
|
|
return s
|
|
}
|
|
superview = s.superview
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
enum TargetViewSelector {
|
|
public static func sibling<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
|
|
guard let viewHost = Introspect.findViewHost(from: entry) else {
|
|
return nil
|
|
}
|
|
return Introspect.previousSibling(containing: TargetView.self, from: viewHost)
|
|
}
|
|
|
|
public static func ancestorOrSibling<TargetView: PlatformView>(from entry: PlatformView) -> TargetView? {
|
|
if let tableView = Introspect.findAncestor(ofType: TargetView.self, from: entry) {
|
|
return tableView
|
|
}
|
|
return sibling(from: entry)
|
|
}
|
|
}
|
|
|
|
/// Allows to safely access an array element by index
|
|
/// Usage: array[safe: 2]
|
|
private extension Array {
|
|
subscript(safe index: Int) -> Element? {
|
|
guard index >= 0, index < endIndex else {
|
|
return nil
|
|
}
|
|
|
|
return self[index]
|
|
}
|
|
}
|