calling dismissPopoverAnimated in viewWillDisappear crashes application - ios

I have two ViewControllers. Let's call them no1 and no2. In no2 I have PopoverViewController with some options and instance of NSTimer. Timer function is calling popToViewController 3 seconds after popup is presented if nothing is clicked in the popover, which is returning user to no1 ViewController. Problem is when this function is triggered, screen is changed to the no1, but application crashes without error message below.
PopoverViewController doesn't have delegate and it is registered as property of second VC as:
#property (nonatomic)UIPopoverController *optionsPopover;
Does anyone have any idea why there is no crash report available? And if there is no reference to the popover why it is crashing?
Implementation in viewWillDisappear looks like this:
if([_optionsPopover isPopoverVisible]){
[_optionsPopover dismissPopoverAnimated:NO];
_optionsPopover = nil;
}
I tried forcing UI to update on main thread (below code), but the result is the same. Crash still exists.
dispatch_async(dispatch_get_main_queue(), ^{
if([_optionsPopover isPopoverVisible]){
[_optionsPopover dismissPopoverAnimated:NO];
_optionsPopover = nil;
}
});

Please try to make strong reference of UIPopoverController
#property (nonatomic,retain)UIPopoverController *optionsPopover;
call below method in - (void)viewDidDisappear:(BOOL)animated instead of viewwilldisappear -
- (void)viewDidDisappear:(BOOL)animated {
if([_optionsPopover isPopoverVisible]){
[_optionsPopover dismissPopoverAnimated:NO];
_optionsPopover = nil;
}
}

Set the property as strong :
#property (strong, nonatomic) UIPopoverController *_optionsPopover;
Remove this line of code :
_optionsPopover = nil;

You are setting _optionsPopover to nil after dismiss..
Edit
Possible problem: timer not invalidated after viewController is dismissed,
if([_optionsPopover isPopoverVisible])
{
[yourTime invalidate]; // added on edit
[_optionsPopover dismissPopoverAnimated:NO];
}
Try: [_optionsPopover dismissPopoverAnimated:NO]; alone, because dismissing it will basically makes it nil..

Related

-[PreviewViewController applicationWillSuspend]: message sent to deallocated instance 0x1806d9e0

