Protocol Delegate between XIB and View Controller - ios

So I have XIB View and View Controller. I want when a button in my XIB clicked (didTapTryAgain Button), the called a function from my view controller. Then I tried to create a protocol and delegate for the class. But it still won't called my function. Here's my XIB view class:
import UIKit
protocol ErrorMessageDelegate {
func refresh(_sender: AnyObject)
}
class ErrorMessage: UIView {
#IBOutlet weak var imageViewError: UIImageView!
#IBOutlet weak var labelError: UILabel!
#IBOutlet weak var buttonTryAgain: UIButton!
static weak var shared: ErrorMessage?
var delegate: ErrorMessageDelegate?
static var message: String?
override func awakeFromNib() {
ErrorMessage.shared = self
labelError.text = ErrorMessage.message
}
#IBAction func didTapTryAgain(_ sender: UIButton) {
delegate?.refresh(_sender: buttonTryAgain)
}
}
And here's my View Controller class:
import Foundation
class BaseViewController: UIViewController, ErrorMessageDelegate {
func refresh(_sender: AnyObject) {
print("I hope my function work here")
}
var uiView = UIView();
override func viewDidLoad() {
super.viewDidLoad()
ErrorMessage.shared?.delegate = self
}
func getErrorMessage(message:String) {
super.viewDidLoad()
Dialog.dismiss()
ErrorMessage.message = message
guard let viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as? ErrorMessage else { return}
self.view.addSubview(viewErrorMessage)
}
}
I'm following this answer for my code, and it still not working. Is anyone know how to do it? Thank you!

Your problem is that you set the delegate for a shared instance here
ErrorMessage.shared?.delegate = self / here shared?. is nil
but here
guard let viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as? ErrorMessage else { return}
self.view.addSubview(viewErrorMessage)
you create a separate instance and add it
You need
var viewErrorMessage:ErrorMessage! // add to the vc
viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as! ErrorMessage
viewErrorMessage.delegate = self
self.view.addSubview(viewErrorMessage)
Also completely git rid of
static weak var shared: ErrorMessage?

Simply use this code then your delegate method will be call.
func getErrorMessage(message:String) {
ErrorMessage.message = message
guard let viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as? ErrorMessage else { return}
viewErrorMessage.delegate = self
self.view.addSubview(viewErrorMessage)
}
and call method where ever you want to open the popup
getErrorMessage(message: "Test Message")

Related

Value pass from delegate method is nil Swift

