Swift Protocols not working - ios

I am trying to call a function using delegates and for some reason it is not working. I set loginPresenter as self in the LoginPresenter init() but it still didn't work. I am not sure what else to try? Am i missing how delegates work?
Login View
class LoginView: UIViewController {
var loginPresenter: LoginPresenterProtocol?
override func viewDidLoad() {
super.viewDidLoad()
}
#objc func loginHandler() {
print("Tapped")
loginPresenter?.loginUser(username: "username", password: "123456")
}
}
Login Presenter View
class LoginPresenter: LoginPresenterProtocol {
weak var view: LoginViewProtocol?
init() {
view?.loginPresenter = self
}
func loginUser(username: String, password: String) {
print("recieved")
}
}
Protocols
protocol LoginViewProtocol: class {
var loginPresenter: LoginPresenterProtocol? { get set }
}
protocol LoginPresenterProtocol: class {
var view: LoginViewProtocol? { get set }
func loginUser(username: String, password: String)
}

The root of your problem is that you probably did not inject presenter to the view or it is not the same instance. I do not know how you deal with initialization, so I provide 2 concepts. It is better to use injection with Viper I guess.
Create init method for your presenter which takes view protocol, this applies for both approaches:
init(view: LoginViewProtocol) {
self.view = view
}
Registering your Dependencies
Below is example of what worked for me using Swinject(transformed to your cause but not tested).
Even though if you are not using storyboard, you can't inject both view to presenter and presenter to view in init. That is why I am using init completed here.
let mainStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
container.register(LoginViewProtocol.self) { _ in
mainStoryboard.instantiateViewController(
withIdentifier: String(describing: LoginView.self)
) as! LoginViewProtocol
}.initCompleted { (r, loginVC) in
loginVC.presenter: r.resolve(LoginPresenterProtocol.self)!
}
container.register(LoginPresenterProtocol.self) { r in
LoginPresenter(
view: r.resolve(LoginViewProtocol.self)!
)
}
Then just resolve view controller when needed to be presented.
Without Dependency injection
Or if you do not want to deal with Dependency injection:
in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
presenter = LoginPresenter(view: self)
}
This could give you an idea how to deal with that. Anyway you will have to figure out what is the best for your cause. If you are not using Dependency injection I strongly recommend it.

Related

How to pass data to the final view controller

I am new to Swift and am building an app to learn. Right now I am making the registration section of the app.
I thought the UX would be better if there were multiple VC's asking a single question, i.e. one for your name, one for your birthdate, etc as opposed to jamming all that into a single view controller. The final view controller collects all of that information and sends a dictionary as FUser object to be saved on Firebase.
I figured I could instantiate the final view controller on each of the previous five view controllers and pass that data directly to the end. I kept getting errors and figured out that the variables were nil. It works just fine if I pass the data directly to the next view controller but it doesn't seem to let me send it several view controllers down. Obviously there's a nuance to how the memory is being managed here that I'm not tracking.
Is there a way to do what I am trying to do or do I have to pass the data through each view controller along the way?
import UIKit
class FirstViewController: UIViewController {
//MARK: - IBOutlets
#IBOutlet weak var firstNameTextField: UITextField!
//MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - IBActions
#IBAction func continueToMiddleViewController(_ sender: Any) {
let vcFinal = storyboard?.instantiateViewController(withIdentifier:
"finalVC") as! finalViewController
vcFinal.firstName = firstNameTextField.text
let vc = storyboard?.instantiateViewController(withIdentifier:
"middleVC") as! middleViewController
vc.modalPresentationStyle = .fullScreen
present(vc, animated: false)
}
...
}
import UIKit
class FinalViewController: UIViewController {
var firstName: String?
...
//MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
}
...
}
TL;DR: The fastest one that would solve your problem is creating a singleton
There are many strategies for this. For a starter, it might be a good idea to read some begginer articles, like this one. I can update this answer if you don't find it useful, but it'd look just like the article
Viewcontroller's variable can't be initiated until any of the init method is called.
There are detailed answers on this thread.
Passing Data between ViewControllers
Another way to approach this problem could be to make use of closures. Note that personally I've moved away from using storyboards but I'll try to explain still. Closures are also referred to as callbacks, blocks, or in some context like here - completions.
You can declare a closure like let onSubmitInfo: (String?) -> Void below, it stores a reference to a block of code that can be executed at a later stage just like a function and it takes an optional string as a parameter just like a function can.
The closures are specified in the initialisers where a block of code is passed into the respective classes below and the closures are then called in the IBActions that will trigger the block of code that is defined where the below classes are initialised:
class First: UIViewController {
// MARK: - IBOutlets
#IBOutlet weak var firstNameTextField: UITextField!
// MARK: - Properties
private let onSubmitInfo: (String?) -> Void
init(onSubmitInfo: (String?) -> Void) {
self.onSubmitInfo = onSubmitInfo
}
// MARK: - IBActions
#IBAction func continue(_ sender: Any) {
onSubmitInfo(firstNameTextField.text)
}
}
class Second: UIViewController {
// MARK: - IBOutlets
#IBOutlet weak var lastNameTextField: UITextField!
// MARK: - Properties
private let onSubmitInfo: (String?) -> Void
init(onSubmitInfo: (String?) -> Void) {
self.onSubmitInfo = onSubmitInfo
}
// MARK: - IBActions
#IBAction func continue(_ sender: Any) {
onSubmitInfo(lastNameTextField.text)
}
}
To manage showing the above views and collecting the values returned by their closures (i.e. onSubmitInfo) we create a FlowController class that will also show the next view when the closure is called.
In FlowController we define the closures or blocks of code to be executed when it is called inside the IBAction in the respective First and Second classes above.
The optional string that is provided in the respective First and Second classes is used as the (firstName) and (secondName) closure properties below:
class FlowController: UIViewController {
private var fistName: String?
private var lastName: String?
...
private func showFirstView() {
let firstViewController = First(onSubmitInfo: { (firstName) in
self.firstName = firstName
showSecondView()
})
navigationController?.pushViewController(
firstViewController,
animated: true)
}
private func showSecondView() {
let secondViewController = Second(onSubmitInfo: { (lastName) in
self.lastName = lastName
showFinalView()
})
navigationController?.pushViewController(
secondViewController,
animated: true)
}
private func showFinalView() {
let finalViewController = Final(
firstName: firstName,
lastName: lastName)
navigationController?.pushViewController(
finalViewController,
animated: true)
}
}
The FlowController finally shows the Final view controller after it has collected the firstName form the First view controller and the lastName form the Second view controller in the showFinalView function above.
class Final: UIViewController {
let firstName: String
let lastName: String
...
}
I hope this is a shove in the right direction. I have moved away from storyboards because I find creating views in code is more verbose and clear on peer reviews and it was also easier for me to manage constraints and just to manage views in general.

