Protocol-Delegate pattern not notifying View Controller - ios

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.

Related

Manage Delegate out UIViewController class

I would like to understand what would be the best way to implement a delegate out UIViewController class
How can I manage the delegate using controller: UIViewController parameter of my function in AuthManager?
These are the two classes I'm working with .. I show you small examples to make you understand
class StartController: UIViewController {
#objc private func presentAuthFacebookController() {
AuthManager.signInWithFacebook(controller: self)
}
}
class AuthManager {
static func signInWithFacebook(controller: UIViewController) {
let loginManager = LoginManager()
loginManager.logIn(permissions: [.publicProfile, .email], viewController: controller) { (result) in
switch result {
case .cancelled : print("\n AuthFacebook: operazione annullata dall'utente \n")
case .failed(let error) : print("\n AuthFacebook: \(error) \n")
case .success(granted: _, declined: let declinedPermission, token: _):
let authVC = ExistingEmailController()
authVC.delegate = // ?????? (controller)
UIApplication.shared.windows.first?.rootViewController?.present(authVC, animated: true, completion: nil)
}
}
}
}
I personally don't think StartController should know about/conform to ExistingEmailControllerDelegate. But if you really want, you can declare controller as a composition type:
static func signInWithFacebook(controller: UIViewController & ExistingEmailControllerDelegate) {
...
authVC.delegate = controller
In my opinion, the whole point of having a AuthManager is to create a layer of abstraction on top of ExistingEmailController, and to encapsulate the logic of authentication. Therefore, StartController shouldn't know, or care, about ExistingEmailControllerDelegate. It only knows about AuthManager.
AuthManager should be the delegate of ExistingEmailController, which implies that signInWithFacebook should not be static, and AuthManager can have an AuthManagerDelegate that StartController conforms to:
class AuthManager : ExistingEmailControllerDelegate {
weak var delegate: AuthManagerDelegate?
func signInWithFacebook(controller: UIViewController) {
...
let authVC = ExistingEmailController()
authVC.delegate = self
UIApplication.shared.windows.first?.rootViewController?.present(authVC, animated: true, completion: nil)
}
func someMethodFromExistingEmailControllerDelegate() {
delegate?.someMethod() // delegating it self.delegate, which StartController conforms to
}
}
protocol AuthManagerDelegate : class {
func someMethod()
}
class StartController: UIViewController, AuthManagerDelegate {
var authManager: AuthManager!
override func viewDidLoad() {
authManager = AuthManager()
authManager.delegate = self
}
#objc private func presentAuthFacebookController() {
authManager.signInWithFacebook(controller: self)
}
func someMethod() {
// write here the code that you would have written in someMethodFromExistingEmailControllerDelegate
}
}

Accessing UI operations from another class

I have a "Share on Facebook" button in my app at 3 different view controllers. So I wrote a class which name is "ShareCentral" and i want to do all sharing operations in this class. But for showing share dialog i need to pass uiviewcontroller to my ShareCentral class. I did like that:
class ShareCentral {
var UIVC: UIViewController
init(vc:UIViewController) {
self.UIVC = vc
}
func shareOnFacebook() {
var content = LinkShareContent(url: URL(string:userProfileLink)!)
do {
try ShareDialog.show(from: UIVC, content: content)
}catch (let error) {
print(error)
}
}
}
And this is my view controller:
class SettingsViewController: UIViewController {
let shareCentral = ShareCentral(vc: self)
#IBAction func shareButtonClicked(_ sender: AnyObject) {
self.shareCentral.shareOnFacebook()
}
}
I am getting following compiler error:
SettingsViewController.swift:40:41: Cannot convert value of type '(SettingsViewController) -> () -> (SettingsViewController)' to expected argument type 'UIViewController'
I know if i change the type of UIVC to "SettingsViewController" the problem will disappear. But as i said before i am gonna use this method in three different view controllers.
How can i resolve this problem?
Try this instead :
class ShareCentral {
unowned var UIVC: UIViewController
init(vc:UIViewController) {
self.UIVC = vc
}
func shareOnFacebook() {
var content = LinkShareContent(url: URL(string:userProfileLink)!)
do {
try ShareDialog.show(from: UIVC, content: content)
} catch (let error) {
print(error)
}
}
}
class SettingsViewController: UIViewController {
var shareVC: ShareVC!
override func viewDidLoad() {
super.viewDidLoad()
self.shareVC = ShareVC(vc: self)
}
#IBAction func shareButtonClicked(_ sender: AnyObject) {
self.shareCentral.shareOnFacebook()
}
}

