Swift - resetting vc in nav controller that is embedded in tab bar - swift

I have a customerViewController that has a simple a form. When the user presses submit, a segue is triggered and another view appears. The problem occurs when the user goes back to the customerViewController and finds all of the old information still there. I could simply reset the form fields, but what I'd really like is to find a way to reset the entire VC. From what I've learned so far, the way to reset a vc that hasn't been pushed is to remove it and then add it back.
customerViewController is the initial view controller in a navigation controller which is embedded in a tab bar controller. I have a tabBarController class that is a UITabBarControllerDelegate. This is where I call:
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if item.tag == 2 { //This is the tab with my navigation controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "CustomerVCID")
var viewcontrollers = self.navigationController?.viewControllers
viewcontrollers?.removeFirst()
viewControllers?.insert(vc, at: 0)
self.navigationController?.setViewControllers(viewcontrollers!, animated: true)
}
The problem with my code is that navigationController?.viewControllers is nil in the code above. I can reference viewControllers which gives me a list of tab bar viewControllers, but I'm not sure how to get from there to the navigation controller.
I guess my question is, assuming that I'm on the right track, how do I reference the view controllers in my navigation controller?

You can reset your form values in vc inside the viewWillAppear(_:),
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
//clear the textfields, textviews values etc. here.
}
}

It turns out, I was over-complicating things by trying to access navigationController.viewControllers or tabBarController.viewControllers. All I needed was viewControllers which is a property of UITabBarController that contains an array of the controllers associated with each tab:
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if item.tag == 2 { //tab with navigation controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vcon = storyboard.instantiateViewController(withIdentifier: "CustomerVCID")
for viewcontroller in viewControllers! {
if let vc = viewcontroller as? UINavigationController {
vc.viewControllers.removeFirst()
vc.viewControllers.insert(vcon, at: 0)
vc.setViewControllers(vc.viewControllers, animated: true)
}
}
}

Related

Presenting ViewController on Tabbar Item

I have a tabbar with 3 items. When I click on the second Item on the Tab, I want to present another tabbar which would be like a presentation and maybe the rootViewcontroller but I am unable to do that because it appears like a normal controller above the main tabbar.
I have extended UITabBarControllerDelegate
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if let controller = self.viewControllers?[self.selectedIndex] as? BaseVC {
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil)
}
}
BaseVC is the Tabbar controller I am trying to present.
but this does not change the behavior of the item.

Navigate from a controller to another controller having a root controller between them

I have 3 controllers: RootController, FirstController and SecondController.
I want to navigate from RootController -> FirstController (here I press a button) -> Take me to SecondController.
How can I do this without Notifications and Observers and without to create a segue from that button to SecondVC because after I need to press < Back and again < Back.
This is what I've tried to do but is not working:
import UIKit
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func goToSecondVC(_ sender: UIButton) {
// Go from first VC to second VC
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let showSecondVC = storyBoard.instantiateViewController(withIdentifier: "goSecondVC") as! SecondViewController
self.present(showSecondVC, animated:true, completion:nil)
}
}
For your code example to work your SecondViewController needs to have the Storyboard ID set to "goSecondVC". This is done in the Identity inspector in the third tab in the right pane. This will present your ViewController modally. However since you have a navigationController you most likely want to push the SecondViewController on the navigationStack like so:
navigationController?.pushViewController(secondVC, animated: true)

How reference `IBOutlet`s in subviews of `UIPageViewController`

