diff --git a/ios/Native/Application/Shared/Emitter/Emitter.swift b/ios/Native/Application/Shared/Emitter/Emitter.swift index f4f6b32..e723f19 100644 --- a/ios/Native/Application/Shared/Emitter/Emitter.swift +++ b/ios/Native/Application/Shared/Emitter/Emitter.swift @@ -9,14 +9,16 @@ import Combine import React @objc(Emitter) -class Emitter: RCTEventEmitter { +class Emitter: RCTEventEmitter, MessageEmitter { + @Injected private var eventEmitter: EventEmitter + override static func requiresMainQueueSetup() -> Bool { return true } override init() { super.init() - EventEmitter.sharedInstance.register(eventEmitter: self) + eventEmitter.register(eventEmitter: self) } override func supportedEvents() -> [String]! { diff --git a/ios/Native/Application/Shared/Emitter/EventEmitter.swift b/ios/Native/Application/Shared/Emitter/EventEmitter.swift index f1a0e18..c27184d 100644 --- a/ios/Native/Application/Shared/Emitter/EventEmitter.swift +++ b/ios/Native/Application/Shared/Emitter/EventEmitter.swift @@ -7,22 +7,34 @@ import Foundation -class EventEmitter { - static let sharedInstance = EventEmitter() +protocol MessageEmitter { + func send(message: String) +} - private var eventEmitter: Emitter? +class NullMessageEmitter: MessageEmitter { + func send(message: String) { + print("Warning: Emiter is not ready yet") + } +} - private init() {} + +protocol EventEmitter { + func send(message: String) + func register(eventEmitter: Emitter) +} + +class DefaultEventEmitter: EventEmitter { + private var eventEmitter: MessageEmitter = NullMessageEmitter() func register(eventEmitter: Emitter) { self.eventEmitter = eventEmitter } func send(message: String) { - eventEmitter?.send(message: message) + eventEmitter.send(message: message) } var isReady: Bool { - return eventEmitter != nil + return (eventEmitter is NullMessageEmitter) == false } } diff --git a/ios/Native/Application/Shared/SharedState.swift b/ios/Native/Application/Shared/SharedState.swift index 61a4b73..2ff8e5d 100644 --- a/ios/Native/Application/Shared/SharedState.swift +++ b/ios/Native/Application/Shared/SharedState.swift @@ -13,12 +13,12 @@ import ReactAppDependencyProvider final class SharedState: ObservableObject { var reactNativeFactory: RCTReactNativeFactory? - private let emitter = EventEmitter.sharedInstance + let container = DependencyContainer() // SwiftUI => RN => SwiftUI @Published var message: String = "" - func send(message: String) { - emitter.send(message: message) + init() { + container.register(EventEmitter.self) { _ in DefaultEventEmitter() } } } diff --git a/ios/Native/Application/Views/CustomButton/CustomButton.swift b/ios/Native/Application/Views/CustomButton/CustomButton.swift index dbc5285..af8d1eb 100644 --- a/ios/Native/Application/Views/CustomButton/CustomButton.swift +++ b/ios/Native/Application/Views/CustomButton/CustomButton.swift @@ -11,50 +11,51 @@ import SwiftUI @objc(CustomButton) class CustomButton: UIView { + @Injected private var emitter: EventEmitter + + private let label: UILabel = { + let lbl = UILabel() + lbl.text = "Hello from native" + lbl.textColor = .white + lbl.translatesAutoresizingMaskIntoConstraints = false + return lbl + }() - private let label: UILabel = { - let lbl = UILabel() - lbl.text = "Hello from native" - lbl.textColor = .white - lbl.translatesAutoresizingMaskIntoConstraints = false - return lbl - }() + private let button: UIButton = { + let btn = UIButton(type: .system) + btn.setTitle("Click Me", for: .normal) + btn.setTitleColor(.white, for: .normal) + btn.translatesAutoresizingMaskIntoConstraints = false + return btn + }() - private let button: UIButton = { - let btn = UIButton(type: .system) - btn.setTitle("Click Me", for: .normal) - btn.setTitleColor(.white, for: .normal) - btn.translatesAutoresizingMaskIntoConstraints = false - return btn - }() + override init(frame: CGRect) { + super.init(frame: frame) + self.backgroundColor = .systemBlue - override init(frame: CGRect) { - super.init(frame: frame) - self.backgroundColor = .systemBlue + self.addSubview(label) + self.addSubview(button) - self.addSubview(label) - self.addSubview(button) + setupConstraints() + button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + } - setupConstraints() - button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) - } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + private func setupConstraints() { + NSLayoutConstraint.activate([ + label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16), + label.centerYAnchor.constraint(equalTo: self.centerYAnchor), - private func setupConstraints() { - NSLayoutConstraint.activate([ - label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16), - label.centerYAnchor.constraint(equalTo: self.centerYAnchor), + button.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 12), + button.centerYAnchor.constraint(equalTo: self.centerYAnchor), + button.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor, constant: -16) + ]) + } - button.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 12), - button.centerYAnchor.constraint(equalTo: self.centerYAnchor), - button.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor, constant: -16) - ]) - } - - @objc private func buttonTapped() { - - } + @objc private func buttonTapped() { + emitter.send(message: "Clicked in UIButton button that was created in RN") + } } diff --git a/ios/Native/Application/Views/ToolboxHeader.swift b/ios/Native/Application/Views/ToolboxHeader.swift index aded016..3c2ae12 100644 --- a/ios/Native/Application/Views/ToolboxHeader.swift +++ b/ios/Native/Application/Views/ToolboxHeader.swift @@ -9,13 +9,14 @@ import SwiftUI struct ToolboxHeader: View { @EnvironmentObject var sharedState: SharedState + @Injected var emitter: EventEmitter var body: some View { VStack { HStack { Text("Actions") Button("Make it blue") { - sharedState.send(message: "hello from Swift!") + emitter.send(message: "hello from Swift!") } } Text("Message: \(sharedState.message)") diff --git a/ios/Native/Utils/DI/DependencyContainer.swift b/ios/Native/Utils/DI/DependencyContainer.swift new file mode 100644 index 0000000..6d1f7cd --- /dev/null +++ b/ios/Native/Utils/DI/DependencyContainer.swift @@ -0,0 +1,47 @@ +// +// DependencyContainer.swift +// Vitaway +// +// Created by Artur Gurgul on 20/06/2025. +// + +final class DependencyContainer: Resolver { + private var factories: [String: (Resolver) -> Any] = [:] + private var instances: [String: Any] = [:] + + func register(_ type: T.Type, cache: Bool = true, factory: @escaping (Resolver) -> T) { + let key = String(describing: type) + factories[key] = factory + + if cache { + instances[key] = factory(self) + } + } + + func registerSingleton(_ type: T.Type, factory: @escaping (Resolver) -> T) { + let key = String(describing: type) + factories[key] = factory + } + + func resolve() -> T { + resolve(type: T.self) + } + + func resolve(type: T.Type) -> T { + let key = String(describing: T.self) + + if let instance = instances[key] as? T { + return instance + } + + guard let factory = factories[key], let instance = factory(self) as? T else { + fatalError("No registered entry for type \(key)") + } + return instance + } + + static var shared: DependencyContainer { + (UIApplication.shared.delegate as! AppDelegate).sharedState.container + } +} + diff --git a/ios/Native/Utils/DI/Injected.swift b/ios/Native/Utils/DI/Injected.swift new file mode 100644 index 0000000..7d2b320 --- /dev/null +++ b/ios/Native/Utils/DI/Injected.swift @@ -0,0 +1,16 @@ +// +// Injected.swift +// Vitaway +// +// Created by Artur Gurgul on 05/07/2025. +// + +import UIKit + +@propertyWrapper +class Injected { + var wrappedValue: T { + DependencyContainer.shared.resolve(type: T.self) + } + +} diff --git a/ios/Native/Utils/DI/Resolver.swift b/ios/Native/Utils/DI/Resolver.swift new file mode 100644 index 0000000..961fd6f --- /dev/null +++ b/ios/Native/Utils/DI/Resolver.swift @@ -0,0 +1,10 @@ +// +// Resolver.swift +// Vitaway +// +// Created by Artur Gurgul on 20/06/2025. +// + +protocol Resolver { + func resolve() -> T +} diff --git a/ios/RNPlayground/AppDelegate.swift b/ios/RNPlayground/AppDelegate.swift index 82a2d95..f86d8fd 100644 --- a/ios/RNPlayground/AppDelegate.swift +++ b/ios/RNPlayground/AppDelegate.swift @@ -9,7 +9,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var reactNativeDelegate: ReactNativeDelegate? - private let sharedState = SharedState() + let sharedState = SharedState() func application( _ application: UIApplication,