Access data / functions from several view controllers

I am trying to build an app with several screens. Every screen should have different buttons which all call one function.
My problem is that I do not understand how to call one function from different view controllers with input parameters.
Also I want to have another variable defined accessible and changeable from every view controller.
This is what I kind of want my code to be:
import UIKit
var address = "address"
public func makeRequest(Command: String){
let url = URL(address + Command)
print(url)
}
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let command = "command"
makeRequest(Command: command)
}
}
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("address")
address = "address2"
}
}
If all classes are view controllers put the method in an extension of UIViewController and the address constant in a struct
struct Constant {
static let address = "address"
}
extension UIViewController {
public func makeRequest(command: String) -> URL? {
guard let url = URL(string: Constant.address + command) else { return nil }
print(url)
return url
}
}
extension UIViewController{
func getName(name: String)-> String{
print("hai \(name)")
return name
}
}
you can write a extension for Viewcontroller and try to call the method like this. if you write the extension for viewcontroller you can directly call them with its reference
self.getName(name:"Raghav")

Using prepareForSegue to pass data to ViewController later in app

I was wondering, when passing data using prepareForSegue, can you pass data to a View Controller later in the app? For example on the first ViewController I have the user enter their name. It's not until the very end, so a few views later, do I need to display their name. Is there a way to pass their name without having to go to the end view right away?
Use a Coordinator.
It's really easy to decouple your ViewControllers:
instead of using segues give every ViewController a delegate
create a coordinator object (this object knows your screen flow, not your screens)
the coordinator creates the ViewControllers (it can use UIStoryboard instantiateViewController(withIdentifier:) so ViewController A does not have to know that ViewController B exists
instead of calling performSegue you just call your delegate and pass in the data
Benefits
Simple to use
Easy to reorder screens in a flow
Highly decoupled (easier testing)
Very nice for A/B testing
Scales a lot (you can have multiple coordinators, one for each flow)
Sample
Let's say you have 3 VCs, the first one asks for your name, the second for your age and the third displays the data. It would make no sense that AgeViewController knew that NameViewController existed, later on you may want to change their order or even merge them.
Name View Controller
protocol NameViewControllerDelegate: class {
func didInput(name: String)
}
class NameViewController: UIViewController {
weak var delegate: NameViewControllerDelegate?
#IBOutlet var nameTextField: UITextField!
//Unimportant stuff ommited
#IBAction func submitName(sender: Any) {
guard let name = nameTextField.text else {
// Do something, it's up to you what
return
}
delegate?.didInput(name: name)
}
}
Age View Controller
protocol AgeViewControllerDelegate: class {
func didInput(age: Int)
}
class AgeViewController: UIViewController {
weak var delegate: AgeViewControllerDelegate?
#IBOutlet var ageTextField: UITextField!
//Unimportant stuff ommited
#IBAction func submitAge(sender: Any) {
guard let ageString = ageTextField.text,
let age = Int(ageString) else {
// Do something, it's up to you what
return
}
delegate?.didInput(age: age)
}
}
Displayer View Controller
class DisplayerViewController: UIViewController {
var age: Int?
var name: String?
}
Coordinator
class Coordinator {
var age: Int?
var name: String?
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
fileprivate lazy var storyboard: UIStoryboard = {
return UIStoryboard(name: "MyStoryboard", bundle: nil)
}()
//This works if you name your screns after their classes
fileprivate func viewController<T: UIViewController>(withType type: T.Type) -> T {
return storyboard.instantiateViewController(withIdentifier: String(describing: type(of: type))) as! T
}
func start() -> UIViewController {
let viewController = self.viewController(withType: NameViewController.self)
viewController.delegate = self
navigationController.viewControllers = [viewController]
return viewController
}
}
Coordinator + Name View Controller Delegate
extension Coordinator: NameViewControllerDelegate {
func didInput(name: String){
self.name = name
let viewController = self.viewController(withType: AgeViewController.self)
viewController.delegate = self
navigationController.pushViewController(viewController, animated: true)
}
}
Coordinator + Age View Controller Delegate
extension Coordinator: AgeViewControllerDelegate {
func didInput(age: Int) {
self.age = age
let viewController = self.viewController(withType: DisplayerViewController.self)
viewController.age = age
viewController.name = name
navigationController.pushViewController(viewController, animated: true)
}
}
Not really. You can pass view by view the item but it's not a proper way of doing things.
I suggest you to have a Static Manager or this kind of stuff to store the information globally in your app to retrieve it later
All the solution are pretty good. Possible you can try the below model also
1. DataModel class
1.1 Should be singleton class
1.2 Declare value
Step 1 : ViewCOntroller-one
1 Create the Sharedinstance of singleton class
1.1 Assign the value
Step 3 :ViewController-two
1 Create the Sharedinstance of singleton class
1.1 Get the value

