Rxswift - Rx.Tap Not Working

RxSwift - Button Tap using Publish Subject

I found very easy and simple way to solve my issue and avoid using Subject, As there was no logic related to my button in VM, I don't need pass my Button tap to my VM either by using Observable or using Subject. Instead I directly accessed my button in my Coordinator like this:

viewController.btnShowOnboarding.rx.tap
.subscribe(onNext: { _ in
self.showOnboardingScreen()
})
.disposed(by: viewController.disposeBag)

RxSwift not working with UIButton

Your subscription is cancelled immediately because of the scope of your DisposeBag. It is created, then goes out of scope and immediately deallocated. You need to retain your bag somewhere. If you are using a view controller or something like that, you can create a property there and assign it to that.

RxSwift: tap a button to do a request with Alamofire, how to make it work?

The main issue here is that when performing the network request, you are not waiting for the network call to respond. So observer.onComplete() is triggered before observer.onNext()

class NetManager: NSObject {

static let standard = NetManager()
var dataRequest: DataRequest?

func request() -> Observable<Bool> {
return Observable<Bool>.create { (observer) -> Disposable in
let data = // ...
// ...
self.dataRequest = AF.request(aURL, method: HTTPMethod.post, parameters: data, encoder: JSONParameterEncoder.prettyPrinted, headers: aHead).response { (response: DataResponse<Data?>) in
self.hud?.hide()
if let data = response.data {
observer.onNext(true)
// ...
}
else {
observer.onNext(false)
}
observer.onComplete() // Moving it here instead will wait for the response
}
return Disposables.create { }
}
}
}

At this point, your code should work as it is, but you're still overcomplicating a very simple thing.

Here is what to do:

btn.rx.tap
.flatMap { _ in
return NetManager.standard.request()
}
.subscribe(onNext: { (success) in
if success {
if let app = UIApplication.shared.delegate as? AppDelegate {
app.goHome()
}
}
})
.disposed(by: rx.disposeBag)

How to force tap on UIButton using RxSwift?

This works ("ReactiveX/RxSwift" ~> 4.0)

someButton.rx.tap
.bind {
debugPrint("button tapped")
}
.disposed(by: disposeBag)

Somewhere else

someButton.sendActions(for: .touchUpInside)

how to connect working steps using RxSwift

There are problems with this specific implementation, but you are on the right track. Consider using my CLE library which takes care of all the gotchas involved. It can be installed using CocoaPods or SPM.

  1. Using flatMapLatest is okay but I think flatMapFirst is better. The difference is in how the system interprets subsequent taps of the button. The flatMapLatest operator will dispose and resubscribe to the inner observable, which in this case will attempt to present viewController1 on top of itself. You will see this as a problem if you tap the button a couple of times before the new view controller presents itself.

  2. There are other problems. If the user dismisses view controller 1 any way other than tapping the button (by swiping down, for example,) there will be a resource leak. Also, you never emit completed events when in your view controllers dismiss which causes resource leaks as well.

  3. No.

With my library, all the possible ways to dismiss a view controller are accounted for and there are no leaks. The code would look like this:

final class MainViewController: UIViewController {
let button = UIButton()
let disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(button)
button.frame = CGRect(x: 50, y: 50, width: 100, height: 30)
button.backgroundColor = .blue
}
}

extension MainViewController {
func bind() {
let somethingDone = button.rx.tap
.flatMapFirst {
doAction()
}

let actionDone = somethingDone
.flatMapFirst(presentScene(animated: true, scene: { _ in actionFlow() }))

actionDone
.subscribe(onNext: {
print("all done!")
})
.disposed(by: disposeBag)
}
}

func doAction() -> Observable<String> {
Observable.create { observer in
let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(5000)
DispatchQueue.main.asyncAfter(deadline: when) {
observer.onSuccess("Something Done!!")
}
return Disposables.create()
}
}

func actionFlow() -> Scene<Void> {
let work1 = Work1ViewController().scene { $0.bind() }
let work2 = work1.action
.flatMapFirst(presentScene(animated: true, scene: { _ in
Work2ViewController().scene { $0.bind() }
}))
.take(1)

return Scene(controller: work1.controller, action: work2)
}

//------------------------------------------------------------------------------
final class Work1ViewController: UIViewController {
let button = UIButton()
let disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
view.addSubview(button)
button.frame = CGRect(x: 50, y: 50, width: 100, height: 30)
button.backgroundColor = .blue
}
}

extension Work1ViewController {
func bind() -> Observable<String> {
return button.rx.tap
.flatMapFirst { doAction() }
}
}

final class Work2ViewController: UIViewController {
let button = UIButton()
let disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkGray
view.addSubview(button)
button.frame = CGRect(x: 50, y: 50, width: 100, height: 30)
button.backgroundColor = .blue
}
}

extension Work2ViewController {
func bind() -> Observable<Void> {
button.rx.tap.asObservable()
}
}

How do I trigger action for rx.tap.bind of UIButton?

The essence of functional reactive programming is to specify the dynamic behavior of a value completely at the time of declaration.
-- Heinrich Apfelmus

So by wanting to perform an action manually, you aren't using FRP properly. That said, the transition to an FRP mindset can be tough.

When you want to deal with an Observable in an imperative manor, you need a Subject. In your case, the goal is to emit an event either when the button is tapped or imperatively. So you do that like this:

class CheckboxView: UIView {
private let button = UIButton()
private let _manualAction = PublishSubject<Void>()

let tap: Observable<Void>

override init(frame: CGRect) {
tap = Observable.merge(
button.rx.tap.asObservable(),
_manualAction
)
super.init(frame: frame)
}

required init?(coder: NSCoder) {
tap = Observable.merge(
button.rx.tap.asObservable(),
_manualAction
)
super.init(coder: coder)
}

deinit {
_manualAction.onCompleted()
}

func actManually() {
_manualAction.onNext(())
}
}

Note that tap is a let not a var this is because if you make it a var it might get replaced which will not unseat any subscribers. They would continue to point to the previous object and force it to stay alive.

Also notice that I'm not using a computed property. That's because doing so would create a different Observable every time tap is called. In general that's not what you want so you should make it a habit to avoid it. (In this particular case, the result is a hot Observable so it would be okay to do, but if you routinely do it, you will be wrong much of the time.)

Lastly, you may be wondering why you have to go through all this just to manually trigger the subscription. Again, remember that manual triggers go against the essence of what FRP is all about. You should avoid it.



Related Topics



Leave a reply



Submit