Q:How to force destroy a viewcontroller in iOS? - ios

Is it possible to recycle/force destroy a UIViewController in iOS ?
I am using this github project to get a custom UIViewControllerTransition:
Here is the flow of the program:
vc1 presents to a nav to which the rootVc is vc2
In vc2 there is a UIButton. When clicked, vc2's NavigatioCcontroller will dismiss.
But the issue is that vc2 is not recycled by the OS, so when I progress to vc1, then present to nav (which rootVc is vc2), vc2 does not call the viewDidLoad method.
Not sure where the problem lies. Is there a way so that when I click vc2's UIButton, force destroy the nav and vc2? This way, when I re-present to nav, vc2's viewDidload will be called again.
Code:
in vc1:
LMLQQSearchSelectViewController *search_vc = [[LMLQQSearchSelectViewController alloc] initWithNibName:#"LMLQQSearchSelectViewController" bundle:nil];
search_vc.fromController = #"KnowledgeViewController";
search_vc.pre_type = #"ENCYCLOPEDIACOL";
LMLQQSearchNavController *nav = [[LMLQQSearchNavController alloc] initWithRootViewController:search_vc];
nav.navigationBarHidden = YES;
__weak typeof(self) weakSelf = self;
_search_header.block = ^(){
weakSelf.transition = [[HYBEaseInOutTransition alloc] initWithPresented:^(UIViewController *presented, UIViewController *presenting, UIViewController *source, HYBBaseTransition *transition) {
HYBEaseInOutTransition *modal = (HYBEaseInOutTransition *)transition;
// If you don't specify, it will use default value
// Default is NO, if set to YES, it will use spring animation.
modal.animatedWithSpring = NO;
} dismissed:^(UIViewController *dismissed, HYBBaseTransition *transition) {
// do nothing
}];
nav.transitioningDelegate = weakSelf.transition;
[weakSelf presentViewController:nav animated:YES completion:NULL];
};

When nav is dismissed, you must be hanging onto a reference to it so that you can redisplay it later. And because nav is holding a reference to vc2, you get the same instance back again when you redisplay nav.
You have two options. One, you could release your reference to nav after it is dismissed, which will then release vc2 as well. Two, you could move the code you want to run every time vc2 appears from viewDidLoad into viewDidAppear.

Related

IOS/Autolayout: Change to Presenting View After Presented Controller Dismissed

I am trying to get autolayout to work properly on a view controller with a scrollview. When I load the view controller the first time, it is not doing what I want. However, if I launch a second modal VC--which happens to be an Edit VC and then dismiss it, without doing anything else, the original VC lays out properly (the way I want).
At first, the size of a textview is not adapted to its content but merely reflects is placeholder view in storyboard.
After launching and dismissing the modal VC, the textview does adopt to its content.
As far as I know there is nothing that happens in the course of the 2ndVC launching that could change anything with the 1st.
My logs show ViewDidLoad is called by the first VC just once when it originally loads. Viewwillappear is called by the first VC twice: when it originally loads and then after the 2nd VC is dismissed.
Are there any methods other than view willappear that fire in a presenting VC when you dismiss a presented one?
This is the code in VC1 to launch the 2nd VC.
UIStoryboard *storyBoard = self.storyboard;
IDEditListVC *editVC =
[storyBoard instantiateViewControllerWithIdentifier:#"editlist"];
editVC.list = _list;
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController: editVC];
[self presentModalViewController:nav animated:YES];
//This is the code in VC1 viewWillAppear to obtain the height of text view. Result changes before and after launching and dismissing 2nd VC
self.textView.text = listText;
float listHeight = self.textView.contentSize.height;
NSLog(#"listHeight%f",listHeight);
Try this
-(void)viewDidLayoutSubviews
{
self.textView.text = listText;
float listHeight = self.textView.contentSize.height;
NSLog(#"listHeight%f",listHeight);
}

Same view controller loads twice on click of button

I want to show a controller as pop-up on click of a button. Suppose I have VC1 that has button, then on click of that the VC2 should load as a pop up. It is working to some extent. But I don't' know for what reason the VC2 is loading twice and the second time it loads it shows black background. Here is my code:
On click of button below function gets called in VC1,
VC2* childVC = [[VC2 alloc]init];
childVC.view.hidden = YES;
[self addChildViewController:childVC];
[self.view addSubview:childVC.view];
[self performSegueWithIdentifier:#"goToVC2" sender:self];
In VC2 viewdidload,
self.view.backgroundColor = [[UIColor blackColor]colorWithAlphaComponent:0.5];
How can I make VC2 appear as a pop up properly?
You don't need to segue and add the child view controller's view to the current view controller's view hierarchy. You should do one or the other. I would suggest the segue as that is a more apple approved approach. So change this:
VC2* childVC = [[VC2 alloc]init];
childVC.view.hidden = YES;
[self addChildViewController:childVC];
[self.view addSubview:childVC.view];
[self performSegueWithIdentifier:#"goToVC2" sender:self];
to
[self performSegueWithIdentifier:#"goToVC2" sender:self];

Is it possible to insert a subview while being on the same tab bar index?

To give some context, I have logic that programmatically decides what view controller to insert into the navigation controller. For example:
If(true){
MyViewController * MyObject = [[MyViewController alloc]init];
myNavigationController = [[UINavigationController alloc]initWithViewController:MyObject];
else {
MyOtherViewController * MyOtherObject = [[MyViewController alloc]init];
myNavigationController = [[UINavigationController alloc]initWithViewController:MyOtherObject];
}
self.tabBarController.viewControllers=[NSArray arrayWithObjects:myNavigationController,nil];
Hopefully that illustrates my point of how I insert views inside of navigation controller. Now onto my problem:
I have an action listener with a button inside of "MyViewController" that essentially replaces the navigation/tab bar index when the user clicks the button. Is it possible to update a navigation/tab bar index with just a button?
MyViewController.m
- (IBAction)MyActionListener:(id)sender {
MyOtherViewController *MyOtherObject = [[MyOtherViewController alloc] initWithNibName:#"MyOtherViewController" bundle:nil];
[self.view insertSubview:MyOtherViewController.view atIndex:2];
}
When I do this, I get a crash EXEC_BAD_ACCESS I'm just wondering if my implementation/approach is wrong. I noticed this question: Update UITabBar Views?
However, doesn't seem to fit the results I am looking for. Hopefully I am clear. Thanks!
Yes it is possible to switch your views on button click with navigation.
You currently in VC1 , you have other two vc VC2 & VC3 and on button click you choose VC2 or VC3 but you did not change the VC1 place.

UITabBar disappears after pushed to new view controller

I have an UITabBarController that has 3 buttons. The second button points to ViewController1 which is connected to another view called ViewController2. After I tap a button in ViewController2 I programmatically present ViewController1 again, that works perfect except one thing. After I "arrived" to ViewController1 the tab bar disappears.
I'm using this method to navigate back to ViewController1. (exactly I navigate to its navigation controller, but already tried with the view)
- (void)presentViewControllerAnimated:(BOOL)animated {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"storyboard" bundle:nil];
UINavigationController *firstViewNavigationController = [storyboard instantiateViewControllerWithIdentifier:#"destination"];
[self presentViewController:firstViewNavigationController animated:animated completion:nil];
}
I call here the first method
- (void)didTapButton:(id)sender {
UIButton *button = (UIButton *)sender;
CGPoint pointInSuperview = [button.superview convertPoint:button.center toView:self.tableView];
[self presentViewControllerAnimated:YES];
}
This method hides the tab bar in the ViewController2, I already tried without it, therefore there is no problem with it.
-(BOOL)hidesBottomBarWhenPushed
{
return YES;
}
I can't figure out why this thing happens, I think it's a fair solution, that worked well for a several times when I needed to present views. I've read it can happen with segues, but I'm doing it with code without segues.
Actually your code works right. There should not be tab bar when you present FirstViewController from SecondViewController. Because when you call instantiateViewControllerWithIdentifier its basically creates a new instance of that view controller, and of course, there is no tab bar.
The right way to go back to your first view controller is to pop SecondViewController (or dismiss it, if it presented modally). So your final code should be like this
- (void)didTapButton:(id)sender {
// If this view controller (i.e. SecondViewController) was pushed, like in your case, then
[self.navigationController popViewControllerAnimated:YES];
// If this view controller was presented modally, then
// [self dismissViewControllerAnimated:YES completion:nil];
}
And of course, your view controller hierarchy in storyboard must be like this:
-- UINavigationController -> FirstViewController -> SecondViewController
|
->UITabBarController____|
-...
-...
I've tried the same and got the same result.
My solution was simple, on the push do this :
UINavigationController *firstViewNavigationController = [storyboard instantiateViewControllerWithIdentifier:#"destination"];
firstViewNavigationController.hidesBottomBarWhenPushed = true; // Insert this and set it to what you want to do
[self presentViewController:firstViewNavigationController animated:animated completion:nil];
and then remove your
-(BOOL)hidesBottomBarWhenPushed
{
return YES;
}

Unbalanced calls to begin/end appearance transitions for <UITabBarController: 0x197870>

I read SO about another user encountering similar error, but this error is in different case.
I received this message when I added a View Controller initially:
Unbalanced calls to begin/end appearance transitions for
<UITabBarController: 0x197870>
The structure of the app is as follow:
I got a 5-tab TabBarController linked to 5 View Controllers. In the initial showing tab, I call out a new View Controller to overlay as an introduction of the app.
I use this code to call the introduction view controller:
IntroVC *vc = [[IntroVC alloc] init];
[self presentModalViewController:vc animated:YES];
[vc release];
After this IntroVC view controller shows up, the above error shows.
p.s. I am using xCode 4.2 & iOS 5.0 SDK, developing iOS 4.3 app.
Without seeing more of the surrounding code I can't give a definite answer, but I have two theories.
You're not using UIViewController's designated initializer initWithNibName:bundle:. Try using it instead of just init.
Also, self may be one of the tab bar controller's view controllers. Always present view controllers from the topmost view controller, which means in this case ask the tab bar controller to present the overlay view controller on behalf of the view controller. You can still keep any callback delegates to the real view controller, but you must have the tab bar controller present and dismiss.
I fixed this error by changing animated from YES to NO.
From:
[tabBarController presentModalViewController:viewController animated:YES];
To:
[tabBarController presentModalViewController:viewController animated:NO];
As posted by danh
You can generate this warning by presenting the modal vc before the app is done initializing. i.e. Start a tabbed application template app and present a modal vc on top of self.tabBarController as the last line in application:didFinishLaunching. Warning appears. Solution: let the stack unwind first, present the modal vc in another method, invoked with a performSelector withDelay:0.0
Try to move the method into the viewWillAppear and guard it so it does get executed just once (would recommend setting up a property)
Another solution for many cases is to make sure that the transition between UIViewControllers happens after the not-suitable (like during initialization) procedure finishes, by doing:
__weak MyViewController *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf presentViewController:vc animated:YES];
});
This is general for also pushViewController:animated:, etc.
I had the same problem. I called a method inside viewDidLoad inside my first UIViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:#selector(loadingView)
withObject:nil afterDelay:0.5];
}
- (void)loadingView{
[self performSegueWithIdentifier:#"loadedData" sender:self];
}
Inside the second UIViewController I did the same also with 0.5 seconds delay. After changing the delay to a higher value, it worked fine. It's like the segue can't be performed too fast after another segue.
I had the same problem when I need to Present My Login View Controller from another View Controller If the the User is't authorized, I did it in ViewDidLoad Method of my Another View Controller ( if not authorized -> presentModalViewController ). When I start to make it in ViewDidAppear method, I solved this problem. I Think that ViewDidLoad only initialize properties and after that the actual showing view algorithm begins! Thats why you must use viewDidAppear method to make modal transitions!
If you're using transitioningDelegate (not the case in this question's example), also set modalPresentationStyle to .Custom.
Swift
let vc = storyboard.instantiateViewControllerWithIdentifier("...")
vc.transitioningDelegate = self
vc.modalPresentationStyle = .Custom
I had this problem because of a typo:
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
instead of
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
It was calling "WillAppear" in the super instead of "DidAppear"
I had lot of problem with the same issue. I solved this one by
Initiating the ViewController using the storyboad instantiateViewControllerWithIdentifier method. i.e Intro *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"introVC"];
[self.tabBarController presentModalViewController : vc animated:YES];
I have the viewcontroller in my storyboard, for some reason using only [[introvc alloc] init]; did not work for me.
I solved it by writing
[self.navigationController presentViewController:viewController
animated:TRUE
completion:NULL];
I had this problem with a third party code. Someone forgot to set the super inside of viewWillAppear and viewWillDisappear in a custom TabBarController class.
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// code...
}
or
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// code...
}
I had the same error. I have a tab bar with 3 items and I was unconsciously trying to call the root view controller of item 1 in the item 2 of my tab bar using performSegueWithIdentifier.
What happens is that it calls the view controller and goes back to the root view controller of item 2 after a few seconds and logs that error.
Apparently, you cannot call the root view controller of an item to another item.
So instead of performSegueWithIdentifier
I used [self.parentViewController.tabBarController setSelectedIndex:0];
Hope this helps someone.
I had the same problem and thought I would post in case someone else runs into something similar.
In my case, I had attached a long press gesture recognizer to my UITableViewController.
UILongPressGestureRecognizer *longPressGesture = [[[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(onLongPress:)]
autorelease];
[longPressGesture setMinimumPressDuration:1];
[self.tableView addGestureRecognizer:longPressGesture];
In my onLongPress selector, I launched my next view controller.
- (IBAction)onLongPress:(id)sender {
SomeViewController* page = [[SomeViewController alloc] initWithNibName:#"SomeViewController" bundle:nil];
[self.navigationController pushViewController:page animated:YES];
[page release];
}
In my case, I received the error message because the long press recognizer fired more than one time and as a result, my "SomeViewController" was pushed onto the stack multiple times.
The solution was to add a boolean to indicate when the SomeViewController had been pushed onto the stack. When my UITableViewController's viewWillAppear method was called, I set the boolean back to NO.
I found that, if you are using a storyboard, you will want to put the code that is presenting the new view controller in viewDidAppear. It will also get rid of the "Presenting view controllers on detached view controllers is discouraged" warning.
In Swift 2+ for me works:
I have UITabBarViewController in storyboard and I had selectedIndex property like this:
But I delete it, and add in my viewDidLoad method of my initial class, like this:
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.selectedIndex = 2
}
I hope I can help someone.
This error will be displayed when trying to present an UINavigationController that is lazily initialized via a closure.
Actually you need to wait till the push animation ends. So you can delegate UINavigationController and prevent pushing till the animation ends.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
waitNavigation = NO;
}
-(void)showGScreen:(id)gvc{
if (!waitNavigation) {
waitNavigation = YES;
[_nav popToRootViewControllerAnimated:NO];
[_nav pushViewController:gvc animated:YES];
}
}
As #danh suggested, my issue was that I was presenting the modal vc before the UITabBarController was ready. However, I felt uncomfortable relying on a fixed delay before presenting the view controller (from my testing, I needed to use a 0.05-0.1s delay in performSelector:withDelay:). My solution is to add a block that gets called on UITabBarController's viewDidAppear: method:
PRTabBarController.h:
#interface PRTabBarController : UITabBarController
#property (nonatomic, copy) void (^viewDidAppearBlock)(BOOL animated);
#end
PRTabBarController.m:
#import "PRTabBarController.h"
#implementation PRTabBarController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.viewDidAppearBlock) {
self.viewDidAppearBlock(animated);
}
}
#end
Now in application:didFinishLaunchingWithOptions:
PRTabBarController *tabBarController = [[PRTabBarController alloc] init];
// UIWindow initialization, etc.
__weak typeof(tabBarController) weakTabBarController = tabBarController;
tabBarController.viewDidAppearBlock = ^(BOOL animated) {
MyViewController *viewController = [MyViewController new];
viewController.modalPresentationStyle = UIModalPresentationOverFullScreen;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[weakTabBarController.tabBarController presentViewController:navigationController animated:NO completion:nil];
weakTabBarController.viewDidAppearBlock = nil;
};
you need make sure -(void)beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated and -(void)endAppearanceTransition is create together in the class.
I had the same issue. When developing I wanted to bypass screens. I was navigating from one view controller to another in viewDidLoad by calling a selector method.
The issue is that we should let the ViewController finish transitioning before transitioning to another ViewController.
This solved my problem: The delay is necessary to allow ViewControllers finish transitioning before transitioning to another.
self.perform(#selector(YOUR SELECTOR METHOD), with: self, afterDelay: 0.5)
For me this error occurred because i didn't have UIWindow declared in the upper level of my class when setting a root view controller
rootViewController?.showTimeoutAlert = showTimeOut
let navigationController = SwipeNavigationController(rootViewController: rootViewController!)
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
Ex if I tried declaring window in that block of code instead of referencing self then I would receive the error
I had this problem when I had navigated from root TVC to TVC A then to TVC B. After tapping the "load" button in TVC B I wanted to jump straight back to the root TVC (no need to revisit TVC A so why do it). I had:
//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:YES];
//Pop self to return to root
[self.navigationController popViewControllerAnimated:YES];
...which gave the error "Unbalanced calls to begin/end etc". The following fixed the error, but no animation:
//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:NO];
//Then pop self to return to root
[self.navigationController popViewControllerAnimated:NO];
This was my final solution, no error and still animated:
//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:NO];
//Then pop self to return to root, only works if first pop above is *not* animated
[self.navigationController popViewControllerAnimated:YES];
I encountered this error when I hooked a UIButton to a storyboard segue action (in IB) but later decided to have the button programatically call performSegueWithIdentifier forgetting to remove the first one from IB.
In essence it performed the segue call twice, gave this error and actually pushed my view twice. The fix was to remove one of the segue calls.
Hope this helps someone as tired as me!
Swift 5
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Delete or comment the below lines on your SceneDelegate.
// guard let windowScene = (scene as? UIWindowScene) else { return }
// window?.windowScene = windowScene
// window?.makeKeyAndVisible()
let viewController = ListVC()
let navViewController = UINavigationController(rootViewController: viewController)
window?.rootViewController = navViewController
}

Resources