Swift delegate & protocols - ios

For protocols and delegates to work, do the view controllers have to be adjacent in the stack?
The function called by my delegate was working fine until I inserted a new vc in between the vc sending the data and the vc receiving them. (If the answer is, "Of course, you idiot!" I'd actually be relieved as I'm really stumped.)

Presumably, you have something like this:
and your code runs along these lines:
protocol MyCustomDelegate: class {
func myFunc(_ value: Int)
}
class VC1: UIViewController, MyCustomDelegate {
func myFunc(_ value: Int) {
print("value: \(value)")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? VC2 {
vc.delegate = self
}
}
}
class VC2: UIViewController {
weak var delegate: MyCustomDelegate?
func someAction(_ sender: Any?) -> Void {
delegate?.myFunc(1)
}
}
Now, you "insert a new VC":
So your code in VC1 is no longer segueing to VC2 ... and thus the delegate won't be set.
One approach, not necessarily the best, would be:
add a delegate var in VC1A, which is set by VC1
when navigating from VC1A to VC2, pass that delegate along
It could look something like this:
protocol MyCustomDelegate: class {
func myFunc(_ value: Int)
}
class VC1: UIViewController, MyCustomDelegate {
func myFunc(_ value: Int) {
print("value: \(value)")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? VC1A {
vc.delegate = self
}
}
}
class VC1A: UIViewController {
weak var delegate: MyCustomDelegate?
func someAction(_ sender: Any?) -> Void {
delegate?.myFunc(1)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? VC1A {
vc.delegate = self.delegate
}
}
}
class VC2: UIViewController {
weak var delegate: MyCustomDelegate?
func someAction(_ sender: Any?) -> Void {
delegate?.myFunc(1)
}
}
If your app flow is simple enough, this would probably work... but, it's not ideal and is prone to problems as your code gets more complex.
You may want to re-think how your code is structured, and how you're trying to manage your data.

Related

I'm trying to use a protocol and delegate pattern which will pass the data in my array back to the parent view controller

I am new to Xcode and am trying to save an array from secondViewController into the View controller. I have a a series of view controllers embedded in a navigation controller so when I click 'back' on the navigation bar I want to keep the data that was collected in an array 'collectionArray' and save to 'collectionArray2' . Here is the protocol delegate method I've tried:
This is in my ViewController where I want the array saved to :
import UIKit
class ViewController: UIViewController {
var collectionArray2: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
let controller = secondViewController()
controller.delegate = self
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let first = segue.destination as! secondViewController
first.collectionArray.append(contentsOf: collectionArray2)
}
}
extension ViewController: PopupDelegate {
func popupSelectedView(array: [String]) {
collectionArray2.append(contentsOf: array)
}
}
This is my secondViewController where I want to take 'collectionArray':
import UIKit
protocol PopupDelegate: class{
func popupSelectedView(array: [String])
}
class secondViewController: UIViewController {
var exerciseButton: String!
var collectionArray: [String] = []
weak var delegate: PopupDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func Exercisess(_ sender: UIButton){
exerciseButton = sender.currentTitle
collectionArray.append(exerciseButton!)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
delegate?.popupSelectedView(array: collectionArray)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let second = segue.destination as! FinalViewController
second.cellLabelArray.append(contentsOf: collectionArray)
}
}
Thank you
your problem is that your are referring to a difference instance of secondViewController. Don't set your delegate in viewDidLoad but when you prepare the segue:
override func viewDidLoad() {
super.viewDidLoad()
//let controller = secondViewController() REMOVE THIS
//controller.delegate = self REMOVE THIS
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let first = segue.destination as! secondViewController
first.collectionArray.append(contentsOf: collectionArray2)
first.delegate = self // set the delate here
}
}
by the way, you should name all your classes starting with capital letters, so it should be SecondViewController

Change Label text in a ContainerView on click of a button from other class (Uiviewcontroller)

