This appears to be a well documented problem, yet the solutions online have not worked.
Here's just a sample list of posts that failed to provide me with a working answer:
ViewWillAppear not executing code
viewWillAppear not getting called
viewWillAppear not called
UINavigationController Inheritance, ViewWillAppear not called
viewWillAppear not called after popToViewController
iPhone viewWillAppear not firing
I have gleaned that my problem with viewWillAppear not getting called has to do with my view hierarchy. I am using a tab controller that is not the highest part of the view hierarchy. One of the tab controller's view controllers is a root view controller to a navigation controller. That's where I am trying to get a working viewWillAppear or viewDidAppear. Here's what I tried that has not worked. Within the tab controller I added this code:
let nav2 = UINavigationController(rootViewController: locationsVC)
nav2.beginAppearanceTransition(true, animated: false)
//...//
viewControllers = [ nav1, nav2, nav3, nav4 ]
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
for vc in self.children {
vc.beginAppearanceTransition(true, animated: animated)
}
}
In the scene delegate, this is my code:
guard let windowScene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: windowScene)
let rootVC = NewOrExistingViewController()
rootVC.beginAppearanceTransition(true, animated: false)
let rootNC = UINavigationController(rootViewController: rootVC)
rootNC.navigationController?.navigationBar.isHidden = true
rootNC.beginAppearanceTransition(true, animated: false)
self.window?.rootViewController = rootNC
let tbc = TabBarViewController()
tbc.selectedIndex = 0
tbc.beginAppearanceTransition(true, animated: false)
rootVC.add(asChildViewController: tbc)
Try calling rootVC.add(asChildViewController: tbc) before rootVC.beginAppearanceTransition(true, animated: false).
All your child VCs need to already be children of the parent before you call func beginAppearanceTransition(_: animated:)
Related
I'm working on a project, the project doesn't have storyboards, it just have a views as xib files with its own class , the question is : how can I navigate between these xib views ?
or is it possible to embed them in navigation controller ?
I try this code but nothing happened ?
let NC = UINavigationController()
#IBAction func showUserProfile(_ sender: Any) {
print("show Profile")
let vc = UserProfile(
nibName: "UserProfile",
bundle: nil)
NC.pushViewController(vc,
animated: true )
}
this is in app delegate
let mainView = BlockListViewController(nibName: "BlockListViewController", bundle: nil)
window?.addSubview(mainView)
let navigationControll = UINavigationController(rootViewController: mainView)
self.window?.rootViewController = navigationControll
self.window?.makeKeyAndVisible()
and I try to navigate when event occur using this
self.navigationController?.pushViewController(UserProfile(), animated: true)
You can't navigate between instances of UIView
according to apple UINavigationController
A container view controller that defines a stack-based scheme for navigating hierarchical content.
A navigation controller is a container view controller that manages
one or more child view controllers
so basically a stack of UIViewController, defined as [UIViewController]
read more about it in the documentation
What you can do is adding each UIView in a UIViewController and navigate thru that simply.
According to your comment you can predefined them into instance of VC and create a UINavigationController with you'r initial then simply push to the desired UIViewController from the UINavigationController
COMMENT UPDATE
As I got from your comment in the main view you already defining the UINavigationController simply replace NC.pushViewController(vc,animated: true )
with self.navigationController.pushViewController(vc, animated: true )
The problem is you are creating new UINavigationController while you already have the first one embedded
Comment update:
if you're using iOS 13+ with scene delegate use this inside willConnectTo
guard let scene = (scene as? UIWindowScene) else { return }
// Instantiate UIWindow with scene
let window = UIWindow(windowScene: scene)
// Assign window to SceneDelegate window property
self.window = window
// Set initial view controller from Main storyboard as root view controller of UIWindow
let mainView = BlockListViewController(nibName: "BlockListViewController", bundle: nil)
let navigationControll = UINavigationController(rootViewController: mainView)
self.window?.rootViewController = navigationControll
// Present window to screen
self.window?.makeKeyAndVisible()
and call self.navigationController.push
like this
#IBAction func didTap(_ sender: UIButton) {
let secondView = secondVC(nibName: "secondVC", bundle: nil)
self.navigationController?.pushViewController(secondView, animated: true)
}
I have a main tabBarController and would like to present a viewController modally when a certain tabBarItem is tapped.
I am loading the viewControllers in my tabBarController as...
func setupViewControllers() {
self.tabBar.isHidden = false
if let firstVC = storyboard?.instantiateViewController(withIdentifier: "first") as? FirstViewController, let secondVC = storyboard?.instantiateViewController(withIdentifier: "second") as? SecondViewController, let thirdVC = storyboard?.instantiateViewController(withIdentifier: "third") as? ThirdViewController, let fourthVC = storyboard?.instantiateViewController(withIdentifier: "fourth") as? FourthViewController, let fifthVC = storyboard?.instantiateViewController(withIdentifier: "fifth") as? FifthViewController {
let firstNavController = UINavigationController(rootViewController: firstVC)
let secondNavController = UINavigationController(rootViewController: secondVC)
let fourthNavController = UINavigationController(rootViewController: fourthVC)
let fifthNavController = UINavigationController(rootViewController: fifthVC)
firstNavController.tabBarItem.image = image
secondNavController.tabBarItem.image = image
fourthNavController.tabBarItem.image = image
fifthNavController.tabBarItem.image = image
thirdVC.tabBarItem.image = image
tabBar.tintColor = nil
//Load tabBar viewControllers
viewControllers = [homeNavController, postNavController, plusMenuVC, meetupNavController, profileNavController]
}
}
I then conformed the tabBarViewController to the UITabBarControllerDelegate to invoke the method...
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if tabBarController.selectedIndex == 2, let thirdVC = viewController as? ThirdViewController {
thirdVC.modalPresentationStyle = .overFullScreen
thirdVC.modalTransitionStyle = .crossDissolve
present(thirdVC, animated: true, completion: nil)
return false
} else { return true }
}
The above however never gets triggered. I tried setting in viewDidLoad
self.delegate = self
I tried setting the root navigation controllers and its ancestor tabBarController delegate to self.
Nothing seemed to work and I hope someone can guide me as I've been unable to debug and find an existing solution...
UPDATE
So I created a dummyThirdVC to replace the thirdVC in the setupViewControllers() function. In the dummyThirdVC, I conformed to UITabBarControllerDelegate and in viewDidLoad I set the self.tabBarController.delegate = self. Then I took the delegate method and entered it into this dummyThirdVC, where inside this delegate method, I instantiated the real thirdVC to present.
The delegate method finally triggers properly, but my issue now is, the dummyThirdVC and its view must first load and appear for the delegate to be set and triggered thereafter.
How can I not show the dummyThirdVC and immediately just present the instantiated, real thirdVC? I had tried dummyThirdVC.viewDidLoad() in my setupViewControllers function to no avail...
I believe your check is wrong. You're checking for selectedIndex to be 2, but the selectedIndex 's value will always be the actual tab bar's selected index, and not the index that is going to be selected, so you're basically never going to reach selectedIndex as 2.
You also cannot present a view controller that is already active, and thirdVC is already active in your tabBar, thus you will get an error. A workaround for this is to use a viewController as placeholder for image and title in the tab bar and instantiate another one for presenting.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController is ThirdViewController {
let vcToPresent = storyboard?.instantiateViewController(withIdentifier: "third") as? ThirdViewController
vcToPresent.modalPresentationStyle = .overFullScreen
vcToPresent.modalTransitionStyle = .crossDissolve
present(vcToPresent, animated: true, completion: nil)
return false
}
return true
}
I am trying to understand why my controller instances are not cleared
I have the HomeTabBarViewControlle embedded in the tab bar and navigation controller. On app launch, I am doing this
let rootVC = AppStoryboards.Main.instance.instantiateViewController(withIdentifier: "HomeTabBarViewController")
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = UINavigationController(rootViewController: rootVC)
Once HomeTabBarViewController with the first controller (i.e HomeDashBoardViewController) loads I have an action in HomeDashBoardViewController which navigating to StatusViewController controller
func btnNextAction() {
let vc = StatusViewController(nibName: "StatusViewController", bundle: nil)
self.navigationcontroller?.interactivePopGestureRecognizer?.isEnabled = false
self.navigationcontroller?.pushViewController(vc, animated: true)
}
Due to some logic I have to load the HomeDashBoardViewController again therefore in StatusViewController back button, I am doing this:
//If I do this self.navigationController?.popToRootViewController(animated: true) its ok.
let vc = ppStoryboards.Main.instance.instantiateViewController(withIdentifier: "HomeTabBarViewController")
vc?.selectedIndex = 0
self.navigationcontroller?.setViewControllers([vc], animated: true)
But the above code is creating multiple instances in the memory for 1st Controller (i.e HomeDashBoardViewController) of the HomeTabBarViewController. The old instances of HomeDashBoardViewController and HomeTabBarViewController not getting cleared
I also tried setting the window root controller again but the same issue. You can check the controller in-memory graph
I've found a memory leak in an app where the following is done:
Imagine two view controllers, each of which calls a function in the appDelegate similar to this:
func switchRootViewController() {
let vc = getTheOtherViewController()
self.window?.rootViewController = vc
}
So far this is working well - the other VC is shown and the one that called the function is deallocated.
But when you present a third view controller from first or second VC via:
present(ThirdViewController(), animated: true)
And then call the above function in the appDelegate from ThirdVC (to show viewController one or two by making it the rootViewController), this ThirdVC and the VC that presented it do not get deallocated.
Any idea why that is?
Can post sample project if needed. VC's are instantiated from storyboard if that makes any difference.
You are messing up the view hierarchy here. You should dismiss all the presented view controllers of the current rootViewController before switching to the new one. This is the only solution I've found to work!
Your switchRootViewController method should be like below,
func switchRootViewController() {
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.dismiss(animated: true, completion: {
let vc = getTheOtherViewController()
self.window?.rootViewController = vc
})
}
}
If you there are multiple view controllers presented,
func switchRootViewController() {
self.view.window!.rootViewController?.dismiss(animated: true, completion: {
let vc = getTheOtherViewController()
self.window?.rootViewController = vc
})
}
I am presenting a view controller after pushing another view controller with animation as my application requires it. The code is as given below:
func pushViewController() {
let viewcontroller = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(withIdentifier: "homeSearchIndentifier") as? HomeSearchViewController
let searchViewController = UIStoryboard(name: "Home", bundle: nil).instantiateViewController(withIdentifier: "searchRideIdentifier") as? SearchRideViewController
searchViewController?.parentView = .homeLandingPage
let duration = 0.4
let transition: CATransition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromBottom
self.navigationController!.view.layer.add(transition, forKey: kCATransition)
self.navigationController?.pushViewController(viewcontroller!, animated: false)
viewcontroller?.present(searchViewController!, animated: true, completion: nil)
}
In the presented view controller i am assigning a Side Menu controller as i need to show the side menu while swiping to the right:
override func viewDidLoad() {
super.viewDidLoad()
self.addSearchListScreen()
self.locationManager.delegate = self
yourLocation.layer.sublayerTransform = CATransform3DMakeTranslation(7, 0, 0)
let leftViewController = UIStoryboard(name: "SideBar", bundle: nil).instantiateViewController(withIdentifier: "sideBar") as? SideBarViewController
let slideMenuController = SlideMenuTrackerController(mainViewController: self, leftMenuViewController: leftViewController!)
AppDelegate.sharedInstance().window?.rootViewController = slideMenuController
leftViewController?.mainViewController = self
slideMenuController.automaticallyAdjustsScrollViewInsets = true
AppDelegate.sharedInstance().window?.backgroundColor = UIColor(red: 236.0, green: 238.0, blue: 241.0, alpha: 1.0)
AppDelegate.sharedInstance().window?.makeKeyAndVisible()
}
Now i have a custom back button in this view controller. On clicking it i need to dismiss the view controller and show the view controller from where it was presented . The name is "HomeSearchViewController"
For dismissing i wrote the following code in the button action:
func navigateToPreviousController() {
self.dismiss(animated: true, completion: nil)
}
But this is not dismissing the view controller. May i know what is the issue?
in order to dismiss the view controller your view controller should be embedded inside a navigation controller.
eg:
let controller =storyboard.instantiateViewController(withIdentifier: "storyboardidentifier") as! yourviewcontroller
let navController = UINavigationController(rootViewController: controller)
present(navController, animated: true, completion: nil)
and then on the presented view controller if you use below code it will dismiss the view controller.
func dismissButtonPressed(){
self.dismiss(animated: true, completion: nil)
}
Cheers!!
Edit: My first answer addressed only part of the problem.
Looking further at your code...
It appears you are pushing HomeSearchViewController onto the navigationController stack, and then also presenting SearchRideViewController from HomeSearchViewController.
However...
In:
HomeSearchViewController viewDidLoad()
you create an instance of
SideBarViewController (leftViewController)
and you create an instance of
SlideMenuTrackerController (slideMenuController)
and assign
self as the .mainViewController and
leftViewController as the .leftMenuViewController
of slideMenuController.
If that's not confusing enough, you also:
AppDelegate.sharedInstance().window?.rootViewController = slideMenuController
which replaces HomeSearchViewController ... which you had just pushed onto the navigationController stack!
So, your view hierarchy is now (or, at least, seems to be if I am following your code):
Window
slideMenuController (instance of SlideMenuTrackerController)
+- searchViewController (instance of SearchRideViewController)
+- leftViewController (instance of SideBarViewController)
In order to "get back to" where you came from, you will need to re-create whatever VC you want to get to and (again) replace AppDelegate.sharedInstance().window?.rootViewController.
Without seeing your entire project, I'm not sure how you should really go about that.
[original answer]
In here:
func pushViewController() {
// ... the other lines
self.navigationController?.pushViewController(viewcontroller!, animated: false)
viewcontroller?.present(searchViewController!, animated: true, completion: nil)
}
You push viewcontroller and also .present(searchViewController!... from viewcontroller (that's a bit odd to begin with, but regardless)...
Then in here:
func navigateToPreviousController() {
self.dismiss(animated: true, completion: nil)
}
You .dismiss a view controller, but you never .pop back on the navigation controller's stack.