Using A Delegate to Pass a var - ios

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)

Related

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

Multiple Navigation Bar colours for a complex storyboard

I have 48 view controllers in my storyboard / project. I wish to have 2 different types of Navigation Bar designs.
Style 1 is a navigation bar and a status bar that is white and grey (colours not important to the question)
Style 2 is a navigation bar without a status bar. This is black.
I have set style 1 in the app delegate and set style 2 within one of the views. To a point this works and style 2 overwrites style 1. However, when I navigate away from the view, style 2 continues to override.
I could set each view controller explicitly but with 48 views and 4 or 5 lines of code to define the style is seems inefficient. If I later choose to change the style I then have 48 instances of code to edit.
My major experience is with PHP and if I had this situation I would make an include statement to reference style1 or style2 as needed.
I have tried to create a function in Swift to call the desired design but I cannot get it work as it doesn't reference the UIViewController in the same way you would when adding it directly. I have only been coding for Swift / Xcode for 3 months so it could my lack of knowledge.
I would like to find a solution that on each view I can call something like below (PseudoCode)
override func viewWillAppear(animated: Bool) {
navBarStyle1() or navBarStyle2()
}
I have not included my code to adjust the colours as I feel this is not needed for the answer.
What would be the best way to manage this efficiently? Is there an equivalent to a PHP include? If the solution is a function, could you provide an example? Or may be the solution is something different?
As requested, here is one of my view controllers:
import UIKit
class DeleteMatchViewController: UIViewController {
var idPass = ""
// OUTLETS
#IBOutlet weak var errorMessage: UILabel!
#IBOutlet weak var information: UILabel!
// ACTIONS
#IBAction func deleteMatch(sender: UIBarButtonItem) {
// connect and delete from server
// delete from core data
// load from core data
let urlParameters = "removed for privacy"
let status = sendSeverV2("\(apiUrl)/removedforprivacy.php", parameters: urlParameters)
if status == "OK"
{
// DELETESINGLE firstname|David
myDatabase("Matches", theCommand: "DELETESINGLE", theQuery: "userid|\(idPass)")
myDatabase("Messages", theCommand: "DELETEMULTIPLE", theQuery: "people|\(userId)-\(idPass)")
myDatabase("Messagesunsent", theCommand: "DELETEMULTIPLE", theQuery: "people|\(userId)-\(idPass)")
// core data
loadMatchesFromCoreData()
// segue to matches table
performSegueWithIdentifier("jumpMatches", sender: nil)
}
if status == "Error"
{
errorMessage.text = "Connection error"
}
if status == "Security"
{
errorMessage.text = "Authentication error"
authError = "yes"
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(animated: Bool) {
self.navigationController?.navigationBarHidden = false
self.tabBarController?.tabBar.hidden = true
errorMessage.text = ""
information.text = "If you delete this match all messages will be erased and only a future mutual match will all you to contact them again."
}
}
A little OOP can go a long way here.
First, let's make some base view controllers:
class GrayStatusBarViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// set up the appearance
}
}
class BlackStatusBarViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// set up the appearance
}
}
Now, we can make one of these for every appearance we want, and we can move any shared behavior into these base classes we want. It probably makes sense for your app to have a BaseViewController which the two classes I just posted inherit from (rather than inheriting from UIViewController directly).
Then, all you have to do is make your pile of view controllers inherit from the correct controller based on theme.
class DeleteMatchViewController: GrayStatusBarViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated) // <-- this is super important
// the rest of the view will appear code for this VC
}
}
Alternatively, you can encapsulate the appearance logic in methods in a class like I previously mentioned, the BaseViewContoller, something like this:
class BaseViewController: UIViewController {
func setUpGrayStatusBar() {
// write that logic
}
func setUpBlackStatusBar() {
// write that logic
}
// etc, as many of these as you want
}
And now, you inherit from this class and call the appropriate method:
class DeleteMatchViewController: BaseViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
setUpGrayStatusBar()
}
}
When it comes to the run time performance of your application, neither of these solutions is any different from simply copy & pasting the same code into all of your individual view controllers. Just because you didn't paste it in more than one place doesn't mean it doesn't run more than once.

How to use delegates properly in Swift?

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

Passing data with delegates/protocols

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.

(Swift) ViewController's UI-elements can not be edited by implemented delegate method

I want to update the label in the DetailViewController everytime I selected a tableRow in the MasterViewController. To achieve this, I designed a delegate, which I have in the MasterVC
protocol TestTableViewControllerDelegate {
func selectedRow(selectedCar : Car)
}
class TestTableViewController: UITableViewController {
...
var delegate : TestTableViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = DetailViewController()
The delegate works just fine, (it is implemented correctly in the DetailVC), it can pass values from TestTableVC to DetailVC and also correctly do println(), which prints a new Car.model String to the console every time I select a row in the TTVC.
The DetailVC looks like this (shortened):
class DetailViewController: UIViewController, TestTableViewControllerDelegate {
#IBOutlet var textLabel: UILabel!
var theCar : Car? {
didSet(newCar) {
refreshUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
refreshUI()
}
func selectedRow(selectedCar : Car) {
theCar = selectedCar
refreshUI()
}
func refreshUI() {
textLabel?.text = theCar!.model
}
}
I can achieve any kind of action with my delegate, expect for refreshing the UI. I have tried numerous ways, this is my latest attempt. Before that, I tried setting the textLabel's text property directly within the delegate method, didn't work. This problem only occurs when working with the UI-elements. I know it has something to do with the view not being loaded yet, but why does my refreshUI() function not work at all?
I am still a beginner, so any tip or help would be much appreciated!
A workaround I've used is to cerate a properly in the delegate and pass the value to it instead of the UI element. When the view loads I update the label's text properly with the value of the delegates property. I would think there's a better way to do this (I'm new to programming) but this is the best soultion I've come up with so far. Will update with sample code soon.

Resources