Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save vzsg/b3b58d244b7b6dc876e7f78c05361203 to your computer and use it in GitHub Desktop.
Save vzsg/b3b58d244b7b6dc876e7f78c05361203 to your computer and use it in GitHub Desktop.
An experiment in method swizzling and Combine to expose traitCollectionDidChange as a Publisher
import Combine
import ObjectiveC
import UIKit
typealias TraitCollectionChange = (UITraitCollection, previous: UITraitCollection?)
private typealias TraitChangeSubject = PassthroughSubject<TraitCollectionChange, Never>
private var traitSubjectKey = "_voodoo_trait_subject"
private var swizzlingPerformed = false
extension UIViewController {
func traitCollectionChangePublisher() -> AnyPublisher<TraitCollectionChange, Never> {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
objc_sync_enter(UIViewController.self)
defer { objc_sync_exit(UIViewController.self) }
if let subject = objc_getAssociatedObject(self, &traitSubjectKey) as? TraitChangeSubject {
return subject.eraseToAnyPublisher()
}
let subject = TraitChangeSubject()
objc_setAssociatedObject(
self,
&traitSubjectKey,
subject,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
if !swizzlingPerformed {
let originalSelector = #selector(UIViewController.traitCollectionDidChange)
let swizzledSelector = #selector(UIViewController._swizzled_traitCollectionDidChange)
let originalMethod = class_getInstanceMethod(UIViewController.self, originalSelector)
let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledSelector)
guard let m1 = originalMethod, let m2 = swizzledMethod else {
return Empty().eraseToAnyPublisher()
}
method_exchangeImplementations(m1, m2)
swizzlingPerformed = true
}
return subject.eraseToAnyPublisher()
}
@objc private dynamic func _swizzled_traitCollectionDidChange(_ previous: UITraitCollection?) {
_swizzled_traitCollectionDidChange(previous)
guard let subject = objc_getAssociatedObject(self, &traitSubjectKey) as? TraitChangeSubject else {
return
}
subject.send((traitCollection, previous: previous))
}
}
import Combine
import UIKit
class ExampleViewController: UIViewController {
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .title1)
label.text = "Hello World!"
label.textColor = .label
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
let pushButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(pushNewViewController))
navigationItem.rightBarButtonItem = pushButton
traitCollectionChangePublisher()
.print()
.sink { _ in }
.store(in: &cancellables)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
print("THIS IS FINE")
}
@objc private func pushNewViewController() {
let vc = ViewController()
show(vc, sender: self)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment