Protocol methods in a class extension are not called under specific conditions - swift

I encountered a weird behavior. The best way I can put it is … Not overridden protocol methods in a class extension are not called while the superclass already conforms to the protocol (via extension). However this happens only while it's build with the release build configuration.
class A: UIViewController {}
extension A: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrollViewDidScroll in superclass")
}
}
class B: A {
// A tableView (and its data source and delegate) is set here…
}
extension B: UITableViewDelegate {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
super.scrollViewDidScroll(scrollView)
print("scrollViewDidScroll in subclass")
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
print("scrollViewDidEndDragging")
}
}
The output:
scrollViewDidScroll in superclass
scrollViewDidScroll in subclass
scrollViewDidEndDragging
however if I build it with the release build configuration, the output is
scrollViewDidScroll in superclass
scrollViewDidScroll in subclass
I can solve the problem if I don't use the extension for protocol conformance approach in the class B and just use the regular way instead (put the methods that implement a protocol into the class).
The question is … how come?

I too encountered the same behaviour.
Xcode version - 12.4
Swift version - 4.2
I also encounter that this behaviour is shown only when applicationDidBecomeActive method is called in the application.(Mostly when you have navigation to some other Screen like authentication etc)
Fix: You need to assign the ViewController as delegate again.
func applicationDidBecomeActive(_ application: UIApplication) {
fixesForCallBackInExt()
}
func fixesForCallBackInExt() {
guard let navController = window?.rootViewController as? UINavigationController else { return }
if let tabVC = navController.topViewController as? UITabBarController, let topVC = tabVC.selectedViewController, topVC.isKind(of: UIViewController.self) {
// Set Your Delegate = topVC
} else if let topVC = navController.topViewController, topVC.isKind(of: UIViewController.self) {
// Set Your Delegate = topVC
}
}

Related

scrollViewDidScroll not called in a CollectionView with custom layout

I want to load more data when the user reaches the end of my CollectionView. Unfortunately, scrollViewDidScroll is never called.
For my CollectionView, I use a custom layout and I think the problem could be at this:
if let layout = exploreCollectionView.collectionViewLayout as? CustomLayout {
layout.delegate = self
}
My class:
class MainViewController: UIViewController, UIScrollViewDelegate { .....
I want to check if the function scrollViewDidScroll works:
// Check if a user wants to load more data at the bottom
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrolled")
}
How do I implement the scrollViewDidScroll in my CustomLayout?
scrollViewDidScroll is a UIScrollViewDelegate method. So you need to make self (MyViewController) a delegate of the collectionview:
exploreCollectionView.delegate = self
And then for instance in an extension:
extension MyViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrolled")
}
}

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.

delegate passing nil in 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.
}

How do you access a UIViewController function from within a UICollectionCell?

I have a function within a UICollectionViewCell that requires access to the
hosting UIViewController. Currently 'makeContribution()' can't be accessed:
What is the proper way of accessing the host UIViewController that has the desired function?
Thanks to the insightful responses, here's the solution via delegation:
...
...
...
{makeContribution}
This is a mildly controversial question - the answer depends a little on your philosophy about MVC. Three (of possibly many) options would be:
Move the #IBAction to the view controller. Problem solved, but it might not be possible in your case.
Create a delegate. This would allow the coupling to be loose - you could create a ContributionDelegate protocol with the makeContribution() method, make your view controller conform to it, and then assign the view controller as a weak var contributionDelegate: ContributionDelegate? in your cell class. Then you just call:
contributionDelegate?.makeContribution()
Run up the NSResponder chain. This answer has a Swift extension on UIView that finds the first parent view controller, so you could use that:
extension UIView {
func parentViewController() -> UIViewController? {
var parentResponder: UIResponder? = self
while true {
if parentResponder == nil {
return nil
}
parentResponder = parentResponder!.nextResponder()
if parentResponder is UIViewController {
return (parentResponder as UIViewController)
}
}
}
}
// in your code:
if let parentVC = parentViewController() as? MyViewController {
parentVC.makeContribution()
}
Well, CollectionView or TableView?
Anyway, Set your ViewController as a delegate of the cell. like this:
#objc protocol ContributeCellDelegate {
func contributeCellWantsMakeContribution(cell:ContributeCell)
}
class ContributeCell: UICollectionViewCell {
// ...
weak var delegate:ContributeCellDelegate?
#IBAction func contributeAction(sender:UISegmentedControl) {
let isContribute = (sender.selectedSegmentIndex == 1)
if isContribute {
self.delegate?.contributeCellWantsMakeContribution(self)
}
else {
}
}
}
class ViewController: UIViewController, UICollectionViewDataSource, ContributeCellDelegate {
// ...
func collectionView(_ collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
cell = ...
if cell = cell as? ContributeTableViewCell {
cell.delegate = self
}
return cell
}
// MARK: ContributeCellDelegate methods
func contributeCellWantsMakeContribution(cell:ContributeCell) {
// do your work.
}
}