mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +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/LegacyComponents:LegacyComponents",
|
||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||
"//Swiftgram/SGSwiftUI:SGSwiftUI",
|
||||
],
|
||||
data = [
|
||||
"//Telegram:GeneratedPresentationStrings/Resources/PresentationStrings.data",
|
||||
|
@ -1,9 +1,85 @@
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import LegacyUI
|
||||
import SGSwiftUI
|
||||
import SwiftUI
|
||||
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 LegacyComponents
|
||||
import TelegramPresentationData
|
||||
import SwiftUI
|
||||
|
||||
public enum LegacyControllerPresentation {
|
||||
case custom
|
||||
@ -438,9 +437,6 @@ open class LegacyController: ViewController, PresentableController {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Swiftgram
|
||||
public var navigationBarHeightModel: ObservedNavigationBarHeight
|
||||
|
||||
public var disposables = DisposableSet()
|
||||
|
||||
public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) {
|
||||
@ -454,7 +450,6 @@ open class LegacyController: ViewController, PresentableController {
|
||||
} else {
|
||||
navigationBarPresentationData = nil
|
||||
}
|
||||
self.navigationBarHeightModel = ObservedNavigationBarHeight(value: 0.0)
|
||||
super.init(navigationBarPresentationData: navigationBarPresentationData)
|
||||
|
||||
if let theme = theme {
|
||||
@ -473,17 +468,13 @@ open class LegacyController: ViewController, PresentableController {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func bind(controller: UIViewController) {
|
||||
open func bind(controller: UIViewController) {
|
||||
self.legacyController = controller
|
||||
if let controller = controller as? TGViewController {
|
||||
controller.customRemoveFromParentViewController = { [weak self] in
|
||||
self?.dismiss()
|
||||
}
|
||||
}
|
||||
if self.legacyController.isHosting {
|
||||
self.addChild(self.legacyController)
|
||||
self.legacyController.didMove(toParent: legacyController)
|
||||
}
|
||||
}
|
||||
|
||||
override open func loadDisplayNode() {
|
||||
@ -593,8 +584,8 @@ open class LegacyController: ViewController, PresentableController {
|
||||
self.validLayout = layout
|
||||
|
||||
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 {
|
||||
var duration: TimeInterval = 0.0
|
||||
if case let .animated(transitionDuration, _) = transition {
|
||||
@ -623,7 +614,6 @@ open class LegacyController: ViewController, PresentableController {
|
||||
if previousSizeClass != updatedSizeClass {
|
||||
self.sizeClass.set(SSignal.single(updatedSizeClass.rawValue as NSNumber))
|
||||
}
|
||||
self.navigationBarHeightModel.value = navigationBarHeight
|
||||
}
|
||||
|
||||
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