Last active
March 5, 2025 23:06
-
-
Save harlanhaskins/30d0f2367c3b86688412a021bd6479f8 to your computer and use it in GitHub Desktop.
A SwiftUI modifier for inserting the parent UIViewController in the environment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import SwiftUI | |
extension View { | |
/// Adds introspection to find the parent view controller in the view hierarchy and | |
/// makes that view controller available to downstream views in the view hierarchy. | |
public func addParentViewControllerIntrospection() -> some View { | |
modifier(ParentViewControllerEnvironmentModifier()) | |
} | |
} | |
extension EnvironmentValues { | |
/// The parent view controller hosting this SwiftUI hierarhy, if there is one available. | |
public var parentViewController: UIViewController? { | |
parentViewControllerBox.value | |
} | |
} | |
// MARK: - Implementation | |
extension EnvironmentValues { | |
@Entry fileprivate var parentViewControllerBox = Weak<UIViewController>(nil) | |
} | |
private struct Weak<T: AnyObject> { | |
weak var value: T? | |
init(_ value: T?) { | |
self.value = value | |
} | |
} | |
private final class IntrospectionView: UIView { | |
var parentViewControllerBinding: Binding<Weak<UIViewController>>? { | |
didSet { | |
updateBinding() | |
} | |
} | |
override func didMoveToWindow() { | |
super.didMoveToWindow() | |
updateBinding() | |
} | |
func updateBinding() { | |
DispatchQueue.main.async { | |
self.parentViewControllerBinding?.wrappedValue.value = self.parentViewController | |
} | |
} | |
var parentViewController: UIViewController? { | |
for responder in sequence(first: self, next: \.next) { | |
if let viewController = responder as? UIViewController { | |
return viewController | |
} | |
} | |
return nil | |
} | |
} | |
private struct IntrospectionViewRepresentable: UIViewRepresentable { | |
@Binding var parentViewController: Weak<UIViewController> | |
func makeUIView(context: Context) -> IntrospectionView { | |
IntrospectionView() | |
} | |
func updateUIView(_ uiView: IntrospectionView, context: Context) { | |
uiView.parentViewControllerBinding = $parentViewController | |
} | |
} | |
private struct ParentViewControllerEnvironmentModifier: ViewModifier { | |
@State var viewController = Weak<UIViewController>(nil) | |
func body(content: Content) -> some View { | |
content | |
.environment(\.parentViewControllerBox, viewController) | |
.background(alignment: .bottomTrailing) { | |
IntrospectionViewRepresentable(parentViewController: $viewController) | |
.frame(width: 0, height: 0) | |
.allowsHitTesting(false) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment