diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3b29812
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+xcuserdata/
+DerivedData/
+.swiftpm/config/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc
diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..346b27e
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,32 @@
+// swift-tools-version: 5.7
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "SwiftUIHosting",
+ platforms: [.iOS(.v13)],
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "SwiftUIHosting",
+ targets: ["SwiftUIHosting"]
+ )
+ ],
+ dependencies: [
+ // Dependencies declare other packages that this package depends on.
+ // .package(url: /* package url */, from: "1.0.0"),
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages this package depends on.
+ .target(
+ name: "SwiftUIHosting",
+ dependencies: []
+ ),
+ .testTarget(
+ name: "SwiftUIHostingTests",
+ dependencies: ["SwiftUIHosting"]
+ ),
+ ]
+)
diff --git a/README.md b/README.md
index 8b13789..a4342dc 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
+# MyLibrary
+A description of this package.
diff --git a/Sources/SwiftUIHosting/HostingController.swift b/Sources/SwiftUIHosting/HostingController.swift
new file mode 100644
index 0000000..f5c7156
--- /dev/null
+++ b/Sources/SwiftUIHosting/HostingController.swift
@@ -0,0 +1,82 @@
+import SwiftUI
+
+@available(iOS 13, *)
+final class HostingController: UIHostingController {
+
+ var onViewDidLayoutSubviews: (UIViewController) -> Void = { _ in }
+
+ init(disableSafeArea: Bool, rootView: Content) {
+ super.init(rootView: rootView)
+
+ // https://www.notion.so/muukii/UIHostingController-safeArea-issue-ec66a560970c4a1cb44f21cc448bc513?pvs=4
+#if USE_SWIZZLING
+ _ = _once_
+ _fixing_safeArea = disableSafeArea
+#else
+ _disableSafeArea = disableSafeArea
+#endif
+ }
+
+ @MainActor required dynamic init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+
+ onViewDidLayoutSubviews(self)
+ }
+}
+
+#if USE_SWIZZLING
+
+private let _once_: Void = {
+ UIView.replace()
+}()
+
+private var _key: Void?
+
+extension UIView {
+
+ fileprivate static func replace() {
+
+ method_exchangeImplementations(
+ class_getInstanceMethod(self, #selector(getter:UIView.safeAreaInsets))!,
+ class_getInstanceMethod(self, #selector(getter:UIView._hosting_safeAreaInsets))!
+ )
+
+ method_exchangeImplementations(
+ class_getInstanceMethod(self, #selector(getter:UIView.safeAreaLayoutGuide))!,
+ class_getInstanceMethod(self, #selector(getter:UIView._hosting_safeAreaLayoutGuide))!
+ )
+
+ }
+
+ fileprivate var _fixing_safeArea: Bool {
+ get {
+ (objc_getAssociatedObject(self, &_key) as? Bool) ?? false
+ }
+ set {
+ objc_setAssociatedObject(self, &_key, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
+ }
+ }
+
+ @objc dynamic var _hosting_safeAreaInsets: UIEdgeInsets {
+ if _fixing_safeArea {
+ return .zero
+ } else {
+ return self._hosting_safeAreaInsets
+ }
+ }
+
+ @objc dynamic var _hosting_safeAreaLayoutGuide: UILayoutGuide? {
+ if _fixing_safeArea {
+ return nil
+ } else {
+ return self._hosting_safeAreaLayoutGuide
+ }
+ }
+
+}
+
+#endif
diff --git a/Sources/SwiftUIHosting/SwiftUIHostingView.swift b/Sources/SwiftUIHosting/SwiftUIHostingView.swift
new file mode 100644
index 0000000..c938d3d
--- /dev/null
+++ b/Sources/SwiftUIHosting/SwiftUIHostingView.swift
@@ -0,0 +1,167 @@
+import SwiftUI
+
+/// A view that hosts SwiftUI for UIKit environment.
+open class SwiftUIHostingView: UIView {
+
+ public struct Configuration {
+
+ /**
+ Registers internal hosting controller into the nearest view controller's children.
+
+ Apple developer explains why we need to UIHostinController should be a child of the view controller.
+ ```
+ When using UIHostingController, make sure to always add the view controller together with the view to your app.
+
+ Many SwiftUI features, such as toolbars, keyboard shortcuts, and views that use UIViewControllerRepresentable, require a connection to the view controller hierarchy in UIKit to integrate properly, so never separate a hosting controller's view from the hosting controller itself.
+ ```
+ */
+ public var registersAsChildViewController: Bool
+
+ /**
+ Fixes handling safe area issue
+ https://www.notion.so/muukii/UIHostingController-safeArea-issue-ec66a560970c4a1cb44f21cc448bc513?pvs=4
+ */
+ public var disableSafeArea: Bool
+
+ public init(
+ registersAsChildViewController: Bool = true,
+ disableSafeArea: Bool = true
+ ) {
+ self.registersAsChildViewController = registersAsChildViewController
+ self.disableSafeArea = disableSafeArea
+ }
+ }
+
+ private var hostingController: HostingController
+
+ private let proxy: Proxy = .init()
+
+ public let configuration: Configuration
+
+ public convenience init(
+ _ name: String = "",
+ _ file: StaticString = #file,
+ _ function: StaticString = #function,
+ _ line: UInt = #line,
+ configuration: Configuration = .init(),
+ @ViewBuilder content: @escaping () -> Content
+ ) {
+ self.init(
+ name,
+ file,
+ function,
+ line,
+ configuration: configuration
+ )
+ setContent(content: content)
+ }
+
+ // MARK: - Initializers
+
+ public init(
+ _ name: String = "",
+ _ file: StaticString = #file,
+ _ function: StaticString = #function,
+ _ line: UInt = #line,
+ configuration: Configuration = .init()
+ ) {
+ self.configuration = configuration
+
+ self.hostingController = HostingController(
+ disableSafeArea: configuration.disableSafeArea,
+ rootView: RootView(proxy: proxy)
+ )
+
+ super.init(frame: .null)
+
+ #if DEBUG
+ let file = URL(string: file.description)?.deletingPathExtension().lastPathComponent ?? "unknown"
+ self.accessibilityIdentifier = [
+ name,
+ file,
+ function.description,
+ line.description,
+ ]
+ .joined(separator: ".")
+ #endif
+
+ hostingController.view.backgroundColor = .clear
+
+ addSubview(hostingController.view)
+ hostingController.view.translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+ hostingController.view.topAnchor.constraint(equalTo: topAnchor),
+ hostingController.view.rightAnchor.constraint(equalTo: rightAnchor),
+ hostingController.view.bottomAnchor.constraint(equalTo: bottomAnchor),
+ hostingController.view.leftAnchor.constraint(equalTo: leftAnchor),
+ ])
+
+ hostingController.onViewDidLayoutSubviews = { controller in
+ // TODO: Reduces number of calling invalidation, it's going to be happen even it's same value.
+ controller.view.invalidateIntrinsicContentSize()
+ }
+
+ }
+
+ @available(*, unavailable)
+ public required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ // MARK: UIView
+
+ /// Returns calculated size using internal hosting controller
+ open override func sizeThatFits(_ size: CGSize) -> CGSize {
+ hostingController.sizeThatFits(in: size)
+ }
+
+ open override func didMoveToWindow() {
+
+ super.didMoveToWindow()
+
+ if configuration.registersAsChildViewController {
+ // https://muukii.notion.site/Why-we-need-to-add-UIHostingController-to-view-controller-chain-14de20041c99499d803f5a877c9a1dd1
+
+ if let _ = window {
+ if let parentViewController = self.findNearestViewController() {
+ parentViewController.addChild(hostingController)
+ hostingController.didMove(toParent: parentViewController)
+ } else {
+ assertionFailure()
+ }
+ } else {
+ hostingController.willMove(toParent: nil)
+ hostingController.removeFromParent()
+ }
+ }
+ }
+
+ // MARK: -
+
+ public final func setContent(
+ @ViewBuilder content: @escaping () -> Content
+ ) {
+ proxy.content = {
+ return SwiftUI.AnyView(
+ content()
+ )
+ }
+ }
+
+}
+
+final class Proxy: ObservableObject {
+ @Published var content: () -> SwiftUI.AnyView? = { nil }
+
+ init() {
+ }
+}
+
+struct RootView: SwiftUI.View {
+ @ObservedObject var proxy: Proxy
+
+ var body: some View {
+ proxy.content()
+ }
+}
diff --git a/Sources/SwiftUIHosting/UIResponder+.swift b/Sources/SwiftUIHosting/UIResponder+.swift
new file mode 100644
index 0000000..af67dcb
--- /dev/null
+++ b/Sources/SwiftUIHosting/UIResponder+.swift
@@ -0,0 +1,12 @@
+import UIKit
+
+extension UIResponder {
+
+ func findNearestViewController() -> UIViewController? {
+ sequence(first: self, next: { $0.next })
+ .first {
+ $0 is UIViewController
+ } as? UIViewController
+ }
+
+}
diff --git a/Tests/SwiftUIHostingTests/MyLibraryTests.swift b/Tests/SwiftUIHostingTests/MyLibraryTests.swift
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/Tests/SwiftUIHostingTests/MyLibraryTests.swift
@@ -0,0 +1 @@
+