I've researched this 100 times, and still can't find the answer to my problem. I have a very simple protocol, but it's always nil. I've tried to add periodDelegate = self but get the error Cannot assign value of type 'ScoreClockPopoverViewController' to type 'PeriodDelegate!' I have other Protocol, using the same setup and work fine.
What am I missing?
Thanks in advance!
import UIKit
protocol PeriodDelegate {
func changePeriodButtonImage(selectedPeriod: Period)
}
class ScoreClockPopoverViewController: UIViewController {
//delegate
var periodDelegate: PeriodDelegate!
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad / periodDelegate \(String(describing: periodDelegate!))")
}
}
Function I need to call is in a UICollectionViewCell`
class HeaderCollectionViewCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
...
}
extension HeaderCollectionViewCell: PeriodDelegate {
func changePeriodButtonImage(selectedPeriod: Period) {
print("blah")
switch selectedPeriod {
case .first:
print("first")
case .second:
print("second")
case .third:
print("third")
case .overtime:
print("overtime")
case .shootout:
print("shootout")
}
}
}
First of all, it is very uncommon to have cell as delegate for view controller. Usualy, it is other way round. But anyways, in your case you have to set periodDelegate as this cell, not self. Cause your cell implements delegate protocol not the VC. But better rethink what do you want to do because it smells like bad design.
Your statement "I have a very simple protocol, but it's always nil." does not make sense.
A protocol is a specialized language. It can't be nil or non-nil.
Your ScoreClockPopoverViewController has a delegate property periodDelegate that conforms to the PeriodDelegate protocol, and that delegate property is nil.
A delegate is a property like any other. It will be nil unless you assign a value to it. It's nil because you never assigned an object as your ScoreClockPopoverViewController's delegate.
Who creates instances of ScoreClockPopoverViewController, and what object is supposed to be the delegate of your ScoreClockPopoverViewController?
Post your code that creates a ScoreClockPopoverViewController. That's likely where you need to assign your delegate. That code might look something like this:
let myScoreClockPopoverViewController = storyboard.instantiateViewControllerWithIdentifier("ScoreClockPopoverViewController")
myScoreClockPopoverViewController.periodDelegate = self
present(myScoreClockPopoverViewController,
animated: true,
completion: nil)
(That code is meant as a guide and you will need to modify it to make it work in your app. You will not be able to paste it into your app without modification.)
If you're displaying your myScoreClockPopoverViewController as a popover, as the name suggests, you'll need to adjust the code above.
I had the same problem and I fixed it following Fangming's answer by just changing
var periodDelegate: PeriodDelegate!
to
weak var periodDelegate: PeriodDelegate? = nil
and changing the call to
periodDelegate?.blablabla()
Swift - Error passing data between protocols / delegates (found nil)
Related
I needed to delegate a click action for my UIView class to my UIViewController class since Swift does not support multiple class inheritance. So i wanted it such that once a button is clicked on my subview, a function in my BrowserViewController class is called.
I am using a protocol to achieve this, but on the function does not triggered when the button is tapped. Please help me out.
View Controller
class BrowseViewController: UIViewController {
var categoryItem: CategoryItem! = CategoryItem() //Category Item
private func setupExplore() {
//assign delegate of category item to controller
self.categoryItem.delegate = self
}
}
// delegate function to be called
extension BrowseViewController: ExploreDelegate {
func categoryClicked(category: ProductCategory) {
print("clicked")
let categoryView = ProductByCategoryView()
categoryView.category = category
categoryView.modalPresentationStyle = .overCurrentContext
self.navigationController?.pushViewController(categoryView, animated: true)
}
}
Explore.swift (subview)
import UIKit
protocol ExploreDelegate: UIViewController {
func categoryClicked(category: ProductCategory)
}
class Explore: UIView {
var delegate: ExploreDelegate?
class CategoryItem: UIView {
var delegate: ExploreDelegate?
var category: ProductCategory? {
didSet {
self.configure()
}
}
var tapped: ((_ category: ProductCategory?) -> Void)?
func configure() {
self.layer.cornerRadius = 6
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.categoryTapped)))
self.layoutIfNeeded()
}
#objc func categoryTapped(_ sender: UIGestureRecognizer) {
delegate?.categoryClicked(category: ProductCategory.everything)
self.tapped?(self.category)
}
}
}
Simply add a print statement inside categoryTapped.
You will then know if it is actually being tapped.
A million things could go wrong, for example, you may have forget to set the UIView to allow intertaction.
After checking that. Next add another print statement inside categoryTapped which shows you whether or not the delegate variable is null.
You'll quickly discover the problem using simple print statements.
print("I got to here!")
It's that easy.
And what about
if delegate == nil { print("it is nil!! oh no!" }
else { print("phew. it is NOT nil.") }
Debugging is really that easy at this level.
Next add a print statement inside setupExplore()
func setupExplore() {
print("setup explore was called")
....
See what happens.
I don't see any piece of code which sets the delegate.
First of all, define delegate as a property inside CategoryItem class, Then you must set the current instance of BrowseViewController to the delegate variable of CategoryItem. Now you can expect your method being called.
There are a few things that could cause the delegate method to not be triggered in this code:
Ensure that isUserInteractionEnabled = true on your CategoryItem. This is probably best done in either the configure() function in the CategoryItem or in the setupExplore() function of the BrowseViewController.
Make sure that the setupExplore() function on the BrowseViewController is being called, and that the category is being set on the CategoryItem to trigger the configure function. Otherwise, either the delegate or the gesture recognizer might not being set.
Side Note - weak vs strong delegate
On a side note, it is usually best practice to make your delegate properties weak var rather that having them be a strong reference, as this makes them prone to strong retain cycles.
Therefore, you might want to consider making the var delegate: ExploreDelegate? on your CategoryItem into weak var delegate: ExploreDelegate?. For more information on this problem, view this post.
For some reason the delegate method is not being called in the main View Controller. I was looking for another answers here, but non of them were helpful for me. Am I missing something here? (I shortened my original code for simplicity sake)
Main View Controller:
class VC: ParserDelegate {
var dataSource = Parser()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
dataSourse.loadAndParse()
}
func didReceiveDataUpdates(store: [WeatherModel]) {
print("Delegate method triggered.")
}
}
Protocol:
protocol ParserDelegate: class {
func didReceiveDataUpdates(store: [WeatherModel])
}
My delegate class:
class Parser {
weak var delegate: ParserDelegate?
func loadAndParse() {
var store = [WeatherModel]()
// Doing something
delegate?.didReceiveDataUpdates(store: store)
}
}
The delegate pattern is being applied correctly here, but one thing that might go wrong here: In your main View Controller you are instantiating a new Parser object and store it in „dataSource“:
var dataSource = Parser()
And when setting your main View Controller as its delegate
dataSource.delegate = self
your main View Controller gets notified as the delegate of this new instance you just created. That means: If an instance of your Parser() class jumps into (assure with debugger, if it actually does)
loadAndParse()
it might be another object and so this parser object has no actual delegate. If this is the issue here, you might consider and outlet in order to be able to talk to this specific Parser() class directly. Hope this helps.
You can also edit this line:
from:
dataSource.delegate = self
dataSourse.loadAndParse()
to:
dataSource.delegate = self
dataSource.loadAndParse()
I read a lot about the delegates but in practice I cannot use it properly.
Description: I have A: UIViewController, B: UIView, C: UIViewController. I want to run segue from A: UIViewController to the C: UIViewController from the inside of B: UIView.
I've tried:
protocol SegueDelegate {
func runSegue(identifier: String)
}
class B: UIView { ... }
where in my A: UIViewController:
override func viewDidLoad() {
B().delegate = self
}
func runSegue(identifier: String) {
self.performSegueWithIdentifier(identifier, sender: self)
}
and trying to call it via:
#IBAction func send(sender: AnyObject) {
let a: SegueDelegate? = nil
a!.runSegue("goToMainPage")
}
but I'm sure that I do not use it properly. Can anyone help me with it? I do not want just an answer. Please describe me it concept shortly
Delegates are just a Design Pattern that you can use in a number of ways. You can look at the Apple Frameworks to see how and where to use delegates as examples. A table view delegate is probably the best known delegate in UIKit.
Delegates serve as a callback mechanism for code to communicate with an instance of an unknown class without knowing more than that that instance will respond to the methods of the delegate protocol.
An alternative to a delegate is to use a closure (what we used to call a block in Objective-C). When to use one vs. the other is a matter of taste. There are a couple of rules of thumb, like for instance outlined here.
What you are doing is, IMO, the proper way to use delegates. You separate the view functionality from the View Controller's functionalities via a delegate, and so the contract for your view is clear: the user needs to respond to the delegate method.
Your code works and is correct. I made a quick implementation here: https://github.com/kristofvanlandschoot/DelegateUsage/tree/master
The main difference from your example, and maybe that's the place where you made a mistake is the third part of your code where you should write something like:
#IBAction func send(sender: AnyObject) {
delegate?.runSegue("segueAB")
}
There are multiple errors in your code, for example:
Here you are creating a new B, and setting A as a delegate of that new instance, no the one you actually want
override func viewDidLoad() {
«B()».delegate = self
}
And here you are creating force unwrapping a nil value
#IBAction func send(sender: AnyObject) {
let a: SegueDelegate? = «nil»
«a!».runSegue("goToMainPage")
}
If what you want to do is tell A to perform a segue to C, from inside B, all you need to do is to call performSegueWithIdentifier on A
For example:
class B: UIView {
weak var referenceToA: UIViewController? = nil // set this somewhere
#IBAction func send(sender: AnyObject) {
guard let a = referenceToA else {
fatalError("you didn't set the reference to a view controller of class A")
}
a.performSegueWithIdentifier("goToMainPage", sender: self)
}
}
I have a UICollectionView with a CollectionReusableView header. I want to pass a string from the collecitonview to the header, so that the header knows which data to load based on the string. I am trying to use delegates/protocols to do this, but keep getting "unexpectedly found nil while unwrapping an optional value." Here is my code:
protocol UserToQuery {
func thisUser(x: String)
}
class Profile: UICollectionViewController {
var ownProfile = true
var delegate:UserToQuery?
override func viewDidLoad() {
super.viewDidLoad()
if self.ownProfile == true {
let username = PFUser.currentUser()?.username
self.delegate!.thisUser(username!)
}
}
}
And here is the code for the Header view:
class ProfileHeader: UICollectionReusableView, UserToQuery {
var id1 = String()
var controller = Profile()
override func awakeFromNib() {
print(id1)
controller.delegate? = self
}
func thisUser(x: String) {
self.id1 = x
getProfileInfo()
}
func getUserData() {
// code here uses the id1 value to get data
}
}
My understanding of delegates/protocols is this: if you want to pass data (i.e., string), to another view, you make the view that receives the string conform to a protocol. This protocol includes a function that is used to pass the string, and when that function is called, it notifies the other view that the string is now available for use, and then you code what you want and use the string. Is that accurate?
In ProfileHeader, you have a variable, controller, which is creating a new instance of Profile, which is NOT the Profile view controller from your storyboard. This is why self.delegate! is nil in Profile.viewDidLoad().
I am going to make the assumption that ProfileHeader is a view in the Profile view controller. In your viewDidLoad, you should set the delegate to the ProfileHeader. See the example code below (I assume an outlet for the ProfileHeader view):
EDIT: ProfileHeader is not an outlet, as mentioned in the comments. Updated my answer to reflect that.
class Profile: UICollectionViewController {
var ownProfile = true
var delegate:UserToQuery?
override func viewDidLoad() {
super.viewDidLoad()
// Set the delegate!
self.delegate = ProfileHeader()
if self.ownProfile == true {
let username = PFUser.currentUser()?.username
// Delegate won't be nil now
self.delegate!.thisUser(username!)
}
}
}
}
As a general flow, the view controller should keep references to the view, not the other way around. So remove the controller property from your ProfileHeader view. The view shouldn't care what view controller is controlling it.
You have some misunderstandings about protocol/delegate, but it’s normal when you start iOS development.
First of all, why does the app crash :
The variable delegate is an optional UserQuery. It’s okay for a delegate to be optional, but it’s never set in your code, so when you call :
self.delegate!.thisUser(username!)
you try to force unwrapping a nil variable, which results in the crash.
Protocols
Now, let’s talk about the protocol/delegate relationship.
You have an UICollectionViewController subclass, which embeds an UICollectionView object. This UICollectionView will be contains a mix of header, footer and cell. Your ProfileHeader class will thus be displayed within your UICollectionView.
In order to populate an UICollectionView, you don’t need to create your own protocol : there are already two protocols for this :
UICollectionViewDataSource is the main protocol to conforms to, because it allows you to populate the collection view
UICollectionViewDelegate is used for further customization of your tableview, i.e. customizing the appearance and handling events.
Since your Profile class inherits from UICollectionViewControlleryou don’t have to named these protocols after your class name since UICollectionViewController already conforms to these protocols as written in Apple docs
You will have to override the delegate and protocol methods in order to display some data. My advice is, before using headers and footers, to use only UICollectionViewCell objects for start easily.
By overriding the method -collectionView:numberOfItemsInSection: and - collectionView:cellForItemAtIndexPath:, you will be able to populate the collection view.
I have been pulling my hair out trying to get this 'Delegate' thing to work in Swift for an App I am working on.
I have two files: CreateEvent.swift and ContactSelection.swift, where the former calls the latter.
CreateEvent's contents are:
class CreateEventViewController: UIViewController, ContactSelectionDelegate {
/...
var contactSelection: ContactSelectionViewController = ContactSelectionViewController()
override func viewDidLoad() {
super.viewDidLoad()
/...
contactSelection.delegate = self
}
func updateInvitedUsers() {
println("this finally worked")
}
func inviteButton(sender: AnyObject){
invitedLabel.text = "Invite"
invitedLabel.hidden = false
toContactSelection()
}
/...
func toContactSelection() {
let contactSelection = self.storyboard?.instantiateViewControllerWithIdentifier("ContactSelectionViewController") as ContactSelectionViewController
contactSelection.delegate = self
self.navigationController?.pushViewController(contactSelection, animated: true)
}
ContactSelection's contents are:
protocol ContactSelectionDelegate {
func updateInvitedUsers()
}
class ContactSelectionViewController: UITableViewController {
var delegate: ContactSelectionDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate?.updateInvitedUsers()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Stuff
self.delegate?.updateInvitedUsers()
}
}
What am I doing wrong? I am still new and don't fully understand this subject but after scouring the Internet I can't seem to find an answer. I use the Back button available in the Navigation Bar to return to my CreateEvent view.
var contactSelection: ContactSelectionViewController = ContactSelectionViewController()
This is instantiating a view controller directly, and the value never gets used. Since it looks like you're using storyboards, this isn't a good idea since none of the outlets will be connected and you'll get optional unwrapping crashes. You set the delegate of this view controller but that's irrelevant as it doesn't get used.
It also isn't a good idea because if you do multiple pushes you'll be reusing the same view controller and this will eventually lead to bugs as you'll have leftover state from previous uses which might give you unexpected outcomes. It's better to create a new view controller to push each time.
In your code you're making a brand new contactSelection from the storyboard and pushing it without setting the delegate.
You need to set the delegate on the instance that you're pushing onto the navigation stack.
It's also helpful to pass back a reference in the delegate method which can be used to extract values, rather than relying on a separate reference in the var like you're doing.
So, I'd do the following:
Remove the var contactSelection
Add the delegate before pushing the new contactSelection object
Change the delegate method signature to this:
protocol ContactSelectionDelegate {
func updateInvitedUsers(contactSelection:ContactSelectionViewController)
}
Change your delegate calls to this:
self.delegate?.updateInvitedUsers(self)