delegate passing nil in swift - swift

I am trying to get to grips with delegates but the delegate I have set up seems to be nil and I am not sure why. I have a HomeViewController where the game is started from, then a UITableViewController where the player selects a row from a table. The row index is then used to pull data to be used in the game. The UITableViewController segues back to the HomeViewController where the game then starts. I thought I had put the correct protocol and delegate code in place but the delegate seems to be nil.
Any help much appreciated!
import UIKit
import Foundation
class HomeViewController: UIViewController, WordListsTableViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// sets up the game here
}
func wordListSelected(selectedWordList: Int) {
// passes the index path of the table to the AppWordList class to create the wordList for the game.
controller.wordList = AppWordList(wordListNumber: selectedWordList)
}
and in the TableViewController
import UIKit
protocol WordListsTableViewControllerDelegate {
func wordListSelected(selectedWordList: Int)
}
class WordListsTableViewController: UITableViewController {
var delegate: WordListsTableViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
reloadData()
tableView.reloadData()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedWordList = Int()
if (indexPath.section) == 2 {
selectedWordList = (indexPath.row) // Console shows the row is being selected ok.
delegate?.wordListSelected(selectedWordList) // IS NIL ???
// exit segue back to the HomeVC
performSegueWithIdentifier("startGameSegue", sender: nil)
}
}

You need to inform the HomeViewController class that has to be the delegate receiver for the class WordListsTableViewController, like this:
import UIKit
import Foundation
class HomeViewController: UIViewController, WordListsTableViewControllerDelegate
{
var wordListTableViewController = WordListTableViewController() // You forgot this
override func viewDidLoad() {
super.viewDidLoad()
wordListTableViewController.delegate = self // And this
// sets up the game here
}
func wordListSelected(selectedWordList: Int) {
// passes the index path of the table to the AppWordList class to create the wordList for the game.
controller.wordList = AppWordList(wordListNumber: selectedWordList)
}

You're missing a very important point about the Delegate Pattern, you need to keep a reference to the class that delegate its function and set it delegate in the class that handle the function. So let suppose you present the WordListsTableViewController by a segue from the HomeViewController like in the following example:
class HomeViewController: UIViewController, WordListsTableViewControllerDelegate {
// the reference to the class that delegate
var wordListTableViewController: WordListsTableViewController!
override func viewDidLoad() {
super.viewDidLoad()
// sets up the game here
}
func wordListSelected(selectedWordList: Int) {
// passes the index path of the table to the AppWordList class to create the wordList for the game.
controller.wordList = AppWordList(wordListNumber: selectedWordList)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// get the reference to the WordListsTableViewController
self.wordListTableViewController = segue.destinationViewController as! WordListsTableViewController
// set as it delegate
self.wordListTableViewController.delegate = self
}
}
And then you should be notified from the WordListsTableViewController, in the above example I assume the use of segues, but if you present the WordListsTableViewController you can use the same principle of keep a reference to the delegate class, like I show in the above example.
I do not apply any concept in code regarding the retain-cycles that can be happen in the use of delegates, but you can read more in my answer of this question about how to implement delegates correctly:
"fatal error: unexpectedly found nil while unwrapping an Optional value" while calling a protocol method
I strongly recommend you read more about the Delegate Pattern in this post:
How Delegation Works – A Swift Developer’s Guide
I hope this help you.

In your HomeViewController you have to set delegate to self:
override func viewDidLoad() {
super.viewDidLoad()
// get reference to your word lists table view controller
// if controller is your table view that should work
// controller.delegate = self
let wordLists = WordListsTableViewController....
// set up delegate
wordLists.delegate = self.
}

Related

UIViewControllers sharing 'generic' IBAction

I have an app with 6 UIViewControllers.
ANY viewcontroller features a function like this one:
#IBAction func onHelp(_ sender: UIBarButtonItem) {
DispatchQueue.main.async(execute: { () -> Void in
let helpVC = self.storyboard?.instantiateViewController(withIdentifier: "Help") as! HelpViewController
helpVC.starter = "MapHelp"
helpVC.helpSubtitle = "Map"
self.present(helpVC, animated: true, completion: nil)
})
}
Any IBAction in any viewcontroller presents the same HelpViewController but passing different parameters (starter and helpSubtitle).
Since I don't like to repeat code, first of all I thought this function should be converted to something more generic.
But: is there any way to create a generic IBAction, working for every viewcontroller?
Create a BaseViewController and add the generic method there.
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func genericMethod(starter: String, helpSubtitle: String){
let helpVC = self.storyboard?.instantiateViewController(withIdentifier: "Help") as! HelpViewController
helpVC.starter = starter
helpVC.helpSubtitle = helpSubtitle
self.present(helpVC, animated: true, completion: nil)
}
#IBAction func onHelp(_ sender: UIButton?) {
//You can use this method as generic IBaction if you want. It can be connected to buttons of all child View Controllers. But doing so will limit your param sending ability. On the plus side though, you won't have to define an IBAction everywhere and you can simply connect your child VC's button to Parent Class' IBAction.
}
}
Now inherit your ViewControllers from this class like:
import UIKit
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func btnTapped(_ sender: Any) {
genericMethod(starter: "View Controller", helpSubtitle: "I was triggered from VC1")
}
}
and
import UIKit
class SecondViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btnTapped(_ sender: Any) {
genericMethod(starter: "View Controller 2", helpSubtitle: "I was triggered from VC2")
}
}
That's it. Both your ViewControllers can call the parent method. If you still want to use the generic IBAction, you can do that too but I'd not recommend that course given that you want to pass params that can vary. If you wanted to do it though, it would look like this:
Bear in mind, the ViewController here has been inherited from the base ViewController which is why it can access the IBActions defined in the parent class. All you have to do is drag and connect.