I try to send data from one view controller (ItenaryVC) through delegate to another view controller (ItenaryFloatingPanelVC). The VC that initiates ItenaryVC is DiscoverVC. When ItenaryVC load up it will make API calls to get some data, and I want to pass the data received from API calls to the delegate that I created which will pass the data to ItenaryFloatingPanelVC. But the data is not received and it is nil. I also actually using FloatingPanel Library to add the panel in ItenaryVC.
Flow
DiscoverVC -> ItenaryVC + ItenaryFloatingPanelVC (as Flaoting Panel)
DiscoverVC.swift
protocol DiscoverVCDelegate : AnyObject {
func didSendSingleLocationData(_ discoverVC : DiscoverVC , location : Location)
}
class DiscoverVC : UIViewController {
//MARK:- IBOutlets
#IBOutlet weak var collectionView: UICollectionView!
weak var delegate : DiscoverVCDelegate?
private var locationResult = [Location]()
private var selectedAtRow : Int!
//MARK:- Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
renderView()
getLocations()
}
private func renderView() {
collectionView.register(UINib(nibName: R.nib.discoverCell.name, bundle: nil), forCellWithReuseIdentifier: R.reuseIdentifier.discoverCell.identifier)
collectionView.delegate = self
collectionView.dataSource = self
}
private func getLocations(location : String = "locations") {
NetworkManager.shared.getLocations(for: location) { [weak self] location in
switch location {
case .success(let locations):
self?.updateDiscoverUI(with: locations)
case .failure(let error):
print(error.rawValue)
}
}
}
private func updateDiscoverUI(with locations : [Location]) {
DispatchQueue.main.async { [weak self] in
self?.locationResult.append(contentsOf: locations)
self?.collectionView.reloadData()
}
}
}
//MARK:- Delegate
extension DiscoverVC : UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedAtRow = indexPath.row
self.performSegue(withIdentifier: R.segue.discoverVC.goToDetails, sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destinationVC = segue.destination as? ItenaryVC else { return}
destinationVC.locationDetails = locationResult[selectedAtRow]
destinationVC.imageURL = locationResult[selectedAtRow].image
destinationVC.getItenaries(at: locationResult[selectedAtRow].itenaryName)
delegate?.didSendSingleLocationData(self, location: locationResult[selectedAtRow])
// Remove tab bar when push to other vc
destinationVC.hidesBottomBarWhenPushed = true
}
}
ItenaryVC.swift
import UIKit
import FloatingPanel
protocol ItenaryVCDelegate : AnyObject {
func didSendItenaryData(_ itenaryVC : ItenaryVC, with itenary : [[Days]])
}
class ItenaryVC: UIViewController {
#IBOutlet weak var backgroundImage: UIImageView!
var imageURL : URL!
var locationDetails: Location! {
didSet {
getItenaries(at: locationDetails.itenaryName)
}
}
weak var delegate : ItenaryVCDelegate?
var itenaries = [[Days]]()
//MARK:- Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setupCard()
setupView()
}
func getItenaries(at itenaries : String = "Melaka"){
NetworkManager.shared.getItenaries(for: itenaries) { [weak self] itenary in
switch itenary {
case .success(let itenary):
// print(itenary)
DispatchQueue.main.async {
self?.itenaries.append(contentsOf: itenary)
self?.delegate?.didSendItenaryData(self! , with: itenary)
}
print(itenaries.count)
case .failure(let error):
print(error.rawValue)
}
}
}
}
//MARK:- Private methods
extension ItenaryVC {
private func setupView() {
backgroundImage.downloaded(from: imageURL)
backgroundImage.contentMode = .scaleAspectFill
}
private func setupCard() {
guard let itenaryFlotingPanelVC = storyboard?.instantiateViewController(identifier: "itenaryPanel") as? ItenaryFloatingPanelVC else { return}
let fpc = FloatingPanelController()
fpc.set(contentViewController: itenaryFlotingPanelVC)
fpc.addPanel(toParent: self)
}
}
ItenaryFloatingPanelVC.swift
import UIKit
class ItenaryFloatingPanelVC: UIViewController{
//MARK:- Outlets
#IBOutlet weak var sloganLabel: UILabel!
#IBOutlet weak var locationLabel: UILabel!
#IBOutlet weak var locDesc: UITextView!
#IBOutlet weak var itenaryTableView: UITableView!
#IBOutlet weak var locDescHC: NSLayoutConstraint!
var itenaries = [[Days]]()
let itenaryVC = ItenaryVC()
let discoverVC = DiscoverVC()
override func viewDidLoad() {
discoverVC.delegate = self
itenaryVC.delegate = self
itenaryTableView.dataSource = self
itenaryTableView.delegate = self
locDescHC.constant = locDesc.contentSize.height
itenaryTableView.register(UINib(nibName: R.nib.itenaryCell.name, bundle: nil), forCellReuseIdentifier: R.nib.itenaryCell.identifier)
}
}
//MARK:- DiscoverVCDelegate
extension ItenaryFloatingPanelVC : DiscoverVCDelegate {
func didSendSingleLocationData(_ discoverVC: DiscoverVC, location: Location) {
print(location)
locationLabel.text = location.locationName
}
}
//MARK:- ItenaryVC Delegate
extension ItenaryFloatingPanelVC : ItenaryVCDelegate {
func didSendItenaryData(_ itenaryVC: ItenaryVC, with itenary: [[Days]]) {
DispatchQueue.main.async {
print(itenary)
self.itenaries.append(contentsOf: itenary)
self.itenaryTableView.reloadData()
print("itenary \(self.itenaries.count)")
}
}
}
Image of ItenaryVC and ItenaryFloatingPanel
Your code doesn't work because you have set the delegate of an newly created ItenaryVC instance:
let itenaryVC = ItenaryVC()
// ...
itenaryVC.delegate = self
itenaryVC here is not the VC that presented self, the ItenaryFloatingPanelVC. It's a brand new one that you created.
Instead of doing that, you can set the delegate in ItenaryVC when you are about to present ItenaryFloatingPanelVC:
private func setupCard() {
guard let itenaryFlotingPanelVC = storyboard?.instantiateViewController(identifier: "itenaryPanel") as? ItenaryFloatingPanelVC else { return }
let fpc = FloatingPanelController()
// here!
self.delegate = itenaryFlotingPanelVC
fpc.set(contentViewController: itenaryFlotingPanelVC)
fpc.addPanel(toParent: self)
}
self is now the ItenaryVC, and itenaryFlotingPanelVC is the VC that self is presenting.
I don't see you call getItenaries() in ItenaryVC. Please check again.