I got small containerView with UILabel on main app screen. I got UIButton on main UIViewController. I want to change text of label that belongs to containerView class by clicking button in UIViewController.
I try to make it with delegation, but for some reason i got a mistake (Unwraping optional)...
I try to make it with Protocol, bud method "addText" in ContView dont works((((((
class ViewController: UIViewController {
var delegate: DelegateProtocol?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func button(_ sender: Any) {
delegate?.addText(String2: "123")
}}
///////////////////////////////////////////////////////////////////////
protocol DelegateProtocol {
func addText(String2: String)
}
//////////////////////////////////////////////////////////////////////
class ContViewController: UIViewController, DelegateProtocol {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "con" {
let vc = segue.destination as! ViewController
vc.delegate = self
}
}
func addText(String2: String) {
label.text = String2
}
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
first make an global variable in your view controller
private var viewController: YourVC?
then give your container view segue an identifier and do follwing
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourviewcontroller Segue identifiew" { //your container view segue identifier
viewController = segue.destination as? SelectedImageVC
}
}
now you can use you viewController to access label in your containerview controller like
if let controller = viewController {
controller.yourlabel.text = "text you want"
}

How can I use one var or let from a view controller to another view controller

I made a popup view controller but now I'm trying to pass the data from the popup to the view controller via prepare function. Let's take a look.
// this is from the pop up view controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let toBeMoved :ViewController = segue.destination as! ViewController
toBeMoved.enterVC()
amountEntered.text = "\(runningNumber1)"
}
// and I added a func in my main VC but I can't get the var in the VC
// totalCash is a label to be changed when the amount is entered from the
// pop up vc
func enterVC () {
totalCash.text = "\(amountEntered)"
}
I added enterVC to viewDidLoad.
Try this and see:
class TestVC: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let toBeMoved :ViewController = segue.destination as! ViewController
toBeMoved.amountEntered = runningNumber1
toBeMoved.enterVC()
}
}
class ViewController: UIViewController {
var amountEntered: Int = 0
func enterVC () {
totalCash.text = "\(amountEntered)"
}
}
or try this also.
class TestVC: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let toBeMoved :ViewController = segue.destination as! ViewController
toBeMoved.enterVC(amountEntered: runningNumber1)
}
}
class ViewController: UIViewController {
func enterVC (amountEntered: Int) {
totalCash.text = "\(amountEntered)"
}
}
//Turns out I needed to put a guard let in order to make this work also added IBAction in the original view controller like so:
class ViewController : UIViewController {
#IBAction func didUnwindSegue (sender: UIStoryboardSegue){
guard let realVC = sender.source as? ViewController else {
return
}
}
}
class TestVC : UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let realVC = segue.destination as? ViewController else {
return
}
realVC.totalCash.text = runningNumber1
}
}

iOS Swift, with one common method in protocol and two implementations in two ViewControllers, how to decide which ViewControllers method is invoked?

