ReactiveSwift - Bind `Action` to `UIControl` - ios

I am trying to bind an Action on a UISwitch.
I have created the Action using the following code
action = Action<UISwitch, Bool, NoError> { (input: UISwitch) -> SignalProducer<Bool, NoError> in
return SignalProducer{ (observer, disposable) in
observer.send(value: input.isOn)
observer.sendCompleted()
}
}
but I am having trouble connecting it to the UISwitch.
Can someone help?

there is another class CocoaAction that is used to wrap Actions and connect them to UIControls (now in ReactiveCocoa and not in core ReactiveSwift, so if you are using RAC 5 you will have to import both)
var switch: UISwitch!
//switch.addTarget() does not retain the target, so if we do not
//keep a strong reference here the cocoaAction will be deallocated
//at the end of viewDidLoad() and you will get unrecognized selector
//errors when the switch tries to execute the action
var switchCocoaAction: CocoaAction!
override func viewDidLoad() {
let action = Action<UISwitch, Bool, NoError> { (input: UISwitch) -> SignalProducer<Bool, NoError> in
return SignalProducer { (observer, disposable) in
observer.send(value: input.isOn)
observer.sendCompleted()
}
}
//unsafe because it will cast anyObject as! UISwitch
self.switchCocoaAction = action.unsafeCocoaAction
switch.addTarget(switchCocoaAction,
action: CocoaAction.selector,
forControlEvents: .ValueChanged
)
}
however, if all you want is a signal emitting the switch.isOn value whenever it changes, you can accomplish that much more easily using the builtin rac_signalForControlEvents
func switchSignal() -> SignalProducer<Bool, NoError> {
switch.rac_signalForControlEvents(.ValueChanged) //returns legacy RACsignal
.toSignalProducer() //legacy RACSignal -> swift SignalProducer
.flatMapError { _ in .empty } //NSError -> NoError, errors not possible here, so ignore them
.map { ($0 as! UISwitch).isOn }
}

Related

How to test method that is called with DispatchQueue.main.async?

