I cannot find an example of the Swift Delegate pattern without the segue included. I want the boss class(boss view) to send orders to the worker class(worker view) and print what the boss says to print. The Delegate is coming up 'nil' when I press either of the buttons (doThisButton/doThatButton) on the boss page.
Edit: My Views are set up as follows: the 'TheBoss' VC has three buttons, 'doThisButton', 'doThatButton', and 'goToTheWorker' button. the 'TheWorker' VC has a text box. The 'doThisButton'/'doThatButton' send information to the text box in 'TheWorker' VC, but do not make 'TheWorker' VC appear. the 'goToTheWorker' button is a show segue that was set up in storyboard to open the 'TheWorker' VC.
This is the 'Boss' class
import UIKit
protocol TheBossesOrdersDelegate {
func doThis(numberOne: Int, stringOne: String)
func doThat(numberTwo: Int, stringTwo: String)
}
class TheBoss: UIViewController {
// Declair Delegate
var delegate: TheBossesOrdersDelegate!
#IBAction func doThisButton(_ sender: UIButton) {
delegate.doThis(numberOne: 75, stringOne: "Do This")
}
#IBAction func doThatButton(_ sender: UIButton) {
delegate.doThat(numberTwo: 125, stringTwo: "Do That")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
This is the 'Worker' class
import UIKit
class TheWorker: UIViewController, TheBossesOrdersDelegate {
let theBoss = TheBoss()
func doThis(numberOne: Int, stringOne: String) {
print("the boss send this number to print: \(numberOne) and this string: \(stringOne)")
theWorkersTextBox.text = "Number: \(numberOne) String:\(stringOne)"
}
func doThat(numberTwo: Int, stringTwo: String) {
print("the boss send this number to print: \(numberTwo) and this string: \(stringTwo)")
theWorkersTextBox.text = "Number: \(numberTwo) String:\(stringTwo)"
}
#IBOutlet weak var theWorkersTextBox: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
theBoss.delegate = self
}
}
It looks like your code has the worker create the boss. Since you do that, you need to tell the boss who it's delegate is:
import UIKit
class TheWorker: UIViewController, TheBossesOrdersDelegate {
let theBoss = {
let result = TheBoss()
result.delegate = self //This is the line you are missing
return result
}()
//The rest of your TheWorker class's code...
}
EDIT:
Based on your comment, you have a confused mess. Post the Boss class code for the button that displays a worker view controller. And your title says "w/o segue", but then in your comment you say "the boss is presented first and contains a button that connects to the worker via a show segue." What? you said without a segue.
Related
I have two view controllers (ViewController and ActionViewController) and one manager (Brain), the second view controller is shown when a user tapped on a button by a show segue created in storyboard and to get back to the first I use a self.dismiss in the second view controller.
The user enter a number on ActionViewController that need to be retrieved in ViewController. So I created Brain to use the delegate pattern.
The problem is that the delegate function inside ViewController is never run, I read other SO answers but nothing work. I used print statement to know where the code is not running anymore and the only block not running is the didUpdatePrice inside ViewController
Here is the code
ViewController
class ViewController: UIViewController, BrainDelegate {
var brain = Brain()
#IBOutlet var scoreLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
brain.delegate = self
scoreLabel.layer.cornerRadius = 25
scoreLabel.layer.masksToBounds = true
}
func didUpdateScore(newScore: String) {
print("the new label is \(newScore)")
scoreLabel.text = newScore
}
}
ActionViewController
class ActionViewController: UIViewController {
var brain = Brain()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addButtonTapped(_ sender: Any) {
brain.newAction(actualScore: 0, newActionValue: 5, isPositive: true)
self.dismiss(animated: true)
}
}
Brain
protocol BrainDelegate {
func didUpdateScore(newScore: String)
}
struct Brain {
var delegate: BrainDelegate?
func newAction(actualScore: Int, newActionValue: Int, isPositive: Bool) {
let newScore: Int
if isPositive {
newScore = actualScore + newActionValue
} else {
newScore = actualScore - newActionValue
}
print("the new score is \(newScore)")
delegate?.didUpdateScore(newScore: String(newScore))
}
}
You dont need an additional Brain class/struct at all, You can achieve it with simple protocol and default extension of protocol.
Step 1: Select your show segue and provide an identifier to that in storyboard as shown below
Step 2: In your ViewController add prepare(for segue method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "testIdentifier" {
guard let destinationViewController = segue.destination as? ActionViewController else { return }
destinationViewController.delegate = self
}
}
Step 3: In your ActionViewController declare a weak property named delegate
class ActionViewController: UIViewController {
weak var delegate: BrainDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addButtonTapped(_ sender: Any) {
delegate?.newAction(actualScore: 0, newActionValue: 5, isPositive: true)
self.dismiss(animated: true)
}
}
Step 4: Add class clause to your BrainDelegate (Class bound protocol) so that you can hold a weak reference to delegate
protocol BrainDelegate: class {
func didUpdateScore(newScore: String)
func newAction(actualScore: Int, newActionValue: Int, isPositive: Bool)
}
Step 5:
Add a default extension to BrainDelegate and provide default implementation of newAction(actualScore:
extension BrainDelegate {
func newAction(actualScore: Int, newActionValue: Int, isPositive: Bool) {
let newScore: Int
if isPositive {
newScore = actualScore + newActionValue
} else {
newScore = actualScore - newActionValue
}
print("the new score is \(newScore)")
self.didUpdateScore(newScore: String(newScore))
}
}
Step 6: In your ActionViewController simply trigger delegate methods as
#IBAction func addButtonTapped(_ sender: Any) {
delegate?.newAction(actualScore: 0, newActionValue: 5, isPositive: true)
self.dismiss(animated: true)
}
This should do the job
First, on Brain you should use class, not struct. That is because when you use struct, passing the variable to another will make a copy, it will not use the same reference. And class will only copy the reference.
That means that your Brain struct will lose the delegate assigned on .delegate = self
second, you need to use the same instance on the second viewController and the first. like this:
on the first viewController
var brain = Brain()
// this one is the one that you will put your "brain.delegate = self"
on the second viewController, you will need to inject this variable from the first viewController into the second. That is to keep the same instance on both. And this will make the delegate callable.
to do this with storyboard you will do on the first ViewController:
// this function should be called when the next viewController should open.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination) {
case let vc as MyViewController:
vc.brain = self.brain
default:
break
}
}
super.prepare(for: segue, sender: sender)
}
inside the second viewController, use:
var brain: Brain?
This is my protocol
protocol PassDataDelegate {
func passData(data: String)
}
My first controller
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
var delegate: PassDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
delegate = SecondViewController()
}
#IBAction func sendDataButtonTapped(_ sender: Any) {
delegate?.passData(data: textField.text!)
performSegue(withIdentifier: "Go", sender: nil)
}
}
And second, final one
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func passData(data: String) {
print("This came from first: \(data). Will change UI.")
myLabel.text = data
}
}
App is crashing on label changing part. It says nil while unwrapping optional. What is wrong here?
SecondViewController() is not the controller designed in the storyboard. It's a brand new instance without connected outlets (which is the reason of the crash). You need the real reference to the SecondViewController instance.
Assuming the SecondViewController instance is the destination view controller of the segue you don't need protocol / delegate, pass the data through the segue
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBAction func sendDataButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "Go", sender: nil)
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Go" {
let secondController = segue.destination as! SecondViewController
controller.passedData = textField.text!
}
}
}
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
var passedData = ""
override func viewDidLoad() {
super.viewDidLoad()
print("This came from first: \(passedData). Will change UI.")
myLabel.text = passedData
}
}
There are several fundamental issues with your code.
I think there might also be some misapprehensions on your side regarding delegation and UIStoryboardSegue mechanism. You should probably read up on that here (Delegation) and here (Segues).
That being said, let me post a solution to your problem with inline comments explaining what's going on.
// Has to be marked as a class protocol (`: class`) so that
// `weak var delegate: PassDataDelegate?` can be weak.
protocol PassDataDelegate: class {
func passData(data: String)
}
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
// Important!
// Make this a `weak` var. In your case, you would fortunately not create a retain cycle
// but there is a big threat of creating those when using delegation patterns with non-weak delegates.
//
// In your case, if you don't make this `weak`, `SecondViewController` would never be deallocated unless you
// cleared this var manually (setting it to `nil`).
//
// Also note that, if you're using `PassDataDelegate` solely for forwarding some data to the next view controller,
// you can dismiss this var entirely. There is no need to have a reference to the second view controller hanging around.
// In fact, as mentioned above, it can be dangerous to do so.
// Additionally, you don't need to make the protocol `: class` then.
private weak var delegate: PassDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// It doesn't make any sense to create a `SecondViewController` here.
// The segue mechanism will create a new instance of `SecondViewController`.
// delegate = SecondViewController()
}
#IBAction func sendDataButtonTapped(_ sender: Any) {
// `delegate?` is `nil` here.
// delegate?.passData(data: textField.text!)
performSegue(withIdentifier: "Go", sender: nil)
}
// This is the proper 'hook' for you to forward data or do anything with a destination
// view controller presented using `UIStoryboardSegue`.
// This method will be called by the system following your call to `performSegue`.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// `UITextField.text` can be `nil`, so safeguard for that here.
// If the destination implements our protocol, we can forward data to it.
if let text = textField.text, let delegate = segue.destination as? PassDataDelegate {
// This is optional. You can hang on to the destination view controller here, but
// review the comments above to reason about whether this makes sense or not.
self.delegate = delegate
// We can safely forward the data (text) here.
delegate.passData(data: text)
}
}
}
SecondViewController can stay as is.
Update
Regarding Delegation
The delegation pattern usually describes a back pointer which talks back to an initiating instance. E.g. using UITableViewDataSource, a UITableView talks back to a thing implementing this protocol to get information about its data and so on.You are essentially doing the opposite here by forwarding data to SecondViewController. As mentioned in the comments, this code even breaks, because the implementation of passData in SecondViewController is using outlets not yet initialised.
Now you can do one of three things here:
1
Keep the pattern you are using right now (which is not delegation to be precise) and change SecondViewController to make things work
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
private var data: String?
override func viewDidLoad() {
super.viewDidLoad()
// It is safe to access `myLabel` in `viewDidLoad`. Outlets have been connected.
if let data = data {
myLabel.text = data
}
}
func passData(data: String) {
self.data = data
// Only access `myLabel` if the view is loaded.
if isViewLoaded {
print("This came from first: \(data). Will change UI.")
myLabel.text = data
}
}
}
This approach is very cumbersome actually, because you need to manoeuvre around the fact that passData may be called at any time. So you don't know if your outlets have been initialised yet, which leads to bloated and repetitive code. Bad.
2
Strip protocols entirely and use a more straightforward approach
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
// This is the proper 'hook' for you to forward data or do anything with a destination
// view controller presented using `UIStoryboardSegue`.
// This method will be called by the system following your call to `performSegue`.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// `UITextField.text` can be `nil`, so safeguard for that here.
// If the destination is a `SecondViewController`, we know that is has `public var data: String` and we can forward data to it.
if let text = textField.text, let destination = segue.destination as? SecondViewController {
// We can safely forward the data (text) here.
destination.data = text
}
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel!
// Deliberatly marking this a `public` to make clear that
// you're intented to set this from the 'outside'.
public var data: String? {
didSet {
if isViewLoaded {
myLabel.text = data
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// It is safe to access `myLabel` in `viewDidLoad`. Outlets have been connected.
if let data = data {
myLabel.text = data
}
}
}
Again, there are things we don't like about his approach:
Still repeating code and having to check for isViewLoaded
You specifically wanted to use protocols, we don't do that here
We could work around the repetitive code issue by providing the data in an init of SecondViewController. However, since you're using segues, the storyboard will instantiate the destination view controller for you and you cannot gain control over that. Now you could strip using segues, but this quickly moves far away from your original question and is a totally different code only approach. So this is no good either.
3
Use protocols but apply the delegation pattern correctly.
protocol DataProvider: class {
func provideData() -> String?
}
protocol DataHandler: class {
var providerDelegate: DataProvider? { get set }
}
class FirstViewController: UIViewController, DataProvider {
#IBOutlet weak var textField: UITextField!
func provideData() -> String? {
return textField.text
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// If the destination is a `DataHandler`, we can set yourselves as its provider.
if let destination = segue.destination as? DataHandler {
destination.providerDelegate = self
}
}
}
class SecondViewController: UIViewController, DataHandler {
#IBOutlet weak var myLabel: UILabel!
weak var providerDelegate: DataProvider?
override func viewDidLoad() {
super.viewDidLoad()
if let data = providerDelegate?.provideData() {
// Safe to access `myLabel`, because we are in `viewDidLoad`.
myLabel.text = data
}
}
}
This approach is the most generic. Both parties don't care what exactly the handler and provider are. Note that in a classical delegation pattern, you would probably not have the DataHandler protocol and check for a concrete type (here SecondViewController) in prepareForSegue. However, this approach is more flexible while still having the delegation weaved into it. This approach is also the most robust from the SecondViewController point of view. Instead of having to handle passData at any moment, it can decide itself when to ask its delegate (DataProvider) for the data. This way, SecondViewController can reason about when all of its outlets, etc. are initialised and it is safe to process the data.
I want to update the label in my DashboardViewController from my AccountViewController when the back button is pressed in AccountViewController.
I have tried passing back a variable from 2nd view to 1st view and updating the label in viewDidLoad and in viewWillAppear but it never updates the label when the 1st view is back on screen.
I tried creating a function in 1st view to update the label with a string passed into the function and calling that function from 2nd view but it says that the label is nil so it couldn't be updated.
My latest attempt was to create a delegate but that didn't work either.
Here is my delegate attempt.
class DashboardViewController: UIViewController, AccountViewControllerDelegate {
#IBOutlet weak var welcome_lbl: UILabel!
func nameChanged(name: String){
var full_name = "Welcome \(name)"
welcome_lbl.text = "\(full_name)"
}
override func viewDidLoad() {
super.viewDidLoad()
AccountViewController.delegate = self
}
}
And then in my AccountViewController I have this
protocol AccountViewControllerDelegate{
func name_changed(name: String)
}
class AccountViewController: UIViewController, UITextFieldDelegate {
var info_changed = false
static var delegate: AccountViewControllerDelegate!
#IBAction func back_btn(sender: AnyObject) {
if(info_changed){
AccountViewController.delegate.name_changed(name_tf.text!)
}
self.dismissViewControllerAnimated(true, completion: nil)
}
Did I mess up the delegate process somehow ? Or is there an easier way to do this?
First. Your delegate should be a normal property of AccountViewController. There is no need to update your name when user press back. You can change DashboardViewController`s name when user change name in AccountViewController. When user go back to DashboardViewController. It`s already show the changed name.
protocol AccountViewControllerDelegate{
func name_changed(name: String)
}
class AccountViewController: UIViewController, UITextFieldDelegate {
var delegate: AccountViewControllerDelegate?
// when user change name through textfield or other control
func changeName(name: String) {
delegate?.name_changed(name)
}
}
Second. When DashboardViewController show AccountViewController. I think it should be push. Set DashboardViewController instance be AccountViewController instance`s delegate.
class DashboardViewController: UIViewController, AccountViewControllerDelegate {
#IBOutlet weak var welcome_lbl: UILabel!
func nameChanged(name: String){
var full_name = "Welcome \(name)"
welcome_lbl.text = "\(full_name)"
}
// present or push to AccountViewController
func showAccountViewController {
let accountViewController = AccountViewController()
accountViewController.delegate = self
// do push view controller
}
}
I have a UIViewController -let's call it parentViewController - and it contains a container. This container has embeddedViewController embedded in it.
Now, my parentViewController contains a method that prints something into the console:
func printSomeData(){
print("some data")
}
embeddedViewController has a button with action asigned to it:
#IBAction func printSomething(sender: AnyObject) {
is there a way that I could call method printSomeData from printSomething in Swift?
There are couple of ways such as by implementing delegate or by posting NSNotification. Here I show the sample delegation pattern. This is exact scenario as your own controller but from this you can get some concept and if you implement this hope this will accomplished your goal.
class ParentController:UIViewController,printing {
override func viewDidLoad() {
//
}
func presentEmbadedController(){
let embadedVC = EmbadedController()
embadedVC.delegate = self
}
func printSomeData() {
print("some date")
}
}
Here is the protocol something like this
protocol printing{
func printSomeData()
}
And then the EmbadedController like this
class EmbadedController:UIViewController {
var delegate: printing?
override func viewDidLoad() {
//
}
#IBAction func printSomething(sender: AnyObject) {
if let _ = delegate{
delegate?.printSomeData()
}
}
}
You can use NSNotificationCenter.defaultCenter().addObserver(...)
I have 2 controllers
and have got 1 global variable, the problem is if I go to controller 2 and click on button northAmericaClick, it will navigate back to control 1, but the value of global variable won't change!
this is my code
controller 1
class OurViewController: UIViewController {
#IBOutlet weak var menuButton: UIBarButtonItem!
#IBOutlet weak var selectedServer: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
selectedServer.setTitle(selected server, forState: UIControlState.Normal) // selected server this is global variable
}
controller 2
class selectServerController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func northAmericaClick(sender: AnyObject) {
selectedserver = "North America"
self.navigationController?.popViewControllerAnimated(true)
}
From
You need to use a delegate. Here is an example how do use a delegate in Swift.
On your first ViewController, set your delegate when you load the second VC:
For example, if you are using the Storyboard Editor:
var secondViewController = (segue.destinationViewController.visibleViewController as MySecondViewControllerClass)
secondViewController.delegate = self
Write a Protocol and define a func to write you values back
For example, create a file called "Protocol.swift" and write something like that:
protocol writeValueBackDelegate {
func writeValueBack(value: String)
}
Add the function to your FirstViewController
func writeValueBack(value: String) {
// this is my value from my second View Controller
}
And to your ViewControllerClass
class ViewController: UIViewController, writeValueBackDelegate
Go to the Second View Controller, and add the delegate here:
class SecondViewController: ViewController {
// delegate for FirstViewController
var delegate: writeValueBackDelegate?
On your Second View Controller, you can now use this to call the func in the first View Controller an pass data.
delegate?.writeValueBack("That is a value")
You also need to indicate that your first view controller implements the protocol: class ViewController: UIViewController, writeValueBackDelegate {
A part of doing it with delegate you also can create singleton class ViewControllersDataModel class and share the variable using it:
import Foundation
class ViewControllersDataModel {
static let sharedInstance = ViewControllersDataModel()
var selectedserver: String = ""
private init() {
}
}
And call it like this:
ViewControllersDataModel.sharedInstance.selectedserver = "Selected Option";
Ok, I can do this with this code, only check when viewWillDisapear and call the parent of this view controller in the navicationController:
override func viewWillDisappear(animated: Bool) {
if ((self.navigationController!.viewControllers.last?.isKindOfClass(ActivityMyViewController)) == true){
let backView:MyViewController = self.navigationController!.viewControllers.last as! MyDetailViewController
backView // do whatever you want
}
}
I hope this code can help you, good luck
thanks guys for helping ;)
it was very simple
i just use then when it comeback ^^"
override func viewWillAppear(animated: Bool) {
selectedServer.setTitle(selectedserv, forState: UIControlState.Normal)
}