|
import SwiftUI |
|
import UIKit |
|
|
|
struct SwiftUIHostingConfiguration<Content: View>: UIContentConfiguration { |
|
private weak var parentViewController: UIViewController? |
|
private let content: Content |
|
|
|
init(parentViewController: UIViewController, @ViewBuilder content: () -> Content) { |
|
self.parentViewController = parentViewController |
|
self.content = content() |
|
} |
|
|
|
private init(parentViewController: UIViewController, content: Content) { |
|
self.parentViewController = parentViewController |
|
self.content = content |
|
} |
|
|
|
func makeContentView() -> any UIView & UIContentView { |
|
let contentView = ContentView(configuration: self) |
|
contentView.layoutMargins = .zero |
|
return contentView |
|
} |
|
|
|
func updated(for state: any UIConfigurationState) -> SwiftUIHostingConfiguration { |
|
guard let parentViewController else { |
|
fatalError("Cannot update \(self) because parentViewController has been deallocated") |
|
} |
|
return SwiftUIHostingConfiguration(parentViewController: parentViewController, content: content) |
|
} |
|
} |
|
|
|
private extension SwiftUIHostingConfiguration { |
|
final class ContentView: UIView, UIContentView { |
|
var configuration: UIContentConfiguration { |
|
get { |
|
currentConfiguration |
|
} |
|
set { |
|
guard let newConfiguration = newValue as? SwiftUIHostingConfiguration<Content> else { |
|
fatalError("Expected configuration to be of type \(SwiftUIHostingConfiguration<Content>.self)") |
|
} |
|
currentConfiguration = newConfiguration |
|
hostingController.rootView = newConfiguration.content |
|
// Removing and re-adding the hosting controller from the view hierarchy and then invalidating |
|
// the intrinsic content size seems to be the only way to cause the UICollectionVIew and |
|
// UITableView to relayout the cells and update the heights. |
|
removeHostingControllerFromViewHierarchy() |
|
addHostingControllerToViewHierarchy() |
|
invalidateIntrinsicContentSize() |
|
} |
|
} |
|
|
|
private let hostingController: UIHostingController<Content> |
|
private var currentConfiguration: SwiftUIHostingConfiguration<Content> |
|
|
|
init(configuration: SwiftUIHostingConfiguration<Content>) { |
|
self.hostingController = UIHostingController(rootView: configuration.content) |
|
self.currentConfiguration = configuration |
|
super.init(frame: .zero) |
|
addHostingControllerToViewHierarchy() |
|
} |
|
|
|
required init?(coder: NSCoder) { |
|
fatalError("init(coder:) has not been implemented") |
|
} |
|
|
|
deinit { |
|
removeHostingControllerFromViewHierarchy() |
|
} |
|
|
|
func supports(_ configuration: any UIContentConfiguration) -> Bool { |
|
configuration is SwiftUIHostingConfiguration<Content> |
|
} |
|
} |
|
} |
|
|
|
private extension SwiftUIHostingConfiguration.ContentView { |
|
private func addHostingControllerToViewHierarchy() { |
|
guard let parentViewController = currentConfiguration.parentViewController else { |
|
fatalError("Cannot setup \(Self.self) as parentViewController has been deallocated") |
|
} |
|
parentViewController.addChild(hostingController) |
|
hostingController.view.translatesAutoresizingMaskIntoConstraints = false |
|
addSubview(hostingController.view) |
|
NSLayoutConstraint.activate([ |
|
hostingController.view.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), |
|
hostingController.view.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), |
|
hostingController.view.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), |
|
hostingController.view.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) |
|
]) |
|
hostingController.didMove(toParent: parentViewController) |
|
} |
|
|
|
private func removeHostingControllerFromViewHierarchy() { |
|
hostingController.willMove(toParent: nil) |
|
hostingController.view.removeFromSuperview() |
|
hostingController.removeFromParent() |
|
} |
|
} |