How to pass data from delegate method to the observable's onNext method in RxSwift? - swift

I have manager class which will connect and manage the data and state of the Bluetooth device.
The manager class conforms to IWDeviceManagerDelegate and has a method which gives the weight data func onReceiveWeightData(_ device: IWDevice!, data: IWWeightData!).
Once I call listenToWeight() from any controller I want to give the data using Observable.
How I fire an onNext event with the data of onReceiveWeightData method to listenToWeight observable?
Below is the code.
class WeightMachineManager: NSObject {
func setup() {
IWDeviceManager.shared()?.delegate = self
IWDeviceManager.shared()?.initMgr()
}
func listenToWeight() -> Observable<IWWeightData> {
let tag = WeightMachineManager.tag
if let connectedDevice = connectedDevice {
IWDeviceManager.shared()?.add(connectedDevice, callback: { (device, code) in
if code == .success {
print("\(tag)[SUCCESS] Device added successfully.")
} else {
print("\(tag)[FAILURE] Failed to add device.")
}
})
} else {
print("\(tag)[FAILURE] Couldn't find any device to connect.")
}
}
}
extension WeightMachineManager: IWDeviceManagerDelegate {
func onReceiveWeightData(_ device: IWDevice!, data: IWWeightData!) {
// TODO:- Pass this data in the onNext event of listenToWeight's observable.
}
}

I've made a lot of assumptions in the below, but the result should look something like this:
class WeightMachineManager {
var connectedDevice: IWDevice?
func setup() {
IWDeviceManager.shared()?.initMgr()
}
func listenToWeight() -> Observable<IWWeightData> {
if let connectedDevice = connectedDevice, let deviceManager = IWDeviceManager.shared() {
return deviceManager.rx.add(connectedDevice)
.flatMap { deviceManager.rx.receivedWeightData() } // maybe this should be flatMapLatest or flatMapFirst. It depends on what is calling listenToWeight() and when.
}
else {
return .error(NSError.init(domain: "WeightMachineManager", code: -1, userInfo: nil))
}
}
}
extension IWDeviceManager: HasDelegate {
public typealias Delegate = IWDeviceManagerDelegate
}
class IWDeviceManagerDelegateProxy
: DelegateProxy<IWDeviceManager, IWDeviceManagerDelegate>
, DelegateProxyType
, IWDeviceManagerDelegate {
init(parentObject: IWDeviceManager) {
super.init(parentObject: parentObject, delegateProxy: IWDeviceManagerDelegateProxy.self)
}
public static func registerKnownImplementations() {
self.register { IWDeviceManagerDelegateProxy(parentObject: $0) }
}
}
extension Reactive where Base: IWDeviceManager {
var delegate: IWDeviceManagerDelegateProxy {
return IWDeviceManagerDelegateProxy.proxy(for: base)
}
func add(_ device: IWDevice) -> Observable<Void> {
return Observable.create { observer in
self.base.add(device, callback: { device, code in
if code == .success {
observer.onNext(())
observer.onCompleted()
}
else {
observer.onError(NSError.init(domain: "IWDeviceManager", code: -1, userInfo: nil))
}
})
return Disposables.create()
}
}
func receivedWeightData() -> Observable<IWWeightData> {
return delegate.methodInvoked(#selector(IWDeviceManagerDelegate.onReceiveWeightData(_:data:)))
.map { $0[1] as! IWWeightData }
}
}

Related

How can i get controlEvent type in RxSwift?

I added textfield two controlEven with RxSwift I want to different things in a method.
textField.rx.controlEvent([.editingDidBegin, .editingDidEnd])
.asObservable()
.subscribe(onNext: { event in
// setFocusing(withType: ControlEvent)
})
private func setFocusing(type: ControlEvent) {
if type == .editingDidBegin {
//....
} else if type ==.editingDidEnd {
//....
}
}
I want to execute everything one method but how can I get controlEvent type? Is it possible?
Try this:
Observable.merge(
[.editingDidBegin, .editingDidEnd]
.map { event in
textField.rx.controlEvent(event).map { event }
}
)
.subscribe(onNext: setFocusing)
func setFocusing(type: UIControl.Event) {
if type == .editingDidBegin {
//....
} else if type == .editingDidEnd {
//....
}
}

UnitTest does't work properly for MVP pattern Swift

I'm trying to write some UnitTests for the first time. My pattern is MVP and I'm trying to test my Presenter. I've created mock class: class TeamViewMock: TeamViewPresenterProtocol { }. It contains all the methods from my real Presenter. Inside the each method I'm trying to set the new value for the property, so when the method called - property should get a new value.
Only one property gets new value out of 4 and I've no clue why the other ones didn't get it.
You may see it in the following code
import XCTest
#testable import NHL
class TeamViewPresenterTest: XCTestCase {
var presenter: TeamViewPresenter!
var viewMock: TeamViewMock!
func setupPresenter() {
viewMock = TeamViewMock()
presenter = TeamViewPresenter(with: viewMock)
}
func testGetData() {
setupPresenter()
presenter.getData(completion: {_ in })
XCTAssertTrue(viewMock.isStart) // This one works and returns true
XCTAssertTrue(viewMock.isStop) // Return error
XCTAssertTrue(viewMock.isEndRefreshing) // Return error
XCTAssertTrue(viewMock.isReload) // Return error
}
}
class TeamViewMock: TeamViewPresenterProtocol {
var isStart = false
var isStop = false
var isEndRefreshing = false
var isReload = false
func startAnimating() {
self.isStart = true // Testing stops here and doesn't go any further...
}
func stopAnimating() {
self.isStop = true
}
func endRefreshing() {
self.isEndRefreshing = true
}
func reloadView(_ teams: NHLDTO) {
self.isReload = true
}
}
class TeamViewPresenter {
// MARK: - Public Properties
private weak var view: TeamViewPresenterProtocol?
public let dataFetcherService = DataFetcherService()
// MARK: - Initializers
init(with view: TeamViewPresenterProtocol) {
self.view = view
}
// MARK: - Public Methods
public func getData(completion: #escaping (AppError) -> Void) {
view?.startAnimating() // Testing stops here and doesn't go any further, but still returns true for the property isStart and error for the rest
dataFetcherService.fetchTeamData { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let error):
completion(error)
print(error)
case .success(let teams):
guard let teams = teams else { return }
self.view?.reloadView(teams)
self.view?.stopAnimating()
self.view?.endRefreshing()
}
}
}
}
protocol TeamViewPresenterProtocol: AnyObject {
func startAnimating()
func stopAnimating()
func reloadView(_ teams: NHLDTO)
func endRefreshing()
}

