I've been looking at and trying all the solutions others have posted to this problem, but I'm still not getting it.
My use case is very simple. I have a viewController with a button, when that button is pressed I want to navigate to another tab and reload the data including an api call.
When using the button, I navigate to the tab fine, but viewDidAppear is not being called.
If on another tab, and navigate using the tab bar, viewDidAppear works fine. Also viewWillAppear is working, but I have to add a manual delay to the functions I want to call so it's not ideal.
So what do I need to do to navigate using self.tabBarController.selectedIndex = 0 and get the functionality of viewDidAppear?
Update: The viewWillAppear method I added gets called but I have to add a delay to my functions in order for them to work, and it's a bit clunky, not ideal. Not sure why viewDidAppear will not work :(
Here is a screenshot of the structure:
I appreciate any help on this one!
The "current" ViewController is my tab index 2:
import UIKit
class PostPreviewVC: UIViewController {
//Here I create a post object and post it to the timeline with the below button
#IBAction func postButtonPressed(_ sender: Any) {
//create the post via Firebase api
self.tabBarController?.selectedIndex = 0
}
}
In my destination viewController:
import UIKit
import Firebase
import SDWebImage
import AVFoundation
class HomeVC: UIViewController {
// MARK: - PROPERTIES
var posts = [Post]()
let refreshControl = UIRefreshControl()
//more properties...
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
configureTableView()
reloadTimeline()
UserFirebase.timeline { (posts) in
self.posts = posts
self.tableView.reloadData()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear")
_ = self.view
setupUI()
configureTableView()
reloadTimeline()
UserFirebase.timeline { (posts) in
self.posts = posts
self.tableView.reloadData()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewWillAppear")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.reloadTimeline()
self.configureTableView()
}
}
//All the tableview code below here...
}
Added a custom class for my tab bar controller:
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear in tabBar custom Class called")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear in tabBar custom Class called")
}
}
When you are using UITabBarController, the method viewDidLoad will called only once when any UIViewController is loaded in memory. After that, when you are navigating the same UIViewController, then it will load from memory.
In order to overcome this problem, you must divide your code in viewDidLoad & viewDidAppear. So, in viewDidLoad, you only put that code which you want to intialize once throughout the app such as adding UIView's or other things, while in viewDidAppear / viewWillAppear, you can make API calls or some functions which fetches dynamic data.
Finally, when you are calling self.tabBarController.selectedIndex = 0, it will call viewDidLoad only once and viewDidAppear / viewWillAppear every time when you are navigating that UIViewController.
Hope this helps to understand like how UITabBarController works.
For UITabBarController viewDidLoad only gets called once. and your viewWillAppear and viewDidAppear get called multiple times. you can either check if your viewWillAppear gets called or not. because your view will appear gets called before your viewDidAppear it's just like going through the reverse engineering process.
You can also add viewDidAppear method into your UITabBarController custom class. and call its superclass method into it in that way I think it will solve your problem.
Note: In the case of UITabbarController, Always do your UI update task and API calling a task in either
viewWillAppear or viewDidAppear
Related
i am calling a GET(method) API on viewDidAppear function of a view controller. i am presenting a new view controller using navigation controller over my first view controller. on the second view controller i am calling an API of Post Method to add another entry into my previous screen Get method API. But when I dismiss the second View Controller the Get API data remains the same and when i again runs the code the data was updated on the first view controller. Can someone tell me that how to check on first view controller that my second view controller is dismissed so that i can call API there.
I got the solution for this. It didn't work by calling the API on viewDidAppear() or viewWIllAppear() . This will be done by using swift closures.
Below is the code:
class 1stViewController: UIViewController {
#IBAction func buttonTapped(_ sender: UIButton) {
guard let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondController.callbackClosure = { [weak self] in
print("call API")
}
self.navigationController?.pushViewController(secondController, animated: true)
}
}
On Second view Controller:
class SecondViewController: UIViewController {
var callbackClosure: ((Void) -> Void)?
override func viewWillDisappear(_ animated: Bool) {
callbackClosure?()
}
}
The title basically asks my question for me. I have a number of view controllers which I'm loading in or dismissing as a user goes through the app. I'm having some asynchrony issues with the dismiss calls being called potentially too much. It's been a bit of a doozy going through the code base and finding what is being called where. I'd like to just print off whenever a new UIViewController hits the points in it's lifecycle functions viewWillAppear() or viewWillDisappear(). Is there a way for me to extent the UIViewController in such a way that all of its subclasses will naturally do this? Or would I have to go through each subclass to add in that code?
Thank you!
You could create a new UIViewController base class with the following:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("\(type(of: self)) will appear")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("\(type(of: self)) will disappear")
}
You'll have to inherit from this class on any view controller whos events you want to log
I am learning Swift, and I am throwing myself in the deep end by building an app to force myself to learn the language. Something I want to implement is that when I transfer from screen 1 to screen 2, audio is played without needing to do anything. The audio is A-14a. The code below is set up so that when I click the Directions button, it plays the audio, but I don't know how to do it right away. Images are below to help show what I mean.
1st Screen 2nd Screen
The code I have is below:
import UIKit
import AVFoundation
class Intervention_Numerals1: UIViewController {
#IBOutlet weak var Directions: UIButton!
#IBOutlet weak var Done: UIButton!
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
setUpElements()
//Audio Test
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "A-N14a", ofType:"mp3")!))
audioPlayer.prepareToPlay()
} catch {
print(error)
}
}
func setUpElements() {
// Style the elements
Utilities.styleFilledButton(Directions)
Utilities.styleFilledButton(Done)
}
#IBAction func Play(_ sender: Any) {
audioPlayer.play()
}
}
Please let me know any advice on how to do this
Use this method
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
audioPlayer.play()
}
There are a couple of methods you can override to know the state of the view controller and add code to run when that state occurs.
As mentioned by Amila what you are looking for is probably viewDidAppear,
Another method that might also help you achieve this is viewWillAppear
I will provide the code of most of these methods and what they do below so you can try them all and experiment with them:
override func viewDidLoad() {
//This is what you already use every time, its called when the VC's view is loaded into memory
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//This happens when the view is *about* to appear, it happens before users see anything from the view, great for updating UI
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//This happens when the view actually appears on screen and when all the animations of loading the View Controller are over.
//not so good for updating UI since users will see a glimpse of previous view data.
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//This happens when the view is *about* to disappear, great example for this is when you begin swiping to go back in a navigation controller.
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
//This happens when view is no longer on screen for user to see, like when that swiping back animation in using navigation controller is *finished*
}
There are a few more of these such as viewWillLayoutSubviews and viewDidLayoutSubviews that occur when views inside your View Controller change.
Use all these to control when what happens, hope this helps :)
I'm wanting to make a subclass of UITableViewController that has some specific functions in it. That way when I make a TableViewController in the storyboard I can just subclass this new class I made.
But when I try to replace UITableViewController with my new class, I get errors about not having viewDidLoad() function or any of the other lifecycle functions.
Here is my subclass of UITableViewController:
class TutorialTVC: UITableViewController {
var tutorialTab: TutorialTab?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.tutorialTab?.dropDownTab()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.tutorialTab?.pullUpTab(0.2)
}
func createTutorialTab(segueNameOnOpen: String) {
self.tutorialTab = TutorialTab(sourceVC: self, putTabBelowView: nil, segueNameOnOpen: segueNameOnOpen)
}
}
As you can see, I tried adding in the override for viewDidLoad(), but even then is states this:
Method does not override any method from its superclass.
I thought that when you subclass something, all of it's functions are brought in as well... Or is there additional code I need to add to make this happen. I'm pretty new to subclassing like this.
My guess is you didn't import UIKit in this file. However you can create your class as subClass of UITableViewController right from the interface as below,
This will handle import of UIKit. Also it will add all the essential methods to start with TableViewController.
I have a collectionView controller with a collectionView populated by a database. In this controller I have reloadData() in 'viewDidAppear' and 'viewWillAppear' functions.
The collectionView controller has a modal segue to a gameViewController. After the 'game' is finished in the gameViewController, the database is updated (this works) and the modal is dismissed back to the collectionView controller.
func gameOver() {
self.dismissViewControllerAnimated(true, completion: nil)
}
However despite 'viewDidAppear' and 'viewWillAppear' being called (I confirm this with a println()) when the gameViewController is dismissed, the collectionView data does not reload. So the updated database data isn't shown in the collectionView.
The collectionView data does reload if I dismiss the collectionView controller, and then reopen it.
How do I ensure the collectionView controller - reloadData() - is called after dismissing the gameViewController?
Code:
class GameViewController: UIViewController {
var collectionView: LevelsCollectionViewController!
override func viewDidLoad() {
super.viewDidLoad()
// code not related to question - creates game scene
}
override func viewDidDisappear(animated: Bool) {
collectionView.reloadData()
}
func gameOver() {
self.dismissViewControllerAnimated(true, completion: nil)
}
class LevelsCollectionViewController: UICollectionViewController {
// code setting up cells
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
println("viewWillAppear")
self.collectionView!.reloadData()
self.collectionView!.setNeedsDisplay()
}
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
println("viewDidAppear")
self.collectionView!.reloadData()
self.collectionView!.setNeedsDisplay()
}
}
UPDATE:
The println() viewWillAppear & viewDidAppear are printed in the console after the dismissViewController, but the collectonView data isn't reloaded.
The database that the collectionView is taken from is updated (done is another function called before gameOver().
I tried the method suggested below of having a reference to UIViewController and calling reloadData on that from viewDidDisappear, but nothing happens.
Actually that's what the completion argument is for
func gameOver() {
self.dismissViewControllerAnimated(true) { () -> Void in
self.collectionView.reloadData()
}
}
Check to make sure that your data source is being updated (a few println's in your viewDidAppear() will help) - in addition, try storing a reference to the collectionView controller and calling reloadData() on it in the viewWillDisappear() method of your gameViewController.
Some days I want to kick my own backside!
I 'stupidly' forgot to reload the temp array (levelProgress) that was holding the information taken from the database (held on array in appDelegate) in the collectionViewController.
Thus while the database and the array in appDelegate were being updated, the reloadData() was still working off the tempArray in the collectionViewController. Resetting this tempArray on viewDidAppear(), then reloadData() did the trick.
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
levelProgress = appDelegate.levelProgress
self.collectionView!.reloadData()
self.collectionView!.setNeedsDisplay()
}
Think I'm going code blind