I have some third party library that has delegate methods. But I like using RX so I should create RxDelegateProxy to receive delegate's callbacks via RX.
Here my custom DelegateProxy class:
extension Reactive where Base:GalleryController {
var selectedImages:Observable<(GalleryController, [Image])> {
let proxy = RxGalleryDelegateProxy.createProxy(for: self.base)
return proxy.imageSubject.asObservable()
}
}
private class RxGalleryDelegateProxy: DelegateProxy<GalleryController, GalleryControllerDelegate>, DelegateProxyType, GalleryControllerDelegate {
private var _imageSubject: PublishSubject<(GalleryController, [Image])>?
public weak fileprivate(set) var galleryController: GalleryController?
internal var imageSubject: PublishSubject<(GalleryController, [Image])> {
if let subject = _imageSubject {
return subject
}
let subject = PublishSubject<(GalleryController, [Image])>()
_imageSubject = subject
return subject
}
static func currentDelegate(for object: GalleryController) -> GalleryControllerDelegate? {
return object.delegate
}
static func setCurrentDelegate(_ delegate: GalleryControllerDelegate?, to object: GalleryController) {
object.delegate = delegate
}
static func registerKnownImplementations() {
self.register { RxGalleryDelegateProxy(parentObject: $0) }
}
private init(parentObject: GalleryController) {
self.galleryController = castOrFatalError(parentObject)
super.init(parentObject: parentObject, delegateProxy: RxGalleryDelegateProxy.self)
}
func galleryController(_ controller: GalleryController, didSelectImages images: [Image]) {
if let subject = _imageSubject {
subject.on(.next((controller, images)))
}
self._setForwardToDelegate(galleryController(controller, didSelectImages: images), retainDelegate: true)
}
deinit {
_imageSubject?.on(.completed)
}
}
In my UIViewController I subscribe with:
final class PhotoLibraryViewController: UIViewController {
private let _bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let gallery = GalleryController()
present(gallery, animated: true, completion: nil)
gallery.rx.selectedImages
.debug("--------")
.observeOn(MainScheduler.instance)
.subscribe(onNext: { (controller, images) in
print("\(images)")
}, onError: { (error) in
DDLogError("Error:\n\(error)")
})
.disposed(by: _bag)
}
}
But all I get in console output via .debug("--------") is:
2018-01-09 20:05:14.814: -------- -> subscribed
2018-01-09 20:05:14.817: -------- -> Event completed
2018-01-09 20:05:14.817: -------- -> isDisposed
So my object is immediately disposing just after creation. What did I do wrong?
Your gallery object is created inside viewDidLoad(), so once viewDidLoad() is completed, the deinit method from gallery is called and it is disposed.
Move your gallery object out of the viewDidLoad():
final class PhotoLibraryViewController: UIViewController {
private let _bag = DisposeBag()
let gallery = GalleryController()
override func viewDidLoad() {
super.viewDidLoad()
present(gallery, animated: true, completion: nil)
gallery.rx.selectedImages
.debug("--------")
.observeOn(MainScheduler.instance)
.subscribe(onNext: { (controller, images) in
print("\(images)")
}, onError: { (error) in
DDLogError("Error:\n\(error)")
})
.disposed(by: _bag)
}
}
Related
My Model saves data to Firestore. Once that data is saved, I'd like it to alert my ViewController so that a function can be called. However, nothing is being passed to my ViewController.
This is my Model:
protocol ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully:Bool)
}
class Model {
var delegate:ProtocolModel?
func createUserAddedRecipe(
docId:String,
completion: #escaping (Recipe?) -> Void) {
let db = Firestore.firestore()
do {
try db.collection("userFavourites").document(currentUserId).collection("userRecipes").document(docId).setData(from: recipe) { (error) in
print("Data Saved Successfully") // THIS OUTPUTS TO THE CONSOLE
// Notify delegate that data was saved to Firestore
self.delegate?.wasDataSavedSuccessfully(dataSavedSuccessfully: true)
}
}
catch {
print("Error \(error)")
}
}
}
The print("Data Saved Successfully") outputs to the console, but the delegate method right below it doesn't get called.
And this is my ViewController:
class ViewController: UIViewController {
private var model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
}
}
extension ViewController: ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully: Bool) {
if dataSavedSuccessfully == true {
print("Result is true.")
}
else {
print("Result is false.")
}
print("Protocol-Delegate Pattern Works")
}
}
Is there something I'm missing from this pattern? I haven't been able to notice anything different in the articles I've reviewed.
So I test your code and simulate something like that
import UIKit
protocol ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully:Bool)
}
class Model {
var delegate:ProtocolModel?
// I use this timer for simulate that firebase store data every 3 seconds for example
var timer: Timer?
func createUserAddedRecipe(
docId:String) {
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { _ in
self.delegate?.wasDataSavedSuccessfully(dataSavedSuccessfully: true)
})
}
}
class NavigationController: UINavigationController {
var model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
// Call this method to register for network notification
model.createUserAddedRecipe(docId: "exampleId")
}
}
extension NavigationController: ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully: Bool) {
print(#function)
}
}
so you can see the result as image below, my delegate update controller that conform to that protocol.
When i try print(self.navigationController) in viewWillApear or viewDidLoad all ok. But when delegate return response from API print(self.navigationController) return nil. What could it be?
extension EnterpriseList: APIDataDelegate {
func successRequest() { //print(self.navigtionController) == nil
DispatchQueue.main.async {
self.navigationController?.popToRootViewController(animated: true)
}
}
func badRequest() {
DispatchQueue.main.async {
Alert.showWarningAlert(withTitle: "Внимание!", andMessage: "Ошибка получения данных, попробуйте чуть позже", whereSender: self)
}
}
}
class EnterpriseList: UIViewController {
let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
dataSource.makeAPICall()
}
}
extension EnterpriseList: APIDataDelegate {
func successRequest() {
DispatchQueue.main.async {
self.navigationController!.popToRootViewController(animated: true)
}
}
func badRequest() {
DispatchQueue.main.async {
}
}
}
protocol APIDataDelegate: class {
func successRequest()
func badRequest()
}
class DataSource {
var delegate: APIDataDelegate?
private let queue = DispatchQueue(label: "Test", qos: .background, attributes: [], autoreleaseFrequency: .inherit, target: nil)
func makeAPICall() {
queue.asyncAfter(deadline: .now() + 2) {[weak self] in
self?.delegate?.successRequest()
}
queue.asyncAfter(deadline: .now() + 10) {[weak self] in
self?.delegate?.successRequest()
}
}
}
The crash is because you are calling the method on deallocated object.
Here is how to fix this:
Make your delegate is weak and you are correctly deallocating the objects
weak var delegate: APIDataDelegate?
Thx #Manoj for answers it's halped me. But i found solution in other. Besides SurveyList i have UserMenu where i create static let view controllers. Removing static i solved my problem.
I would like to know what is a proper way of setting delegates in the ViewModel in MVVM pattern in Swift.
I'm instantiating the ViewController from another class:
let viewModel = DashboardViewModel()
let viewController = DashboardViewController(viewModel: viewModel)
My ViewModel:
protocol DashboardViewModelType {
var items: [Item] { get }
var reloadDelegate: DashboardDataReloadDelegate? { get set }
}
protocol DashboardDataReloadDelegate: class {
func reloadData()
}
class DashboardViewModel: DashboardViewModelType {
var items: [Item] = []
weak var reloadDelegate: DashboardDataReloadDelegate?
init() {
loadItems()
}
func loadItems() {
let databaseFetcher = DatabaseDaysFetcher()
databaseFetcher.getDays(onData: { (items) in
self.items = items
reloadDelegate?.reloadData() //delegate is nil here
}) { (error) in
print(error)
}
}
}
and ViewController:
class DashboardViewController: UIViewController {
var viewModel: DashboardViewModelType?
init(viewModel: DashboardViewModelType) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
self.viewModel!.reloadDelegate = self // it is executed after
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension DashboardViewController: DashboardDataReloadDelegate {
func reloadData() {
print("data reloaded")
}
}
So the main problem is that if I want to inject the viewModel in another class I'm instantiating the viewModel when delegate is not yet set. Would it be better to declare loadItems inside the DashboardViewModelType protocol and then call this function from the init or viewDidLoad inside the ViewController?
Yes, you could inject DatabaseDaysFetcher in the init for the DashboardViewModel and then as you say, move loadItems to the DashboardViewModelType protocol.
Then when you call loadItems, it should callback in to the caller.
Then use [weak self] in the loadItems callback.
This would negate the need for the delegate.
protocol DashboardViewModelType {
init(databaseFetcher: DatabaseDaysFetcher)
func loadItems(completion: ([Item]) -> Void, error: (Error) -> Void)
}
final class DashboardViewModel: DashboardViewModelType {
private var databaseFetcher: DatabaseDaysFetcher
init(databaseFetcher: DatabaseDaysFetcher) {
self.databaseFetcher = databaseFetcher
}
func loadItems(completion: ([Item]) -> Void, onError: (Error) -> Void) {
self.databaseFetcher.getDays(onData: { (items) in
completion(items)
}) { (error) in
onError(error)
}
}
}
When developing an iOS app I come across random (sometimes occurring, sometimes not) EXC_BAD_ACCESS error in the following code:
import UIKit
class OrderTripDetailsController: UIViewController, OrderAware {
var order: Order?
var orderService = OrderService()
// MARK: Properties
#IBOutlet weak var driverName: UILabel!
#IBOutlet weak var autoColor: UILabel!
#IBOutlet weak var autoPlates: UILabel!
#IBOutlet weak var autoBrandModel: UILabel!
#IBOutlet weak var map: YMKMapView!
// MARK: Actions
// MARK: Navigation
override func viewDidLoad() {
if let cab = order?.orderCab {
autoColor.text = cab.cab.auto.color.name
autoPlates.text = cab.cab.auto.plates
driverName.text = cab.cab.driver.fullname
autoBrandModel.text = "\(cab.cab.auto.brand) \(cab.cab.auto.model)"
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
orderService.controllerDelegate = self
orderService.startSync()
}
override func viewWillDisappear(animated: Bool) {
orderService.stop() // <-- this is the line that fails
super.viewWillDisappear(animated)
}
// MARK: Order Aware
func setOrder(order: Order){
self.order = order
}
}
OrderService
Order Service is a class that starts timer and stops it. After stop is called, sometimes EXC_BAD_ACCESS occurs
import Locksmith
import RealmSwift
class OrderService {
// MARK: delegates
var controllerDelegate: UIViewController?
// Get the default Realm
let realm = try! Realm()
var timer = NSTimer()
//MARK: Domain actions
//repeat every n time if any dfound
#objc func initOrderSync(timer:NSTimer) {
func onRemoteReceived (order: Order) -> Void {
debugPrint("remote received", order.id)
if let o = realm.objectForPrimaryKey(OrderPo.self, key: order.orderHash) {
if o.orderStatus != order.orderStatus {
debugPrint("order status updated", order.id, order.orderStatus)
OrderUtils.navigateToOrderStatus(order,
viewController: controllerDelegate!)
}
try! realm.write {
o.orderStatus = order.orderStatus
}
}
}
if let authToken = AuthUtils.getToken() {
let orders = getOrdersByStatuses(OrderUtils.activeStatuses)
if orders.count > 0 {
for o in orders {
OrderRemoteService().getOrderFromRemote(o.id!, token: authToken, callback: onRemoteReceived)
}
} else {
debugPrint("invalidating timer; no orders")
stop()
}
} else {
debugPrint("invalidating timer; no auth")
stop()
}
}
func startSync() {
if !self.timer.valid {
debugPrint("starting order sync", controllerDelegate?.restorationIdentifier)
self.timer = NSTimer.scheduledTimerWithTimeInterval(20, target: self, selector: Selector("initOrderSync:"), userInfo: nil, repeats: true)
} else {
debugPrint("sync is already started", controllerDelegate?.restorationIdentifier)
}
}
func stop() {
dispatch_async(GlobalMainQueue, {
if self.timer.valid {
self.timer.invalidate()
debugPrint("invalidated timer", self.controllerDelegate?.restorationIdentifier)
} else {
debugPrint("sync is already stopped", self.controllerDelegate?.restorationIdentifier)
}
})
}
// MARK: orders
func getOrders() -> Results<OrderPo> {
return try! realm.objects(OrderPo.self).sorted("orderTime")
}
func getOrdersByStatuses(statuses: [String]) -> Results<OrderPo> {
var qString = "'\(statuses.first!)'"
if statuses.count > 1 {
for s in 1...statuses.count-1 {
qString += ",'\(statuses[s])'"
}
}
return try! realm.objects(OrderPo.self).filter("orderStatus IN {\(qString)}").sorted("orderTime")
}
}
Could anyone help with any ideas why it might happen?
Update 20.08.2016
Found out that OrderService is being deinitialised after 10 seconds for some reason.
I want to Decorate UIViewController with the ability to adjust it's interface when setInteractionEnabled method is called from another class (ex. Network State Manager). All changes (if any) should be provided in the concrete controller by overriding onInteractionChanged. Here is my code:
import Foundation
typealias InteractionClosure = ((enabled: Bool) -> Void)
protocol Interaction: class {
var onInteractionChanged: InteractionClosure? { get set }
func setInteractionEnabled(enabled: Bool)
}
extension Interaction where Self: UIViewController {
// Default: Do nothing
// Throws: - Extensions may not contain stored properties
var onInteractionChanged: InteractionClosure? = nil
func setInteractionEnabled(enabled: Bool) {
onInteractionChanged?(enabled: enabled)
}
}
extension UIViewController : Interaction {}
How to add default implementation for onInteractionChanged?
Answering my own question is something usually I don't do, but here is my solution:
typealias InteractionClosure = (enabled: Bool) -> Void
protocol Interaction: class {
func addOnInteractionChanged(closure: InteractionClosure)
func setInteractionEnabled(enabled: Bool)
}
extension Interaction where Self: UIViewController {
func addOnInteractionChanged(closure: InteractionClosure) {
onInteractionChanged = closure
}
func setInteractionEnabled(enabled: Bool) {
onInteractionChanged?(enabled: enabled)
}
// MARK: - Private
private var onInteractionChanged: InteractionClosure? {
get {
let wrapper =
objc_getAssociatedObject(self, &icAssociationKey) as? ClosureWrapper
return wrapper?.closure
}
set(newValue) {
objc_setAssociatedObject(self,
&icAssociationKey,
ClosureWrapper(newValue),
.OBJC_ASSOCIATION_RETAIN)
}
}
}
extension UIViewController : Interaction {}
// Helpers
private var icAssociationKey: UInt8 = 0
private class ClosureWrapper {
var closure: InteractionClosure?
init(_ closure: InteractionClosure?) {
self.closure = closure
}
}
Client class:
class LoginViewController: UIViewController {
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.setup()
}
// MARK: - Private
private func setup() {
// ...
addOnInteractionChanged { [unowned self] (enabled) in
self.signInButton.enabled = enabled
self.activityIndicatorView.hidden = !enabled
}
}
}
In manager class:
visibleViewController?.setInteractionEnabled(true)
If you would like property to have only { get } ability, you can use:
protocol TestProtocol {
var testClosure: ((_ parameter: Bool) -> Void)? { get }
}
extension TestProtocol {
var testClosure: ((_ parameter: Bool) -> Void)? {
return { parameter in
print(parameter)
}
}
}