mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
SGSwiftUI lib to embed views into Telegram's stack
This commit is contained in:
parent
bb6c04e52d
commit
a9dfb01961
@ -27,6 +27,7 @@ swift_library(
|
|||||||
"//submodules/LegacyUI:LegacyUI",
|
"//submodules/LegacyUI:LegacyUI",
|
||||||
"//submodules/LegacyComponents:LegacyComponents",
|
"//submodules/LegacyComponents:LegacyComponents",
|
||||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||||
|
"//Swiftgram/SGSwiftUI:SGSwiftUI",
|
||||||
],
|
],
|
||||||
data = [
|
data = [
|
||||||
"//Telegram:GeneratedPresentationStrings/Resources/PresentationStrings.data",
|
"//Telegram:GeneratedPresentationStrings/Resources/PresentationStrings.data",
|
||||||
|
@ -1,9 +1,85 @@
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
|
||||||
import UIKit
|
|
||||||
import LegacyUI
|
import LegacyUI
|
||||||
|
import SGSwiftUI
|
||||||
|
import SwiftUI
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct MySwiftUIView: View {
|
||||||
|
weak var wrapperController: LegacyController?
|
||||||
|
|
||||||
|
var num: Int64
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
ForEach(1..<20, id: \.self) { i in
|
||||||
|
Button("TAP: \(i)") {
|
||||||
|
print("Tapped \(i)")
|
||||||
|
}.buttonStyle(AppleButtonStyle())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func mySwiftUIViewController(_ num: Int64) -> ViewController {
|
||||||
|
let legacyController = LegacySwiftUIController(
|
||||||
|
presentation: .modal(animateIn: true),
|
||||||
|
theme: defaultPresentationTheme,
|
||||||
|
strings: defaultPresentationStrings
|
||||||
|
)
|
||||||
|
legacyController.statusBar.statusBarStyle = defaultPresentationTheme.rootController
|
||||||
|
.statusBarStyle.style
|
||||||
|
legacyController.title = "Controller: \(num)"
|
||||||
|
|
||||||
|
let swiftUIView = SGSwiftUIView<MySwiftUIView>(
|
||||||
|
navigationBarHeight: legacyController.navigationBarHeightModel,
|
||||||
|
containerViewLayout: legacyController.containerViewLayoutModel,
|
||||||
|
content: { MySwiftUIView(wrapperController: legacyController, num: num) }
|
||||||
|
)
|
||||||
|
let controller = UIHostingController(rootView: swiftUIView, ignoreSafeArea: true)
|
||||||
|
legacyController.bind(controller: controller)
|
||||||
|
|
||||||
|
return legacyController
|
||||||
|
}
|
||||||
|
20
Swiftgram/SGSwiftUI/BUILD
Normal file
20
Swiftgram/SGSwiftUI/BUILD
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "SGSwiftUI",
|
||||||
|
module_name = "SGSwiftUI",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
# "-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/LegacyUI:LegacyUI",
|
||||||
|
"//submodules/Display:Display",
|
||||||
|
"//submodules/TelegramPresentationData:TelegramPresentationData"
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
145
Swiftgram/SGSwiftUI/Sources/SGSwiftUI.swift
Normal file
145
Swiftgram/SGSwiftUI/Sources/SGSwiftUI.swift
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import Display
|
||||||
|
import Foundation
|
||||||
|
import LegacyUI
|
||||||
|
import SwiftUI
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
public class ObservedValue<T>: ObservableObject {
|
||||||
|
@Published var value: T
|
||||||
|
|
||||||
|
init(_ value: T) {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SGSwiftUIView<Content: View>: View {
|
||||||
|
let content: Content
|
||||||
|
|
||||||
|
@ObservedObject var navigationBarHeight: ObservedValue<CGFloat>
|
||||||
|
@ObservedObject var containerViewLayout: ObservedValue<ContainerViewLayout?>
|
||||||
|
|
||||||
|
public init(
|
||||||
|
navigationBarHeight: ObservedValue<CGFloat>,
|
||||||
|
containerViewLayout: ObservedValue<ContainerViewLayout?>,
|
||||||
|
@ViewBuilder content: () -> Content
|
||||||
|
) {
|
||||||
|
self.navigationBarHeight = navigationBarHeight
|
||||||
|
self.containerViewLayout = containerViewLayout
|
||||||
|
self.content = content()
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
content
|
||||||
|
.modifier(CustomSafeAreaPadding(navigationBarHeight: navigationBarHeight, containerViewLayout: containerViewLayout))
|
||||||
|
.background(Color.yellow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct CustomSafeAreaPadding: ViewModifier {
|
||||||
|
@ObservedObject var navigationBarHeight: ObservedValue<CGFloat>
|
||||||
|
@ObservedObject var containerViewLayout: ObservedValue<ContainerViewLayout?>
|
||||||
|
|
||||||
|
public func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
.padding(.top, totalTopSafeArea > navigationBarHeight.value ? totalTopSafeArea : navigationBarHeight.value)
|
||||||
|
.padding(.bottom, (containerViewLayout.value?.safeInsets.bottom ?? 0) + (containerViewLayout.value?.intrinsicInsets.bottom ?? 0))
|
||||||
|
.padding(.leading, containerViewLayout.value?.safeInsets.left ?? 0)
|
||||||
|
.padding(.trailing, containerViewLayout.value?.safeInsets.right ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalTopSafeArea: CGFloat {
|
||||||
|
(containerViewLayout.value?.safeInsets.top ?? 0) +
|
||||||
|
(containerViewLayout.value?.intrinsicInsets.top ?? 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class LegacySwiftUIController: LegacyController {
|
||||||
|
public var navigationBarHeightModel: ObservedValue<CGFloat>
|
||||||
|
public var containerViewLayoutModel: ObservedValue<ContainerViewLayout?>
|
||||||
|
|
||||||
|
override public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) {
|
||||||
|
navigationBarHeightModel = ObservedValue<CGFloat>(0.0)
|
||||||
|
containerViewLayoutModel = ObservedValue<ContainerViewLayout?>(initialLayout)
|
||||||
|
super.init(presentation: presentation, theme: theme, strings: strings, initialLayout: initialLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
|
let newNavigationBarHeight = navigationLayout(layout: layout).navigationFrame.maxY
|
||||||
|
if navigationBarHeightModel.value != newNavigationBarHeight {
|
||||||
|
navigationBarHeightModel.value = newNavigationBarHeight
|
||||||
|
}
|
||||||
|
if containerViewLayoutModel.value != layout {
|
||||||
|
containerViewLayoutModel.value = layout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func bind(controller: UIViewController) {
|
||||||
|
super.bind(controller: controller)
|
||||||
|
addChild(legacyController)
|
||||||
|
legacyController.didMove(toParent: legacyController)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
public required init(coder _: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIHostingController {
|
||||||
|
public convenience init(rootView: Content, ignoreSafeArea: Bool) {
|
||||||
|
self.init(rootView: rootView)
|
||||||
|
|
||||||
|
if ignoreSafeArea {
|
||||||
|
disableSafeArea()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableSafeArea() {
|
||||||
|
guard let viewClass = object_getClass(view) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeText(string: String, key: Int16) -> String {
|
||||||
|
let nsString = string as NSString
|
||||||
|
let result = NSMutableString()
|
||||||
|
for i in 0 ..< nsString.length {
|
||||||
|
var c: unichar = nsString.character(at: i)
|
||||||
|
c = unichar(Int16(c) + key)
|
||||||
|
result.append(NSString(characters: &c, length: 1) as String)
|
||||||
|
}
|
||||||
|
return result as String
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewSubclassName = String(cString: class_getName(viewClass)).appending(encodeText(string: "`JhopsfTbgfBsfb", key: -1))
|
||||||
|
|
||||||
|
if let viewSubclass = NSClassFromString(viewSubclassName) {
|
||||||
|
object_setClass(view, viewSubclass)
|
||||||
|
} else {
|
||||||
|
guard
|
||||||
|
let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String,
|
||||||
|
let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0)
|
||||||
|
else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let method = class_getInstanceMethod(UIView.self, #selector(getter: UIView.safeAreaInsets)) {
|
||||||
|
let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = { _ in
|
||||||
|
.zero
|
||||||
|
}
|
||||||
|
|
||||||
|
class_addMethod(
|
||||||
|
viewSubclass,
|
||||||
|
#selector(getter: UIView.safeAreaInsets),
|
||||||
|
imp_implementationWithBlock(safeAreaInsets),
|
||||||
|
method_getTypeEncoding(method)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
objc_registerClassPair(viewSubclass)
|
||||||
|
object_setClass(view, viewSubclass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@ import Display
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
public enum LegacyControllerPresentation {
|
public enum LegacyControllerPresentation {
|
||||||
case custom
|
case custom
|
||||||
@ -438,9 +437,6 @@ open class LegacyController: ViewController, PresentableController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Swiftgram
|
|
||||||
public var navigationBarHeightModel: ObservedNavigationBarHeight
|
|
||||||
|
|
||||||
public var disposables = DisposableSet()
|
public var disposables = DisposableSet()
|
||||||
|
|
||||||
public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) {
|
public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) {
|
||||||
@ -454,7 +450,6 @@ open class LegacyController: ViewController, PresentableController {
|
|||||||
} else {
|
} else {
|
||||||
navigationBarPresentationData = nil
|
navigationBarPresentationData = nil
|
||||||
}
|
}
|
||||||
self.navigationBarHeightModel = ObservedNavigationBarHeight(value: 0.0)
|
|
||||||
super.init(navigationBarPresentationData: navigationBarPresentationData)
|
super.init(navigationBarPresentationData: navigationBarPresentationData)
|
||||||
|
|
||||||
if let theme = theme {
|
if let theme = theme {
|
||||||
@ -473,17 +468,13 @@ open class LegacyController: ViewController, PresentableController {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func bind(controller: UIViewController) {
|
open func bind(controller: UIViewController) {
|
||||||
self.legacyController = controller
|
self.legacyController = controller
|
||||||
if let controller = controller as? TGViewController {
|
if let controller = controller as? TGViewController {
|
||||||
controller.customRemoveFromParentViewController = { [weak self] in
|
controller.customRemoveFromParentViewController = { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.legacyController.isHosting {
|
|
||||||
self.addChild(self.legacyController)
|
|
||||||
self.legacyController.didMove(toParent: legacyController)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override open func loadDisplayNode() {
|
override open func loadDisplayNode() {
|
||||||
@ -593,8 +584,8 @@ open class LegacyController: ViewController, PresentableController {
|
|||||||
self.validLayout = layout
|
self.validLayout = layout
|
||||||
|
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
let navigationBarHeight = self.navigationLayout(layout: layout).navigationFrame.maxY
|
|
||||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||||
if let legacyTelegramController = self.legacyController as? TGViewController {
|
if let legacyTelegramController = self.legacyController as? TGViewController {
|
||||||
var duration: TimeInterval = 0.0
|
var duration: TimeInterval = 0.0
|
||||||
if case let .animated(transitionDuration, _) = transition {
|
if case let .animated(transitionDuration, _) = transition {
|
||||||
@ -623,7 +614,6 @@ open class LegacyController: ViewController, PresentableController {
|
|||||||
if previousSizeClass != updatedSizeClass {
|
if previousSizeClass != updatedSizeClass {
|
||||||
self.sizeClass.set(SSignal.single(updatedSizeClass.rawValue as NSNumber))
|
self.sizeClass.set(SSignal.single(updatedSizeClass.rawValue as NSNumber))
|
||||||
}
|
}
|
||||||
self.navigationBarHeightModel.value = navigationBarHeight
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override open func dismiss(completion: (() -> Void)? = nil) {
|
override open func dismiss(completion: (() -> Void)? = nil) {
|
||||||
@ -646,90 +636,3 @@ open class LegacyController: ViewController, PresentableController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Swiftgram
|
|
||||||
private protocol AnyUIHostingViewController: AnyObject {}
|
|
||||||
extension UIHostingController: AnyUIHostingViewController {}
|
|
||||||
|
|
||||||
extension UIViewController {
|
|
||||||
var isHosting: Bool { self is AnyUIHostingViewController }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct MySwiftUIView: View {
|
|
||||||
weak var wrapperController: LegacyController?
|
|
||||||
|
|
||||||
var num: Int64
|
|
||||||
|
|
||||||
@ObservedObject var navigationBarHeight: ObservedNavigationBarHeight
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background(Color.green)
|
|
||||||
.padding(.top, self.navigationBarHeight.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// func containerLayoutUpdatedNotification(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
// print("[\(num)] SwiftUI container update with navigationBarHeight \(navigationBarHeight)")
|
|
||||||
// self.navigationBarHeightValue = navigationBarHeight
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func mySwiftUIViewController(_ num: Int64) -> ViewController {
|
|
||||||
let legacyController = LegacyController(presentation: .navigation, theme: defaultPresentationTheme, strings: defaultPresentationStrings)
|
|
||||||
legacyController.statusBar.statusBarStyle = defaultPresentationTheme.rootController.statusBarStyle.style
|
|
||||||
legacyController.title = "Controller: \(num)"
|
|
||||||
|
|
||||||
let swiftUIView = MySwiftUIView(wrapperController: legacyController, num: num, navigationBarHeight: legacyController.navigationBarHeightModel)
|
|
||||||
let controller = UIHostingController(rootView: swiftUIView)
|
|
||||||
legacyController.bind(controller: controller)
|
|
||||||
|
|
||||||
return legacyController
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ObservedNavigationBarHeight: ObservableObject {
|
|
||||||
@Published var value: CGFloat
|
|
||||||
|
|
||||||
init(value: CGFloat) {
|
|
||||||
self.value = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user