Function Overriding in iOS

I have created BaseClassviewController and all my controllers are derived from this controller. I am doing the following steps:
Set custom delegate in BaseClassViewController.
Implement all function of protocol in BaseClassViewController.
Then I am pushing HomeController derived from BaseClassViewController.
Again I am pushing DetailController also derived from BaseClassViewController.
Now when delegate function is called I should get control in DetailController but I am getting control in HomeController.
So my question is why its not calling top controller at navigation i.e DetailController and is it possible to call delegate functions in both controllers?
P.S I am overriding delegate functions in all child controllers.
EDIT: After reading answers and comments I think I have not been clear that much so adding following code snippet.
In Helper Class:
#objc protocol SampleDelegate: class
{
#objc optional func shouldCallDelegateMethod()
}
class SampleHelper: NSObject
{
var sampleDelegate:SampleDelegate!
static var sharedInstance = SampleHelper()
//It is triggered
func triggerDelegateMethod()
{
sampleDelegate!.shouldCallDelegateMethod()
}
func apiCall()
{
let urlString = URL(string: "https://google.com")
if let url = urlString {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
} else {
if let usableData = data {
self. triggerDelegateMethod()
}
}
}
task.resume()
}
}
}
In BaseClass
class BaseClassViewController: UIViewController,SampleDelegate{
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(true)
SampleHelper.sharedInstance.delegate = self;
}
func shouldCallDelegateMethod()
{
//Override
}
}
In HomeController i.e 1st controller to be pushed
class HomeViewController: BaseClassViewController{
override func shouldCallDelegateMethod()
{
//
}
}
In DetailController i.e 2nd controller is pushed after HomeController from HomeController.
class DetailViewController: BaseClassViewController{
override func viewDidLoad()
{
super.viewDidLoad()
SampleHelper.sharedInstance.apiCall()
}
override func shouldCallDelegateMethod()
{
//
}
}
Now my question is when delegate is triggered from helper class it calls shouldCallDelegateMethod in HomeViewController but not in DetailViewController. But DetailViewController is at top of navigation array.
Also is there any possibility I can trigger same function in both controller at a time with delegate only?
In BaseClassviewController you should have a delegate variable/property.
In HomeController and DetailController you need to set that delegate variable/property to self if you want that class to be listening to the delegate callbacks.
The basic problem is that you are using delegate with a singleton.
Setting the delegate in viewWillAppear is not a good solution either. In short, when view controllers are being shown and hidden, the delegate on your singleton will changed all the time.
Don't use delegates with singletons. Use a completion callback. Otherwise you will keep running into problems.
func apiCall(onCompletion: (() -> Void)?) {
let urlString = URL(string: "https://google.com")
if let url = urlString {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
} else if let usableData = data {
onCompletion?()
}
}
task.resume()
}
}
called as
SampleHelper.apiCall {
// do something
}
Edit 2
After you posted your code, i realize that you have used the singleton class for delegation.
Delegates allows an object to send a message to another object.
Answer for your query is "No". You can not trigger same function in both controller at a time with delegate.
If you really want to listen an event in both class at a time, i would suggest you to use NSNotificationCenter instead of delegate.
Thats not the correct way to achieve this. I think proper way to set delegate only in respective UIViewController rather than implementing that protocol on BaseViewController and then overriding in child classes. So your implementation should be like.
In HomeViewController
class HomeViewController: BaseClassViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
SampleHelper.sharedInstance.delegate = self;
}
func shouldCallDelegateMethod() {
// Provide implementation
}
}
In DetailViewController
class DetailViewController: BaseClassViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewDidLoad()
SampleHelper.sharedInstance.delegate = self;
}
func shouldCallDelegateMethod() {
// Provide implementation
}
}
Using this imeplementation you will be having only one-to-one communication design pattern, ensuring right UIViewController to be called.

