PresentViewController only working once - ios

I'm a bit dazzled here. And I do think it might be something stupid, but here it goes.
I'm using ABPadLockScreen to set a password and it works just fine when I open the app first time and if the app is closed (terminated) and reopened, but if I just go home and return to it I get the following warning
Warning: Attempt to present <ABPadLockScreenViewController: 0x7fdc70f1d5a0> on <ViewController: 0x7fdc70f18e00> whose view is not in the window hierarchy!
Just for more information here is the code that triggers it, it's inside the viewDidAppear
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
if (!self.pin && delegate.terminated) {
// [[[UIAlertView alloc] initWithTitle:#"No Pin" message:#"Please Set a pin before trying to unlock" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
//return;
[self dismissViewControllerAnimated:YES completion:nil];
} else if (!self.isPin && !delegate.terminated) {
ABPadLockScreenViewController *lockScreen = [[ABPadLockScreenViewController alloc] initWithDelegate:self complexPin:YES];
[lockScreen setAllowedAttempts:3];
lockScreen.modalPresentationStyle = UIModalPresentationFullScreen;
lockScreen.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:lockScreen animated:YES completion:nil];
}
If you're wondering about the delegate, it was so the Password Screen would show up when the app was simply closed and reopened and that's when the warning occurs.
Also, if I simply switch from one view to another, the damn thing shows up again.
Thanks in advance

Related

UIAlertView delayed or not showing up when calling methods from another view

UIAlertView delayed or not showing up when pass another view. Any help would be greatly appreciated.
-(void)viewDidLoad{
levelContentController = [[UIAlertView alloc] initWithTitle:#""
message:#"Loading...
delegate:self
cancelButtonTitle:nil
otherButtonTitles:nil];
}
-(void)passToTestView:(id)sender{
[levelContentController show];
ViewController *viewController = [[ViewController alloc]init];
clickedLevelId = [[NSString alloc] init];
clickedLevelId = [testIdStringArray objectAtIndex:[sender tag]-1];
[viewController sendIndexMethod:sendIndex];
[viewController testCompletedArrayMethod:arrayOfCompletedTest];
[viewController parseTestURL:buttonTag getTestIdString:clickedLevelId];
viewController.viewSoundCheck = _levelSoundCheck;
[self presentViewController:viewController animated:YES completion:nil];
}
-(void)viewWillDisappear:(BOOL)animated{
[levelContentController dismissWithClickedButtonIndex:0 animated:YES];
}
The above code has some problems.
When passToTestView: selector is called, it'll try to present the alertView. But within the same method you are trying to present another view controller.
This will in turn call viewWillDisappear: where you are hiding the alertView.
It's recommended that if you want to present the alertView while displaying ViewController, create and display UIAlertView instance in the ViewController class's viewDidLoad or viewWillAppear:. Do not initialise and display it in this viewController.
To make the UIAlertView appear, you should call:
[contentLoadingController show];
Note that UIAlertView is deprecated in iOS9, and you should use UIAlertController.

UIAlertView is not positioned at the center of the screen