In code I do it like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateBadgeValuesForTabBarItems()
}
private func updateBadgeValuesForTabBarItems() {
DispatchQueue.main.async {
self.setBadge(value: self.viewModel.numberOfUnreadMessages, for: .threads)
self.setBadge(value: self.viewModel.numberOfActiveTasks, for: .tasks)
self.setBadge(value: self.viewModel.numberOfUnreadNotifications, for: .notifications)
}
}
and in tests:
func testViewDidAppear() {
let view = TabBarView()
let model = MockTabBarViewModel()
let center = NotificationCenter()
let controller = TabBarController(view: view, viewModel: model, notificationCenter: center)
controller.viewDidLoad()
XCTAssertFalse(model.numberOfActiveTasksWasCalled)
XCTAssertFalse(model.numberOfUnreadMessagesWasCalled)
XCTAssertFalse(model.numberOfUnreadNotificationsWasCalled)
XCTAssertFalse(model.indexForTypeWasCalled)
controller.viewDidAppear(false)
XCTAssertTrue(model.numberOfActiveTasksWasCalled) //failed
XCTAssertTrue(model.numberOfUnreadMessagesWasCalled) //failed
XCTAssertTrue(model.numberOfUnreadNotificationsWasCalled) //failed
XCTAssertTrue(model.indexForTypeWasCalled) //failed
}
But all my four latest assertions failed. Why? How can I test it with success?
I think the best approach to test this is to mock the DispatchQueue. You can create a protocol that defines the functionality that you want to use:
protocol DispatchQueueType {
func async(execute work: #escaping #convention(block) () -> Void)
}
Now extend DispatchQueue to conform to your protocol, like:
extension DispatchQueue: DispatchQueueType {
func async(execute work: #escaping #convention(block) () -> Void) {
async(group: nil, qos: .unspecified, flags: [], execute: work)
}
}
Note I had to omit from the protocol the parameters you didn't use in your code, like group, qos, and flags, since protocol don't allow default values. And that's why the extension had to explicitly implement the protocol function.
Now, in your tests, create a mocked DispatchQueue that conforms to that protocol and calls the closure synchronously, like:
final class DispatchQueueMock: DispatchQueueType {
func async(execute work: #escaping #convention(block) () -> Void) {
work()
}
}
Now, all you need to do is inject the queue accordingly, perhaps in the view controller's init, like:
final class ViewController: UIViewController {
let mainDispatchQueue: DispatchQueueType
init(mainDispatchQueue: DispatchQueueType = DispatchQueue.main) {
self.mainDispatchQueue = mainDispatchQueue
super.init(nibName: nil, bundle: nil)
}
func foo() {
mainDispatchQueue.async {
*perform asynchronous work*
}
}
}
Finally, in your tests, you need to create your view controller using the mocked dispatch queue, like:
func testFooSucceeds() {
let controller = ViewController(mainDispatchQueue: DispatchQueueMock())
controller.foo()
*assert work was performed successfully*
}
Since you used the mocked queue in your test, the code will be executed synchronously, and you don't need to frustratingly wait for expectations.
You don't need to call the code in the updateBadgeValuesForTabBarItems method on the main queue.
But if you really need it, you can do something like this:
func testViewDidAppear() {
let view = TabBarView()
let model = MockTabBarViewModel()
let center = NotificationCenter()
let controller = TabBarController(view: view, viewModel: model, notificationCenter: center)
controller.viewDidLoad()
XCTAssertFalse(model.numberOfActiveTasksWasCalled)
XCTAssertFalse(model.numberOfUnreadMessagesWasCalled)
XCTAssertFalse(model.numberOfUnreadNotificationsWasCalled)
XCTAssertFalse(model.indexForTypeWasCalled)
controller.viewDidAppear(false)
let expectation = self.expectation(description: "Test")
DispatchQueue.main.async {
expectation.fullfill()
}
self.waitForExpectations(timeout: 1, handler: nil)
XCTAssertTrue(model.numberOfActiveTasksWasCalled)
XCTAssertTrue(model.numberOfUnreadMessagesWasCalled)
XCTAssertTrue(model.numberOfUnreadNotificationsWasCalled)
XCTAssertTrue(model.indexForTypeWasCalled)
}
But this is not good practice.
I had use DispatchQueue.main.asyncAfter() in my test along with expectation otherwise it was failing before text could set inside DispatchQueue.main.async {}
Methods to Test:
func setNumpadTexts(_ numpad: NumericalKeyboardVC) {
numpad.setTexts(belowNumberLabelText: Currency.symbol, enterKeyText: NSLocalizedString("Add", comment:""))
}
func setTexts(belowNumberLabelText: String? = "", enterKeyText: String) {
DispatchQueue.main.async {
self.belowNumberDisplayLbl.text = belowNumberLabelText
self.enterBtn.setTitle(enterKeyText, for: .normal)
}
}
Test:
func testSetNumpadTexts() {
sut.setNumpadTexts(numpad)
let expectation = expectation(description: "TextMatching")
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
//then
XCTAssertEqual(self.numpad.enterBtn.title(for: .normal), NSLocalizedString("Add", comment:""))
XCTAssertEqual(self.numpad.belowNumberDisplayLbl.text, Currency.symbol)
expectation.fulfill()
})
wait(for: [expectation], timeout: 2.0)
}
You should
Inject the dependency (DispatchQueue) into your view controller, so that you can change it in the tests
Invert the dependency using a protocol, to better conform to SOLID principles (Interface seggregation and Dependency Inversion)
Mock DispatchQueue in your tests, so that you can control your scenario
Lets apply those three items:
To invert the dependency, we will need an abstract type, that is, in Swift, a protocol. We then extend DispatchQueue to conform to that protocol
protocol Dispatching {
func async(execute workItem: DispatchWorkItem)
}
extension DispatchQueue: Dispatching {}
Next, we need to inject the dependency into our view controller. That means, pass anything that is dispatching to our view controller
final class MyViewController {
// MARK: - Dependencies
private let dispatchQueue: Dispatching // Declading that our class needs a dispatch queue
// MARK: - Initialization
init(dispatchQueue: Dispatching = DispatchQueue.main) { // Injecting the dependencies via constructor
self.dispatchQueue = dispatchQueue
super.init(nibName: nil, bundle: nil) // We must call super
}
#available(*, unavailable)
init(coder aCoder: NSCoder?) {
fatalError("We should only use our other init!")
}
// MARK: - View lifecycle
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateBadgeValuesForTabBarItems()
}
// MARK: - Private methods
private func updateBadgeValuesForTabBarItems() {
dispatchQueue.async { // Using our dependency instead of DispatchQueue directly
self.setBadge(value: self.viewModel.numberOfUnreadMessages, for: .threads)
self.setBadge(value: self.viewModel.numberOfActiveTasks, for: .tasks)
self.setBadge(value: self.viewModel.numberOfUnreadNotifications, for: .notifications)
}
}
}
Lastly, we need to create a mock for our tests. In this case, by following the testing doubles, we should create a Fake, that is, a DispatchQueue mock that doesn't really work in production, but works on our tests
final class DispatchFake: Dispatching {
func async(execute workItem: DispatchWorkItem) {
workItem.perform()
}
}
When we're testing, all we need to do is create our System Under Test(the controller, in this case), passing a fake dispatching instance
You can easy achieve this by checking if current thread is main and execute code synchronously in this case.
For example in presenter I update view in this way:
private func updateView(with viewModel: MyViewModel) {
if Thread.isMainThread {
view?.update(with: viewModel)
} else {
DispatchQueue.main.async {
self.view?.update(with: viewModel)
}
}
}
And then I can write synchronous unit tests for my presenter:
func testOnViewDidLoadFetchFailed() throws {
presenter.onViewDidLoad()
// presenter is calling interactor.fetchData when onViewDidLoad is called
XCTAssertEqual(interactor.fetchDataCallsCount, 1)
// test execute fetchData completion closure manually in the main thread
interactor.fetchDataCalls[0].completion(.failure(TestError()))
// presenter will call updateView(viewModel:) internally in synchronous way
// because we have check if Thread.isMainThread in updateView(viewModel:)
XCTAssertEqual(view.updateCallsCount, 1)
guard case .error = view.updateCalls[0] else {
XCTFail("error expected, got \(view.updateCalls[0])")
return
}
}
Here is a small proof of concept of how you could achieve it:
func testExample() {
let expectation = self.expectation(description: "numberOfActiveTasks")
var mockModel = MockModel()
mockModel.numberOfActiveTasksClosure = {() in
expectation.fulfill()
}
DispatchQueue.main.async {
_ = mockModel.numberOfActiveTasks
}
self.waitForExpectations(timeout: 2, handler: nil)
}
and here is the MockModel:
struct MockModel : Model {
var numberOfActiveTasks: Int {
get {
if let cl = numberOfActiveTasksClosure {
cl()
}
//we dont care about the actual value for this test
return 0
}
}
var numberOfActiveTasksClosure: (() -> ())?
}
To test asynchronous code you should modify your updateBadgeValuesForTabBarItems function and call it directly from your tests with a completion closure:
func updateBadgeValuesForTabBarItems(completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
self.setBadge(value: self.viewModel.numberOfUnreadMessages, for: .threads)
self.setBadge(value: self.viewModel.numberOfActiveTasks, for: .tasks)
self.setBadge(value: self.viewModel.numberOfUnreadNotifications, for: .notifications)
completion?()
}
}
Now you are be able to call this function as before in your regular code e.g.: updateBadgeValuesForTabBarItems(). But for tests you can add a completion closure and use XCTestExpectation to wait:
func testBadge() {
...
let expectation = expectation(description: "Badge")
updateBadgeValuesForTabBarItems {
XCTAssertTrue(model.numberOfActiveTasksWasCalled)
XCTAssertTrue(model.numberOfUnreadMessagesWasCalled)
XCTAssertTrue(model.numberOfUnreadNotificationsWasCalled)
XCTAssertTrue(model.indexForTypeWasCalled)
expectation.fulfill()
}
wait(for: [expectation], timeout: 1)
}