instantiated class is nil when called inside viewdidload()

I am trying to learn the VIPER architecture model and one thing I can't figure out is when I do the following:
Instantiate promotionPresenter class
Instantiate promotionsViewController
assign promotionsViewController.presenter = (instantiated promotionPresenter class from step 1)
try to access the instantiated presenter class from inside viewdidload() function within promotionviewController class.
presenter is nil.
Why is presenter nil? I already instantiated it.
import UIKit
/*
* The Router responsible for navigation between modules.
*/
class PromotionsWireframe : PromotionsWireframeInput {
// Reference to the ViewController (weak to avoid retain cycle).
var promotionsViewController: PromotionsViewController!
var promotionsPresenter: PromotionsPresenter!
var rootWireframe: RootWireframe!
init() {
let promotionsInteractor = PromotionsInteractor()
// Presenter is instantiated
promotionsPresenter = PromotionsPresenter()
promotionsPresenter.interactor = promotionsInteractor
promotionsPresenter.wireframe = self
promotionsInteractor.output = promotionsPresenter
}
func presentPromotionsIntefaceFromWindow(_ window: UIWindow) {
//view controller is instantiated
promotionsViewController = promotionsViewControllerFromStoryboard()
//presenter of view controller is assigned to instantiaed class
promotionsViewController.presenter = promotionsPresenter
promotionsPresenter.view = promotionsViewController
}
private func promotionsViewControllerFromStoryboard() -> PromotionsViewController {
let storyboard = UIStoryboard(name: "PromotionsStoryboard", bundle: nil )
let viewController = storyboard.instantiateViewController(withIdentifier: "promotionsViewController") as! PromotionsViewController
return viewController
}
}
import UIKit
class PromotionsViewController : UIViewController, PromotionsViewInterface {
// Reference to the Presenter's interface.
var presenter: PromotionsModuleInterface!
var promotions: [Promotion]!
/*
* Once the view is loaded, it sends a command
* to the presenter asking it to update the UI.
*/
override func viewDidLoad() {
super.viewDidLoad()
// getting error because presenter is unwrapped as nil
self.presenter.updateView()
}
func showPromotionsData(_ promotions: [Promotion]) {
// need to implement
}
}
import Foundation
class PromotionsPresenter : PromotionsModuleInterface, PromotionsInteractorOutput {
// Reference to the View (weak to avoid retain cycle).
var view: PromotionsViewInterface!
// Reference to the Interactor's interface.
var interactor: PromotionsInteractorInput!
var wireframe: PromotionsWireframe!
func updateView() {
self.interactor.fetchLocalPromotions()
}
func PromotionsFetched(_promotions: [Promotion]) {
// need to implement
}
}
I suggest you take this boilerplate (https://github.com/CheesecakeLabs/Boilerplate_iOS_VIPER) and read this post (https://www.ckl.io/blog/best-practices-viper-architecture/) in order to learn not only how to correctly initialize your VIPER modules, but also how to automate VIPER files creation and initiazlization

Resources