Swift: using delegates to send data to another view controller

How do I use delegates to send data to another view controller and then display it in the collection view? My problem is with moving the array across using delegates.
Below is an example of what I am working on.
When I use usersList in the ThirdViewController, I get an error that says 'Unexpectedly found nil while implicitly unwrapping an Optional value'
protocol ExampleDelegate {
func delegateFunction(usersArray: Array<User>)
}
class ViewController: UIViewController {
private var model: Users = ViewController.createAccount()
var exampleDelegate: ExampleDelegate?
#IBAction func ShowUsers(_ sender: UIButton) {
let ShowUsersVC = storyboard?.instantiateViewController(identifier: "ThirdViewController") as! ThirdViewController
var userList: Array<User> = model.listOfUsers
exampleDelegate?.delegateFunction(usersArray: userList )
present(ShowUsersVC, animated: true)
}
}
class ThirdViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var usersList: Array<User>!
override func viewDidLoad() {
super.viewDidLoad()
let GetUsersVC = storyboard?.instantiateViewController(identifier: "ViewController") as! ViewController
GetUsersVC.showMomentsDelegate = self
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension ThirdViewController: ExampleDelegate {
func delegateFunction(usersArray: Array<User>)
usersList = usersArray
}
You don't need delegates in this case. You are sending data forwards, so just do it like this:
class ViewController: UIViewController {
private var model: Users = ViewController.createAccount()
var exampleDelegate: ExampleDelegate?
#IBAction func showUsers(_ sender: UIButton) {
let showUsersVC = storyboard?.instantiateViewController(identifier: "ThirdViewController") as! ThirdViewController
var userList: Array<User> = model.listOfUsers
showUsersVC.usersList = userList /// pass the data!
present(showUsersVC, animated: true)
}
}
Also in Swift you should lowercase objects like userList, as well as functions like showUsers.

Delegate won't work using three view controllers

