uikitrx-swiftrx-cocoa

Can't pass the RxSwift PublishRelay value from custom view


I am trying to extend my KeyboardView view with rx action with no success. According to debugging with breakpoints value is passed to the relay but extension is not called despite further subscription in a view controller. What might be a problem and how to fix it?

final class KeyboardView: UIView {

    private let disposeBag = DisposeBag()
    
    fileprivate let buttonTappedRelay = PublishRelay<ActionType>()
    
    private let digitButtons: [KeyboardButton] = {
        return stride(from: 0, through: 9, by: 1)
            .compactMap { $0 }
            .map { KeyboardButton(actionType: .digit($0)) }
    }()
    
    private let eraseButton: KeyboardButton = {
        let button = KeyboardButton(actionType: .erase)
        return button
    }()
    
    public override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupViews()
        setupConstraints()
        setupActions()
    }
    
    private func setupViews() { ... }
    
    private func setupConstraints() { ... }
    
    private func setupActions() {
        eraseButton.rx.buttonTap
            .asObservable()
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { [weak self] actionType in
                self?.buttonTappedRelay.accept(actionType)
            }).disposed(by: self.disposeBag)
        
        for button in digitButtons {
            button.rx.buttonTap
                .asObservable()
                .observeOn(MainScheduler.instance)
                .subscribe(onNext: { [weak self] actionType in
                    self?.buttonTappedRelay.accept(actionType)
                }).disposed(by: self.disposeBag)
        }
    }
    
}

extension Reactive where Base: KeyboardView {
    
    internal var buttonTap: ControlEvent<ActionType> {
        return ControlEvent<ActionType>(events: base.buttonTappedRelay.asObservable() )
    }
    
}

Solution

  • Your problem is likely in code you haven't shown. Note that the below code compiles, runs and works:

    final class KeyboardView: UIView {
    
        private let disposeBag = DisposeBag()
    
        fileprivate let buttonTappedRelay = PublishRelay<ActionType>()
    
        private let digitButtons: [KeyboardButton] = {
            return stride(from: 0, through: 9, by: 1)
                .compactMap { $0 }
                .map { KeyboardButton(actionType: .digit($0)) }
        }()
    
        private let eraseButton: KeyboardButton = {
            let button = KeyboardButton(actionType: .erase)
            return button
        }()
    
        public override init(frame: CGRect) {
            super.init(frame: frame)
    
            setupViews()
            setupActions()
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        private func setupViews() {
            let stack = UIStackView(frame: bounds)
            stack.distribution = .equalSpacing
            stack.addArrangedSubview(eraseButton)
            for each in digitButtons {
                stack.addArrangedSubview(each)
            }
            addSubview(stack)
        }
    
        private func setupActions() {
            eraseButton.rx.buttonTap
                .asObservable()
                .observeOn(MainScheduler.instance)
                .subscribe(onNext: { [weak self] actionType in
                    self?.buttonTappedRelay.accept(actionType)
                }).disposed(by: self.disposeBag)
    
            for button in digitButtons {
                button.rx.buttonTap
                    .asObservable()
                    .observeOn(MainScheduler.instance)
                    .subscribe(onNext: { [weak self] actionType in
                        self?.buttonTappedRelay.accept(actionType)
                    }).disposed(by: self.disposeBag)
            }
        }
    
    }
    
    final class ViewController: UIViewController {
        weak var keyboard: KeyboardView?
    
        override func loadView() {
            super.loadView()
            let keyboard = KeyboardView(frame: view.bounds)
            view.addSubview(keyboard)
            self.keyboard = keyboard
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            keyboard!.rx.buttonTap
                .debug("🟣")
                .subscribe()
        }
    }
    
    extension Reactive where Base: KeyboardView {
    
        internal var buttonTap: ControlEvent<ActionType> {
            return ControlEvent<ActionType>(events: base.buttonTappedRelay.asObservable() )
        }
    }
    
    enum ActionType {
        case digit(Int)
        case erase
    }
    
    class KeyboardButton: UIButton {
        let actionType: ActionType
        init(actionType: ActionType) {
            self.actionType = actionType
            super.init(frame: CGRect.zero)
            backgroundColor = .red
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
    extension Reactive where Base: KeyboardButton {
        var buttonTap: Observable<ActionType> {
            base.rx.tap.map { base.actionType }
        }
    }