"Unsupported action method signature". FolioReader Swift , Error

please help me to solve this error using FolioReaderKit Swift
enter image description here
I was also facing same crash while using Folioreader in my app. I have fixed it by doing below changes in my code.
In FolioReaderAudioPlayer.swift file. Update these 5 methods.
#objc func pause() -> MPRemoteCommandHandlerStatus {
playing = false
if !isTextToSpeech {
if let player = player , player.isPlaying {
player.pause()
}
} else {
if synthesizer.isSpeaking {
synthesizer.pauseSpeaking(at: .word)
}
}
return .success
}
#objc func togglePlay() -> MPRemoteCommandHandlerStatus {
isPlaying() ? pause() : play()
return .success
}
#objc func play() -> MPRemoteCommandHandlerStatus {
if book.hasAudio {
guard let currentPage = self.folioReader.readerCenter?.currentPage else { return .commandFailed }
currentPage.webView?.js("playAudio()")
} else {
self.readCurrentSentence()
}
return .success
}
#objc func playPrevChapter() -> MPRemoteCommandHandlerStatus {
stopPlayerTimer()
// Wait for "currentPage" to update, then request to play audio
self.folioReader.readerCenter?.changePageToPrevious {
if self.isPlaying() {
self.play()
} else {
self.pause()
}
}
return .success
}
#objc func playNextChapter() -> MPRemoteCommandHandlerStatus {
stopPlayerTimer()
// Wait for "currentPage" to update, then request to play audio
self.folioReader.readerCenter?.changePageToNext {
if self.isPlaying() {
self.play()
}
}
return .success
}

Test PublishSubject for ViewState