My custom RxDelegateProxy immediately disposing

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)
}
}

How to make function for HTTP request on other class in Swift?

I'm writing a app to make a http requests with SwiftHTTP, but I want to create a class to make this, and in the ViewController I call the functions to do this. So, I create a UIButton to call the functions.
I don't know if I need use threads or exists ways to make this easy.
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self,action:#selector(self.clickRequest(sender:)), for: UIControlEvents.touchUpInside )
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#objc func clickRequest (sender: UIButton) {
}
}
HTTPService.swift
import Foundation
import SwiftHTTP
class HTTPService {
}
How I make this in others Apps. But inside of ViewController.swift.
HTTP.GET("https://www.host.com/example",requestSerializer: JSONParameterSerializer()) {
response in
if let err = response.error {
print(err.localizedDescription)
return
}
} catch let err as NSError {
print(err)
}
}
Create one method with successBlock and failureBlock inside your HTTPService service class like below,
class HTTPService {
func makeRequest(params: [String: Any], successBlock: () -> Void, failureBlock: () -> Void) {
HTTP.GET("https://www.host.com/example",requestSerializer: JSONParameterSerializer()) {
response in
if let err = response.error {
print(err.localizedDescription)
failureBlock() // Call failure block
return
} else {
successBlock() // Call success block
}
} catch let err as NSError {
print(err)
failureBlock() // Call failure block
}
}
}
You can call this method like below,
#objc func clickRequest (sender: UIButton) {
HTTPService().makeRequest(params: ["name": "userName"], successBlock: {
// Handle API success here. E.g Reloading table view
}) {
// Handle API failure here. E.g Showing error to user
}
}
Thanks

Why I can't call a method stored in parent UIViewController from embedded UITableViewController?

I have a parent ui view controller and it has a method responsible for printing data to the console:
func printSomeData() {
print("printing some data")
}
It also has a container with embedded UITableViewController. The table itself has a pull to refresh functionality implemented and it prints the string when user pulls the table:
func refresh(refreshControl: UIRefreshControl) {
print("Refreshing!!")
refreshControl.endRefreshing()
}
Now I want to call printsomeData from the refresh method.
This is what I try:
parent UIViewController:
class MainMenu: UIViewController, printing{
func printSomeData() {
print("some date")
}
}
embedded UITableViewController:
protocol printing{
func printSomeData()
}
class MainMenuTableViewController: UITableViewController {
var delegate: printing?
func refresh(refreshControl: UIRefreshControl) {
print("Refreshing!!")
if let _ = delegate{
delegate?.printSomeData()
}
refreshControl.endRefreshing()
}
But now when I pull the table I only see Refreshing!!, there is no way I could see printing some data. What am I doing wrong?
Where are you assigning the delegate?
And write the optional method call as a single line
delegate?.printSomeData()
or like that:
if self.delegate != nil {
self.delegate!.printSomeData()
}
Inside MainMenu
override func viewDidLoad() {
super.viewDidLoad()
// tableViewController is placeholder for `MainMenuTableViewController` reference
tableViewController.delegate = self
}
If i have understand you correctly and the MainMenu has a ContainerView with MainMenuTableViewController than should this solve your problem:
class MainMenu: UIViewController, Printer {
func printSomeData() {
print("some date")
}
}
protocol Printer {
func printSomeData()
}
class MainMenuTableViewController: UITableViewController {
var printer: Printer? {
guard let printer = self.parentViewController as? Printer else {
return nil
}
return printer
}
func refresh(refreshControl: UIRefreshControl) {
print("Refreshing!!")
printer?.printSomeData()
refreshControl.endRefreshing()
}
}

Resources