Why RxSwift Subscribe just run once in First launch viewWillAppear?

I write a subscribe in viewWillAppear.
But it also run once in first launch app.
When I push to another viewcontroller, I use dispose().
Then I back in first viewcontroller, my subscribe func in viewWillAppear don't run.
What's wrong with my rx subscribe?
var listSubscribe:Disposable?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
listSubscribe = chatrooms.notifySubject.subscribe({ json in
print("*1") //just print once in first launch
self.loadContents()
})
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
let controllers = tabBarController?.navigationController?.viewControllers
if (controllers?.count)! > 1 {
listSubscribe?.dispose()
}
}
RxSwift documentation says "Note that you usually do not want to manually call dispose; this is only an educational example. Calling dispose manually is usually a bad code smell."
Normally, you should be doing something like this -
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
whatever.subscribe(onNext: { event in
// do stuff
}).disposed(by: self.disposeBag)
}
As for your question, I believe you don't need to re-subscribe because you subscription will be alive and 'notifySubject' will send you updates whenever there are any.
Maybe you can get some reactive implementation of viewWillAppear and similar functions? And forget about manual disposables handling... For example your UIViewController init will contain something like this:
rx.driverViewState()
.asObservable()
.filter({ $0 == .willAppear })
.take(1) // if you need only first viewWillAppear call
.flatMapLatest({ _ in
// Do what you need
})
And the implementation of driverViewState:
public extension UIViewController {
public enum ViewState {
case unknown, didAppear, didDisappear, willAppear, willDisappear
}
}
public extension Reactive where Base: UIViewController {
private typealias _StateSelector = (Selector, UIViewController.ViewState)
private typealias _State = UIViewController.ViewState
private func observableAppearance(_ selector: Selector, state: _State) -> Observable<UIViewController.ViewState> {
return (base as UIViewController).rx
.methodInvoked(selector)
.map { _ in state }
}
func driverViewState() -> Driver<UIViewController.ViewState> {
let statesAndSelectors: [_StateSelector] = [
(#selector(UIViewController.viewDidAppear(_:)), .didAppear),
(#selector(UIViewController.viewDidDisappear(_:)), .didDisappear),
(#selector(UIViewController.viewWillAppear(_:)), .willAppear),
(#selector(UIViewController.viewWillDisappear(_:)), .willDisappear)
]
let observables = statesAndSelectors
.map({ observableAppearance($0.0, state: $0.1) })
return Observable
.from(observables)
.merge()
.asDriver(onErrorJustReturn: UIViewController.ViewState.unknown)
.startWith(UIViewController.ViewState.unknown)
.distinctUntilChanged()
}
}

Reactive5 flatMap observeValues block did not call

override func viewDidLoad() {
commitButton.reactive.controlEvents(.touchUpInside).flatMap(.latest) { (button) -> Signal<Bool, NoError> in
return self.createSignInSignal()
}.observeValues({
// did not call
print("Sign in result: \($0)")
})
}
func signin(name: String, password: String, result: ((Bool)->Void)) {
...
}
// create SignIn Signal
private func createSignInSignal() -> Signal<Bool, NoError> {
let (signInSignal, observers) = Signal<Bool, NoError>.pipe()
self.signin(name: nameTextField.text!, password: passworkTextField.text!, result: ({success in
observers.send(value: success)
observers.sendCompleted()
}))
return signInSignal
}
When click login button, FlatMap observeValues block did not call. When I use map, It work well, I know the difference between map and flatmap, How can I let the code work using flatmap?

How to use #objc protocol with optional and extensions at the same time?

This code does not compile and might sound stupid as it is, but i'll explain why it's so important!
#objc protocol p {
optional func f1()
func f2()
}
extension p {
func f1() { }
func f2() { }
}
class foo: p {
}
Compiler says Type c does not conform to protocol 'p' and that's maybe because you can not use #objc optional and extensions at the same time (and does not make sence in this scenario either). But consider the following example:
I want to set a selector on a non-optional method defined in protocol in my extension (main reason i used #objc):
func f1() { } -> func f1() { ... #selector(Self.f2) ... }
And i also want my f2() function to have default behaviour. If i mark f2() as optional, it can not be used in #selector because compiler does not know if this method actually exists in the case of need. Sure there're lots of nasty workarounds like global methods, sending Selectors to methods as input and etc, but is there a clean way to achieve it?
This is the practical issue
#objc
protocol Refreshable {
weak var refreshControl: UIRefreshControl? { get set }
optional func setupRefreshControl()
func refresh()
}
#objc
protocol ContentLoader {
func load(reset: Bool)
}
extension Refreshable where Self: ContentLoader {
func refresh() {
delay(0.75) { [weak self] in
self?.load(true)
}
}
}
extension Refreshable where Self: UICollectionViewController {
func setupRefreshControl() {
let newRefreshControl = UIRefreshControl()
newRefreshControl.tintColor = UIColor.grayColor()
newRefreshControl.addTarget(self, action: #selector(Self.refresh), forControlEvents: .ValueChanged)
collectionView?.addSubview(newRefreshControl)
refreshControl = newRefreshControl
}
}
Now if a ViewController implements Refreshable and ContentLoader, it does not find the default refresh function, but it does find setupRefreshControl. So i figured let's mark refresh as optional too, but by doing that, you can not send it to selector any more.
I even tried this:
func refresh() -> optional func refresh()
and
let str = "refresh"
let sel = Selector(str)
It silents the compiler yes, but does not work either... rises unrecognized selector sent to instance....
I think this is not possible in swift (because of the way it bridges to #objc protocols). But this is a work around(using Obj-c associated objects) to solve the unrecognized selector sent to instance... problem.
fileprivate class AssociatedObject: NSObject {
var closure: (() -> ())? = nil
func trigger() {
closure?()
}
}
// Keys should be global variables, do not use, static variables inside classes or structs.
private var associatedObjectKey = "storedObject"
protocol CustomProtocol: class {
func setup()
}
extension CustomProtocol where Self: NSObject {
fileprivate var associatedObject: AssociatedObject? {
get {
return objc_getAssociatedObject(self, &associatedObjectKey) as? AssociatedObject
}
set {
objc_setAssociatedObject(self, &associatedObjectKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func setup() {
let object = AssociatedObject()
object.closure = { [weak self] in // Do not forget to use weak in order to avoid retain-cycle
self?.functionToCallIndirectlyWithSelector()
}
let selector = #selector(object.trigger)
// Uncomment next line to test it's functionality
object.perform(selector)
// Here, you must add selector to the target which needs to call the selector, for example:
// refreshControl.addTarget(object, action: selector, forControlEvents: .valueChanged)
self.associatedObject = object
}
func functionToCallIndirectlyWithSelector() {
print("Function got called indirectly.")
}
}
class CustomClass: NSObject, CustomProtocol {}
let instance = CustomClass()
instance.setup()
I added Self: NSObject constraint to be able to test it's functionality in playground, I'm not sure if it's necessary or not.

Swift: Accept a closure with varying parameters

Suppose I have a function that accepts a callback with a sender, like this:
func performAction(aNumber: Double, completion: (sender: UIButton) -> Void) {
// Does some stuff here
let button = getAButtonFromSomewhere()
completion(button)
}
And so one possible way to call this function is by passing an existing function for the callback, rather than defining the closure in-place:
performAction(10, completion: myCallback)
func myCallback(sender: UIButton) {
sender.setTitle("foo", forState: .Normal)
}
Back in my definition for performAction, how can I define the completion block to accept a UIButton or any subclass of it?
As an example, suppose I have a UIButton subclass called CustomButton. So in my callback, I'm only interested in accepting a CustomButton. I'd like to do this:
performAction(10, completion: myCallback)
// This produces a compiler error:
func myCallback(sender: CustomButton) {
sender.setTitle("foo", forState: .Normal)
}
// This works, but forces me to cast to my custom class:
func myCallback(sender: UIButton) {
let realButton = sender as! CustomButton
realButton.setTitle("foo", forState: .Normal)
}
But the compiler won't allow it, because the definition of performAction requires the callback to accept a UIButton specifically (even though CustomButton is a UIButton subclass).
I'd like performAction to be generic so that it can be packaged in a library, and work with any UIButton subclass. Is this possible to do in Swift?
EDIT: I tried to simplify what I'm doing with the example above, but I think it just caused confusion. Here's the actual code that I'm trying to make work, with some improvements thanks to #luk2302:
public extension UIButton {
private class Action: AnyObject {
private var function: Any
init(function: Any) {
self.function = function
}
}
// Trickery to add a stored property to UIButton...
private static var actionsAssocKey: UInt8 = 0
private var action: Action? {
get {
return objc_getAssociatedObject(self, &UIButton.actionsAssocKey) as? Action
}
set(newValue) {
objc_setAssociatedObject(self, &UIButton.actionsAssocKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
internal func performAction(sender: UIButton) {
if let function = self.action!.function as? () -> Void {
function()
// THIS IS WHERE THINGS BREAK NOW:
} else if let function = self.action!.function as? (sender: self.Type) -> Void {
function(sender: self)
}
}
public func addTarget(forControlEvents event: UIControlEvents, action: () -> Void) {
self.action = Action(function: action)
self.addTarget(self, action: "performAction:", forControlEvents: event)
}
public func addTarget<B: UIButton>(forControlEvents: UIControlEvents, actionWithSender: (sender: B) -> Void) {
self.action = Action(function: actionWithSender)
self.addTarget(self, action: "performAction:", forControlEvents: forControlEvents)
}
}
The only piece that breaks now is the line that I commented, at (sender: self.Type) (self being either UIButton, or some subclass of it).
So this deviates from my original question slightly, but how can I can I cast function to a closure accepting a sender of the same type as self? This code works perfectly if I hard-code the type, but it should be able to work for any UIButton subclass.
You can make the UIButton subclass a generic parameter for the performAction function, but then you will need to cast the button before passing it to the callback, unless you also have a generic way of "getting" the right type of button.
// performAction() works with any type of UIButton
func performAction<B: UIButton>(aNumber: Double, completion: (sender: B) -> Void)
{
// Assuming getAButtonFromSomewhere returns UIButton, and not B, you must cast it.
if let button = getAButtonFromSomewhere() as? B {
completion(sender: button)
}
}
Ask yourself this: what should happen if you pass a closure of type (CustomButton -> Void) as completion and then getAButtonFromSomewhere returns an instance of UIButton? The code then cannot invoke the closure since the UIButton is not a CustomButton.
The compiler simply does not allow you to pass (CustomButton -> Void) to (UIButton -> Void) because (CustomButton -> Void) is more restrictive than (UIButton -> Void). Note that you can pass (UIButton -> Void) to a closure of type (CustomButton -> Void) since (UIButton -> Void) is less restrictive - you can pass everything you pass to the first to the second as well.
Therefore either make func use a generic type as #jtbandes suggests or use your initial approach a little bit improved:
func myCallback(sender: UIButton) {
if let realButton = sender as? CustomButton {
realButton.setTitle("foo", forState: .Normal)
}
}
Both solutions will result in the setTitle to not be invoked whenever the returned value of getAButtonFromSomewhere is not a CustomButton.

Resources