I have a button on 2nd viewController, after pressing that button, I would like to dismiss the 2nd viewController and go back to the 1st view controller and immediately call a function that coded inside 1st ViewController swift file.
May I know how can I do that? By segue?
There are many way to do this one of the best way is using protocol and delegate.
You can create one protocol and extend that protocol in your ViewController1. Now create the delegate of protocol in ViewController2 and pass reference of that delegate in the ViewController1's prepareForSegue method.
First create one protocol like this
protocol PassdataDelegate {
func passData()
}
Now extend this protocol in ViewController1 like this and pass the reference of delegate in prepareForSegue method
class ViewController1 : UIViewController, PassdataDelegate {
func passData() {
//Here call your function
self.callMyFunction()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "SegueIdentifier") {
let destVC = segue.destinationViewController as! ViewController2
destVC.delegate = self
}
}
}
Now create the delegate object of protocolin ViewController2 like this
class ViewController2 : UIViewController {
var delegate: PassdataDelegate?
//Now call the method of this delegate in Button action
#IBAction func buttonClick(sender: UIButton) {
self.delegate.passData()
//Now dismiss the controller
}
}
Note: - Here i am passing stringbut you can pass any type of object that you have declare in your delegate method.
You can refer unwind segue.
class ViewController1 {
#IBAction func doSomeStuffAfterReload(segue: UIStoryboardSegue) {
// do whatever you need to do here.
}
}
On storyboard, from ViewController2 Ctrl+Drag from the button to the exit outlet and select doSomeStuffAfterReload.
You can see it in action here: https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
Happy coding^^
Related
I need to pass a String and Array from my Third ViewController to my First ViewController directly using protocol/delegate, I have no problem doing it from VC 2 to VC 1 but I'm having a hard time with this. Also after clicking a button in my VC3 I need to go back to VC 1 and update the VC UI how would I do that? Would that have to be in viewdidload?
This in Swift UIKit and Storyboard
You need two protocols, and your firstVC and SecondVC have to conform those. When pushing new ViewController you need to give the delegate of that ViewController to self. On your third VC, when you click the button you need to call your delegate and pass your data to that delegate method, then repeat the same for other.
For FirstVC
protocol FirstProtocol: AnyObject {
func firstFunction(data: String)
}
class FirstVC: UIViewController, FirstProtocol {
weak var delegate: FirstProtocol?
#IBAction func buttonClicked(_ sender: Any) {
let secondVC = SecondVC()
secondVC.delegate = self
navigationController?.pushViewController(secondVC, animated: true)
}
func firstFunction(data: String) {
navigationController?.popToRootViewController(animated: true)
print(data)
}
}
You handle your navigation from your root. For better experience you can use something like coordinator pattern to handle it.
protocol SecondProtocol: AnyObject {
func secondFunction(data: String)
}
class SecondVC: UIViewController, SecondProtocol {
weak var delegate: FirstProtocol?
#objc func buttonClicked() {
let thirdVC = ThirdVC()
thirdVC.delegate = self
navigationController?.pushViewController(thirdVC, animated: true)
}
func secondFunction(data: String) {
delegate?.firstFunction(data: data)
}
}
Second VC is something that you just need to pass parameters.
class ThirdVC: UIViewController {
weak var delegate: SecondProtocol?
#objc func buttonClicked() {
delegate?.secondFunction(data: "data") // PASS YOUR ARRAY AND STRING HERE
}
}
What you need is unwind segue. Unwind segue will act like segue, only backward, popping, in this case, VC2. You can read here for more information.
Updating data code would be put in a function similar to prepareToSegue() but for unwind segue in your VC1.
Example of the function inside VC1:
#IBAction func unwindToDestination(_ unwindSegue: UIStoryboardSegue) {
switch unwindSegue.identifier {
case SegueIdentifier.yourSegueIdentifier:
let sourceVC = unwindSegue.source as! SourceVC
dataToPass = sourceVC.dataToPass
reloadData()
default:
break
}
}
Here is a different approach that accomplishes what you described by performing a Present Modally segue directly from View Controller 3 to View Controller 1, and sharing the string and array values by way of override func prepare(for segue....
In Main.storyboard, I set up 3 View Controllers, and have segues from 1 to 2, 2 to 3, and 3 to 1. These are Action Segues directly from the buttons on each VC, which is why you won't see self.performSegue used inside any of the View Controller files. Here is the picture:
In the first view controller, variables are initialized (with nil values) that will hold a String and an Array (of type Int in the example, but it could be anything):
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var updatableTextLabel: UILabel!
var string: String?
var array: [Int]?
override func viewDidLoad() {
super.viewDidLoad()
// These will only not be nil if we came here from the third view controller after pressing the "Update First VC" button there.
// The values of these variables are set within the third View Controller's .prepare(for segue ...) method.
// As the segue is performed directly from VC 3 to VC 1, the second view controller is not involved at all, and no unwinding of segues is necessary.
if string != nil {
updatableTextLabel.text = string
}
if let a = array {
updatableTextLabel.text? += "\n\n\(a)"
}
}
}
The second view controller doesn't do anything except separate the first and third view controllers, so I didn't include its code.
The third view controller assigns the new values of the string and array inside prepare (this won't be done unless you press the middle button first, to demonstrate both possible outcomes in VC 1). This is where your string and array get passed directly from 3 to 1 (skipping 2 entirely).
import UIKit
class ThirdViewController: UIViewController {
var theString = "abcdefg"
var theArray = [1, 2, 3]
var passValuesToFirstVC = false
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func updateFirstVC(_ sender: UIButton) {
passValuesToFirstVC = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if passValuesToFirstVC && segue.identifier == "toFirst" {
// Cast segue.destination (which on its own has type UIViewController, the superclass to all your custom View Controllers) to the specific subclass that your first View Controller belongs to
let destinationVC = segue.destination as! FirstViewController
// When your first view controller loads, it will receive these values for the 'string' and 'array' variables. They replace the original 'nil' values these had in the FirstViewController definition.
destinationVC.string = theString
destinationVC.array = theArray
}
}
}
Note that there is an IBOutlet to the label on the first View Controller which contains the text to be updated.
After visiting the third view controller, pressing the "Update First VC Text" button, and then performing the segue back to the first, here is how it will look:
This doesn't address the part about protocols and delegates in your question (as I'm not sure how they're being used in your program, and other answers have already addressed that), but it illustrates the method of transferring variables directly from one View Controller to another without unwinding segues or using the UINavigationController.
This question might be asked already about hiding buttons, but I was wondering if I could just click a button which would affect the variables in another view controller. For example, I have firstViewController and endViewController. There's a button in endViewController that the user presses which should change a variable in the firstViewController. Is there a way to access the endViewController button from the firstViewController?
Edit
I haven't tried much so far except control clicking the endViewController button into the firstViewController (which didn't work).
class firstViewController: UIViewController {
#IBAction func nextButton(_ sender: Any) { //button that sits in endViewController
}
}
You can use the DELEGATE PATTERN to pass data back:
Here's a little help on delegates between two view controllers:
Step 1: Make a protocol in the UIViewController that you will be removing/will be sending the data.
protocol FooTwoViewControllerDelegate:class {
func myVCDidFinish(_ controller: FooTwoViewController, text: String)
}
Step2: Declare the delegate in the sending class (i.e. UIViewcontroller)
class FooTwoViewController: UIViewController {
weak var delegate: FooTwoViewControllerDelegate?
[snip...]
}
Step3: Use the delegate in a class method to send the data to the receiving method, which is any method that adopts the protocol.
#IBAction func saveColor(_ sender: UIBarButtonItem) {
delegate?.myVCDidFinish(self, text: colorLabel.text) //assuming the delegate is assigned otherwise error
}
Step 4: Adopt the protocol in the receiving class
class ViewController: UIViewController, FooTwoViewControllerDelegate {
Step 5: Implement the delegate method
func myVCDidFinish(_ controller: FooTwoViewController, text: String) {
colorLabel.text = "The Color is " + text
controller.navigationController.popViewController(animated: true)
}
Step 6: Set the delegate in the prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
let vc = segue.destination as! FooTwoViewController
vc.colorString = colorLabel.text
vc.delegate = self
}
}
And that should work. This is of course just code fragments, but should give you the idea. For a long explanation of this code you can go over to my blog entry here:
segues and delegates
If you are interested in what's going on under the hood with a delegate I did write on that here:
under the hood with delegates
original answer
First View Controller
The code for the First View Controller is
import UIKit
class FirstViewController: UIViewController, DataEnteredDelegate {
#IBOutlet weak var label: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showSecondViewController" {
let secondViewController = segue.destinationViewController as! SecondViewController
secondViewController.delegate = self
}
}
func userDidEnterInformation(info: String) {
label.text = info
}
}
Note the use of our custom DataEnteredDelegate protocol.
Second View Controller and Protocol
The code for the second view controller is
import UIKit
// protocol used for sending data back
protocol DataEnteredDelegate: class {
func userDidEnterInformation(info: String)
}
class SecondViewController: UIViewController {
// making this a weak variable so that it won't create a strong reference cycle
weak var delegate: DataEnteredDelegate? = nil
#IBOutlet weak var textField: UITextField!
#IBAction func sendTextBackButton(sender: UIButton) {
// call this method on whichever class implements our delegate protocol
delegate?.userDidEnterInformation(textField.text!)
// go back to the previous view controller
self.navigationController?.popViewControllerAnimated(true)
}
}
Note that the protocol is outside of the View Controller class.
That's it. Running the app now you should be able to send data back from the second view controller to the first.
Original post: https://stackoverflow.com/a/33229483/13783496
There are 2 methods:
You can create a segue from button in endviewcontroller to firstviewcontroller in storyboard. You can configure the func prepare(for segue: UIStoryboardSegue, sender: Any?) for it.
let endVC = endViewcontroller()
endVC.color = "blue"
You can keep the variable whose value needs to be changed as static datatype.
On the click action of button, you can access variable as,
EndViewController.color = "Red".
Kindly use static variables only if you want other Viewcontrollers to access it directly.
I m confuse about call back using protocol and delegate.
The problem is. I have two viewcontrollers vcA & vcB
and vcA have a tableView, vcB have a button.
vcA click the cell to vcB.
Then I want to click the button in vcB and do the following two things.
1.vcA tableView reloadData.
2.vcB popViewcontroller To vcA.
I can't understand how to solve this issue.
Have any sample to teach me?
Thanks.
This is the delegate solution , but it's better to put the self.tableView.reloadData() method inside viewDidAppear , as it's being called when you pop VcB
class VcA: UIViewController ,TableRefresh {
func reloadTable()
{
// reload here
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let des = segue.destination as! VcB
des.delegate = self
}
}
protocol TableRefresh {
func reloadTable()
}
class VcB: UIViewController {
var delegate: TableRefresh?
#IBAction func closeClicked(_ sender: UIButton) {
self.delegate?.reloadTable()
// pop here
}
}
I hope this could work for you:
class vcA : UIViewController {
override func viewWillAppear(_ animated: Bool) {
tableView.reloadData()
}
}
class vcB : UIViewController {
#IBAction fun button(bin : UIButton){
self.navigationController.popViewController(true)
}
}
It's not possible to call the methods of any VC objects if it's not in your memory.
For the first time, it vcB will not be in memory. Hence the object of the same is not in existence.
If you really want to call a function of that class and if its feasible crate an object of vcB and call the method.
or you might try a shared object of the VC and keep on using the same if it's feasible. you may post exact scenario so that people can suggest something better
Your vcA has a reference to the vcB, so you are able to write something like this in the vcA
class vcA {
var vcB: vcB?
...
vcB?.doSmth()
....
}
but there is no way for you to call vcA from the vcB since it doesn't have a reference to it. So to let vcA know that something happened in vcB or to call some function from vcB you can do several things.
Delegates, Key-Value Observing, Reactive Programming and some others.
Since you asked for the delegates solution lets stick to it.
The general idea behind delegates is as the name says to delegate someone else to do something. In your case, you want to delegate button click handling to the vcA. To do so, you will need a couple of things.
Next steps are just the implementation of the idea described above.
class VcA {
var vcB: VcB?
...
vcB?.delegate = self
...
vcB?.doSmth()
....
}
extension VcA: VcBDelegate {
func buttonIsClicked() {
// reload data
// pop vcB
}
}
protocol VcBDelegate: class {
func buttonIsClicked()
}
class VcB {
var delegate: VcBDelegate?
...
// when the button is clicked
// delegate handling to someone who is 'listening' for this event
self.delegate?.buttonIsClicked()
...
}
Notice how delegate in VcB is optional, meaning that if no one is signed as a delegate for VcB, the event will simply be ignored.
There are multiple ways to do this.
1: Put tableView.reloadData() in viewDidAppear() and then just pop vcB
2: When you are pushing vcB you create a reference to it and then in vcB you have a listener that you apply to the reference. like so:
class viewControllerB: UIViewController {
var reloadTableView: (() -> ())
#objc func buttonPressed() {
reloadTableView()
self.navigationController?.popViewController(animated: true)
}
}
And then:
let vcB = viewControllerB()
vcB.reloadTableView = {
self.tableView.reloadData()
}
present(vcB, animated: true)
3: Do as Sh_Khan
I want to segue from a view container within "H" that is presented using the navigation controller connected to the Split View Controller. How can I accomplish this? I have tried regular performSegueWithIdentifier using locally linked storyboard ID's but that removes the top navigation bar. I want to retain the top navigation bar and execute the segue as if it was done using the master navigation controller (rows that select which view controller is being presented in the detail view).
Any help is greatly appreciated!
Here is an example of how to perform a segue from an embedded ViewController.
ViewController.swift
import UIKit
protocol SegueHandler: class {
func segueToNext(identifier: String)
}
class ViewController: UIViewController, SegueHandler {
func segueToNext(identifier: String) {
self.performSegueWithIdentifier(identifier, sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EmbedH" {
let dvc = segue.destinationViewController as! HViewController
dvc.delegate = self
}
}
}
HViewController.swift
import UIKit
class HViewController: UIViewController {
weak var delegate: SegueHandler?
#IBAction func pressH(sender: UIButton) {
delegate?.segueToNext("GoToGreen")
}
}
Setup:
Use delegation to have the HViewController tell its embedding viewController to perform the segue.
Create a protocol called SegueHandler which just describes a class that implements the method segueToNext(identifier: String).
protocol SegueHandler: class {
func segueToNext(identifier: String)
}
Make your viewController implement this protocol by adding it to the class declaration line:
class ViewController: UIViewController, SegueHandler {
and by implementing the required function.
Add a delegate property to HViewController:
weak var delegate: SegueHandler?
Click on the embed segue arrow between ViewController and HViewController. Give it the identifier "EmbedH" in the Attributes Inspector.
Create a show segue between ViewController and the GreenViewController by Control dragging from the viewController icon at the top of ViewController to the GreenViewController. Name this segue "GoToGreen" in the Attributes Inspector.
In prepareForSegue for ViewController, when the "EmbedH" segue happens, set the delegate property of HViewController to self (ViewController).
When the user clicks the H button in the HViewController, call delegate?.segueToNext("GoToGreen") to trigger the segue in ViewController.
Here it is running in the simulator:
I was needing exactly what #vacawama proposed here, though I couldn't reproduce that, I tried exactly your steps but self.delegate?.segueToNext("GoToGreen") got called but neither the protocol itself nor the container view controller. After an entire day searching about this approach I realized the problem was with the swift version. Just replace this:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EmbedH" {
let dvc = segue.destination as! HViewController
dvc.delegate = self
}
}
for this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EmbedH" {
let dvc = segue.destination as! HViewController
dvc.delegate = self
}
}
Other detail I was missing was about the embedded segue. Be sure to connect the container View to the HViewController, not the View Controller itself, otherwise the Embed option for segue won't appear.
Hello learning swift and am stuck with calling a method through delegate. Checked multiple answers with similar issues and have tried the solutions but have not been able to successfully apply them to my own situation however I am close.
I have a delegator class named ViewController that holds a variable I would like to change. I have another view called MoodScroll which serves as the delegate. Moodscroll has a button being used to change the value for the variable in ViewController.
ViewController :
class ViewController: UIViewController, AVAudioPlayerDelegate, MoodScrollDelegate {
var alarmSoundType: String?
func acceptData(data: String?) {
alarmSoundType = "\(data)"
print(data)
}
}
MoodScroll :
protocol MoodScrollDelegate {
func acceptData(data: String?)
}
import UIKit
class MoodScroll: UIViewController {
#IBAction func WTF(sender: AnyObject) {
self.delegate?.acceptData("hello")
print("function called")
}
}
The IBAction calls fine as it prints "function called" in the console however it doesn't pass the value to ViewController as alarmSoundType remains nil and also the print command is not called in ViewController as well.
It seems you still have some confusion about delegation : if ViewController conforms to MoodScrollDelegate protocol, then your ViewController object will be the delegate, not the MoodScroll object.
Where do you set the delegate property of your MoodScroll object ?
If this object is created programmatically from your ViewController object, you should set it after initialization :
myMoodScrollObject.delegate = self
Is the object is created using Interface Builder, you can either use an outlet variable for delegate, or set it in prepareForSegue:sender of your ViewController class :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let scroll = segue.destinationViewController as? MoodScroll{
scroll.delegate = self
}
}
One picky note: the way you have described your problem, it's actually ViewController what you should call the delegate of MoodScroll. Most likely you're probably forgetting to set the delegate property of MoodScroll.
I don't know how these two view controllers relate to each other in your code, but very often you would set the delegate property in the prepareForSegue method of ViewController, for example:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SegueToMoodScroll" {
let moodScrollController = segue.destinationViewController as! MoodScroll
moodScrollController.delegate = self
}
}