I read if you instantiate a class (UIViewController) directly, via an initializer, IBOutlets won't be connected. My problem is I'm referencing a nil IBOutlet, causing a crash.
I have a lot of observers that update views in vc1, vc2, and vc3 (the "pages" in UIPageViewController). To keep my code clean, I want to add all the observers in the UIPageViewController class and not in the viewDidLoad of vc1, vc2, & vc3.
However, I can't reference the IBOutlets of subviews from UIPageViewController because they come up nil. Does anyone have advice on how to set up UIPageViewController in a way I can access the subview's IBOutlets?
I have had trouble in other apps updating page views that are not visible on the screen, (ex: updating a view in vc3 while vc1 is on screen). Is UIPageViewController a bad idea for this sort of goal? Should I just make one large view that the user can pan across?
// UIPageViewController.swift
var vc1: ViewController1 = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
return (storyboard.instantiateViewController(withIdentifier: "ViewController1") as? ViewController1)!
}()
var vc2: ViewController2 = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
return (storyboard.instantiateViewController(withIdentifier: "ViewController2") as? ViewController2)!
}()
var vc3: ViewController3 = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
return (storyboard.instantiateViewController(withIdentifier: "ViewController3") as? ViewController3)!
}()
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if viewController == vc2 {
return vc1
} else if viewController == vc3 {
return vc2
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if viewController == vc1 {
return vc2
} else if viewController == vc2 {
return vc3
}
return nil
}
func viewDidLayoutSubviews() {
// Adding observer for ViewController1
NotificationCenter.default.addObserver(self, selector: #selector(vc1.handleEventDataChange), name: .eventDataChanged, object: nil)
}
-
// ViewController1.swift
#IBOutlet weak var scoreBoard: UIView!
func viewDidLoad() {
super.viewDidLoad()
}
func handleEventDataChange() {
if scoreBoard.alpha != 0 { // <-- Crash here
// update scoreboard
}
}
EDIT:
In addition to JRTurton's answer, I also discovered that I was adding observers in the wrong class.
NotificationCenter.default.addObserver(self, selector: #selector(vc1.handleEventDataChange), name: .eventDataChanged, object: nil)
should be
NotificationCenter.default.addObserver(vc1, selector: #selector(vc1.handleEventDataChange), name: .eventDataChanged, object: nil)
You’re loading your view controllers from the storyboards, so the outlets will be getting connected (assuming it’s all wired up properly in the storyboard).
The most likely cause of the issue is that the methods are being called before the view controllers have loaded their view so they haven’t had a chance to connect anything yet.
Externally called functions that have side effects on outlets should probably begin with a check of isViewLoaded to prevent this issue. You should ideally be configuring model objects to pass to your view controller, which the view controller can either act on immediately (if the view is loaded) or use for configuration at viewDidLoad
(Also, view did layout sub views is not a great place to be adding observers - that can get called a lot)

show segue from modal viewController

I am trying to implement a push/show segue from a UITableViewController that is presented modally. I am able to perform a push segue from a row in the tableview, but it is delayed. I know it has something to do with the navigation hierarchy stack, and I have tried many things like resetting the rootViewController but am not having any luck...thanks for any help!
// present place tableViewController ---> modally
func handleShowPlacesVC() {
let vc = PlacesTableVC()
let navigationController = UINavigationController(rootViewController: vc)
present(navigationController, animated: true, completion: nil)
}
// show details tableViewController ---> push segue from tableview
func handleShowCurrentUserPlaceDetailsVC() {
let vc = CurrentUserPlaceDetailsVC()
navigationController?.pushViewController(vc, animated: true)
}

Swift: How do you pass information to a view controller underneath the current one?

I have two view controllers, HomeViewController, and SecondViewController, which is pushed on top of HomeViewController at the top of the stack. I have a segue from HomeVC to SecondVC by means of 'Show'. I understand how to pass information from SecondVC to HomeVC - using the prepare for segue method using identifiers. But I do not have a segue from SecondVC to HomeVC, as it is embedded in a navigation controller. How would I pass information, for example a string, from SecondVC back to HomeVC upon popping the SecondVC from the navigation stack?
You need to pass a ViewModel from the HomeViewController to the SecondViewController, what you do is give your Segue an Identifier and then override prepareForSegue in your HomeViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "mySegueIdentifier" {
if let destination = segue.destinationViewController as? SecondViewController {
destination.aViewModel = aViewModel;
}
}
}
In your SecondViewController you need to declare the ViewModel:
class SecondViewController: UIViewController {
var aViewModel: AViewModel?
//rest of code
}
And create a ViewModel, use a struct type:
struct AViewModel
{
let AValueIWantToPassAround: String
let AnotherValueIWantToPassAround: Int
}