How to send a complete closure better - ios

What I want is to send a closures to a UIViewController to tell it what it will do in the end. But when it comes to a package of UIViewControllers it will be a little messy.
class ViewController: UIViewController {
private var complete: ()->()
init(complete: ()->()){
self.complete = complete
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ViewController2: UIViewController {
private var complete: ()->()
init(complete: ()->()){
self.complete = complete
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here are my UIViewControllers. What want to do is to send a complete closure to UIViewController2, but I have to push UIViewController first. So what I have to do is send the closure to UIViewController, then UIViewController send the closure to UIViewController2. It is not messy when there are only two UIViewControllers. But it will be very messy when there comes out a package of UIViewControllers.
Any better solutions?

You the following code to handle the completion handler
First, create a base class of UIViewController and define the completionHandler
import UIKit
public class MyController: UIViewController {
var completionHandler: ((Bool, String) -> Void)? // Bool and String are the return type, you can choose anything you want to return
public func onComplete(completion : ((Bool, String) -> Void)?)
{
self.completionHandler = completion
}
override public func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override public func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In ViewController1, you need to call another ViewController like this
class ViewController: MyController {
// Initialization of view objects
override func viewDidLoad()
{
super.viewDidLoad()
//You are loading another ViewController from current ViewController
let controller2 = self.storyboard?.instantiateViewControllerWithIdentifier("controller2") as? MyController
controller2?.onComplete({ (finished, event) in
self.completionHandler?(finished, event)
})
}
//This is your button action or could be any event on which you will fire the completion handler
#IBAction func buttonTapped()
{
self.completionHandler(boolType, controllerName)
}
and where ever, you will create a new ViewController you will need to set its completionHandler by writing the line
controller2?.onComplete({ (finished, event) in
self.completionHandler?(finished, event)
})

Related

Delegate always getting nil value in dynamic framework class

I used delegation for passing data to ViewController B to A in dynamic framework . B is my dynamic framework ViewController . A is my app ViewController . I am always set delegate as self in my A class
Without dynamic framework it works perfectly
Class B code : Inside dynamic framework (Using .xib)
import UIKit
public protocol MediaDataDelegate: class{
func mediaDidFinish(controller:
LoginViewController,transactionId:String,returnURL: String)
}
public class LoginViewController: UIViewController {
public var message = ""
public var delegate: MediaDataDelegate?
public init() {
super.init(nibName: "LoginViewController", bundle: Bundle(for: LoginViewController.self))
print("message 1234 :\(message)")
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override open func viewDidLoad() {
super.viewDidLoad()
print("message 1234 :\(message)")
}
public class func logToConsole(_ msg: String) {
print(msg);
}
#IBAction func backToMainBtnTapped(_ sender: UIButton) {
self.delegate?.mediaDidFinish(controller: self, transactionId: "WERTYQWRCT", returnURL: "www.media.com")
}
}
Class A Code:Inside Other App (Using Storyboard)
Click on conduct IPVButton navigate to dynamic framework view controller
I also pass some value to message string but in dynamic framework class getting empty string.
import UIKit
import NBView
class ViewController: UIViewController ,MediaDataDelegate{
var loginVC = LoginViewController()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
LoginViewController.logToConsole("hello media")
}
#IBAction func conductIPVBtnTapped(_ sender: Any) {
loginVC.delegate = self
present(loginVC, animated: true, completion: nil)
}
func mediaDidFinish(controller: LoginViewController, transactionId:
String, returnURL: String) {
print("Trans Id\(transactionId)")
print("return URl \(returnURL)")
}
}
It is because you are instantiating incorrectly the LoginViewController
You need to do it this way, since you wrote that you have it in a .xib file:
let loginVC = LoginViewController(nibName: yourNibName, bundle: nil)
Always have a weak reference to your delegate, otherwise you will have a retain cycle:
weak var delegate: MediaDataDelegate?
Also, you don't need to use public everywhere where you thought it might fit. Use it wisely and when needed. Here you don't need it. Remove it from everywhere in your LoginViewController

How to receive same callback in two ViewControllers when one is opened?

I want to receive the same callback in the ViewController that is opened at in the time that server response in my Swift Application.
I have two ViewControllers. The first ViewController registers a callBack from a class "NetworkService".
The second ViewController is Opened from the first ViewController and the second receives the "NetworkService" from the firstViewController initialized in a variable, and then registers the same callBack.
When I try to receive the callback from the server, if the first ViewController is opened I get the response. If I open the second ViewController and I resend the response I get this correctly in the second ViewController.
BUT if I return to the first ViewController and I get the response, its' only received on the Second ViewController all times.
class NetworkService {
var onFunction: ((_ result: String)->())?
func doCall() {
self.onFunction?("result")
}
}
class MyViewController: UIViewController {
let networkService = NetworkService()
override func viewDidLoad() {
super.viewDidLoad()
networkService.onFunction = { result in
print("I got \(result) from the server!")
}
}
}
I open the secondViewController like:
let vc = self.storyboard!.instantiateViewController(withIdentifier: "second") as! SecondViewController
vc. networkService = networkService
self.navigationController?.pushViewController(vc, animated: true)
And the Second ViewController:
class SecondViewController: UIViewController {
var networkService: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
networkService!.onFunction = { result in
print("I got \(result) from the server!")
}
}
}
How would it be possible to receive the response in the first ViewController again, then return to first ViewController from the second calling the popViewController?
self.navigationController?.popViewController(animated: false)
How about calling the function within viewDidAppear on both ViewControllers so that you get your response every time you switch between the two views? You wouldn't need to pass networkService between the ViewControllers.
override func viewDidAppear(_ animated: Bool) {
networkService!.onFunction = { result in
print("I got \(result) from the server!")
}
}
You can use notification but you will have to register and deregister VC as you switch between views. Other option is to use delegate, you will need to share NetworkService instance. Quick example of how this could work with protocol.
protocol NetworkServiceProtocol {
var service: NetworkService? { get }
func onFunction(_ result: String)
}
class NetworkService {
var delegate: NetworkServiceProtocol?
func doCall() {
self.delegate?.onFunction("results")
}
func update(delegate: NetworkServiceProtocol) {
self.delegate = delegate
}
}
class VC1: UIViewController, NetworkServiceProtocol {
var service: NetworkService?
init(service: NetworkService? = nil) {
self.service = service
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.service?.update(delegate: self)
}
func onFunction(_ result: String) {
print("On Function")
}
}

Proper way of setting delegates in MVVM

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

super init isn't called on all paths before returning from initializer

I made a framework that wraps Alamofire.
In my Framework when testing (in Test target) i have this code that works grate.
import Foundation
#testable import NetworkManager
class MockRouter: Router {
enum APICalls {
case func1
case func2
}
var calls: APICalls!
init(calls: APICalls) {
self.calls = calls
}
}
When i add it as a framework to a different project
import Foundation
import NetworkManager
class JokesRouter: Router {
enum APICalls {
case func1
case func2
}
var calls: APICalls!
init(calls: APICalls) {
self.calls = calls
}
}
I get an error:
super init isn't called on all paths before returning from initializer
So i added Super.init()
init(calls: APICalls) {
super.init()
self.calls = calls
}
And now i get this error:
super.init cannot be called outside of an initializer
Any idea what is the problem?
You'll need to declare an empty public init() {} in Router.
Though it's a cryptic error, the problem is that the subclass cannot call super.init() because that initializer is not accessible outside of the NetworkManager module unless you declare it as public. The automatically-generated init() in Router is not public by default.
It worked in your test class because the #testable import sidestepped the default internal access restriction.
I was trying to create an Inheritance feature of the Swift Programming.
I created the ViewController as super class.
Then I created another class as subclass name as ViewControllerA of super class ViewController
ViewController Class :
import UIKit
class ViewController: UIViewController {
//: public variables
var area: CGFloat?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
public func printArea(){
print("Print area in Super class")
}
public func calculateArea(){
}
}
ViewControllerA as subclass :
import UIKit
class ViewControllerA: ViewController {
var length: CGFloat?
init( len: CGFloat) {
self.length = len
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func printArea() {
print("The are of A \(area!)")
}
override func calculateArea() {
area = length! * length!
}
}
I have got the error.
Must call a designated initializer of the superclass 'ViewController' in subclass init method.
So I declare the empty init method in super class i.e ViewController
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
It worked for me.

Reload TableViewController from parent

I have a TableViewController, lets call it A, that is in a container view of another view controller, B. I need A to reload it's data when a value changes in B. I also need it to get this changed value from B. Any ideas?
Have you considered using notifications?
So, in B – I would do something like:
// ViewControllerB.swift
import UIKit
static let BChangedNotification = "ViewControllerBChanged"
class ViewControllerB: UIViewController {
//... truncated
func valueChanged(sender: AnyObject) {
let changedValue = ...
NSNotificationCenter.defaultCenter().postNotificationName(
BChangedNotification, object: changedValue)
}
//... truncated
}
Followed up with A looking something like this – where ValueType is simply the type of the value you mentioned:
import UIKit
class ViewControllerA: UITableViewController {
//... truncated
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//...truncated
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "onBChangedNotification:",
name: BChangedNotification,
object: nil)
}
//... truncated
func onBChangedNotification(notification: NSNotification) {
if let newValue = notification.object as? ValueType {
//...truncated (do something with newValue)
self.reloadData()
}
}
}
Lastly – don't forget to remove the observer in A's deinit method:
import UIKit
class ViewControllerA: UITableViewController {
//... truncated
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
//... truncated
}

Resources