Skip to content

Instantly share code, notes, and snippets.

@aheze
Last active July 30, 2022 17:24
Show Gist options
  • Save aheze/e71e5ede2b373c90abfae7a9e985f711 to your computer and use it in GitHub Desktop.
Save aheze/e71e5ede2b373c90abfae7a9e985f711 to your computer and use it in GitHub Desktop.
Combine alternative to NotificationCenter
import Combine
import UIKit
class ViewModel {
var enableSubject = PassthroughSubject<Bool, Never>() /// Similar to NotificationCenter
var cancellables = Set<AnyCancellable>() /// Lets you store combine subscribers
}
class ViewController: UIViewController {
let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
for index in 0 ..< 5 {
let customView = CustomView(viewModel: viewModel)
customView.frame = .init(x: index * 60, y: 200, width: 50, height: 50)
customView.backgroundColor = .systemBlue
view.addSubview(customView)
}
let enableButton = UIButton(type: .system)
enableButton.setTitle("Enable", for: .normal)
enableButton.frame = .init(x: 0, y: 300, width: 100, height: 50)
enableButton.addTarget(self, action: #selector(enableAllViews), for: .touchUpInside)
view.addSubview(enableButton)
let disableButton = UIButton(type: .system)
disableButton.setTitle("Disable", for: .normal)
disableButton.frame = .init(x: 120, y: 300, width: 100, height: 50)
disableButton.addTarget(self, action: #selector(disableAllViews), for: .touchUpInside)
view.addSubview(disableButton)
}
@objc func enableAllViews() {
viewModel.enableSubject.send(true) /// This will call the `.sink`s in the subviews
}
@objc func disableAllViews() {
viewModel.enableSubject.send(false)
}
}
class CustomView: UIView {
let viewModel: ViewModel
init(viewModel: ViewModel) {
self.viewModel = viewModel
super.init(frame: .zero)
viewModel.enableSubject.sink { enable in
self.isUserInteractionEnabled = enable
self.backgroundColor = enable ? .systemGreen : .systemRed
}
.store(in: &viewModel.cancellables) /// Must store this subscriber/cancellable so that it's kept alive!
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
/// Here's an alternative via a `@Published` property.
/// There's one thing to keep in mind — `.sink`s on `@Published` properties get called immediately,
/// so if you don't want this, add a `dropFirst`.
/// See https://stackoverflow.com/q/60568858/14351818 for more info.
import Combine
import UIKit
class ViewModel {
@Published var isEnabled = false
var cancellables = Set<AnyCancellable>() /// Lets you store combine subscribers
}
class ViewController: UIViewController {
let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
for index in 0 ..< 5 {
let customView = CustomView(viewModel: viewModel)
customView.frame = .init(x: index * 60, y: 200, width: 50, height: 50)
/// `CustomView`'s `viewModel.$isEnabled.sink` sets the background color **initially**, so no need to set anything here.
view.addSubview(customView)
}
let enableButton = UIButton(type: .system)
enableButton.setTitle("Enable", for: .normal)
enableButton.frame = .init(x: 0, y: 300, width: 100, height: 50)
enableButton.addTarget(self, action: #selector(enableAllViews), for: .touchUpInside)
view.addSubview(enableButton)
let disableButton = UIButton(type: .system)
disableButton.setTitle("Disable", for: .normal)
disableButton.frame = .init(x: 120, y: 300, width: 100, height: 50)
disableButton.addTarget(self, action: #selector(disableAllViews), for: .touchUpInside)
view.addSubview(disableButton)
}
@objc func enableAllViews() {
viewModel.isEnabled = true
}
@objc func disableAllViews() {
viewModel.isEnabled = false
}
}
class CustomView: UIView {
let viewModel: ViewModel
init(viewModel: ViewModel) {
self.viewModel = viewModel
super.init(frame: .zero)
viewModel.$isEnabled
/// Note, if you observe a single `@Published` property, the `.sink` is fired initially too.
/// You can avoid this by inserting `.dropFirst()` just before `.sink`.
.sink { enable in
self.isUserInteractionEnabled = enable
self.backgroundColor = enable ? .systemGreen : .systemRed
}
.store(in: &viewModel.cancellables)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@aheze
Copy link
Author

aheze commented Jul 30, 2022

Result:

RPReplay_Final1659200899.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment