I'm currently building a homework tracking app where you can add courses in a TableView. In one ViewController, I have a list of courses that already exist. I also have a button that allows the user to add new courses. When they click the button, the app triggers a modal segue to a new ViewController where they can fill out a form to add a new course. However, when they finish and click the button that dismisses the current ViewController to go back to the courses list, I can't find a way of updating the courses list with the course that the user just added. I know that if using a segue, you can use the prepare method. However I am calling
dismiss(animated: true, completion: nil)
The method that I want to call in order to reload the table is in the first ViewController. Is there a way to call the load method in the first ViewController before or after the second ViewController has dismissed?
As per your requirement, a simple "delegation" will work.
Step 1: Define a protocol.
protocol ViewController2Delegate: class {
func refresh()
}
Step2: Create a delegation at ViewController2
weak var delegation: ViewController2Delegate?
Step3: As you are using "segue" from a button to create ViewController2 from ViewController1, use prepareForSegue method in ViewController1 and set that ViewController1 is conforming the delegate. ViewController1 will conform the delegate and reload the table.
Step4: In ViewController2, on tap of doneButton, call delegate?.refresh() as per your requirement (before dismissing / after dismissing of viewcontroller2 - use completionBlock of dismiss() method).
you can use viewWillappear function in first view controller to refresh or just using completion handler as next:
1) in the second controller add this variable.
var completion: (() -> Void)?
2) in the first controller before showing the second controller you need to add this code.
let controller = secondController()
controller.completion = {
// here you can refresh your first controller
}
3) to make the refresh here is the last step you need to do in the second controller before you call the dismiss function.
if let completion = completion{
completion()
}
You can call reloadData in viewWillAppear on ViewController1
override func viewWillAppear(_ animated: Bool) {
tableview.reloadData()
}
You could do the loadData or refreshData on viewWillAppear of the FirstViewController, like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
refresData()
}
The viewWillAppear of FirstViewController will be called right after you call to dismiss from the SecondViewController.
You can use a delegate for the first controller in you second view controller and call it before you call the dismiss. If you want to to run after the dismiss, then you can call it in the completion of the dismiss function.
Related
SCENARIO
Xcode 11.5, Swift 5
Using Core Data
User wants to update their profile. VC2 is dismissed after user taps save. VC1 area highlighted in yellow should reflect the change.
PROBLEM
Data is being saved correctly. However, VC1 elements highlighted in yellow doesn't automatically update. If I go to another tab then come back, the view elements refresh with the updated changes.
MY CODE
I have a setupUI() method that lays out the elements and have tried adding it to VC1's viewWillAppear method, but no luck.
//VC1:
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
setupUI()
}
viewWillAppear is not called when you dimiss the modal that fill the data you need to use a delegate
1- When you show the modal vc
let vc = SomeVC()
vc.delegate = self // declare property delegate inside the modal of that type / protocol
// present
2- when you dimiss the modal
self.delegate?.setupUI()
// dimiss
You could use a delegate method to perform some changes in VC1 in response to some action in VC2. In this case you will set the delegate in VC1 and call the delegate method in VC2. Ideal place to make this call would be in completion block of dismiss.
//VC1
public protocol MyProtocol: class {
func delegateMethod()
}
In the viewDidLoad method set the delegate for VC2
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
setupUI()
//VC2 is the instance of view controller you are going to push from this page
VC2.delegate = self
}
Make sure VC1 confirms to MyProtocol protocol
extension VC1: MyProtocol {
func delegateMethod() {
// reload view here
}
}
Declare the delegate in VC2
//VC2
var delegate: MyProtocol?
Then call the delegate method in completion of dismiss
self.dismiss(animated: false, completion: {
self.delegate?.delegateMethod()
})
Alternatively you could use observers to respond to any changes as well, but that might be an overkill. Check out this article, they discuss the whole thing in detail.
setting vc2.modalPresentationStyle = .fullScreen will solve it without the need to make any delegates.
I want to refresh the whole page controller on back press.
I am navigating the viewcontroller using code.
My Code
let GTC = self.storyboard?.instantiateViewController(withIdentifier: "GoToCart")as! GoToCart
self.navigationController?.pushViewController(GTC, animated: true)
Using viewWillAppear to reload your UI. As you use navigationController?.pushViewController, the view will be retained and stored in stack.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Reload the UI
}
viewWillAppear(_:)
viewWillAppear is called the first time the view is displayed as well as when the view is displayed again, so it can be called multiple times during the life of the view controller object. It’s called when the view is about to appear as a result of the user tapping the back button, when the view controller’s tab is selected in a tab bar controller etc. Make sure to call super.viewWillAppear() at some point in the implementation. You can refresh your UI in this method
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Reload the UI
}
A better approach is to use protocol
Create protocol from where you want to pop back(GoToCart)
Create delegate variable in GoToCart
Extend GoToCart protocol in MainViewController
Give reference to GoToCart of MainViewController when
navigate
Define delegate Method in MainViewController
Then you can call delegate method from GoToCart
Example
In GoToCart: Write code below..
protocol GoCartControllerDelegate
{
func childViewControllerResponse(parameter)
}
class GoToCart:UIViewController
{
var delegate: ChildViewControllerDelegate?
....
}
Then in mainViewController implement the protocol function end extend to the protocol
class MainViewController:UIViewController,GoCartControllerDelegate
{
// Define Delegate Method
func childViewControllerResponse(parameter)
{
//...here update what you want to update according to the situation
}
}
2 Important thing
when navigating to the gocart controller code like this
let GTC = self.storyboard?.instantiateViewController(withIdentifier: "GoToCart")as! GoToCart
GTC.delegate = self
self.navigationController?.pushViewController(GTC, animated: true)
and when popping from gocartViewController
code like this
self.navigationController?.popViewController(animated:true)
self.delegate?.childViewControllerResponse(parameter)
I have a searchViewController where I search for users and UITableView gets updated dynamically with user information. The cell for the UITableView is custom - it has a UIImage, the usernameLabel, and a button called "Add".
What I want is that if the user clicks on the add button of the cell, it should pass the user information on that cell (image and username) to another view controller that has a UITableView that is a friend list.
However, so far the only way I know is by using performSegue to pass the data on to the other viewController holding the friendlist UITable. But by this method, every time I click the add button it segues to the other view controller which I don't want. I want it to stay on the searchViewController when the add button is clicked - I only want the data to be passed.
Is there any way I can do this? Is using NSUserDefaults advisable for passing data of this sort?
For simplicity I will use FriendListVC and AddVC
If you are going to your AddVC from FriendListVC via a bar button item or something and your stack looks like:-
FriendListVC -> AddVC
There are two approaches you can use:-
1) Create a delegate of your friendListVC in your addVC and modify the friendListVC datasource on any changes there
2) Or, and I recommend this approach, just reload your FriendListVC datasource on it's viewWillAppear. viewWillAppear will get called even if you navigate back. Thus even if you add a deleteVC in the future and navigate back, the viewWillAppear will perform the updates and it will be independent of any other VC
Hope that helps
Use delegate for passing data between view controllers. you can find this useful
Passing data between 2 UIViewController using delegate and protocol
you can use NSUserDefaults but delegate pattern is better than this.
You can use callback method best and easy way to pass data one controller to another
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
let viewControllerB = segue.destinationViewController as! ViewControllerB
viewControllerB.callback = { message in
//Do what you want in here!
}
}
In ViewControllerB:
var callback : (String -> Void)?
#IBAction func search(sender: AnyObject) {
callback?("Pass data to view controller1")
self.dismissViewControllerAnimated(true, completion: nil)
}
The easiest way to do this is by making an instance of the view controller that you want to pass data to, in the current view controller. I will write you a sample code for this.
class yourTableViewController: UITableViewController {
var controllerToPassData: UIViewController()
func clickTableButton(sender: UIButton) {
controllerToPassData.count += 1
}
}
class controllerwhereDataisPassed: UIViewController {
var count: Int!
}
Pick the instance of the controller where you want to pass data to from the navigationController stack and use this code.
I created a file called "Util.swift" and it has a class "class VC: UIViewController" and has a 2 functions:
Next(segue: String) {
performSegueWithIdentifier(segue, completion: nil)
}
Back() {
dismissViewControllerAnimated(true, completion: nil)
}
So basically, the functions just tell the current viewController to go to the next View or go back to the previous view.
However, in another file, I have an IBAction which is attached to a button and in that action I call the function "Back" in the Util file by doing this:
#IBACTION func ~~~ {
nextVC = Util()
nextVC.back()
}
However, when I click the button, it doesn't do anything.
When I try to connect another button to an action that calls the "next" function in the Util file by doing the same thing and putting in the segue indetifier parameter, it says SIGABRT error: no segue identifier called "xxx".
Can anyone help me? Should I simply use XIB and not use segues?
You have to initialize the UIViewController either with storyBoard or with XIB. If you are doing with factory init() method then it doesn't do anything for you; that means, that is not a viewController which is associated either with storyBoard or XIB. So all you need to do is instantiate the viewController with either options. Before that you need to set an identifier for the viewController to instantiate it.
let storyBoard = UIStoryBoard(name: "MainStoryBoard", bundle: nil)
let vc = storyBoard.instanstiateViewControllerWithIdentifier("YOUR_IDENTIFIER") as! Util
//call your Next method like this
vc.Next(segue:"identifier")
Please refer the document
Apple Doc
In order to use segues, the view controller instance needs to be associated with a Storyboard. In order to be associated with a storyboard, a view controller instance either needs to be the result of a segue or instantiated from a storyboard via instantiateViewControllerWithIdentifier.
When you create an instance of Util via Util() you have a view controller instance that isn't associated with a storyboard and isn't actually presented on screen.
As a result, when you try and perform a segue, you get an error since the segue can't be found.
Also, when you try and dismiss the view controller in back() you are trying to dismiss a view controller that isn't presented.
I am not sure why you want to wrap two fairly simple functions inside next and back, but you can do this using a superclass for all of your view controllers and have this superclass implement your next and back functions:
class MyViewContollerSuperclass: UIViewController {
func next(segue: String) {
self.performSegueWithIdentifier(segue, sender: self)
}
func back() {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Then you actual view controller would be declared as:
class MyActualViewController: MyViewContollerSuperclass {
#IBACTION func ~~~ {
self.back()
}
}
So, I have Navigation Controller. there are segue from Root View Controller to other View Controller.
When I want to get access to other View Controller I override prepareForSegue method and use destinationViewController property.
But that's not ok for me. All my stuff in prepareForSegue will be execute every time when segue is called, but I don't want it. Secondly, it destroys logic of my code: after performSegueWithIdentifier(actually before) execution jumps to other place in code.
It would be great if I can get access to other View Controller like I did it with Root ViewController - by keyword self, for example.
That's code example to make my question more clearer:
func startWorking() {
/*here we made some stuff for current VC
...
...
*/
//next we go to new View Controller
performSegueWithIdentifier("newVC", sender: nil)
//then all actions that I want to do begin at another method - prepareForSegue
//But I want get access to View Controller that user sees now!
//For example present some view:
let someView = UIView(frame: someFrame)
/*question subject*/.view.addSubview(somView)
}
/question subject/ - is the current ViewController that I have presented by segue and point of my question.
Sergey Gamayunov,
You can always access the top mostViewController in navigation stack using,
let viewCOntroller = self.navigationController?.topViewController
EDIT
I believe if you cant get your logic around the prepareForSegue or self.navigationController?.topViewController you must take a look into your design pattern :)
That being said I understand all you want to do is to access the ViewController after performSegue without using prepareForSegue, you can use this code
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if viewController is YourDestinationViewControllerClass {
print("You have access to viewController loaded do whatever you want")
}
}
The function stated above is a navigation controller delegate :) So you will have to declare your viewController to confirm UINavigationControllerDelegate. like
class ViewController: UIViewController,UINavigationControllerDelegate
and in
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
}
Thats it you are good to go :) Happy coding buddy :)