Skip to content

Instantly share code, notes, and snippets.

@harlanhaskins
Last active March 5, 2025 23:06
Show Gist options
  • Save harlanhaskins/30d0f2367c3b86688412a021bd6479f8 to your computer and use it in GitHub Desktop.
Save harlanhaskins/30d0f2367c3b86688412a021bd6479f8 to your computer and use it in GitHub Desktop.
A SwiftUI modifier for inserting the parent UIViewController in the environment
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