I'm trying to test the main functionality of my ViewModel. The important step is to test te loaded state completed. But for sure, for a better test it could be interesting to test al states.
I was reading a lot of post and information about RxTest and RxBlocking but I'm not able to test this module. If someone can help me, it would be great!
struct Product: Equatable { }
struct Promotion { }
protocol ProductsRepository {
func fetchProducts() -> Observable<Products>
func fetchPromotions() -> Observable<[Promotion]>
}
struct ProductCellViewModel: Equatable {
let product: Product
}
struct Products {
let products: [Product]
}
enum ProductsViewState: Equatable {
case loading
case empty
case error
case loaded ([ProductCellViewModel])
}
class ProductsViewModel {
var repository: ProductsRepository
let disposeBag = DisposeBag()
private var productCellViewModel: [ProductCellViewModel]
private var promotions: [Promotion]
// MARK: Input
init(repository: ProductsRepository) {
self.repository = repository
productCellViewModel = [ProductCellViewModel]()
promotions = [Promotion]()
}
func requestData(scheduler: SchedulerType) {
state.onNext(.loading)
resetCalculate()
repository.fetchProducts()
.observeOn(scheduler)
.flatMap({ (products) -> Observable<[ProductCellViewModel]> in
return self.buildCellViewModels(data: products)
}).subscribe(onNext: { (cellViewModels) in
self.productCellViewModel = cellViewModels
}, onError: { (error) in
self.state.onNext(.error)
}, onCompleted: {
self.repository.fetchPromotions()
.flatMap({ (promotions) -> Observable<[Promotion]> in
self.promotions = promotions
return Observable.just(promotions)
}).subscribe(onNext: { (_) in
self.state.onNext(.loaded(self.productCellViewModel))
}, onError: { (error) in
self.state.onNext(.error)
}).disposed(by: self.disposeBag)
}).disposed(by: disposeBag)
}
// MARK: Output
var state = PublishSubject<ProductsViewState>()
// MARK: ViewModel Map Methods
private func buildCellViewModels(data: Products) -> Observable <[ProductCellViewModel]> {
var viewModels = [ProductCellViewModel]()
for product in data.products {
viewModels.append(ProductCellViewModel.init(product: product))
}
return Observable.just(viewModels)
}
func resetCalculate() {
productCellViewModel = [ProductCellViewModel]()
}
}
The goal is to be able to test all of ProductsViewState after viewmodel.requestData() is being called
The key here is that you have to inject your scheduler into the function so you can inject a test scheduler. Then you will be able to test your state. BTW that state property should be a let not a var.
class ProductsViewModelTests: XCTestCase {
var scheduler: TestScheduler!
var result: TestableObserver<ProductsViewState>!
var disposeBag: DisposeBag!
override func setUp() {
super.setUp()
scheduler = TestScheduler(initialClock: 0)
result = scheduler.createObserver(ProductsViewState.self)
disposeBag = DisposeBag()
}
func testStateLoaded() {
let mockRepo = MockProductsRepository(products: { .empty() }, promotions: { .empty() })
let viewModel = ProductsViewModel(repository: mockRepo)
viewModel.state.bind(to: result).disposed(by: disposeBag)
viewModel.requestData(scheduler: scheduler)
scheduler.start()
XCTAssertEqual(result.events, [.next(0, ProductsViewState.loading), .next(1, .loaded([]))])
}
func testState_ProductsError() {
let mockRepo = MockProductsRepository(products: { .error(StubError()) }, promotions: { .empty() })
let viewModel = ProductsViewModel(repository: mockRepo)
viewModel.state.bind(to: result).disposed(by: disposeBag)
viewModel.requestData(scheduler: scheduler)
scheduler.start()
XCTAssertEqual(result.events, [.next(0, ProductsViewState.loading), .next(1, .error)])
}
func testState_PromotionsError() {
let mockRepo = MockProductsRepository(products: { .empty() }, promotions: { .error(StubError()) })
let viewModel = ProductsViewModel(repository: mockRepo)
viewModel.state.bind(to: result).disposed(by: disposeBag)
viewModel.requestData(scheduler: scheduler)
scheduler.start()
XCTAssertEqual(result.events, [.next(0, ProductsViewState.loading), .next(1, .error)])
}
}
struct StubError: Error { }
struct MockProductsRepository: ProductsRepository {
let products: () -> Observable<Products>
let promotions: () -> Observable<[Promotion]>
func fetchProducts() -> Observable<Products> {
return products()
}
func fetchPromotions() -> Observable<[Promotion]> {
return promotions()
}
}

AVCaptureMetadataObjectDelegate not receiving callback

