Delegate always getting nil value in dynamic framework class - ios

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

Related

Passing data to various view controllers via delegate

Struggling to learn the basics of passing data via delegates. I am trying to pass a string from my viewController to my viewController2 and print it. I am getting the error:
"Type ViewController2 has no member delagate" in my view controller 2.
I cannot figure out where I have gone wrong.
viewController 1:
protocol datadelagate {
func printThisString(string: String)
}
class ViewController: UIViewController {
var delegate: datadelagate?
override func viewDidLoad() {
delegate?.printThisString(string: "This was passed from first controller to second controller")
}
}
This is my viewController 2:
class ViewController2: UIViewController, datadelagate {
func printThisString(string: String) {
print(string)
}
override func viewDidLoad() {
super.viewDidLoad()
ViewController2.delagate = self
print(String.self)
}
}
If you want ViewController2 to print some value from ViewController, you might have to do it this way:
protocol datadelagate {
func printThisString(string: String)
func getStringFromVC1() -> String
}
class ViewController: UIViewController, datadelagate {
let someString: String = "From VC1"
func printThisString(string: String) {
print(string)
}
func getStringFromVC1() -> String {
return someString
}
override func viewDidLoad() {
ViewController2.delagate = self
}
}
class ViewController2: UIViewController {
var delegate: datadelagate?
override func viewDidLoad() {
super.viewDidLoad()
//This is how something from VC2 is sent to VC1's scope.
delegate?.printThisString(string: "Calling the delegate to print something from ViewController2 on first ViewController")
//The below call gets you some value from VC1. (This is what you wanted, I belive...)
print(delegate?.getStringFromVC1())
}
}
Now for some explanation:
For simple understanding, assume a delegate as a person who does some specific job (protocol).
You have a `delegate'
You ask your delegate to work with your friend, and your friend acknowledges. (assigns your delegate by You.delegate = self, where self is your friend)
Now, through your delegate, you can do something with your friend, by asking your delegate to do some job (defined in protocol).
EDIT
The code above won't work, as non-static data members are trying to be accessed without creating an instance
Working code
import UIKit
class ViewController2: UIViewController {
static let sharedInstance = ViewController2()
weak var delegate: DataDelagate?
override func viewDidLoad() {
super.viewDidLoad()
//This is how something from VC2 is sent to VC1's scope.
delegate?.printThis(string: "Calling the delegate to print something from ViewController2 on first ViewController")
//The below call gets you some value from VC1. (This is what you wanted, I belive...)
print(delegate?.getStringFromVC1() ?? "s")
}
}
class ViewController: UIViewController {
static let sharedInstance = ViewController2()
var someString: String = "From VC1"
override func viewDidLoad() {
super.viewDidLoad()
ViewController2.sharedInstance.delegate = self
}
}
extension ViewController: DataDelagate {
func printThis(string: String) {
print(string)
}
func getStringFromVC1() -> String {
return someString
}
}
protocol DataDelagate: AnyObject {
func printThis(string: String)
func getStringFromVC1() -> String
}

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

Clean Swift - Routing without segues

I found Router in Clean Swift architecture is responsible to navigate and pass data between view controllers. Some samples and articles depict that Routers use segue to communicate with view controllers. What would be the convenient design when I don't want to use any segue from Storyboard. Is it possible to pass data without segue in Clean Swift? If you describe with simplest complete example, would be appreciated.
Article says that you can:
// 2. Present another view controller programmatically
You can use this to manually create, configure and push viewController.
Example.
Let's pretend that you have ViewController with button (handle push):
final class ViewController: UIViewController {
private var router: ViewControllerRouterInput!
override func viewDidLoad() {
super.viewDidLoad()
router = ViewControllerRouter(viewController: self)
}
#IBAction func pushController(_ sender: UIButton) {
router.navigateToPushedViewController(value: 1)
}
}
This ViewController has router that implements ViewControllerRouterInput protocol.
protocol ViewControllerRouterInput {
func navigateToPushedViewController(value: Int)
}
final class ViewControllerRouter: ViewControllerRouterInput {
weak var viewController: ViewController?
init(viewController: ViewController) {
self.viewController = viewController
}
// MARK: - ViewControllerRouterInput
func navigateToPushedViewController(value: Int) {
let pushedViewController = PushedViewController.instantiate()
pushedViewController.configure(viewModel: PushedViewModel(value: value))
viewController?.navigationController?.pushViewController(pushedViewController, animated: true)
}
}
The navigateToPushedViewController func can takes any parameter you want (it is good to encapsulate parameters before configure new vc, so you may want to do that).
And the PushedViewController hasn't any specific implementation. Just configure() method and assert (notify you about missing configure() call):
final class PushedViewModel {
let value: Int
init(value: Int) {
self.value = value
}
}
final class PushedViewController: UIViewController, StoryboardBased {
#IBOutlet weak var label: UILabel!
private var viewModel: PushedViewModel!
func configure(viewModel: PushedViewModel) {
self.viewModel = viewModel
}
override func viewDidLoad() {
super.viewDidLoad()
assert(viewModel != nil, "viewModel is nil. You should call configure method before push vc.")
label.text = "Pushed View Controller with value: \(viewModel.value)"
}
}
Note: also, i used Reusable pod to reduce boilerplate code.
Result:
As above article explained you can use option 2/3/4 of navigateToSomewhere method as per your app design.
func navigateToSomewhere()
{
// 2. Present another view controller programmatically
// viewController.presentViewController(someWhereViewController, animated: true, completion: nil)
// 3. Ask the navigation controller to push another view controller onto the stack
// viewController.navigationController?.pushViewController(someWhereViewController, animated: true)
// 4. Present a view controller from a different storyboard
// let storyboard = UIStoryboard(name: "OtherThanMain", bundle: nil)
// let someWhereViewController = storyboard.instantiateInitialViewController() as! SomeWhereViewController
// viewController.navigationController?.pushViewController(someWhereViewController, animated: true)
}
You need pass data across protocols
protocol SecondModuleInput {
// pass data func or variable
var data: Any? { get set }
}
protocol SecondModuleOutput {
// pass data func or variable
func send(data: Any)
}
First presenter
class FirstPresenter: SecondModuleOutput {
var view: UIViewController
var secondModuleInputHandler: SecondModuleInput?
// MARK: SecondModuleInput
func send(data: Any) {
//sended data from SecondPresenter
}
}
Second presenter
class SecondPresenter: SecondModuleInput {
var view: UIViewController
var secondModuleOutputHandler: SecondModuleOutput?
static func configureWith(block: #escaping (SecondModuleInput) -> (SecondModuleOutput)) -> UIViewController {
let secondPresenter = SecondPresenter()
secondPresenter.secondModuleOutputHandler = block(secondPresenter)
return secondPresenter.view
}
// Sending data to first presenter
func sendDataToFirstPresenter(data: Any) {
secondModuleOutputHandler?.send(data: data)
}
// MARK: FirstModuleInput
var data: Any?
}
Router
class FirstRouter {
func goToSecondModuleFrom(firstPresenter: FirstPresenter, with data: Any) {
let secondPresenterView = SecondPresenter.configureWith { (secondPreseter) -> (SecondModuleOutput) in
firstPresenter.secondModuleInputHandler = secondPreseter
return firstPresenter
}
//Pass data to SecondPresenter
firstPresenter.secondModuleInputHandler?.data = data
//Go to another view controller
//firstPresenter.view.present(secondPresenterView, animated: true, completion: nil)
//firstPresenter.view.navigationController.pushViewController(secondPresenterView, animated: true)
}
}

How to send a complete closure better

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

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