RxSwift - rx.tap not working - swift

I have view controller. Inside it I have view
lazy var statusView: StatusView = {
var statusView = StatusView()
return statusView
}()
Inside statusView I have button
lazy var backButton: UIButton = {
var button = UIButton(type: .system)
button.titleLabel?.font = UIFont().regularFontOfSize(size: 20)
return button
}()
In controller I have
override func viewDidLoad() {
super.viewDidLoad()
setupRx()
}
func setupRx() {
_ = statusView.backButton.rx.tap.subscribe { [weak self] in
guard let strongSelf = self else { return }
print("hello")
}
}
But when I tap to button, nothing get printed to console.
What am I doing wrong ?

In general nothing wrong, but there's a minor hidden trick.
You use
backButton.rx.tap.subscribe { [weak self] in
But you need to use
backButton.rx.tap.subscribe { [weak self] _ in ...
Did you notice underscore in the second version? The second version calls method
public func subscribe(_ on: #escaping (Event<E>) -> Void)
of ObservableType. In this case on closure to deliver an event is provided, but we just ignore incoming parameter of this closure using underscore

It looks like the subscription is going out of scope as soon as setupRx returns. If you add a DisposeBag to the view controller and add the subscription to the dispose bag, does that solve the problem? Something like this:
func setupRx() {
statusView.backButton.rx.tap
.subscribe { [weak self] in
guard let strongSelf = self else { return }
print("hello")
}
}
.addDisposableTo(self.disposeBag)
}
Hope that helps.

Related

How to pass chain view controller presenter with observable

I'm new in the RxSwift development and I've an issue while presentation a view controller.
My MainViewController is just a table view and I would like to present detail when I tap on a item of the list.
My DetailViewController is modally presented and needs a ViewModel as input parameter.
I would like to avoid to dismiss the DetailViewController, I think that the responsability of dismiss belongs to the one who presented the view controller, i.e the dismiss should happen in the MainViewController.
Here is my current code
DetailsViewController
class DetailsViewController: UIViewController {
#IBOutlet weak private var doneButton: Button!
#IBOutlet weak private var label: Label!
let viewModel: DetailsViewModel
private let bag = DisposeBag()
var onComplete: Driver<Void> {
doneButton.rx.tap.take(1).asDriver(onErrorJustReturn: ())
}
override func viewDidLoad() {
super.viewDidLoad()
setup()
bind()
}
private func bind() {
let ouput = viewModel.bind()
ouput.id.drive(idLabel.rx.text)
.disposed(by: bag)
}
}
DetailsViewModel
class DetailsViewModel {
struct Output {
let id: Driver<String>
}
let item: Observable<Item>
init(with vehicle: Observable<Item>) {
self.item = item
}
func bind() -> Output {
let id = item
.map { $0.id }
.asDriver(onErrorJustReturn: "Unknown")
return Output(id: id)
}
}
MainViewController
class MainViewController: UIViewController {
#IBOutlet weak private var tableView: TableView!
private var bag = DisposeBag()
private let viewModel: MainViewModel
private var detailsViewController: DetailsViewController?
override func viewDidLoad(_ animated: Bool) {
super.viewDidLoad(animated)
bind()
}
private func bind() {
let input = MainViewModel.Input(
selectedItem: tableView.rx.modelSelected(Item.self).asObservable()
)
let output = viewModel.bind(input: input)
showItem(output.selectedItem)
}
private func showItem(_ item: Observable<Item>) {
let viewModel = DetailsViewModel(with: vehicle)
detailsViewController = DetailsController(with: viewModel)
item.flatMapFirst { [weak self] item -> Observable<Void> in
guard let self = self,
let detailsViewController = self.detailsViewController else {
return Observable<Void>.never()
}
self.present(detailsViewController, animated: true)
return detailsViewController.onComplete.asObservable()
}
.subscribe(onNext: { [weak self] in
self?.detailsViewController?.dismiss(animated: true)
self?.detailsViewController? = nil
})
.disposed(by: bag)
}
}
MainViewModel
class MainViewModel {
struct Input {
let selectedItem: Observable<Item>
}
struct Output {
let selectedItem: Observable<Item>
}
func bind(input: Input) -> Output {
let selectedItem = input.selectedItem
.throttle(.milliseconds(500),
latest: false,
scheduler: MainScheduler.instance)
.asObservable()
return Output(selectedItem: selectedItem)
}
}
My issue is on showItem of MainViewController.
I still to think that having the DetailsViewController input as an Observable isn't working but from what I understand from Rx, we should use Observable as much as possible.
Having Item instead of Observable<Item> as input could let me use this kind of code:
item.flatMapFirst { item -> Observable<Void> in
guard let self = self else {
return Observable<Void>.never()
}
let viewModel = DetailsViewModel(with: item)
self.detailsViewController = DetailsViewController(with: viewModel)
guard let detailsViewController = self.detailsViewController else {
return Observable<Void>.never()
}
present(detailsViewController, animated: true)
return detailsViewController
}
.subscribe(onNext: { [weak self] in
self?.detailsViewController?.dismiss(animated: true)
self?.detailsViewController = nil
})
.disposed(by: bag)
What is the right way to do this?
Thanks
You should not "use Observable as much as possible." If an object is only going to ever have to deal with a single item, then just pass the item. For example if a label is only ever going to display "Hello World" then just assign the string to the label's text property. Don't bother wrapping it in a just and binding it to the label's rx.text.
Your second option is much closer to what you should have. It's a fine idea.
You might find my CLE library interesting. It takes care of the issue you are trying to handle here.

Do we need to repeat `guard let self = self else { return }` inside each nested closure to keep strong self?

I need to keep strong self inside my inner clousures.
I know that it's enough to declare [weak self] only once for outer closure.
But what about guard let self = self else { return }, is it enough to declare it once for outer closure also? Do we have any edge cases here ?
apiManager.doSomething(user: user) { [weak self] result in
guard let self = self else { return }
self.storageManager.doSomething(user: user) { result in
// guard let self = self else { return } <- DO WE NEED IT HERE ?
self.doSomething()
}
}
Seems like language analyser says NO - one declaration is enough , but want to be sure.
Yes, one is enough.
If you write
guard let self = self else { return }
you'll create a new local variable that will hold a strong reference to the outside weak self.
It's the same as writing
guard let strongSelf = self else { return }
and then use strongSelf for the rest of the block.
Update Swift 5.7
You don't need a new guard in doSomething(user:), when that method works synchronously.
With Swift 5.7, you can use the rewrite syntax guard let self else
apiManager.doSomething(user: user) { [weak self] result in
guard let self else { return }
self.storageManager.doSomething(user: user) { result in
// You don't a new guard here, when doSomething works synchronously
self.doSomething()
}
}
But, when your guard only returns, than you could also write it so:
Same behaviour.
apiManager.doSomething(user: user) { [weak self] _ in
self?.storageManager.doSomething(user: user) { _ in
self?.doSomething()
}
}
No, in short you do not need that. If you outer closure uses [weak self] then you should not worry about the inner closure as it will already have a weak reference to self. On the other hand if your outer closure is not using [weak self] it is reasonable to put it for the inside block. A more detailed explation can be found here.
The safest approach is to use [weak self] 2 times in both outter and inner closure.
There is edge case, where using [weak self] 1 time will cause memory leak.
Consider the following escaping closure case, which keeps reference to closures (Example copied from https://stackoverflow.com/a/62352667/72437)
import UIKit
public class CaptureListExperiment {
public init() {
}
var _someFunctionWithTrailingClosure: (() -> ())?
var _anotherFunctionWithTrailingClosure: (() -> ())?
func someFunctionWithTrailingClosure(closure: #escaping () -> ()) {
print("starting someFunctionWithTrailingClosure")
_someFunctionWithTrailingClosure = closure
DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
self?._someFunctionWithTrailingClosure!()
print("finishing someFunctionWithTrailingClosure")
}
}
func anotherFunctionWithTrailingClosure(closure: #escaping () -> ()) {
print("starting anotherFunctionWithTrailingClosure")
_anotherFunctionWithTrailingClosure = closure
DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
self?._anotherFunctionWithTrailingClosure!()
print("finishing anotherFunctionWithTrailingClosure")
}
}
func doSomething() {
print("doSomething")
}
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
guard let self = self else { return }
self.anotherFunctionWithTrailingClosure {
self.doSomething()
}
}
}
// go ahead and add `deinit`, so I can see when this is deallocated
deinit {
print("deinit")
}
}
func performExperiment() {
let obj = CaptureListExperiment()
obj.testCompletionHandlers()
Thread.sleep(forTimeInterval: 1.3)
}
performExperiment()
/* Output:
starting someFunctionWithTrailingClosure
starting anotherFunctionWithTrailingClosure
finishing someFunctionWithTrailingClosure
doSomething
finishing anotherFunctionWithTrailingClosure
*/
Since, this kind of edge case is very hard to spot. I think using [weak self] 2 times, will be the safest approach and common pattern, without causing developer much headache.

