-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
/*.xcodeproj | ||
xcuserdata/ | ||
DerivedData/ | ||
.swiftpm/config/registries.json | ||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
.netrc |
8 changes: 8 additions & 0 deletions
8
.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] | ||
), | ||
] | ||
) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
# MyLibrary | ||
|
||
A description of this package. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import SwiftUI | ||
|
||
@available(iOS 13, *) | ||
final class HostingController<Content: View>: UIHostingController<Content> { | ||
|
||
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 |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<RootView> | ||
|
||
private let proxy: Proxy = .init() | ||
|
||
public let configuration: Configuration | ||
|
||
public convenience init<Content: View>( | ||
_ 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<Content: SwiftUI.View>( | ||
@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() | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import UIKit | ||
|
||
extension UIResponder { | ||
|
||
func findNearestViewController() -> UIViewController? { | ||
sequence(first: self, next: { $0.next }) | ||
.first { | ||
$0 is UIViewController | ||
} as? UIViewController | ||
} | ||
|
||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|