I have a protocol
protocol HandleEmbedController: class {
func printMsg()
}
and 2 container views and 2 corresponding ViewControllers
class EnemyBaseVC: UIViewController, HandleEmbedController {
override func viewDidLoad() {
super.viewDidLoad()
}
var value1 = ""
func printMsg(){
print("printing some embedded message")
}
}
and
class EnemyBase2VC: UIViewController, HandleEmbedController {
func printMsg() {
print("enemy base 2 message")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
and both use the protocol HandleEmbedController and implement printMsg function.
In the main ViewController I have
class HomeBaseVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
var handleEmbedController:HandleEmbedController?
#IBAction func onclick(_ sender: UIButton) {
handleEmbedController?.printMsg()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "embedseg"){
if let embed = segue.destination as? EnemyBaseVC {
self.handleEmbedController = embed
}
}
if (segue.identifier == "embedseg2"){
if let embed = segue.destination as? EnemyBase2VC {
self.handleEmbedController = embed
}
}
}
}
When button is clicked always EnemyBaseVC method is invoked and prints
printing some embedded message
Is there any way to decide which method is to be invoked?
UPDATE
If you have two container views, both segues will be triggered on load, and handleEmbedController will reference the one ViewController that is loaded last.
If you have some logic to decide which one should be referenced, then you can use it to decide which ViewController will be referenced, like so:
class HomeBaseVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
var handleEmbedController:HandleEmbedController?
// comes from your decision logic
var decisionMaker: Bool = false
#IBAction func onclick(_ sender: UIButton) {
handleEmbedController?.printMsg()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "embedseg"),
let embed = segue.destination as? EnemyBaseVC,
decisionMaker {
self.handleEmbedController = embed
}
else if (segue.identifier == "embedseg2"),
let embed = segue.destination as? EnemyBase2VC,
!decisionMaker {
self.handleEmbedController = embed
}
}
}
be aware that this will set the handleEmbedController on load, if you need more complex behavior, you might as well handle the assignment of handleEmbedController elsewhere than in the segue.
Since this is a scenario where your base ViewController must communicate with more than one object, you can also use notifications instead of delegations. This way, you can decide which message to send when user taps the button. Your base ViewController would look something like this
class HomeBaseVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// comes from your decision logic
var decisionMaker: Bool = true
#IBAction func onclick(_ sender: UIButton) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "BUTTON_WAS_TAPPED"), object: nil, userInfo: ["decision" : decisionMaker])
}
}
while the enemy ViewControllers would look like this (the second one would be the same except the decision value to handle)
class EnemyBaseVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "BUTTON_WAS_TAPPED"),
object: nil,
queue: nil) { notification in
if let userInfo = notification.userInfo,
let decision = userInfo["decision"] as? Bool,
decision {
self.printMsg()
}
}
}
var value1 = ""
private func printMsg(){
print("printing some embedded message")
}
}

I'm unable to pass data from one VC to another [duplicate]

This question already has answers here:
Passing data with unwind segue
(3 answers)
Closed 5 years ago.
I'm passing the data from one VC back to the first VC. I'm using this code:
#IBAction func goBack(_ sender: Any) {
dismiss(animated: true, completion: nil)
print(self.entryField.text!)
performSegue(withIdentifier: "sendText", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destVC = segue.destination as! ViewController
let printName = self.entryField.text!
print(self.entryField.text!)
destVC.nameToDisplay=printName
}
This is my code of the VC in which the data is.
The code of the VC in which I want to display my result.
var nameToDisplay = ""
override func viewWillAppear(_ animated: Bool) {
titleDisplay.text=nameToDisplay
}
I'm unable to pass the data, I tried printing the nameToDisplay but it gives empty string.
A reasonable pattern for passing back the value from second controller to the first one could be like this:
class FirstViewController: UIViewController {
//......
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let secondViewController = segue.destination as? SecondViewController {
secondViewController.nameHandler = { (name) -> Void in
titleDisplay.text=nameToDisplay //second controller will call back here to pass the name value when it's going back.
}
}
}
//......
}
class SecondViewController: UIViewController {
//......
var nameHandler:((_ name:String)->Void)? //a closure to call back name
#IBAction func goBack(_ sender: Any) {
if let name = self.entryField.text {
self.nameHandler?(name) //call back and pass the name to the first controller
}
dismiss(animated: true, completion: nil)
}
//......
}
You are looking for one to one communication between viewcontrollers. This can be achieved by different ways in iOS.
1- Delegation
2- blocks, closures.
The above solution is using block. I will tell you with delegates
class FirstVC: UIViewController, PassData {
func pushVC() {
let secondVC = SecondVC()
secondVC.delegate = self
self.navigationController?.pushViewController(secondVC, animated: true)
}
func passDataOnDismiss(data: String) {
print(data)
}}
protocol PassData: class {
func passDataOnDismiss(data: String)
}
class SecondVC: UIViewController {
weak var delegate: PassData?
#IBAction func didButtonPress() {
self.delegate?.passDataOnDismiss(data: "I am passing this string back to First VC")
self.navigationController?.popViewController(animated: true)
}
}

Resources