Is use delegate to pass data between three view controllers. I set delegate for both view controllers I want to pass data from to self inside of MapViewController - MapViewController shall receive data from both VCs.
I also added weak var delegate: MapViewController? to both VCs - but somehow it's only working for one of them.
MapViewController:
var newStartItem: MKMapItem?
override func viewDidLoad() {
super.viewDidLoad()
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
searchVC.delegate = self
newVC = (storyboard?.instantiateViewController(withIdentifier: "newLocation") as! NewLocationTableViewController)
newVC.delegate = self
...
}
func addStartAnnotationToMap() {
guard let item = newStartItem else { return }
guard let coordinates = item.placemark.location?.coordinate else { return }
addPin(title: item.name!, subtitle: "", coordinates: coordinates)
}
}
NewLocationViewController: (this one is not working)
class NewLocationTableViewController: UIViewController {
weak var delegate: MapViewController?
...
func passData() {
guard let mapItem = starts?.first else { return }
delegate?.newStartItem = mapItem
delegate?.addStartAnnotationToMap()
}
Xcode won't return any errors either!
You should declare a protocol:
protocol MapViewControllerDelegate: class {
func addStartAnnotationToMap()
}
Then change your delegate variable to:
weak var delegate: MapViewControllerDelegate?
And make MapViewController conform to MapViewControllerDelegate protocol like this:
class MapViewController: UIViewController, MapViewControllerDelegate {
Your delegate must be a reference from the type of your protocol
In this case: weak var delegate: MapViewControllerDelegate?
For this
newVC = (storyboard?.instantiateViewController(withIdentifier: "newLocation") as! NewLocationTableViewController)
newVC.delegate = self
and
weak var delegate: MapViewController?
to work you need to verify that at some point inside the map vc you present that location vc like
self.present(newVC,animated:true,completion:nil)
or push it , plus print it to verify it's not nil and guard doesn't return if starts is empty
func passData() {
print("delegate \(delegate) item \(starts?.first)")
guard let mapItem = starts?.first else { return }
delegate?.newStartItem = mapItem
delegate?.addStartAnnotationToMap()
}

Calling function inside function in another function

I'm new in iOS Programming, and I had this problem. Let's say I have these two function:
class BaseViewController: UIViewController, ErrorMessageDelegate {
var uiView = UIView();
var viewErrorMessage:ErrorMessage!
func refresh(_sender: AnyObject) {
print("testing")
}
func getErrorMessage(message:String) {
super.viewDidLoad()
Dialog.dismiss()
ErrorMessage.message = message
viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as! ErrorMessage
viewErrorMessage.delegate = self
self.view.addSubview(viewErrorMessage)
func removeSubView() {
viewErrorMessage.removeFromSuperview()
}
}
}
I want to call function removeSubView inside function refresh. I had to do that because I need to override refresh function to my subclass. And I need to put the function removeSubView in getErrorMessage because I should to. Does anyone know how to do that?
Please refer below code. It will help you to resolve the issue.
Code:
class BaseViewController: UIViewController, ErrorMessageDelegate {
var uiView = UIView();
var viewErrorMessage:ErrorMessage!
func refresh(_sender: AnyObject) {
removeSubView()
}
func getErrorMessage(message:String) {
super.viewDidLoad()
Dialog.dismiss()
ErrorMessage.message = message
viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as! ErrorMessage
viewErrorMessage.delegate = self
self.view.addSubview(viewErrorMessage)
}
func removeSubView() {
viewErrorMessage.removeFromSuperview()
}
}
Yes, that is possible
func a() {
c()
}
func b() {
c()
}
func c() {
print("hello world")
}
no it is not possible - but I wonder why you would do that?

How to encapsulate an UIViewController (like UIAlertController) in Swift?

I have a ViewController in my Storyboard which works like an alert (with a title, a message, and two buttons).
I would like to encapsulate it to be able to use it anywhere in my code, like this :
let alert = CustomAlertViewController(title: "Test", message: "message de test.", view: self.view, delegate: self)
self.present(alert, animated: false, completion: nil)
My problem is that the IBOutlets are not initialised...
My CustomAlertViewController :
public protocol CustomAlertProtocol {
func alertAccepted()
}
class CustomAlertViewController: UIViewController {
var delegate :CustomAlertProtocol? = nil
var parentView :UIView?
var blurScreenshot :SABlurImageView?
var alertTitle :String? = nil
var alertMessage :String? = nil
#IBOutlet weak var oAlertView: UIView!
#IBOutlet weak var oAlertTitle: UILabel!
#IBOutlet weak var oAlertMessage: UILabel!
//MARK: - Main
public convenience init(title: String?, message: String?, view: UIView, delegate: CustomAlertProtocol) {
self.init()
self.alertTitle = title
self.alertMessage = message
self.delegate = delegate
self.parentView = view
}
override func viewDidLoad() {
oAlertTitle.text = self.alertTitle
oAlertMessage.text = self.alertMessage
}
#IBAction func onAcceptButtonPressed(_ sender: AnyObject) {
delegate?.alertAccepted()
}
}
Set the Custom Class property of your View Controller to CustomAlertViewController
and Storyboard ID to whatever you want - e.g. CustomAlertViewControllerIdentifier in the Identity Inspector of the InterfaceBuilder.
And then instantiate it like following:
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
guard let vc = storyboard.instantiateViewControllerWithIdentifier("CustomAlertViewControllerIdentifier") as? CustomAlertViewController else {
return
}
edit:
You can then put that code in a class function like:
extension CustomAlertViewController {
class func instantiateFromStoryboard(title: String?, message: String?, view: UIView, delegate: CustomAlertProtocol) -> CustomAlertViewController {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let vc = storyboard.instantiateViewControllerWithIdentifier("CustomAlertViewControllerIdentifier") as! CustomAlertViewController
vc.title = title
vc.message = message
vc.view = view
vc.delegate = delegate
return vc
}
}
and then use like:
let myCustomAlertViewController = CustomAlertViewController.instantiateFromStoryboard(title: "bla", ...)

Resources