Last active
July 30, 2022 17:24
-
-
Save aheze/e71e5ede2b373c90abfae7a9e985f711 to your computer and use it in GitHub Desktop.
Combine alternative to NotificationCenter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Result:
RPReplay_Final1659200899.mp4