Swift: determinate NSProgressIndicator, async refreshing and waiting for return

Working in Swift3; I've got a pretty expensive operation running in a loop iterating through stuff and building it into an array that on return would be used as the content for an NSTableView.
I wanted a modal sheet showing progress for this so people don't think the app is frozen. By googling, looking around in here and not a small amount of trial and error I've managed to implement my progressbar and have it show progress adequately as the loop progresses.
The problem right now? Even though the sheet (implemented as an NSAlert, the progress bar is in the accesory view) works exactly as expected, the whole thing returns before the loop is finished.
Here's the code, hoping somebody can tell me what am I doing wrong:
class ProgressBar: NSAlert {
var progressBar = NSProgressIndicator()
var totalItems: Double = 0
var countItems: Double = 0
override init() {
progressBar.isIndeterminate = false
progressBar.style = .barStyle
super.init()
self.messageText = ""
self.informativeText = "Loading..."
self.accessoryView = NSView(frame: NSRect(x:0, y:0, width: 290, height: 16))
self.accessoryView?.addSubview(progressBar)
self.layout()
self.accessoryView?.setFrameOrigin(NSPoint(x:(self.accessoryView?.frame)!.minX,y:self.window.frame.maxY))
self.addButton(withTitle: "")
progressBar.sizeToFit()
progressBar.setFrameSize(NSSize(width:290, height: 16))
progressBar.usesThreadedAnimation = true
self.beginSheetModal(for: ControllersRef.sharedInstance.thePrefPane!.mainCustomView.window!, completionHandler: nil)
}
}
static var allUTIs: [SWDAContentItem] = {
var wrappedUtis: [SWDAContentItem] = []
let utis = LSWrappers.UTType.copyAllUTIs()
let a = ProgressBar()
a.totalItems = Double(utis.keys.count)
a.progressBar.maxValue = a.totalItems
DispatchQueue.global(qos: .default).async {
for uti in Array(utis.keys) {
a.countItems += 1.0
wrappedUtis.append(SWDAContentItem(type:SWDAContentType(rawValue: "UTI")!, uti))
Thread.sleep(forTimeInterval:0.0001)
DispatchQueue.main.async {
a.progressBar.doubleValue = a.countItems
if (a.countItems >= a.totalItems && a.totalItems != 0) {
ControllersRef.sharedInstance.thePrefPane!.mainCustomView.window?.endSheet(a.window)
}
}
}
}
Swift.print("We'll return now...")
return wrappedUtis // This returns before the loop is finished.
}()
In short, you're returning wrappedUtis before the asynchronous code has had a chance to finish. You cannot have the initialization closure return a value if the update process itself is happening asynchronously.
You clearly successfully diagnosed a performance problem in the initialization of allUTIs, and while doing this asynchronously is prudent, you shouldn't be doing that in that initialization block of the allUTIs property. Move this code that initiates the update of allUTIs into a separate function.
Looking at ProgressBar, it's really an alert, so I'd call it ProgressAlert to make that clear, but expose the necessary methods to update the NSProgressIndicator within that alert:
class ProgressAlert: NSAlert {
private let progressBar = NSProgressIndicator()
override init() {
super.init()
messageText = ""
informativeText = "Loading..."
accessoryView = NSView(frame: NSRect(x:0, y:0, width: 290, height: 16))
accessoryView?.addSubview(progressBar)
self.layout()
accessoryView?.setFrameOrigin(NSPoint(x:(self.accessoryView?.frame)!.minX,y:self.window.frame.maxY))
addButton(withTitle: "")
progressBar.isIndeterminate = false
progressBar.style = .barStyle
progressBar.sizeToFit()
progressBar.setFrameSize(NSSize(width:290, height: 16))
progressBar.usesThreadedAnimation = true
}
/// Increment progress bar in this alert.
func increment(by value: Double) {
progressBar.increment(by: value)
}
/// Set/get `maxValue` for the progress bar in this alert
var maxValue: Double {
get {
return progressBar.maxValue
}
set {
progressBar.maxValue = newValue
}
}
}
Note, this doesn't present the UI. That's the job of whomever presented it.
Then, rather than initiating this asynchronous population in the initialization closure (because initialization should always be synchronous), create a separate routine to populate it:
var allUTIs: [SWDAContentItem]?
private func populateAllUTIs(in window: NSWindow, completionHandler: #escaping () -> Void) {
let progressAlert = ProgressAlert()
progressAlert.beginSheetModal(for: window, completionHandler: nil)
var wrappedUtis = [SWDAContentItem]()
let utis = LSWrappers.UTType.copyAllUTIs()
progressAlert.maxValue = Double(utis.keys.count)
DispatchQueue.global(qos: .default).async {
for uti in Array(utis.keys) {
wrappedUtis.append(SWDAContentItem(type:SWDAContentType(rawValue: "UTI")!, uti))
DispatchQueue.main.async { [weak progressAlert] in
progressAlert?.increment(by: 1)
}
}
DispatchQueue.main.async { [weak self, weak window] in
self?.allUTIs = wrappedUtis
window?.endSheet(progressAlert.window)
completionHandler()
}
}
}
Now, you declared allUTIs to be static, so you can tweak the above to do that, too, but it seems like it's more appropriate to make it an instance variable.
Anyway, you can then populate that array with something like:
populateAllUTIs(in: view.window!) {
// do something
print("done")
}
Below, you said:
In practice, this means allUTIs is only actually initiated when the appropriate TabViewItem is selected for the first time (which is why I initialize it with a closure like that). So, I'm not really sure how to refactor this, or where should I move the actual initialization. Please keep in mind that I'm pretty much a newbie; this is my first Swift (also Cocoa) project, and I've been learning both for a couple of weeks.
If you want to instantiate this when the tab is selected, then hook into the child view controllers viewDidLoad. Or you can do it in the tab view controller's tab​View(_:​did​Select:​)
But if the population of allUTIs is so slow, are you sure you want to do this lazily? Why not trigger this instantiation sooner, so that there's less likely to be a delay when the user selects that tab. In that case, you might trigger it the tab view controller's own viewDidLoad, so that the tab that needs those UTIs is more likely to have them.
So, if I were considering a more radical redesign, I might first change my model object to further isolate its update process from any specific UI, but rather to simply return (and update) a Progress object.
class Model {
var allUTIs: [SWDAContentItem]?
func startUTIRetrieval(completionHandler: (() -> Void)? = nil) -> Progress {
var wrappedUtis = [SWDAContentItem]()
let utis = LSWrappers.UTType.copyAllUTIs()
let progress = Progress(totalUnitCount: Int64(utis.keys.count))
DispatchQueue.global(qos: .default).async {
for uti in Array(utis.keys) {
wrappedUtis.append(SWDAContentItem(type:SWDAContentType(rawValue: "UTI")!, uti))
DispatchQueue.main.async {
progress.completedUnitCount += 1
}
}
DispatchQueue.main.async { [weak self] in
self?.allUTIs = wrappedUtis
completionHandler?()
}
}
return progress
}
}
Then, I might have the tab bar controller instantiate this and share the progress with whatever view controller needed it:
class TabViewController: NSTabViewController {
var model: Model!
var progress: Progress?
override func viewDidLoad() {
super.viewDidLoad()
model = Model()
progress = model.startUTIRetrieval()
tabView.delegate = self
}
override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
super.tabView(tabView, didSelect: tabViewItem)
if let item = tabViewItem, let controller = childViewControllers[tabView.indexOfTabViewItem(item)] as? ViewController {
controller.progress = progress
}
}
}
Then the view controller could observe this Progress object, to figure out whether it needs to update its UI to reflect this:
class ViewController: NSViewController {
weak var progress: Progress? { didSet { startObserving() } }
weak var progressAlert: ProgressAlert?
private var observerContext = 0
private func startObserving() {
guard let progress = progress, progress.completedUnitCount < progress.totalUnitCount else { return }
let alert = ProgressAlert()
alert.beginSheetModal(for: view.window!)
progressAlert = alert
progress.addObserver(self, forKeyPath: "fractionCompleted", context: &observerContext)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let progress = object as? Progress, context == &observerContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
dispatchPrecondition(condition: .onQueue(.main))
if progress.completedUnitCount < progress.totalUnitCount {
progressAlert?.doubleValue = progress.fractionCompleted * 100
} else {
progress.removeObserver(self, forKeyPath: "fractionCompleted")
view.window?.endSheet(progressAlert!.window)
}
}
deinit {
progress?.removeObserver(self, forKeyPath: "fractionCompleted")
}
}
And, in this case, the ProgressAlert only would worry about doubleValue:
class ProgressAlert: NSAlert {
private let progressBar = NSProgressIndicator()
override init() {
super.init()
messageText = ""
informativeText = "Loading..."
accessoryView = NSView(frame: NSRect(x:0, y:0, width: 290, height: 16))
accessoryView?.addSubview(progressBar)
self.layout()
accessoryView?.setFrameOrigin(NSPoint(x:(self.accessoryView?.frame)!.minX,y:self.window.frame.maxY))
addButton(withTitle: "")
progressBar.isIndeterminate = false
progressBar.style = .barStyle
progressBar.sizeToFit()
progressBar.setFrameSize(NSSize(width: 290, height: 16))
progressBar.usesThreadedAnimation = true
}
/// Set/get `maxValue` for the progress bar in this alert
var doubleValue: Double {
get {
return progressBar.doubleValue
}
set {
progressBar.doubleValue = newValue
}
}
}
I must note, though, that if these UTIs are only needed for that one tab, it raises the question as to whether you should be using a NSAlert based UI at all. The alert blocks the whole window, and you may want to block interaction with only that one tab.

