diff --git a/Swiftgram/Playground/Sources/AppDelegate.swift b/Swiftgram/Playground/Sources/AppDelegate.swift index 68beb2d5bd..0028bfc0f4 100644 --- a/Swiftgram/Playground/Sources/AppDelegate.swift +++ b/Swiftgram/Playground/Sources/AppDelegate.swift @@ -3,6 +3,7 @@ import SwiftUI import AsyncDisplayKit import Display +let SHOW_SAFE_AREA = false @objc(AppDelegate) final class AppDelegate: NSObject, UIApplicationDelegate { @@ -18,7 +19,6 @@ final class AppDelegate: NSObject, UIApplicationDelegate { hostView.containerView.backgroundColor = UIColor.white self.window = window - let navigationController = NavigationController( mode: .single, theme: NavigationControllerTheme( @@ -30,7 +30,49 @@ final class AppDelegate: NSObject, UIApplicationDelegate { mainWindow.viewController = navigationController - navigationController.setViewControllers([mySwiftUIViewController(0)], animated: false) + let rootViewController = mySwiftUIViewController(0) + + if SHOW_SAFE_AREA { + // Add insets visualization + rootViewController.view.layoutMargins = .zero + rootViewController.view.subviews.forEach { $0.removeFromSuperview() } + + let topInsetView = UIView() + let leftInsetView = UIView() + let rightInsetView = UIView() + let bottomInsetView = UIView() + + [topInsetView, leftInsetView, rightInsetView, bottomInsetView].forEach { + $0.backgroundColor = .systemRed + $0.alpha = 0.3 + rootViewController.view.addSubview($0) + $0.translatesAutoresizingMaskIntoConstraints = false + } + + NSLayoutConstraint.activate([ + topInsetView.topAnchor.constraint(equalTo: rootViewController.view.topAnchor), + topInsetView.leadingAnchor.constraint(equalTo: rootViewController.view.leadingAnchor), + topInsetView.trailingAnchor.constraint(equalTo: rootViewController.view.trailingAnchor), + topInsetView.bottomAnchor.constraint(equalTo: rootViewController.view.safeAreaLayoutGuide.topAnchor), + + leftInsetView.topAnchor.constraint(equalTo: rootViewController.view.topAnchor), + leftInsetView.leadingAnchor.constraint(equalTo: rootViewController.view.leadingAnchor), + leftInsetView.bottomAnchor.constraint(equalTo: rootViewController.view.bottomAnchor), + leftInsetView.trailingAnchor.constraint(equalTo: rootViewController.view.safeAreaLayoutGuide.leadingAnchor), + + rightInsetView.topAnchor.constraint(equalTo: rootViewController.view.topAnchor), + rightInsetView.trailingAnchor.constraint(equalTo: rootViewController.view.trailingAnchor), + rightInsetView.bottomAnchor.constraint(equalTo: rootViewController.view.bottomAnchor), + rightInsetView.leadingAnchor.constraint(equalTo: rootViewController.view.safeAreaLayoutGuide.trailingAnchor), + + bottomInsetView.bottomAnchor.constraint(equalTo: rootViewController.view.bottomAnchor), + bottomInsetView.leadingAnchor.constraint(equalTo: rootViewController.view.leadingAnchor), + bottomInsetView.trailingAnchor.constraint(equalTo: rootViewController.view.trailingAnchor), + bottomInsetView.topAnchor.constraint(equalTo: rootViewController.view.safeAreaLayoutGuide.bottomAnchor) + ]) + } + + navigationController.setViewControllers([rootViewController], animated: false) self.window?.makeKeyAndVisible() diff --git a/Swiftgram/Playground/Sources/SwiftUIViewController.swift b/Swiftgram/Playground/Sources/SwiftUIViewController.swift index 0291b9bfc9..0f2c820d9b 100644 --- a/Swiftgram/Playground/Sources/SwiftUIViewController.swift +++ b/Swiftgram/Playground/Sources/SwiftUIViewController.swift @@ -6,288 +6,62 @@ import UIKit import LegacyUI import TelegramPresentationData -public class SwiftUIViewControllerInteraction { - let push: (ViewController) -> Void - let present: ( - _ controller: ViewController, - _ in: PresentationContextType, - _ with: ViewControllerPresentationArguments? - ) -> Void - let dismiss: (_ animated: Bool, _ completion: (() -> Void)?) -> Void - - init( - push: @escaping (ViewController) -> Void, - present: @escaping ( - _ controller: ViewController, - _ in: PresentationContextType, - _ with: ViewControllerPresentationArguments? - ) -> Void, - dismiss: @escaping (_ animated: Bool, _ completion: (() -> Void)?) -> Void - ) { - self.push = push - self.present = present - self.dismiss = dismiss - } -} -// -//public protocol SwiftUIView: View { -// var controllerInteraction: SwiftUIViewControllerInteraction? { get set } -//} -// -//struct MySwiftUIView: SwiftUIView { -// var controllerInteraction: SwiftUIViewControllerInteraction? -// -// -// var num: Int64 -// -// var body: some View { -// Color.orange -// } -//} -// -//struct CustomButtonStyle: ButtonStyle { -// func makeBody(configuration: Configuration) -> some View { -// configuration.label -// .padding() -// .background(Color.blue) -// .foregroundColor(.white) -// .cornerRadius(8) -// .frame(height: 44) // Set a fixed height for all buttons -// } -//} -// -//private final class SwiftUIViewControllerNode: ASDisplayNode { -// private let hostingController: UIHostingController -// private var isDismissed = false -// private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? -// -// init(swiftUIView: Content) { -// self.hostingController = UIHostingController(rootView: swiftUIView) -// super.init() -// -// // For debugging -// self.backgroundColor = .red.withAlphaComponent(0.3) -// hostingController.view.backgroundColor = .blue.withAlphaComponent(0.3) -// } -// -// override func didLoad() { -// super.didLoad() -// -// // Defer the setup to ensure we have a valid view controller hierarchy -// DispatchQueue.main.async { [weak self] in -// self?.setupHostingController() -// } -// } -// -// private func setupHostingController() { -// guard let viewController = findViewController() else { -// assert(true, "Error: Could not find a parent view controller") -// return -// } -// -// viewController.addChild(hostingController) -// view.addSubview(hostingController.view) -// hostingController.didMove(toParent: viewController) -// -// // Ensure the hosting controller's view has a size -// hostingController.view.frame = self.bounds -// -// print("SwiftUIViewControllerNode setup - Node frame: \(self.frame), Hosting view frame: \(hostingController.view.frame)") -// } -// -// private func findViewController() -> UIViewController? { -// var responder: UIResponder? = self.view -// while let nextResponder = responder?.next { -// if let viewController = nextResponder as? UIViewController { -// return viewController -// } -// responder = nextResponder -// } -// return nil -// } -// -// override func layout() { -// super.layout() -// hostingController.view.frame = self.bounds -// print("SwiftUIViewControllerNode layout - Node frame: \(self.frame), Hosting view frame: \(hostingController.view.frame)") -// } -// -// func containerLayoutUpdated( -// layout: ContainerViewLayout, -// navigationHeight: CGFloat, -// transition: ContainedViewLayoutTransition -// ) { -// if self.isDismissed { -// return -// } -// -// self.validLayout = (layout, navigationHeight) -// -// let frame = CGRect( -// origin: CGPoint(x: 0, y: 0), -// size: CGSize( -// width: layout.size.width, -// height: layout.size.height -// ) -// ) -// -// transition.updateFrame(node: self, frame: frame) -// -// print("containerLayoutUpdated - New frame: \(frame)") -// -// // Ensure hosting controller view is updated -// hostingController.view.frame = bounds -// } -// -// func animateOut(completion: @escaping () -> Void) { -// guard let (layout, navigationHeight) = validLayout else { -// completion() -// return -// } -// self.isDismissed = true -// let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) -// -// let frame = CGRect( -// origin: CGPoint(x: 0, y: 0), -// size: CGSize( -// width: layout.size.width, -// height: layout.size.height -// ) -// ) -// -// transition.updateFrame(node: self, frame: frame, completion: { _ in -// completion() -// }) -// } -// -// override func didEnterHierarchy() { -// super.didEnterHierarchy() -// print("SwiftUIViewControllerNode entered hierarchy") -// } -// -// override func didExitHierarchy() { -// super.didExitHierarchy() -// hostingController.willMove(toParent: nil) -// hostingController.view.removeFromSuperview() -// hostingController.removeFromParent() -// print("SwiftUIViewControllerNode exited hierarchy") -// } -//} -// -//public final class SwiftUIViewController: ViewController { -// private var swiftUIView: Content -// -// public init( -// _ swiftUIView: Content, -// navigationBarTheme: NavigationBarTheme = NavigationBarTheme( -// buttonColor: ACCENT_COLOR, -// disabledButtonColor: .gray, -// primaryTextColor: .black, -// backgroundColor: .clear, -// enableBackgroundBlur: true, -// separatorColor: .gray, -// badgeBackgroundColor: THEME.navigationBar.badgeBackgroundColor, -// badgeStrokeColor: THEME.navigationBar.badgeStrokeColor, -// badgeTextColor: THEME.navigationBar.badgeTextColor -// ), -// navigationBarStrings: NavigationBarStrings = NavigationBarStrings( -// back: "Back", -// close: "Close" -// ) -// ) { -// self.swiftUIView = swiftUIView -// super.init(navigationBarPresentationData: NavigationBarPresentationData( -// theme: navigationBarTheme, -// strings: navigationBarStrings -// )) -// -// self.swiftUIView.controllerInteraction = SwiftUIViewControllerInteraction( -// push: { [weak self] c in -// guard let strongSelf = self else { return } -// strongSelf.push(c) -// }, -// present: { [weak self] c, context, args in -// guard let strongSelf = self else { return } -// strongSelf.present(c, in: context, with: args) -// }, -// dismiss: { [weak self] animated, completion in -// guard let strongSelf = self else { return } -// strongSelf.dismiss(animated: animated, completion: completion) -// } -// ) -// } -// -// @available(*, unavailable) -// required init(coder _: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// override public func loadDisplayNode() { -// self.displayNode = SwiftUIViewControllerNode(swiftUIView: swiftUIView) -// } -// -// override public func containerLayoutUpdated( -// _ layout: ContainerViewLayout, -// transition: ContainedViewLayoutTransition -// ) { -// super.containerLayoutUpdated(layout, transition: transition) -// -// (self.displayNode as! SwiftUIViewControllerNode).containerLayoutUpdated( -// layout: layout, -// navigationHeight: navigationLayout(layout: layout).navigationFrame.maxY, -// transition: transition -// ) -// } -// -// public func animateOut(completion: @escaping () -> Void) { -// (self.displayNode as! SwiftUIViewControllerNode) -// .animateOut(completion: completion) -// } -//} -// -// -//func mySwiftUIViewController(_ num: Int64) -> ViewController { -// let controller = SwiftUIViewController(MySwiftUIView(num: num)) -// controller.title = "Controller: \(num)" -// return controller -//} - - -public protocol SwiftUIView: View { - var controllerInteraction: SwiftUIViewControllerInteraction? { get set } -} - -struct MySwiftUIView: SwiftUIView { - var controllerInteraction: SwiftUIViewControllerInteraction? +struct MySwiftUIView: View { + weak var wrapperController: LegacyController? var num: Int64 var body: some View { - Color.orange -// VStack { -// Button("Push") { -// self.controllerInteraction?.push(mySwiftUIViewController(num + 1)) -// } -// Button("Modal") { -// self.controllerInteraction?.present(mySwiftUIViewController(num + 1), .window(.root), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) -// } -// Button("Dismiss") { -// self.controllerInteraction?.dismiss(true, {}) -// } -// } + VStack { + Text("Hello, World!") + .font(.title) + .foregroundColor(.black) + + Spacer(minLength: 0) + + Button("Push") { + self.wrapperController?.push(mySwiftUIViewController(num + 1)) + }.buttonStyle(AppleButtonStyle()) + Spacer() + Button("Modal") { + self.wrapperController?.present(mySwiftUIViewController(num + 1), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }.buttonStyle(AppleButtonStyle()) + Spacer() + if num > 0 { + Button("Dismiss") { + self.wrapperController?.dismiss() + }.buttonStyle(AppleButtonStyle()) + Spacer() + } + } +// .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.green) + } +} + + +struct AppleButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(.headline) + .foregroundColor(.white) + .padding() + .frame(minWidth: 0, maxWidth: .infinity) + .background(Color.blue) + .cornerRadius(10) + .scaleEffect(configuration.isPressed ? 0.95 : 1) + .opacity(configuration.isPressed ? 0.9 : 1) } } func mySwiftUIViewController(_ num: Int64) -> ViewController { let legacyController = LegacyController(presentation: .navigation, theme: defaultPresentationTheme, strings: defaultPresentationStrings) legacyController.statusBar.statusBarStyle = defaultPresentationTheme.rootController.statusBarStyle.style - legacyController.title = "Controller: root" + legacyController.title = "Controller: \(num)" - let controller = UIHostingController(rootView: MySwiftUIView(num: num)) + let swiftUIView = MySwiftUIView(wrapperController: legacyController, num: num) + let controller = UIHostingController(rootView: swiftUIView) legacyController.bind(controller: controller) - legacyController.addChild(controller) - controller.didMove(toParent: legacyController) - return legacyController } diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift index 11257c2b60..1f85ad2dd0 100644 --- a/submodules/LegacyUI/Sources/LegacyController.swift +++ b/submodules/LegacyUI/Sources/LegacyController.swift @@ -4,6 +4,7 @@ import Display import SwiftSignalKit import LegacyComponents import TelegramPresentationData +import SwiftUI public enum LegacyControllerPresentation { case custom @@ -475,6 +476,10 @@ open class LegacyController: ViewController, PresentableController { self?.dismiss() } } + if self.legacyController.isHosting { + self.addChild(self.legacyController) + self.legacyController.didMove(toParent: legacyController) + } } override open func loadDisplayNode() { @@ -614,6 +619,11 @@ open class LegacyController: ViewController, PresentableController { if previousSizeClass != updatedSizeClass { self.sizeClass.set(SSignal.single(updatedSizeClass.rawValue as NSNumber)) } + if let sai = self.controllerNode.controllerView?.safeAreaInsets { + print("Safe area 1", sai) + } + print("Safe area 2", self.controllerNode.safeAreaInsets) + print("Safe area 3", self.view.safeAreaInsets) } override open func dismiss(completion: (() -> Void)? = nil) { @@ -636,3 +646,24 @@ open class LegacyController: ViewController, PresentableController { } } } + +extension LegacyController { + +// private func syncLegacyControllerSafeArea() { +// let recommendedSafeAreaInsets = self.contextImpl.safeAreaInset() +// let currentSafeAreaInsets = self.legacyController.view.safeAreaInsets +// let additionalInsets = UIEdgeInsets(top: recommendedSafeAreaInsets.top - currentSafeAreaInsets.top, left: recommendedSafeAreaInsets.left - currentSafeAreaInsets.left, bottom: recommendedSafeAreaInsets.bottom - currentSafeAreaInsets.bottom, right: recommendedSafeAreaInsets.left - currentSafeAreaInsets.right) +// self.legacyController.additionalSafeAreaInsets = additionalInsets +// self.legacyController.viewSafeAreaInsetsDidChange() +// self.legacyController.view.setNeedsLayout() +// } + +} + +// MARK: Swiftgram +private protocol AnyUIHostingViewController: AnyObject {} +extension UIHostingController: AnyUIHostingViewController {} + +extension UIViewController { + var isHosting: Bool { self is AnyUIHostingViewController } +}