Passing data with delegates/protocols - ios

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.

Related

Swift delegate protocol is nil

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)

Swift protocol method is not being called by the delegate

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()

How to properly set a property in a protocol from a conforming class?

I am writing a custom table header view that can expand/collapse, I wrote a protocol for it like below:
protocol ExpandableHeadViewDelegate{
var expandStateReference : [String : Bool] { get set }
var tblVw : UITableView { get set }
func didTapActionFromHeadVw(_ view: ExpandableHeadView, tag: Int)
}
Here is my expandable head view class, what Im trying to achieve is when this view is tapped I will be able to call the methods that I need from the UITableView where expandableView is embedded:
class ExpandableHeadView: UIView {
var delegate : ExpandableHeadViewDelegate?
//set up tap recognizer
.
.
.
.
private func viewTapped {
if let delegate = delegate {
delegate.tblVw.reloadData()
}
}
}
My viewcontroller that utilizes this class is as below:
class PlayersViewController: UIViewController,UITableViewDelegate,
UITableViewDataSource, ExpandableHeadViewDelegate {
var expandedCells = [String : Bool]() //
#IBOutlet var tableVw: UITableView! // compile time error
}
The issue I am facing is even though I declare my 'var tableVw' in my delegate class, Xcode gives me an error:
Protocol requires property 'tableVw' with type 'UITableView'; do you
want to add stub?
Setting var expandedCells .. however sets properly. If I set my tableVw as non-IBOutlet it can compile, but I want my tableVw to be an IBOutlet property.
EDIT:
Adding a stub variable for tableVw and assigning my tableView IBOutlet to it works. But Im still curious if this can be achieved without using a stub variable
internal var tableVw: UITableView?
#IBOutlet var tableView: UITableView!
.
.
func viewDidload {
self.tableVw = self.tableView
}
Quite simply - declare the property in your protocol as UITableView!. The ! matters - a lot.
! makes a variable an optional, but an explicitly unwrapped one. This basically means, that the unwrapping is done for you, under the hood. You promise the compiler, that although this variable can contain a nil, you will make sure it will be set with a proper value before you try to access it. In case of IBOutlets this is done for you by the SDK.

Passing data between views in ONE ViewController in Swift

All of the searches I've done focus on passing data between view controllers. That's not really what I'm trying to do. I have a ViewController that has multiple Views in it. The ViewController has a slider which works fine:
var throttleSetting = Float()
#IBAction func changeThrottleSetting(sender: UISlider)
{
throttleSetting = sender.value
}
Then, in one of the Views contained in that same ViewController, I have a basic line that (for now) sets an initial value which is used later in the DrawRect portion of the code:
var RPMPointerAngle: CGFloat {
var angle: CGFloat = 2.0
return angle
}
What I want to do is have the slider's value from the ViewController be passed to the View contained in the ViewController to allow the drawRect to be dynamic.
Thanks for your help!
EDIT: Sorry, when I created this answer I was having ViewControllers in mind. A much easier way would be to create a method in SomeView and talk directly to it.
Example:
class MainViewController: UIViewController {
var view1: SomeView!
var view2: SomeView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the views here
view1 = SomeView()
view2 = SomeView()
view.addSubview(view1)
view.addSubview(view2)
}
#IBAction func someAction(sender: UIButton) {
view1.changeString("blabla")
}
}
class SomeView: UIView {
var someString: String?
func changeString(someText: String) {
someString = someText
}
}
Delegate:
First you create a protocol:
protocol NameOfDelegate: class { // ": class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}
In your Views you have to add:
class SomeView: UIView, NameOfDelegate {
// your code
func someFunction() {
// change your slider settings
}
}
And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two classes so they can talk to each other.
class MainViewController: UIViewController {
weak var delegate: NameOfDelegate?
#IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}
I used a button here just to show how you could use the delegate. Just replace it with your slider to change the properties of your Views
EDIT: One thing I forgot to mention is, you'll somehow need to assign SomeView as the delegate. But like I said, I don't know how you're creating the views etc so I can't help you with that.
In the MVC model views can't communicate directly with each other.
There is always a view controller who manages the views. The views are just like the controllers minions.
All communication goes via a view controller.
If you want to react to some view changing, you can setup an IBAction. In the method you can then change your other view to which you might have an IBOutlet.
So in your example you might have an IBAction for the slider changing it's value (as in your original question) from which you could set some public properties on the view you would like to change. If necessary you could also call setNeedsDisplay() on the target view to make it redraw itself.

Using A Delegate to Pass a var

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)

Resources