I'm struggling to find what’s going wrong with my code. I’m trying to dismiss a UITableViewController using delegate but getting a EXC_BAD_ACCESS.
The UITableViewController is called (modal segue) from the root view controller of my application. When the rootViewController try to dismiss the UITableViewController everything seems to be all right because the rootViewController view is presented but after a milisecond the error arise.
- (void) dismissFormAViewController: (FormAViewController*) vc{
[vc dismissViewControllerAnimated:YES completion:^{
NSLog(#"complete.");
}];
}
I can see the string Complete on my console.
typically this means that you are accessing some memory that isn't a valid object anymore,
to debug turn on zombies in your run scheme, this will give you at least the class that is being accessed... Then if it is not obvious, you can back track with malloc logging
Related
After a viewcontroller has been presented modally, the initial preferredfocusedview is called. However, after we dismiss the viewcontroller and it has been dealloc. preferredfocusedview is not called after presenting the viewcontroller again. Running on tvOS 9.2.
Even adding the following did not help:
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setNeedsFocusUpdate];
[self updateFocusIfNeeded];
}
Anyone know what's going on? Or if there's anyways to debug this?
Edit:
the way I am adding the viewcontroller:
viewController = [[UIViewController alloc] init];
[viewController addChildViewController:self];
[viewController.view addSubview:self.view];
[self didMoveToParentViewController:viewController];
If you are using a container view, having multiple ViewControllers or adding only one View Controller, the preferredFocusEnvironments method must be called from the rootView Controller indicating which View Controller to focus.
For eg.
View Controller A has a container View having ViewControllers B and ViewController C inside the Container.
View Controller A should have preferredFocusEnvironments returning which ViewController to focus.
This way, preferredFocusEnvironments on ViewController B or ViewController C will be called whenever the view becomes visible.
If the ViewController A doesn't have preferredFocusEnvironments, then it won't be called on the containerView ViewControllers.
Implementing custom focus behavior in tvOS 9 is disaster. Apple already mentioned that there is a limitation on redirecting focus specially when presenting/ dismissing a viewcontroller in WWDC.
tvOS10 will handle munch better with preferredFocusEnvironments.
https://developer.apple.com/videos/play/wwdc2016/215/
When I needed to fix this focus redirection issues in viewDidAppear in tvOS 9, I had exactly same issues. Sometimes it works, sometimes not. No clue what so ever. But after I put split second delay on setNeedsFocusUpdate / updateFocusIfNeeded in viewDidAppear it was way better in terms of consistency. preferredFocusedView get called all the time.
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self setNeedsFocusUpdate];
[self updateFocusIfNeeded];
});
}
Do this in both presented and presenting view controllers, if you are manually changing focus. This is all from my observation and I don't think there is a proper way to achieve some focus behavior because tvOS API is kind of new and premature. Sorry about not being able to give you good explanation why this might work. Good luck.
I have an app that has an initial login screen then when the user wants to sign up, they are presented with a registration form that is three view controllers presented modally. When the user completes the form on the third screen (by pressing a "Done" button), I want the user to be taken back to the initial login screen.
I have tried doing this in the third view controller:
[self dismissViewControllerAnimated:NO completion:nil]
[self.presentingViewController dismissViewControllerAnimated:NO completion:nil]
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:NO completion:nil]
However it only dismissed two of the view controllers and not all 3. Why did this happen?
As other people pointed out, there are more elegant/efficient/easier ways to achieve similar results from the UX perspective: via a navigation controller, or a page view controller, or other container.
Short/quick answer: you need to go one step further in the chain of presenting view controllers, because the dismissal request needs to be sent to the controller that's presenting, and not to the one that's being presented. And you can send the dismiss request to that controller only, it will take care of popping from the stack the child controllers.
UIViewController *ctrl = self.presentingViewController.presentingViewController.presentingViewController;
[ctrl dismissViewControllerAnimated:NO completion:nil]
To explain why, and hopefully help other people better understand the controller presenting logic in iOS, below you can find are more details.
Let's start from Apple documentation on dismissViewControllerAnimated:completion:
Dismisses the view controller that was presented modally by the view controller.
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal.
Thus [self dismissViewControllerAnimated:NO completion:nil] simply forwarded the request to self.presentingViewController. Which means the first two lines had the same effect (actually the 2nd line did nothing as there was no presented controller after the 1st one executed).
This is why your dismissal of view controllers worked only the top 2 ones. You should've start with self.presentingViewController and go along the chain of presenting view controllers. But this is not very elegant and can cause problems if later on the hierarchy of view controllers changes.
Continuing to read on the documentation, we stumble upon this:
If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack.
So you needn't call dismissViewControllerAnimated:completion: three times, a call on the controller that you want to come back will suffice. At this point, passing a reference to that controller would be more reliable than navigating through the stack of view controllers.
There are some more useful details in the documentation, for example regarding what transitions apply when dismissing multiple controllers at once.
I recommend you go through the whole documentation, not only for this method, but for all methods/classes that you use in your application. You'll likely discover things that will make your life easier.
And if you don't have the time to read all Apple's documentation on UIKit, you can read it when you run into problems, like in this case with dismissViewControllerAnimated:completion: not working as you thought it would.
As a closing note, there are some more subtle issues with your approach, as the actual dismissal takes place in another runloop cycle, as it's possible to generate console warnings and not behave as expected. This is why further actions regarding presenting/dismissing other controllers should be done in the completion block, to give a change to UIKit to finish updating its internal state.
Totally understood. What I will do is embed a navigation controller instead of using modal. I have a case just like you. I have LoginViewController to be the root view controller of the UINavigationController. SignupViewController will be presented by push method. For ResetPasswordViewController, I will use modal because it's supposed to go back to LoginViewController no matter the results. Then, you can dismiss the whole UINavigationController from SignupViewController or LoginViewController.
Second approach will be like, you come up with your own mechanism to reference the presented UIViewController via a shared instance. Then, you can easily dismiss it. Be careful with the memory management. After dismissing it, you should consider whether you need to nil it right away.
I know three ways to dismiss several viewControllers:
Use a chain of completion blocks
~
UIViewController *theVC = self.presentingViewController;
UIViewController *theOtherVC = theVC.presentingViewController;
[self dismissViewControllerAnimated:NO
completion:^
{
[theVC dismissViewControllerAnimated:NO
completion:^
{
[theOtherVC dismissViewControllerAnimated:NO completion:nil];
}];
}];
Use 'viewWillAppear:' method of viewControllers
~
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.shouldDismiss)
{
CustomVC *theVC = (id)self.presentingViewController;
theVC.shouldDismiss = YES;
[self dismissViewControllerAnimated:NO completion:nil];
}
}
Pass a reference to LoginVC1 further down the chain.
(This is the best approach so far)
Imagine you have some StandardVC, which presented LoginVC1.
Then, LoginVC1 presented LoginVC2.
Then, LoginVC2 presented LoginVC3.
An easy way of doing what you want would be to call (from inside your LoginVC3.m file)
[myLoginVC1 dismissViewControllerAnimated:YES completion:nil];
In this case your LoginVC1 would lose its strong reference (from StandardVC), which means that both LoginVC2 and LoginVC3 would also be deallocated.
So, all you need to do is let your LoginVC3 know that LoginVC1 exists.
If you don't want to pass a reference of LoginVC1, you can use:
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:NO completion:nil];
However, the above approaches are NOT the correct ways of doing what you want to do.
I would recommend you doing the following:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if (!self.isUserLoggedIn)
{
[UIApplication sharedApplication].keyWindow.rootViewController = self.myLoginVC;
}
return YES;
}
Then, when user finished his login process, you can use
[UIApplication sharedApplication].keyWindow.rootViewController = self.myUsualStartVC;
In a project I'm writing I get this error when I present a new view controller:
Attempt to present.... while a presentation is in progress!
I think it happens because I first present a new view controller, and then in that view I present another view controller.
- (void)loadLabelSettings {
LabelSettingsViewController *labelSettings =
[[LabelSettingsViewController alloc] init];
labelSettings.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:labelSettings animated:YES completion:nil];
}
The program doesn't crash or anything it runs just fine, and there is no errors or warnings in my code. So my question is: Is it something I should be concerned with and if yes how do I solve it?
Thanks in advance :)
It is, like you said, probably caused by presenting two view controllers at the same time. Wait with presenting the second view controller until the first one has been fully presented. A good location would be to do this in viewDidAppear.
In my case, I connected a UIViewControllers UIButton with a second UIViewController by a UIStoryboardSegue. Inside my code a called it a second time programmatically. So pressing the UIButton caused presenting the specified view two times.
I figured out my problem, as Scott wrote it was because I was presenting 2 view controllers at the same time. It happened because I had a button that had a UILongPressGestureRecognizer, that showed the new view controller. The problem was that when using a UILongPressGestureRecognizer, the method that is being called, is called twice. First when the long press is detected and when your finger is released from the screen. So the presentViewController method of the same view, was called twice. I fixed this by only reacting to the first detection. Here is the code :
- (void)loadButtonSettings:(UILongPressGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
}
}
...
SecondViewController *svc = [SecondViewController new];
[self presentViewController:svc animated:YES completion:NULL];
}
This code is exactly the same as what I used in another app, but here I'm using presentViewController rather than presentModalViewController
(completion:NULL makes them effectively identical. Same result, at least.)
Both attempts at creating a modal view are structured the same way. Those lines in the main view, a view controller in the Storyboard, and matching .h and .m files. The only difference is that here I want a programmatic trigger, so it's impossible to drag a segue and be done with it.
I have an object set to recognize a gesture and call the transition method. That's probably what's causing the problem (part of it, at least), but it is necessary.
Using a UIButton would be cheating. No deadline, no shortcuts.
EDIT: NSLog output shows something odd.
2012-04-05 10:41:12.047 MyApp[5962:707] <SecondViewController: 0x1d8c130>
2012-04-05 10:41:12.479 MyApp[5962:707] <SecondViewController: 0x1d8e360>
So I'm doing something stupid again that happens to have a very simple fix, right?
Edit again: presentViewController… was being called more than once. Fixed it. Still black, though.
Back to performSegueWithIdentifier:sender: instead of the much easier presentViewController:animated:completion:
Terminating app due to uncaught exception 'NSInvalidArgumentException", reason: 'Receiver … has no segue with identifier …'
I told it to perform a segue, but there isn't one in the Storyboard (I can't add one, there is no Storyboard Segues section under 'Connections inspector' for the object I'm attempting to use), so it crashes. This is normal behavior.
What I want is to have a modal view without needing to create a segue. I've done it, so I know it's possible.
All I need is a bit of help getting it to work.
.
performSegueWithIdentifier:#"Identifier" sender:nil NSSInvalidArgumentException
presentViewController:viewController animated:YES completion:NULL Empty View
Got it.
Replace the lines in the question with this:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard"
bundle:nil];
SecondViewController *viewController =
[storyboard instantiateViewControllerWithIdentifier:#"SecondView"];
[self presentViewController:svc animated:YES completion:NULL];
Credit for this solution goes to IturPablo's self-answered question:
TabBarController, overwriting shouldSelectViewController to do a segue
Are you looking for performSegueWithIdentifier:sender:? The docs seem to match your description:
"you can call this method to trigger a segue programmatically, perhaps
in response to some action that cannot be specified in the storyboard
resource file"
I have a very simple app. 1 navigationController with 2 viewControllers.
The first view has only a button.
The second view has a map view (MKMapView).
I checked show currentlocation property of this map view
I created outlet and then connect everything by using Builder Interface.
In the dealloc method of the second one, I set nil to delegate of mapview, and then release mapview outlet.
mapView.delegate = nil;
[mapview release];
When I tap the button in view1, view2 will be loaded, and then I tap back button. If I do it normally, everything works well. But If I do it very quickly, repeat many times. The app will be crashed.
If I do not release mapView or I do not check showcurrentlocation property, app works well.
I can't figure out why this happen. Anyone helps me solve this problem. Thanks so much !
My fix in controller class was to ..
(void)dealloc
{
mapView.showUserLocation = NO; // Work around bug in MKMapView
[super dealloc];
}
Try launching your app via Instruments with Zombie instrument added. It should show you overreleased object. You'd also should add the Allocations instrument with VM tracker to see how memory consumption goes over time. This way you'd get your answer very quick.
p.s. more code would help better.