How do I unsubscribe from an observable?

If I have something that looks like this:
func foo() -> Observable<Foo> {
return Observable.create { observer in
// ...
}
}
func bar() {
foo().observeOn(MainScheduler.instance)
.subscribeNext {
// ...
}
.addDisposableTo(disposeBag)
}
If I want to unsubscribe from the observable later on in bar, how would I do that?
Update
I'm aware I can call dispose, but according to the RxSwift docs:
Note that you usually do not want to manually call dispose; this is only educational example. Calling dispose manually is usually a bad code smell.
So is unsubscribe just not implemented? I've gone spelunking through the RxSwift code, and to the extent that I can understand what's going on, it doesn't look like the Disposable that is returned from the subscribe methods is ever anything with useful functionality (other than disposing).
You add the Observable returned by foo to disposeBag. It disposes the subscription when it's deallocated.
You can "manually" release the disposeBag by calling
disposeBag = nil
somewhere in your class.
After question edit: You want to selectively unsubscribe from some Observables, probably when some conditions are met. You can use another Observable which represents these conditions and use takeUntil operator to cancel the subscription as needed.
//given that cancellingObservable sends `next` value when the subscription to `foo` is no longer needed
foo().observeOn(MainScheduler.instance)
.takeUntil(cancellingObservable)
.subscribeNext {
// ...
}
.addDisposableTo(disposeBag)
Details
Xcode Version 11.0 (11A420a), iOS 13, Swift 5
RxSwift v 5.0.1
Solution
if you want to unsubscribe from observable you just need to reset disposeBag
// Way 1
private lazy var disposeBag = DisposeBag()
//...
disposeBag = DisposeBag()
// Way 2
private lazy var disposeBag: DisposeBag? = DisposeBag()
//...
disposeBag = nil
Full sample
import UIKit
import RxSwift
class Service {
private lazy var publishSubject = BehaviorSubject<Int>(value: count)
private lazy var count = 0
var counter: Observable<Int> { publishSubject }
func unsubscribeAll() { publishSubject.dispose() }
func increaseCounter() {
count += 1
publishSubject.onNext(count)
}
}
class ViewController: UIViewController {
private lazy var service = Service()
private lazy var disposeBag = DisposeBag()
private weak var navigationBar: UINavigationBar!
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
subscribeToObservables()
}
}
// MARK: Work with subviews
extension ViewController {
private func setupNavigationBar() {
let navigationBar = UINavigationBar()
view.addSubview(navigationBar)
navigationBar.translatesAutoresizingMaskIntoConstraints = false
navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
navigationBar.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
navigationBar.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
let navigationItem = UINavigationItem()
var barButtons = [UIBarButtonItem]()
barButtons.append(createNavigationItemButton(title: "subscr.", action: #selector(subscribeButtonTapped)))
barButtons.append(createNavigationItemButton(title: "unsubscr.", action: #selector(unsubscribeButtonTapped)))
navigationItem.leftBarButtonItems = barButtons
navigationItem.rightBarButtonItem = createNavigationItemButton(title: "+", action: #selector(increaseCounterNavigationItemButtonTapped))
navigationBar.items = [navigationItem]
self.navigationBar = navigationBar
}
private func createNavigationItemButton(title: String, action: Selector?) -> UIBarButtonItem {
return UIBarButtonItem(title: title, style: .plain, target: self, action: action)
}
}
// MARK: Work with observers
extension ViewController {
private func subscribeToObservables() {
service.counter.subscribe { [weak self] value in
guard let value = value.element,
let navigationItem = self?.navigationBar?.items?.first else { return }
navigationItem.title = "Counter \(value)"
print(value)
}.disposed(by: disposeBag)
}
private func unsubscribeFromObservables() { disposeBag = DisposeBag() }
}
// MARK: Button actions
extension ViewController {
#objc func increaseCounterNavigationItemButtonTapped(_ source: UIBarButtonItem) { service.increaseCounter() }
#objc func subscribeButtonTapped(_ source: UIBarButtonItem) { subscribeToObservables() }
#objc func unsubscribeButtonTapped(_ source: UIBarButtonItem) { unsubscribeFromObservables() }
}
Screenshot of the sample app
Since above answer focuses on unsubscribing to the specific or particular observable, I will talk about unsubscribing all the observable together correctly and efficiently.
How to Subscribe:
this.myVariable$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(value => {
// Do the thing you want to do after there is change in myVariable
});
How to Unsubscribe:
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
using the above ngOnDestroy you can unsubscribe from all the observable.
You could just override the disposeBag with a fresh one. If you need other observables to stay alive, create multiple disposeBags. If you think about it this is what happens when a class get's deallocated → Its disposeBag gets deallocated, releasing all subscriptions that class had.
Example:
Note: The example just shows the principle, I don't know why you would ever build something specifically as I have below. That being said, it may be interesting for clearing cells with prepareForReuse, depending on your architecture.
class MyClass {
var clearableDisposeBag = DisposeBag()
let disposeBag = DisposeBag()
let dependencies: Dependencies
let myObservable: MyObservable
var myOtherObservable: MyOtherObservable?
init(dependencies: Dependencies) {
self.dependencies = dependencies
self.myObservable = dependencies
.myObservable
.subscribe(onNext: doSomething)
.disposed(by: disposeBag)
}
private func doSomething() {/*...*/}
private func doSomethingElse() {/*...*/}
private func subscribeToChanges() {
/// clear previous subscription
clearableDisposeBag = DisposeBag()
/// subscribe again
myOtherObservable = dependencies
.myOtherObservable
.subscribe(onNext: doSomethingElse)
.disposed(by: clearableDisposeBag)
}
}
What I did was creating another DisposeBag
var tempBag = DisposeBag()
fun bar() {
foo().subscribe().addDisposable(tempBag)
}
so when you want to dispose you can just do tempBag = nil when you want to release. while you still have another DisposeBag that keep other disposables alive.

Swift programmatically create function for button with a closure

In Swift you can create a function for a button like this:
button.addTarget(self, action: #selector(buttonAction), forControlEvents: .TouchUpInside)
However is there a way I can do something like this:
button.whenButtonIsClicked({Insert code here})
That way I do not even have too declare an explicit function for the button. I know I can use button tags but I would prefer to do this instead.
Create your own UIButton subclass to do this:
class MyButton: UIButton {
var action: (() -> Void)?
func whenButtonIsClicked(action: #escaping () -> Void) {
self.action = action
self.addTarget(self, action: #selector(MyButton.clicked), for: .touchUpInside)
}
// Button Event Handler:
// I have not marked this as #IBAction because it is not intended to
// be hooked up to Interface Builder
#objc func clicked() {
action?()
}
}
Substitute MyButton for UIButton when you create buttons programmatically and then call whenButtonIsClicked to set up its functionality.
You can also use this with UIButtons in a Storyboard (just change their class to MyButton) and then call whenButtonIsClicked in viewDidLoad.
#IBOutlet weak var theButton: MyButton!
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
// be sure to declare [unowned self] if you access
// properties or methods of the class so that you
// don't create a strong reference cycle
theButton.whenButtonIsClicked { [unowned self] in
self.count += 1
print("count = \(self.count)")
}
A much more capable implementation
Recognizing the fact that programmers might want to handle more events than just .touchUpInside, I wrote this more capable version which supports multiple closures per UIButton and multiple closures per event type.
class ClosureButton: UIButton {
private var actions = [UInt : [((UIControl.Event) -> Void)]]()
private let funcDict: [UInt : Selector] = [
UIControl.Event.touchCancel.rawValue: #selector(eventTouchCancel),
UIControl.Event.touchDown.rawValue: #selector(eventTouchDown),
UIControl.Event.touchDownRepeat.rawValue: #selector(eventTouchDownRepeat),
UIControl.Event.touchUpInside.rawValue: #selector(eventTouchUpInside),
UIControl.Event.touchUpOutside.rawValue: #selector(eventTouchUpOutside),
UIControl.Event.touchDragEnter.rawValue: #selector(eventTouchDragEnter),
UIControl.Event.touchDragExit.rawValue: #selector(eventTouchDragExit),
UIControl.Event.touchDragInside.rawValue: #selector(eventTouchDragInside),
UIControl.Event.touchDragOutside.rawValue: #selector(eventTouchDragOutside)
]
func handle(events: [UIControl.Event], action: #escaping (UIControl.Event) -> Void) {
for event in events {
if var closures = actions[event.rawValue] {
closures.append(action)
actions[event.rawValue] = closures
} else {
guard let sel = funcDict[event.rawValue] else { continue }
self.addTarget(self, action: sel, for: event)
actions[event.rawValue] = [action]
}
}
}
private func callActions(for event: UIControl.Event) {
guard let actions = actions[event.rawValue] else { return }
for action in actions {
action(event)
}
}
#objc private func eventTouchCancel() { callActions(for: .touchCancel) }
#objc private func eventTouchDown() { callActions(for: .touchDown) }
#objc private func eventTouchDownRepeat() { callActions(for: .touchDownRepeat) }
#objc private func eventTouchUpInside() { callActions(for: .touchUpInside) }
#objc private func eventTouchUpOutside() { callActions(for: .touchUpOutside) }
#objc private func eventTouchDragEnter() { callActions(for: .touchDragEnter) }
#objc private func eventTouchDragExit() { callActions(for: .touchDragExit) }
#objc private func eventTouchDragInside() { callActions(for: .touchDragInside) }
#objc private func eventTouchDragOutside() { callActions(for: .touchDragOutside) }
}
Demo
class ViewController: UIViewController {
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
let button = ClosureButton(frame: CGRect(x: 50, y: 100, width: 60, height: 40))
button.setTitle("press me", for: .normal)
button.setTitleColor(.blue, for: .normal)
// Demonstration of handling a single UIControl.Event type.
// If your closure accesses self, be sure to declare [unowned self]
// to prevent a strong reference cycle
button.handle(events: [.touchUpInside]) { [unowned self] _ in
self.count += 1
print("count = \(self.count)")
}
// Define a second handler for touchUpInside:
button.handle(events: [.touchUpInside]) { _ in
print("I'll be called on touchUpInside too")
}
let manyEvents: [UIControl.Event] = [.touchCancel, .touchUpInside, .touchDown, .touchDownRepeat, .touchUpOutside, .touchDragEnter,
.touchDragExit, .touchDragInside, .touchDragOutside]
// Demonstration of handling multiple events
button.handle(events: manyEvents) { event in
switch event {
case .touchCancel:
print("touchCancel")
case .touchDown:
print("touchDown")
case .touchDownRepeat:
print("touchDownRepeat")
case .touchUpInside:
print("touchUpInside")
case .touchUpOutside:
print("touchUpOutside")
case .touchDragEnter:
print("touchDragEnter")
case .touchDragExit:
print("touchDragExit")
case .touchDragInside:
print("touchDragInside")
case .touchDragOutside:
print("touchDragOutside")
default:
break
}
}
self.view.addSubview(button)
}
}
If you don't want to do anything "questionable" (i.e., using Objective-C's dynamic capabilities, or adding your own touch handlers, etc.) and do this purely in Swift, unfortunately this is not possible.
Any time you see #selector in Swift, the compiler is calling objc_MsgSend under the hood. Swift doesn't support Objective-C's dynamicism. For better or for worse, this means that in order to swap out the usage of this selector with a block, you'd probably need to perform some black magic to make it work, and you'd have to use Objective-C constructs to do that.
If you don't have any qualms about doing "yucky dynamic Objective-C stuff", you could probably implement this by defining an extension on UIButton, and then associate a function to the object dynamically using associated objects. I'm going to stop here, but if you want to read more, NSHipster has a great overview on associated objects and how to use them.
This one will work !
Make sure you don't alter the tag for buttons
extension UIButton {
private func actionHandleBlock(action:(()->())? = nil) {
struct __ {
var closure : (() -> Void)?
typealias EmptyCallback = ()->()
static var action : [EmptyCallback] = []
}
if action != nil {
// __.action![(__.action?.count)!] = action!
self.tag = (__.action.count)
__.action.append(action!)
} else {
let exe = __.action[self.tag]
exe()
}
}
#objc private func triggerActionHandleBlock() {
self.actionHandleBlock()
}
func addAction(forControlEvents control :UIControlEvents, ForAction action:#escaping () -> Void) {
self.actionHandleBlock(action: action)
self.addTarget(self, action: #selector(triggerActionHandleBlock), for: control)
}
}
You can also just subclass UIView and have a property that is a closure like vacawama has.
var action: () -> ()?
Then override the touchesBegan method to call the function whenever the button is touched. With this approach though you don't get all the benefits of starting with a UIBitton.
let bt1 = UIButton(type: UIButtonType.InfoDark)
bt1.frame = CGRectMake(130, 80, 40, 40)
let bt2 = UIButton(type: UIButtonType.RoundedRect)
bt2.frame = CGRectMake(80, 180, 150, 44)
bt2.backgroundColor = UIColor.purpleColor()
bt2.tintColor = UIColor.yellowColor()
bt2.setTitle("Tap Me", forState: UIControlState.Normal)
bt2.addTarget(self, action: "buttonTap", forControlEvents: UIControlEvents.TouchUpInside)
let bt3 = UIButton(type: UIButtonType.RoundedRect)
bt3.backgroundColor = UIColor.brownColor()
bt3.tintColor = UIColor.redColor()
bt3.setTitle("Tap Me", forState: UIControlState.Normal)
bt3.frame = CGRectMake(80, 280, 150, 44)
bt3.layer.masksToBounds = true
bt3.layer.cornerRadius = 10
bt3.layer.borderColor = UIColor.lightGrayColor().CGColor
self.view.addSubview(bt1)
self.view.addSubview(bt2)
self.view.addSubview(bt3)
}
func buttonTap(button:UIButton)
{
let alert = UIAlertController(title: "Information", message: "UIButton Event", preferredStyle: UIAlertControllerStyle.Alert)
let OKAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil)
alert.addAction(OKAction)
}