How can I invoke a function/method in another firstViewController swift file while the control/state is in secondViewController.
In Second ViewController when a button is pressed the secondViewController should invoke a function in firstViewController and transfer the control/state to thirdViewController to which it was pushed from secondViewController.
secondViewController Button Action
#IBAction func EnterGallery(_ sender: Any){
// Want to invoke EnterGallery function in firstViewController and dismiss from secondViewController
self.dismiss(animated: true, completion: nil)}
firstViewController pushViewController function
func EnterGallery() {
let dest = self.storyboard?.instantiateViewController(withIdentifier:
"GalleryViewController") as! GalleryViewController // thirdViewController
self.navigationController?.pushViewController(dest, animated: true)
}
Please Note: I am not passing any Data from secondViewController to firstViewController. I just want my firstViewController to push to thirdViewController while I just dismiss from secondViewController which was presented from firstViewController with the present function.
Once I dismiss from secondViewController I want my screen to go directly to thirdViewController.
Basically I just want to invoke a function in another ViewController without any data passing from initial ViewController. So I cannot use Protocols and Delegates or Notifications and Observers. How should I approach this?
There are many other cases where I need to use this similar functionality. So I am not sure how to exactly perform this.
As I am new to Swift, any help will be appreciated.
Thanks in advance.
Your viewController should know about other viewControllers and should be able to interact with them.
Here is a good article about passing data between viewControllers (or just interacting between viewControllers - as you like)
The most common practice is delegation pattern. In two words about delegation:
Create a delegate protocol:
protocol MyDelegate {
func doSmth()
}
Add delegate property to viewController that will trigger something in anotherViewController:
var delegate: MyDelegate?
anotherViewController should conform MyDelegate protocol:
class anotherViewController: MyDelegate {
func doSmth() {
print("I am doing something")
}
}
And then assign your class that is conformed to MyDelegate protocol into this property
viewController.delegate = anotherViewController
Thats it! Now you can trigger delegate method inside viewController
delegate.doSmth()
Google delegate pattern for this. YT: https://youtu.be/DBWu6TnhLeY
Hopefully this helps you out. By the way delegate pattern works even if you don’t want to pass data in between.
Related
In VC#1, I have a UITableView. When I tap on a cell, I am brought to VC#2 where information about that cell is displayed.
I want to be able to press a button in VC#2 which changes the title of the cell it corresponds with in VC#1, but I am confused on how to do this?
Should I create a variable in VC#2 to save the indexPath for the cell that was tapped, and then call a function in VC#1 from VC#2 that uses that indexPath to update the cell? If I did this, wouldn't VC#1 need to be static so I know I'm modifying the right instance of VC#1? I'm using a push segue and a navigation controller to go back, so creating a new instance of VC#1 wouldn't reference the same VC im trying to modify as I believe?
Is there an easier way to do this?
You should use the delegate pattern.
VC1 should know what cell that VC2 is showing. You should have an IndexPath property in VC1 that stores what cell is VC2 currently displaying, right?
Now, create a protocol called VC2Delegate:
protocol VC2Delegate : class {
func titleDidChange(_ vc2: VC2, to title: String)
}
Now, add this property in VC2:
weak var delegate: VC2Delegate?
Now, when you think the title of the cell should change, call the delegate:
delegate?.titleDidChange(self, to: "Some Title")
That's all for VC2.
Make VC1 conform to VC2Delegate:
extension VC1: VC2Delegate {
func titleDidChange(_ vc2: VC2, to title: String) {
// set the text of the table cell here...
}
}
Now, when you are passing data to VC2 from VC1, probably in the prepareForSegue method, do
vc2.delegate = self
Learn more about delegates here.
You can pass every data you want through view controllers using delegates
First create a protocol whatever you want
protocol ViewControllerDelegate {
func getSelected(value:Int)
}
Create a variable from your ViewController you want pass the data
var delegate: ViewControllerDelegate?
On didSelectRowAt method you will do
if delegate != nil {
delegate.getSelected(value: indexPath.row)
}
self.navigationController?.popViewController(animated: true)
On ViewController that will receive data you have to do this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? SecondViewController {
vc.delegate = self
}
}
extension YourViewController: ViewControllerDelegate {
fun getSelected(value:Int) {
// Get value from another view controller and manage it
}
}
This code is in Swift 4
If you don't understand something let me know
It's wrong approach you are pursuing. You must separate your data layer from your presentation layer. So in VC#2 you edit your visualized data, then VC#1 reloads the data to update its view.
Short answer: You should not do that at all.
View controllers should not modify other view controller's views.
You should modify the data model in VC2, then send a message back to VC1 telling it to update the cell.
(In the push segue you can set up VC1 to be VC2's delegate, then define a protocol that VC2 uses to notify VC1 about the indexPath's of the data model that need to be updated.)
I have a pretty complicated setup in terms of view controllers. I have reasons for it that are kind of out of the scope of this question. So I have 3 view controllers.
ViewControllerA is the main view controller in this case. ViewControllerB is a container view controller that is displayed from ViewControllerA. ViewControllerB has a button that has a segue to display ViewControllerC. Then in ViewControllerC there is a button to dismiss to go back.
ViewController's A and B can be different. Depending on if the user is editing an object or creating a new object. The things I'm talking about remain constient between those two cases.
Basically my goal is when a user dismisses ViewControllerC it changes a button text on ViewControllerB. Depending on the users actions on ViewControllerC.
I was thinking about using self.presentingViewController somehow or something along those lines but I can't figure out how to access that specific button within ViewControllerB.
Any ideas of how I can achieve this?
I suggest you use a protocol to define a common method to update button text. Both ViewControllerB's can then conform to this protocol. Then use a delegate callback approach to call these methods from your ViewControllerC.
When you present ViewControllerC from ViewControllerB you can set the delegate property to self before presenting it. You would do this in different places depending on how you are presenting ViewControllerC. As you said you're using a segue to do it, then you should do this in the prepareForSegue method.
Declare a protocol that defines a method to update the button's text like this:
protocol ChangeableButtonTextViewController {
func updateButtonText(newText: String)
}
Then make your EditViewControllerB and CreateViewControllerB conform to this protocol to update the button text:
class EditViewControllerB: UIViewController, ChangeableButtonTextViewController {
func updateButtonText(newText: String) {
button.text = newText
}
// Other stuff in your ViewController
}
Add a delegate property to ViewControllerC like this:
var delegate: ChangeableButtonTextViewController?
Add a prepareForSegue method to EditViewControllerB and CreateViewControllerB which would look something like:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
segue.destination as! ViewControllerC).delegate = self
}
You can then do something like this in ViewControllerC:
func dismiss() {
delegate.updateButtonText("NewText")
}
Let me know if you need any further clarifications.
In my app I have a “main” ViewController. On app launch in the body of its viewDidAppear, I retrieve the value of a UserDefaults boolean: if the user is not logged in, I present modally another viewcontroller this way:
self.performSegue(withIdentifier:"loginView", sender:self)
This launches modally AuthViewController, a viewcontroller that contains a “Container View”, a region of AuthViewController that includes a UIPageViewController (AuthPageViewController).
This last one has two “pages”: LoginViewController and RegisterViewController.
LoginViewController is used to login users: once the user is logged in I want to call a function (no matter what it does) on the main ViewController.
So, to summarize, I’m in LoginViewController and I want to call a method of ViewController.
While trying a solution I discovered the following:
I can’t get a reference to presentingViewController because LoginViewController has not been opened directly from ViewController. Moreover, AuthViewController was not launched with present(_:animated:completion:) but with self.performSegue as I said before;
instantiating a ViewController from LoginViewController and calling a method works but it’s not a good idea;
NSNotification works like a charm;
apparently delegates aren’t working or I’m missing something about their implementation in the contest of this app;
Can you help me understand how to call a ViewController method from my LoginViewController? Code examples are very well received, but I appreciate any advice.
Let me explain it with code of how to use delegate in this case,
First you need create a delegate in LoginViewController like this
protocol LoginViewControllerDelegate: class {
func goToContent(animated:Bool)
}
then create a variable for delegate like this
weak var delegate: LoginViewControllerDelegate?
Now in you ViewController you have to assign the delegate like this in your prepare prepareforsegue method, as you are using segue like this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourSegueIdentifier" {
let nextScene = segue.destination as! LoginViewController
nextScene.delegate = self
}
}
and implement that method like this
func goToContent(animated: Bool) {
print("test")
}
in this way you are able to call goToContent from LoginViewController
I have 2 view controllers VCA and VCB.
Moving from VCA to VCB, with somevalue, is working fine
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("VCB") as! VCB
vc.entity = somevalue
self.navigationController?.pushViewController(vc, animated: true)
But for reverse, I want to call a method in VCA from VCB after uploading certain data from VCB. And after that refresh textfields valuesin VCA. I could have resfreshing code in viewwillappear in VCA but due to some reason i am n ot doing that but trying to implement delegate.
I have written some code as:
VCA:
class ProfileEditViewController:UIViewControoler, MobileVerifyDelegate{
override func viewDidLoad() {
super.viewDidLoad()
let mobileVC = MobileVerificationViewController()
mobileVC.delegate = self
}
//MARK: - Mobileverify Delegate method
func delegateMethod() {
print("a")
}
}
VCB:
protocol MobileVerifyDelegate{
func delegateMethod()
}
class MobileVerificationViewController: UIViewController{
var delegate: MobileVerifyDelegate! = nil
func certainFunction(){
//aftersuccessful upload
self?.delegate.delegateMethod()// code crashes
}
}
Thanks in advance
In your viewDidLoad of VCA you've created mobileVC but when you transition to VCB, you're creating a new instance of VCB named vc. mobileVC is unused the way it is. You have a few options:
make mobileVC a class property or set the delegate while creating vc.
The latter would be:
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("VCB") as! VCB
vc.entity = someValue
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
On a sidenote, make your delegate confirming the class protocol so you can set the delegate as weak.
protocol MobileVerifyDelegate: class {
func delegateMethod()
}
class MobileVerificationViewController: UIViewController {
weak var delegate: MobileVerifyDelegate?
func certainFunction() {
// After successful upload
delegate?.delegateMethod()
}
}
Notice that when you set an implicitly unwrapped property, it is already nil. So it's redundant to set it as nil again.
var delegate: MobileVerifyDelegate! = nil // "= nil" is not needed
I don't know what your case is but the easiest solution would be to move the delegate method and delegate to VCB. If for whatever reason you VCA has to be the delegate class then you need to create an instance of it or pass it to VCB.
//Set delegate when you push to the new controller in VCA
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("VCB") as! VCB
vc.entity = somevalue
vc.delegate = self //Sets VCA as VCB's Delegate
self.navigationController?.pushViewController(vc, animated: true)
//Inside VCB
self.delegateMethod() //Now you can call any delegate protocol methods from VCA
Ya, Delegate is the way that you need to get what you aspected. there is some problem in completely implementing delegate in swift. here i provide link which fully guide you how to implemented delegate in swift.
Delegate In Swift
As you said that, App crash on calling delegate. that means, you method is not available in VCA or delegate has not reference of VCA. kindly check this two condition.
Thanks
Let's say I have a firstViewController and a secondViewController. The first one contains a firstButton and the second one - a secondButton. Here's what I want to do: when user clicks the secondButton, some firstButton's property changes.
Unfortunately, when I create an instance of a firstViewController in a secondViewController and then trying to access a firstButton, I get an error:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
So, technically, I'm trying to do this as follows:
var ins = firstViewController()
#IBAction func secondButtonisPressed(){
ins.firstButton.alpha = 0
}
What is the proper way to implement that?
Thanks in advance.
Your problem here is that the IBOutlets of your firstViewController are only available (!= nil) after the viewDidLoad() firstViewController's method has being called.
In other words, you have to present the view, before you can make any changes to a UIViewController IBOutlet.
How you can solve this?
Add a variable into FirstViewController that works as a flag for you.
for example: var hideFirstButton = false
in the viewDidLoad or viewWillAppear method of FirstViewController check for hideFirstButton's value and hide or show your firstButton.
Then, before you present your FirstViewController change the value of hideFirstButton to the needed for your application to run fine.
UPDATE:
Other workaround, using Storyboard is (This approach has the inconvenient that the completion handler is called after viewWillAppear() so the button is visible for a second):
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstViewController = storyboard.instantiateViewControllerWithIdentifier("FirstViewController") as! FirstViewController
self.presentViewController(firstViewController, animated: true, completion: {
//This lines will be called after the view is loaded so the code will run
firstViewController.firstButton.alpha = 0
})
EXAMPLE: an example at GitHub
You could try to do this using delegation, similar to the way Apple does it in their existing frameworks. For an example, look at the way that you use UITableViewDelegate when working with a UITableView object.
If you wanted to use delegation to tell secondViewController that firstButton was pressed using delegation, you could do it as follows:
Step 1:
Create a protocol containing a method for the button press event.
protocol buttonPressDelegate {
func buttonPressed() -> Void
}
Step 2:
In firstViewController, declare that you have an instance of an object of type buttonPressProtocol.
var buttonPressDelegateObj: buttonPressDelegate?
Step 3:
In firstViewController, initialize your buttonPressDelegateObj to contain a reference to your instance of secondViewController. If you want you can create a method to set the reference contained in buttonPressDelegateObj, or do it viewDidLoad in firstViewController, etc.
buttonPressDelegateObj = secondViewControllerObj
Step 4:
In secondViewController, declare that you adopt the buttonPressDelegate protocol.
class secondViewController: UIViewController, buttonPressDelegate {
Step 5:
In secondViewController, implement the protocol method buttonPressed() by adding the function with your desired implementation. Here's an example:
func buttonPressed() {
secondButton.alpha = 0
}
Step 6:
Create an #IBAction on the button in firstViewController, so that when the button is pressed it calls buttonPressDelegateObj.buttonPressed() and you can respond to the event
#IBAction func firstButtonPressed() {
if (buttonPressDelegateObj != nil) {
buttonPressDelegateObj.buttonPressed()
}
else {
print("You forgot to set your reference in buttonPressDelegateObj to contain an instance of secondViewController!")
}
}
Note: This is just one way that you could do this. To tell firstViewController that secondButton was pressed (go the other way), have firstViewController implement the protocol buttonPressDelegate, have secondViewController contain a reference to firstViewController as an instance of type buttonPressDelegate?, and create an #IBAction in secondViewController that fires when secondButton is pressed that calls your the buttonPressDelegate method.
Note: There is a similar pattern employed in the Android world to get a Fragment to communicate to an Activity, that you can read more about here