I have to fix some bugs in an old application and got a really weird one. All alert views are shown in the top left corner for some reason. App doesn't use storyboards, main window is loaded from xib. What can cause this?
UPDATE: Code to initialize alert view is pretty standard. I'm calling it inside one of the controllers of UITabBarController. Same bug occurs even if I leave app's main window empty and initialize alert view in application:didFinishLaunchingWithOptions:
UIAlertView *testAlert = [[UIAlertView alloc] initWithTitle:#"Title"
message:#"Message"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil, nil];
[testAlert show];
I fixed this by simply replacing the [alert show] with the present [self presentViewController:alert animated:YES completion:nil];

Why calling MFMailComposeViewController deallocates the view that called it?

I have this simple function to write mail on ios 8.0, xcode 6.1. Nothing special, it ever worked till ios 8.0 said hello.
The function sendMail sends a mail when a button gets tapped (set via storyboard). I debugged it and the code seems ok, but it crashes with a EXC_BAD_ACCESS code=1 error whenever you're supposed to return back to the main app, like when you tap the send or dismiss button of the MFMailComposeViewController. It seems to me that my View, i mean the view calling the MFMailComposeViewController is deallocated when the MFMailComposeViewController is called, so that when it get dismissed, there's nothing to return on. Some ideas to solve the problem?
[The function didFinishWithResult is never reached: the crash happens before.]
EDIT: To be precise it crashes with bad_access if in the presentViewController i set animated to NO. If it is YES it complain about 'unbalanced calls to begin/end appearance transitions' and tapping send/dismiss does nothing (it not returns. The mail view is alive but tap button does nothing)
EDIT: I am right about the deallocation. He have the same problem but it does not seems to have a valid solution MFMailComposeViewController crash in iOS6 ARC
In my case Arc is turned off and i cannot turn it on for various reasons. The theory is confirmed by zombie instrument. It says 'An Objective-C message was sent to a deallocated 'View' object (zombie) at address: 0x14e01720'
View.h
included MFMailComposeViewControllerDelegate and imported
#import <MessageUI/MFMailComposeViewController.h>
View.m
-(IBAction)sendEmail:(id)sender{
NSString *mailDiretta=emailText.currentTitle;
composer = [[MFMailComposeViewController alloc] init];
[composer setMailComposeDelegate:self];
if ([MFMailComposeViewController canSendMail]) {
[composer setToRecipients:[NSArray arrayWithObjects:mailDiretta, nil]];
[composer setSubject:#"Infos"];
[composer setMessageBody:#"Write to me, dude!" isHTML:NO];
[composer setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentViewController:composer animated:YES completion:NULL];
[composer release];
} else
[composer release];
}
// function below is never reached
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
if (error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"error"
message:[NSString stringWithFormat:#"error %#", [error description]]
delegate:nil cancelButtonTitle:#"dismiss" otherButtonTitles:nil, nil];
[alert show];
[self dismissViewControllerAnimated:YES completion:NULL];
} else {
[self dismissViewControllerAnimated:YES completion:NULL];
}
}
I would assume it's because you're releasing the composer before it finishes.
[composer release];
EDIT: How is this property initialized and why is it a property? Create it in the method and try. Also, your unbalanced calls are happening because you're animating a UIAlert at the same time you are animating the mail controller dismiss. Each needs to finish prior to prevent that message.
composer = [[MFMailComposeViewController alloc] init];
Try to remove the property and initialize in the function.
MFMailComposeViewController *composer = [[MFMailComposeViewController alloc] init];
Make sure you added the delegate properly as well.
#import <MessageUI/MessageUI.h>
#interface YourViewController : UIViewController <MFMailComposeViewControllerDelegate>
Set your delegate like this
composer.mailComposeDelegate = self;
For the unbalanced calls, rearrange your alert like this...
[self dismissViewControllerAnimated:YES completion:NULL];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"error"
message:[NSString stringWithFormat:#"error %#", [error description]]
delegate:nil cancelButtonTitle:#"dismiss" otherButtonTitles:nil, nil];
[alert show];
EDIT 2:
After seeing your comment about not being able to use ARC due to one class I would advice you to simply set a -fno-objc-arc compiler flag on that class and enable ARC across your project and make your life WAY easier.
Disable Automatic Reference Counting for Some Files

Window coordinates changing when alertview is visible while switching rootviewcontroller

I am facing an issue with window coordinates for new UINavigationController as windows rootViewController, so I created a simple sample project which also reproduces it.
I launch the app with an initial ViewController having a button with the following action:
- (IBAction)buttonTapped:(id)sender {
[[[UIAlertView alloc] initWithTitle:#"" message:#"New VC in 3 seconds" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
[self performSelector:#selector(delayCall) withObject:nil afterDelay:3];
}
- (void)delayCall {
NewViewController *nvc = [[NewViewController alloc] initWithNibName:#"NewViewController" bundle:nil];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:nvc];
[[UIApplication sharedApplication].keyWindow setRootViewController:nav];
}
NewViewController is in landscape mode, but comes up as in the below screenshot if a UIAlertView is shown before changing the rootViewController of the window and is not dismissed.
If I don't show the alert view or dismiss the AlertView before changing the rootViewController the new ViewController comes up fine.
Is there anything that can be done to prevent this? Switching rootViewController and showing AlertView is part of a larger app we are creating. Unfortunately we cannot change it now, so we'll have to find some workaround for this flow itself.

Duplicate UIViewControllers on UINavigationController stack after application wakes

I just learnt IOS programming and am writing my first iPhone app.
My app provides info on an MKMapView, the view controller of which is the root view controller of a UINavigationController. If the mobile signal is bad i use mapViewDidFailLoadingMap:withError: to make the app push one of two different view controllers onto the navigation controller stack depending on what the user is doing. Code follows:
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error
{
NSLog(#"mapViewDidFailLoadingMap: %#", [error localizedDescription]);
[aiView stopAnimating];
if (mapTypeInt == 0) {
NSString *message =
[NSString stringWithFormat:#"Your signal is not currently
strong enough to download a map. Switching to table view."];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Maps Unavailable"
message:message
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
if (currentMode == #"retracingSteps")
{
RetraceViewController *rvc =
[[RetraceViewController alloc] initWithNibName:#"RetraceViewController" bundle:nil];
[[self navigationController] pushViewController:rvc animated:YES];
}
else{
TripTableViewController *ttvc = [[TripTableViewController alloc] init];
[[self navigationController] pushViewController:ttvc animated:YES];
}
}
else{
[self setMapType:0];
NSString *message = [NSString stringWithFormat:
#"Your signal is not currently strong enough to download a satellite map.
Switching to Standard Map view."];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Can't Use Satellite Maps"
message:message
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
}
}
Yesterday I tested the poor mobile signal in a rural valley and the correct second view controller was pushed onto the stack. What I then noticed when I locked the phone and rechecked the app after a few minutes was that on waking, the root view controller was displayed quickly followed by the view controller I expected. In effect what this did was to push an identical copy of the second view controller onto the stack. I discovered this when I had to tap the back button half a dozen times to get back to the root view controller.
What I would like the app to do on wake up is to immediately display the view controller that was live when the phone is locked rather than the root view controller. I do not know how to do this.
This is probably happened because even you lock the screen your app still receiving locations update (location services can run code in a real background mode), also when you push a view controller the previous one in the stack still exist and still receiving location update and doing everything has been designed to do, so even you push another controller, if something happen that for the implemented logic has to push another controller, this view controller has access to the navigation controller so has just to add to the stack another controller and push it (the navigation controller still the same for all the VC in the stack)
So what you have to do is stop to handle this cases when you push another controller and restart to do that when you pop back the controller
This bug was caused by the handler mapViewDidFailLoadingMap:withError: being called multiple times before control passed to the pushed view controller. I fixed the bug by making three changes to the handler method:
1) I removed the UIAlertView show and moved it to the pushed view controller;
2) I used a flag (initialized in viewDidAppear:) to return from the handler without executing the push if the handler was being called a second time;
3) I checked for the pushed view controller's existence before allocing it.
When all three of these changes were made, transitions between view controllers happened correctly. Code follows:
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error
{
[aiView stopAnimating];
if (mapViewFailed > 0) {
mapViewFailed ++;
return;
}
if (mapTypeInt == 0) {
[map setUserTrackingMode:MKUserTrackingModeNone];
[self removeAsNoticationsObserver];
if ([currentMode isEqualToString:#"retracingSteps"])
{
if (!rvc) {
rvc = [[RetraceViewController alloc] initWithNibName:#"RetraceViewController" bundle:nil];
}
[[self navigationController] pushViewController:rvc animated:YES];
}
else{
if (!ttvc) {
ttvc = [[TripTableViewController alloc] init];
}
[[self navigationController] pushViewController:ttvc animated:YES];
}
mapViewFailed ++;
}
else{
[self setMapType:0];
NSString *message = [NSString stringWithFormat:#"Your signal is not currently strong enough to download a satellite map. Switching to Standard Map view."];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Can't Use Satellite Maps"
message:message
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[av show];
}
}
No doubt a more elegant solution is available which I don't know about yet, being an iOS newb.

Resources