Protocol doesn't get read in swift xcode project

i have a UITableview that has unique cells,
each cell has it's own class and they have actions that i want to connect to my main UITableviewcontroller
I attach a protocol and open it in the tableviewcontroller
but it doesn't get read
how could I initialise it or what am I doing wrong ?
here is my cell class :
import UIKit
class AddFaxHeadlineTableViewCell: UITableViewCell {
var delegate: AddFaxHeadlineProtocol?
#IBOutlet weak var addButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func onAddFaxNumberPressed(_ sender: Any) {
delegate?.faxButtonPressed()
}
}
protocol AddFaxHeadlineProtocol{
func faxButtonPressed()
}
and in my tableviewcontroller I extend the protocol:
class SummaryMainTableViewController: UITableViewController, AddFaxHeadlineProtocol, AddEmailHeadlineProtocol {
but the function itself never gets read:
func faxButtonPressed() {
var indexToInsert = 0
for forIndex in 0..<sectionsData.count {
// render the tick mark each minute (60 times)
if (sectionsData[forIndex] == "addFaxHeadline") {
indexToInsert = forIndex + 1
}
}
sectionsData.insert("addNewFax", at: indexToInsert)
mainTableView.reloadData()
}
You need to call:
cell.delegate = self
In your cellForRowAtIndex method
This is the common mistake done in protocols and delegates to forget to call delegate.
Here are few examples you can check all have missing is calling delegate:-
Swift delegate beetween two VC without segue
Delegate seems to not be working, according to the console
How to present another view controller after dismiss from navigation controller in swift?
Another way to go inside the vc without protocols
let cell = ///
cell.addButton.tag = indexPath.row
cell.addButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender:UIButton) {
print(sender.tag)
}
Check that you are doing this:
cell.delegate = self (It's required)
Then improve your line of code like below. Because you will not set delegate then by calling this delegate method directly will get crashed.
#IBAction func onAddFaxNumberPressed(_ sender: Any) {
if let delegateObject = delegate {
delegateObject.faxButtonPressed()
}
}
Second,
In this line, delegateObject.faxButtonPressed(), you will need to send some parameter to identify that will cell is clicked. So you can pass here button tag or you can pass cell also.

#IBDesignable with protocol

I have a UIview xib within a view controller, UIview class have two buttons with protocol function, but the protocol function never called when I press button, storyboard image like below
protocol method like below
import UIKit
#objc protocol TopViewDelegate: NSObjectProtocol {
#objc optional func pressRefreshButton()
#objc optional func pressMenuButton()
}
UIView class
#IBDesignable class OnJob_Top: UIView,TopViewDelegate {
weak var delegate : TopViewDelegate? = nil
#IBAction func refreshButtonTouchUpInside(_ sender: UIButton) {
delegate?.pressRefreshButton!()
}
#IBAction func menuButtonTouchUpInside(_ sender: UIButton) {
delegate?.pressMenuButton!()
print("come come")
}
view controller class
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let topView = OnJob_Top()
topView.delegate = self
}
}
extension HomeViewController:TopViewDelegate {
func pressMenuButton() {
print("come") // never come here
}
func pressRefreshButton() {
print("come") // never come here
}
}
Consider this code:
let topView = OnJob_Top()
topView.delegate = self
In the first line, you create a completely new OnJob_Top view.
In the second line, you make it the delegate.
In the third line... but there is no third line. The view vanishes in a silent puff of smoke. It is useless.
Meanwhile, the view in the storyboard never gets a delegate. So its delegate methods are never called.

Stop animation of first view controller when second is started swift programming