I am making QR scanner. My code is working when all of it written in one place inside ViewController but when I modularised it then I am not getting callback inside AVCaptureMetadataOutputObjectsDelegate.
import Foundation
import UIKit
import AVFoundation
class CameraSource : NSObject {
private var session : AVCaptureSession?
private var inputDevice : AVCaptureDeviceInput?
private var videoPreviewLayer : AVCaptureVideoPreviewLayer?
private var captureMetadataOutput : AVCaptureMetadataOutput?
func setCaptureMetadataOutput() {
self.captureMetadataOutput = nil
self.captureMetadataOutput = AVCaptureMetadataOutput()
}
func getCaptureMetadataOutput() -> AVCaptureMetadataOutput? {
return self.captureMetadataOutput
}
func setInputDevice(inputDevice : AVCaptureDeviceInput?) {
self.inputDevice = inputDevice
}
func getInputDevice() -> AVCaptureDeviceInput? {
return self.inputDevice
}
func setSession(session : AVCaptureSession?) {
self.session = session
}
func getSession() -> AVCaptureSession? {
return self.session
}
func setMetadataObjects(metaObjects : [AVMetadataObject.ObjectType], delegate : AVCaptureMetadataOutputObjectsDelegate) {
assert(self.captureMetadataOutput != nil)
self.captureMetadataOutput!.setMetadataObjectsDelegate(delegate, queue: DispatchQueue.main)
self.captureMetadataOutput!.metadataObjectTypes = metaObjects
}
func initViewoPreviewLayer(videoGravity : AVLayerVideoGravity, orientation : AVCaptureVideoOrientation) {
assert(session != nil)
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: session!)
videoPreviewLayer!.videoGravity = videoGravity
videoPreviewLayer!.connection!.videoOrientation = orientation
}
func addVideoLayerToImageView(imageView : UIImageView) {
assert(self.videoPreviewLayer != nil)
imageView.layer.addSublayer(self.videoPreviewLayer!)
self.videoPreviewLayer!.frame = imageView.bounds
}
func startSession() {
assert(session != nil)
self.session!.startRunning()
}
/*==========================================================================
STATIC FUNCTIONS
==========================================================================*/
static func getBackCamera() -> AVCaptureDevice {
return AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back)!
}
static func getFrontCamera() -> AVCaptureDevice {
return AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .front)!
}
static func isCameraAvailable() -> Bool {
if #available(iOS 10.0, *) {
let count : Int = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera],
mediaType: AVMediaType.video,
position: .unspecified).devices.count
if count > 0 { return true }
}
else {
let count = AVCaptureDevice.devices(for: AVMediaType.video).count
if count > 0 { return true }
}
return false
}
/*==========================================================================
CAMERA BUILDER CLASS
==========================================================================*/
class Builder {
var cameraSource : CameraSource
init() {
cameraSource = CameraSource()
}
func createSession() -> Builder {
if (cameraSource.getSession() != nil) {
cameraSource.setSession(session: nil)
}
cameraSource.setSession(session: AVCaptureSession())
return self
}
func setSessionPreset(preset : AVCaptureSession.Preset) -> Builder {
assert(cameraSource.getSession() != nil)
cameraSource.getSession()!.sessionPreset = preset
return self
}
func attachInputDevice(camera : AVCaptureDevice) throws -> Builder {
try self.prepareInputDevice(camera: camera)
try self.addInputToSession()
assert(cameraSource.inputDevice != nil)
return self
}
func addOutputToSessionForMetaData() throws -> CameraSource {
cameraSource.setCaptureMetadataOutput()
assert(cameraSource.getSession() != nil && cameraSource.getCaptureMetadataOutput() != nil)
if !cameraSource.getSession()!.canAddOutput(cameraSource.getCaptureMetadataOutput()!) {
throw AppErrorCode.cameraError("Unable to attach output to camera session")
}
cameraSource.getSession()!.addOutput(cameraSource.getCaptureMetadataOutput()!)
return self.cameraSource
}
/*==========================================================================
BUILDER PRIVATE FUNCTIONS
==========================================================================*/
private func prepareInputDevice(camera : AVCaptureDevice) throws {
do {
let inputDevice = try AVCaptureDeviceInput(device: camera)
cameraSource.setInputDevice(inputDevice: inputDevice)
} catch let error as NSError {
print(error.localizedDescription)
throw AppErrorCode.cameraError("Unable to attach input to camera session")
}
}
private func addInputToSession() throws {
if(cameraSource.getSession() == nil) {
throw AppErrorCode.cameraError("Unable to create camera session")
}
assert(cameraSource.getInputDevice() != nil && cameraSource.getSession()!.canAddInput(cameraSource.getInputDevice()!))
cameraSource.getSession()!.addInput(cameraSource.getInputDevice()!)
}
}
}
My QR scanner Code looks like
import UIKit
import Foundation
import AVFoundation
protocol QRScannerDelegate {
func scannedData(_ scannedString : String)
}
class QRScanner : NSObject {
private var cameraSource : CameraSource?
var delegate : QRScannerDelegate?
func prepareCamera (delegate : QRScannerDelegate) throws -> QRScanner {
do {
self.delegate = delegate
self.cameraSource = try CameraSource
.Builder()
.createSession()
.setSessionPreset(preset: .photo)
.attachInputDevice(camera: CameraSource.getBackCamera())
.addOutputToSessionForMetaData()
self.cameraSource!.setMetadataObjects(metaObjects: [.qr], delegate: self as AVCaptureMetadataOutputObjectsDelegate)
} catch let err as NSError {
print(err.localizedDescription)
self.cameraSource = nil
throw AppErrorCode.cameraError("Unable to process camera with one or more issue")
}
return self
}
func initViewoPreviewLayer(videoGravity : AVLayerVideoGravity, orientation : AVCaptureVideoOrientation) -> QRScanner{
assert(cameraSource != nil)
self.cameraSource!.initViewoPreviewLayer(videoGravity: videoGravity, orientation: orientation)
return self
}
func addVideoLayerToImageView(imageView : UIImageView) -> QRScanner{
assert(cameraSource != nil)
self.cameraSource!.addVideoLayerToImageView(imageView: imageView)
return self
}
func startSession() {
assert(cameraSource != nil)
self.cameraSource!.startSession()
}
}
extension QRScanner : AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
print("Delegate called")
if metadataObjects.count == 0 {
self.delegate?.scannedData("No Data")
} else {
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if metadataObj.type == AVMetadataObject.ObjectType.qr {
if metadataObj.stringValue != nil {
print("Scanner Getting data: \(metadataObj.stringValue!)")
self.delegate?.scannedData(metadataObj.stringValue!)
}
}
}
}
}
I have implemented the QRScannerDelegate in my ViewController but I am not getting anything in there. Moreover I am not getting callback inside AVCaptureMetadataOutputObjectsDelegate even.
I tried passing the ViewController instance as AVCaptureMetadataOutputObjectsDelegate then I was getting callback with the scanned info.
So My question is why is this happening?
1) When I am passing normal class as AVCaptureMetadataOutputObjectsDelegate I am not getting callback. But.
2) Whe I am passing UIViewController instance as AVCaptureMetadataOutputObjectsDelegate then I am able to get callback.
UPDATE
This is how I am calling prepareCamera from my View Controller
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
do {
try QRScanner().prepareCamera(delegate: self)
.initViewoPreviewLayer(videoGravity: .resizeAspectFill, orientation: .portrait)
.addVideoLayerToImageView(imageView: self.qrScannerImageView)
.startSession()
} catch {
print("Some Camera Error")
}
self.createOverlay()
}
Its hard to say for sure without knowing how you called prepareCamera as this is what triggers setMetadataObjectsDelegate but to me it looks like you may not be keeping a strong reference to QRScanner in your ViewController (instantiating it as in instance variable) Which could explain why the callback is getting hit when your ViewController is your AVCaptureMetadataOutputObjectsDelegate as the ViewController is still in memory.
It's also worth noting that if the ViewController is your QRScannerDelegate you will want to define delegate as weak var delegate : QRScannerDelegate? to prevent a memory leak.
EDIT:
Change
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
do {
try QRScanner().prepareCamera(delegate: self)
.initViewoPreviewLayer(videoGravity: .resizeAspectFill, orientation: .portrait)
.addVideoLayerToImageView(imageView: self.qrScannerImageView)
.startSession()
} catch {
print("Some Camera Error")
}
self.createOverlay()
}
to
var qrScanner = QRScanner()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
do {
try self.qrScanner.prepareCamera(delegate: self)
.initViewoPreviewLayer(videoGravity: .resizeAspectFill, orientation: .portrait)
.addVideoLayerToImageView(imageView: self.qrScannerImageView)
.startSession()
} catch {
print("Some Camera Error")
}
self.createOverlay()
}
and change
protocol QRScannerDelegate {
func scannedData(_ scannedString : String)
}
to
protocol QRScannerDelegate: class {
func scannedData(_ scannedString : String)
}
To Allow a weak delegate
AVCaptureMetadataOutputObjectsDelegate is tough, but you can do some really cool stuff with it! So keep at it.
I pulled some QRScanner code I wrote a while ago and put it into a gist for you if you want to check it out. Its a bit more stripped down than what you have, but you may find it helpful.
https://gist.github.com/aChase55/733ea89af1bfa80c65971d3bc691f0b2