My Application is getting crashed with the following error.
-[PreviewViewController applicationWillSuspend]: message sent to deallocated instance 0x1806d9e0
My application have two view controllers one is HomeViewController and other one is PreviewViewController.
In home view controller i am displaying a table view. When selecting the row of table view i am presenting the preview view controller.
I selected one row then preview view controller is presented.
PreviewViewController *previewController = [[PreviewViewController alloc]initWithPreviewImage:[[kfxKEDImage alloc] initWithImage:imgCaptured] withSourceofCapture:_typeOfCapture typeOfDocumentCaptured:PHOTO];
[self presentViewController:previewController animated:YES completion:nil];
Dismissed the preview view controller.
[self dismissViewControllerAnimated:YES completion:nil];
Application goes into background then it is not crashed.
I selected two rows one after another. Application goes into background then it is crashed. I don't know why it is behaving like that. If anyone know the solution please tell me.
Thanks In Advance
I had this problem, it was caused by someone overriding 'dealloc' in a UIViewController category.
https://github.com/taphuochai/PHAirViewController/issues/13
#chrishulbert
Remove this:
- (void)dealloc
{
self.phSwipeHander = nil;
}
Replace dealloc with this:
/// This is so that phSwipeGestureRecognizer doesn't create a swipe gesture in *every* vc's dealloc.
- (BOOL)phSwipeGestureRecognizerExists {
return objc_getAssociatedObject(self, SwipeObject) ? YES : NO;
}
- (void)ph_dealloc
{
if (self.phSwipeGestureRecognizerExists) {
self.phSwipeHander = nil;
}
[self ph_dealloc]; // This calls the original dealloc.
}
/// Swizzle the method into place.
void PH_MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL) {
Method origMethod = class_getInstanceMethod(c, origSEL);
Method overrideMethod = class_getInstanceMethod(c, overrideSEL);
if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, overrideMethod);
}
}
/// Swizzle dealloc at load time.
+ (void)load {
SEL deallocSelector = NSSelectorFromString(#"dealloc"); // Because ARC won't allow #selector(dealloc).
PH_MethodSwizzle(self, deallocSelector, #selector(ph_dealloc));
}

Setting UILabel text is not working

Here is my .h file
#import <UIKit/UIKit.h>
#interface PersonViewController : UIViewController
#property(strong,nonatomic) NSString *personTitle;
And here is my .m file
#interface PersonViewController ()
#property (weak, nonatomic) IBOutlet UILabel *titleView;
#end
#implementation PersonViewController
//stuff …
-(void)setPersonTitle:(NSString *)personTitle
{
[self.titleView setText:personTitle];// also self.titleView.text=personTitle
[self.titleView setNeedsDisplay];
NSLog(#"The title shoud match as %# :: %#",personTitle,self.titleView.text);
}
-(NSString *)personTitle
{
return self.titleView.text;
}
//… more stuff
#end
The logging shows that the value is (null) for self.titleView.text whereas personTitle prints the appropriate value.
I remember doing this same thing a number of times and it worked. Any ideas why it’s failing this time?
update I use storyboard to set my scenes. And I am using xcode-5 and iOS-7
update: how I call
The user clicks a button, leading to a push segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"enter prepare for segue.");
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
if ([segue.identifier isEqualToString:the_identifier_for_person]) {
NSLog(#"segue to person is progressing“);
if ([segue.destinationViewController isKindOfClass:[PersonViewController class]]) {
NSLog(#"segue to person destination is a match");
PersonViewController *aPerson = (PersonViewController *)segue.destinationViewController;
aPerson.personTitle=((MyItem*)self.allItems[indexPath.row]).title;
NSLog(#"segue to person is done");
}
}
}
This sounds like you forgot to wire up your UILabel in the storyboard. Can you confirm that self.titleView is not null?
View controllers create their views on demand, but can spot that only via a call to view. When the view is loaded, your outlets will be populated.
Either call view to force loading or keep the string in abeyance until you get viewDidLoad.
(aside: prior to iOS 6, views would also be released in low-memory situations so the idiomatic thing is to store the string and populate on viewDidLoad)
Having accepted another answer, I wanted to show the pattern that I actually used to solve the problem, in case someone else comes looking. This pattern is best practice (yes, I forgot it for a long moment there).
#pragma mark - update UI
-(void)setPersonTitle:(NSString *)personTitle
{
_personTitle=personTitle;
if (self.view.window) [self updateUI];//only if I am on screen; or defer to viewWillAppear
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateUI];
}
-(void)updateUI
{
self.titleView.text=self.personTitle;
}
It is always important to update the ui when the data has changed, which is why I must make the call inside setPersonTitle. But because the IBOutlets are not yet set when I set personTitle in prepareForSegue, then I must also make the call inside viewWillAppear.
Do you actually call the -(void)setPersonTitle:(NSString *)personTitle method?
It seems that you aren't calling it correctly which would result in the title being null.
After reviewing the prepareForSeque it is clear that you are not calling the method. You are actually just changing the #property named personTitle.
In the viewDidLoad you should have it so that self.titleView.text = self.personTitle;

Zombie UIWebView when it's deallocated just after alert() called from javascript

I'm using a UIWebView, and it pops an alert when a button is clicked(from javascript). There is another button (in native side), which closes controller, so deallocs also UIWebView.
The problem is, if I touch the button in UIWebView, and touch to close button before alert is populated, my controller and UIWebView are deallocated, but alert remains on screen. Then if I click any button on alert, application crashes and gives following error:
[UIWebView modalView:didDismissWithButtonIndex:]: message sent to deallocated instance
And this method is called from private method
[UIModalView(Private) _popoutAnimationDidStop:finished:]
I'm using ARC, and my dealloc is like this:
- (void)dealloc {
[_myWebView stopLoading];
_myWebView.delegate = nil;
_myWebView = nil;
}
But this does not solve my problem because I think UIModalView has a reference of my webview as a delegate, and I could not set it to nil because its private.
How can I solve it?
Regards
Find a way to set the delegate on UIAlertView to nil before deallocating UIWebView.
This is an Apple bug in their handling of the alert view. Open a bug report.
In the meantime, here are some workarounds:
Create a category on UIAlertView:
#interface UIAlertView (QuickDismiss) #end
#implementation UIAlertView (QuickDismiss)
- (void)__quickDismiss
{
[self dismissWithClickedButtonIndex:self.cancelButtonIndex animated:NO];
}
#end
Now, in your view controller's dealloc method, call this:
[[UIApplication sharedApplication] sendAction:#selector(__quickDismiss) to:nil from:nil forEvent:nil];
This will dismiss all alert views that are currently open, including the ones displayed by your web view.
If that does not work, you can always iterate all subviews of all UIApplication.sharedApplication.windows objects, checking whether [view.class.description hasPrefix:#"UIAlertView"] is true, and dismissing that. This is a less elegant method than the previous one, and should be last resort.
Good luck.
Finally, I find a great solution which actully works. I use method swizzle to hook UIAlertView Delegate function - (void)didPresentAlertView:(UIAlertView *)alertView
- (void)didPresentAlertView:(UIAlertView *)alertView
{
if ([self.delegate respondsToSelector:#selector(didPresentAlertView:)]) {
[self.delegate didPresentAlertView:alertView];
}
if ([self.delegate isKindOfClass: [UIWebView class]]) {
uiWebView = self.delegate;
}
}
I just retain UIWebView instance in this function so that the UIWebView instance as UIAlertView's delegate will not be released before UIAlertView instance being released.

Warning when dismissing view controller programatically

I have a TableViewController with a button that triggers an [NSURLConnection sendAsynchronousRequest...] event, and also loads a modal segue through performSegueWithIdentifier:sender: which targets a small view controller. The purpose of this overlay view controller is to show a loading graphic and to prevent user interaction while the data is sent through the NSURLConnection.
In the completion block of the NSURLConnection, I call a method which removes the data in the TableViewController (its just a batch listing), and then calls dismissViewControllerAnimated:completion: on the overlay view controller.
Everything works except for dismissing the overlay view controller, which throws a warning in the debugger which says:
"Warning: Attempt to dismiss from view controller while a presentation or dismiss is in progress!"
I have found various questions and answers about this error, particularly about using the performSelector:object:withDelay methods, but so far nothing has worked.
This is particularly annoying because I use a similar process in another area of the app, except that the dismissViewController is called from selecting a UITableViewCell, and this works fine...
The relevant bits of my code are shown below:
#import "ViewBatch.h"
#interface ViewBatch ()
#property (strong, nonatomic) LoadingOverlayViewController *loadingOverlay;
#end
#implementation ViewBatch
#synthesize loadingOverlay;
....
- (IBAction)exportBatch:(id)sender
{
if ([productArray count] > 0) {
[self performSegueWithIdentifier:#"loadingSegue" sender:self];
[self processData];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"loadingSegue"]) {
loadingOverlay = segue.destinationViewController;
}
}
- (void)processData
{
// Code to create a file and NSURLRequest...
// ....
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
if ([responseData length] > 0 && error == nil)
{
// Not used for this request yet...
}
else if ([responseData length] == 0 && error == nil)
{
// Success...
[self didSendData];
}
else if (error != nil)
{
// Connection error...
NSLog(#"Error: %#", error);
}
}];
}
- (void)didSendData
{
// Reset the batch...
[productArray removeAllObjects];
[self.tableView reloadData];
[loadingOverlay dismissViewControllerAnimated:YES completion:NULL];
}
And the loading view controller is just:
#import <UIKit/UIKit.h>
#interface LoadingOverlayViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *statusLabel;
#property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
#end
....
....
#import "LoadingOverlayViewController.h"
#interface LoadingOverlayViewController ()
#end
#implementation LoadingOverlayViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.activityIndicator startAnimating];
}
#end
I'm not sure about the exact cause of the problem, but here are some observations about your code.
You do not need an overlay in order to prevent user interaction. Simply turn off user interaction with -NSApplication beginIgnoringInteractionEvents.
You do not need an entire view controller in order to show an overlay view. Just show the view. For example I often place a large UIActivityIndicatorView in the middle of my view while an NSURLConnection is happening.
You do not need an NSOperationQueue in order to use NSURLConnection asynchronously. It is already asynchronous. Just create the NSURLConnection and wait for the delegate messages to arrive. As it is, you are bolluxing yourself because you are setting up a secondary queue, the message arrives on that queue, and you call didSendData which calls reloadData on the table - in the background, which is illegal. If you did this the normal way, your delegate messages would arrive on the main thread which is exactly what you want.
Never mind, I got it worked out.
Amazing what half an hours break can do to organise one's thoughts...
The process was finishing and calling dismissViewController before the actual view was even finished loading. A simple delegate call from the viewDidLoad on the overlay sorted things out.
I had this problem as well. I noticed it was a result from copying and pasting a button in my nib file. The result of this was that I had to IBActions tied two 1 UIButton.

Update UIViewController after Dismissing Modal Segue

I am currently designing the structure for my first iPhone game and ran into a problem. Currently, I have a 'MenuViewController' that allows you to pick the level to play and a 'LevelViewController' where the level is played.
A UIButton on the 'MenuViewController' triggers a modal segue to the 'LevelViewController'.
A UIButton on the 'LevelViewController' triggers the following method to return to the 'MenuViewController':
-(IBAction)back:(id)sender //complete
{
[self dismissModalViewControllerAnimated:YES];
}
The problem is, I have a UILabel on the menu page that prints the number of total points a player has. Whenever I go back to the menu from the level, I want this label to automatically update. Currently, the label is defined programmatically in the 'MenuViewController':
-(void)viewDidLoad {
[super viewDidLoad];
CGRect pointsFrame = CGRectMake(100,45,120,20);
UILabel *pointsLabel = [[UILabel alloc] initWithFrame:pointsFrame];
[pointsLabel setText:[NSString stringWithFormat:#"Points: %i", self.playerPoints]];
[self.pointsLabel setTag:-100]; //pointsLabel tag is -100 for id purposes
}
self.playerPoints is an integer property of MenuViewController
Is there a way I could update the label? Thanks ahead of time!
This is a perfect case for delegation. When the LevelViewController is done, it needs to fire off a delegate method which is handled in the MenuViewController. This delegate method should dismiss the modal VC and then do whatever else you need it to do. The presenting VC should normally handled the dismissal of modal views it presents.
Here is a basic example of how to implement this:
LevelViewController.h (Above the Interface declaration):
#protocol LevelViewControllerDelegate
-(void)finishedDoingMyThing:(NSString *)labelString;
#end
Same file inside ivar section:
__unsafe_unretained id <LevelViewControllerDelegate> _delegate;
Same File below ivar section:
#property (nonatomic, assign) id <LevelViewControllerDelegate> delegate;
In LevelViewController.m file:
#synthesize delegate = _delegate;
Now in the MenuViewController.h, #import "LevelViewController.h" and declare yourself as a delegate for the LevelViewControllerDelegate:
#interface MenuViewController : UIViewController <LevelViewControllerDelegate>
Now inside MenuViewController.m implement the delegate method:
-(void)finishedDoingMyThing:(NSString *)labelString {
[self dismissModalViewControllerAnimated:YES];
self.pointsLabel.text = labelString;
}
And then make sure to set yourself as the delegate for the LevelViewController before presenting the modal VC:
lvc.delegate = self; // Or whatever you have called your instance of LevelViewController
Lastly, when you are done with what you need to do inside the LevelViewController just call this:
[_delegate finishedDoingMyThing:#"MyStringToPassBack"];
If this doesn't make sense, holler and I can try to help you understand.
Make a property self.pointsLabel that points to the UILabel, then you can just call something like [self.pointsLabel setText:[NSString stringWithFormat:#"Points: %i", self.playerPoints]]; to update the label with the new score
In your modal view header file, add the property:
#property (nonatomic,assign) BOOL updated;
Then in your main view controller, use didViewAppear with something like:
-(void)viewDidAppear:(BOOL)animated{
if (modalView.updated == YES) {
// Do stuff
modalView.updated = NO;
}
}
Where "modalView" is the name of that UIViewController that you probably alloc/init there.
Add more properties if you want to pass more info, like what level the user picked.

Resources