How to stop animation of first view controller when second is started in swift programming. I have created a function which stops animation in first view controller. I want it to be called in second view controller.
In first View Controller
func stopAni(){
self.resultView.stopAnimating()
ButtonAudioPlayer.stop()
ButtonAudioPlayer1.stop()
ButtonAudioPlayer2.stop()
ButtonAudioPlayer3.stop()
ButtonAudioPlayer4.stop()
ButtonAudioPlayer5.stop()
ButtonAudioPlayer6.stop()
Not sure how to call this function in second view controller.
You can create a delegate something like:
protocol StopAnimationDelegate{
func stopAnimations()
}
Then, on your first view controller you're going to adopt this protocol:
class FirstViewController : UIViewController, StopAnimationDelegate{
//..... here code
func stopAnimations(){
//Stop your animations or call your method stopAni here.
}
//.... here more code
#IBAction func openSecondViewController(sender:UIButton){
self.performSegueWithIdentifier("segue_first_second",sender:nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segue_first_second"{
let secondViewController = segue.destinationViewController as! SecondViewController
secondViewController.delegate = self
}
}
}
On your second view controller, you can make something like:
class SecondViewController: UIViewController{
var delegate:StopAnimationDelegate?
#override func viewDidLoad(){
delegate?.stopAnimations()
}
}
Note: That's a way of how you can accomplish that, but all depends on what you need to do, for example you can simply stop the animations when you perform the segue (but again, that depends on what you want to do).
Another option, is using NSNotificationCenter to post a notification to Stop the animation, something like:
In First View Controller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "stopAnim", name: "kStopAnimations", object: nil)
}
//...Your stopAnim method
//... More Code
}
class SecondViewController : UIViewController{
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().postNotificationName("kStopAnimations", object: nil)
}
}

Pass data by protocol while using Container to view another ViewController in Swift

I started working on this question app.
I began by tableView of the categories:
For data exchange, I decided to use a protocol:
protocol Category {
func data(object:AnyObject)
}
In the first ViewController has the following code:
class ViewController: UIViewController {
var items:[String] = ["Desktop","Tablet","Phone"]
let CategoriesData:Category? = nil
override func viewDidLoad() {
super.viewDidLoad()
CategoriesData?.data(items)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In the second ViewController (tableView in Container) have the following code:
class CategoriesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, Category {
#IBOutlet var table: UITableView!
var items:[String] = []
func data(object: AnyObject) {
self.items = (object as? [String])!
print(object)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewCell = self.table.dequeueReusableCellWithIdentifier("SegueStage") as! TableViewCell
cell.nameLabel.text = items[indexPath.row]
return cell
}
}
For me, apparently it's all right. But nothing appeared on the simulator.
My question is: If the Container use to present another viewController as passing data by protocols should be done?
EDITED
I answered why the TO:s solution didn't work as intended, but I just realised that I haven't given a viable answer to how to use protocols as delegates for the ViewController -> ViewController communication. I'll leave the half-answer below until someone can possibly answer the full question better.
In the way protocol is used in your code, you define your protocol Category to be a delegate for instances of the type ViewController. When an instance of type ViewController is initialised in---and hence owned locally in the scope of---some other class, the instance can delegate callbacks to the owning class.
The problem is that your CategoriesViewController does not contain any instances of type ViewController. We note that both these classes are, in themselves, subclasses of UIViewController, but none of them contain instances of one another. Hence, your CategoriesViewController does indeed conform to protocol Category, by implemented the protocol method data(...), but there's no ViewController instance in CategoriesViewController that can do callbacks to this function. Hence, your code compile file, but as it is, method data(...) in CategoriesViewController will never be called.
I might be mistaken, but as far as I know, protocol delegates are used to do callbacks between models (for model in MVC design) and controllers (see example below), whereas in your case, you want a delegate directly between two controllers.
As an example of model-delegate-controller design, consider some custom user control, with some key property value (e.g. position in rating control), implemented as a subclass of UIView:
// CustomUserControl.swift
protocol CustomUserControlDelegate {
func didChangeValue(value: Int)
}
class CustomUserControl: UIView {
// Properties
// ...
private var value = 0 {
didSet {
// Possibly do something ...
// Call delegate.
delegate?.didChangeValue(value)
}
}
var delegate: CustomUserControlDelegate?
// ... some methods/actions associated with your user control.
}
Now lets assume an instance of your CustomUserControl is used in a a view controller, say ViewController. Your delegate functions for the custom control can be used in the view controller to observe key changes in the model for CustomUserControl, much like you'd use the inherent delegate functions of the UITextFieldDelegate for UITextField instances (e.g. textFieldDidEndEditing(...)).
For this simple example, use a delegate callback from the didSet of the class property value to tell a view controller that one of it's outlets have had associated model update:
// ViewController.swift
Import UIKit
// ...
class ViewController: UIViewController, CustomUserControlDelegate {
// Properties
// ...
#IBOutlet weak var customUserControl: CustomUserControl!
// Instance of CustomUserControl in this UIViewController
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Custom user control, handle through delegate callbacks.
customUserControl.delegate = self
}
// ...
// CustomUserControlDelegate
func didChangeValue(value: Int) {
// do some stuff with 'value' ...
}
}