popToRootViewController crashes when tableView is still scrolling - ios

When I give a good swipe to my tableView and press the "Back" button before the tableView ended it's scrolling, my app crashes. I've tried the following:
- (void) closeViewController
{
[self killScroll];
[self.navigationController popToRootViewControllerAnimated:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)killScroll
{
CGPoint offset = sellersTableView.contentOffset;
[sellersTableView setContentOffset:offset animated:NO];
}
That didn't work, same crash. I don't see why, the error I'm getting is the following:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'
So that means that the tableView is still requesting a cell when everything is already being deallocated. Makes no sense.
Then I tried this:
- (void) closeViewController
{
[self.navigationController popToRootViewControllerAnimated:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)dealloc
{
sellersTableView.dataSource = nil;
sellersTableView.delegate = nil;
sellersTableView = nil;
}
Gives me the same error. Any ideas?
Update:
My delegate methods
creation
if (textField == addSellerTextField) {
sellersTableView = [[UITableView alloc] initWithFrame:CGRectMake(addSellerTextField.frame.origin.x + addSellerTextField.frame.size.width + 10, addSellerTextField.frame.origin.y - [self heightForTableView] + 35, 200, [self heightForTableView])];
sellersTableView.delegate = self;
sellersTableView.dataSource = self;
sellersTableView.backgroundColor = [[UIColor grayColor] colorWithAlphaComponent:0.05];
sellersTableView.separatorColor = [[UIColor grayColor] colorWithAlphaComponent:0.15];
sellersTableView.rowHeight = 44;
sellersTableView.layer.opacity = 0;
[self.companyView addSubview:sellersTableView];
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{sellersTableView.layer.opacity = 1;} completion:nil];
}
cellForRowAtIndexPath
if (tableView == sellersTableView) {
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.backgroundColor = [UIColor clearColor];
if ([sellersArray count] > 0) {
cell.textLabel.text = [sellersArray objectAtIndex:indexPath.row];
} else {
UILabel *noSellersYetLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, sellersTableView.frame.size.width, [self heightForTableView])];
noSellersYetLabel.text = #"no sellers yet";
noSellersYetLabel.textAlignment = NSTextAlignmentCenter;
noSellersYetLabel.textColor = [UIColor grayColor];
[cell addSubview:noSellersYetLabel];
sellersTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
}
removing
- (void) textFieldDidEndEditing:(UITextField *)textField
{
if (textField == addSellerTextField) {
[self updateSellers:textField];
}
}
- (void)updateSellers:(UITextField *)textField
{
[textField resignFirstResponder];
[self hideSellersTableView];
}
- (void)hideSellersTableView
{
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{sellersTableView.layer.opacity = 0;} completion:nil];
sellersTableView.dataSource = nil;
sellersTableView.delegate = nil;
[sellersTableView removeFromSuperview];
sellersTableView = nil;
}
Solution
So apparently putting the dataSource = nil and delegate = nil into textFieldDidEndEditing fixed the problem. Thanks everybody for the answers!

It's strange behaviour of UITableView. The easiest way to resolve this issue just set the dataSource and delegate property of UITAbleView to nil before you make a call of function popToRootViewControllerAnimated. Furthermore you can use more common solution and add the code that set the properties to nil into the -dealloc method. In addition you no need the -killScroll method.
After a short research I have realized what the problem is. This unusual behaviour appeared in iOS 7. The scroll view retained by its superview may send message to delegate after the delegate is released. It happens due to -removeFromSuperview implementation UIScrollView triggers -setContentOffset: and, eventually, send message to delegate.

Just add following lines at the beginning of dealloc method:
sellersTableView.delegate = nil;
sellersTableView.dataSource = nil;
No need to use hacks like your killScroll method.
Also, I can't see why you want to call both popToRootViewController and dismissViewController.
If you dismiss a view controller which is embedded in a navigation controller, navigation controller itself as well as all contained view controllers will be released.
In your case you'll have just weird animation.

setContentOffset method won't help you, try to set
sellersTableView.dataSource = nil;
somewhere in your viewWillDisappear method.
This is not a good practice of course.

Change you closeViewController like below and see if works
(void) closeViewController
{
sellersTableView.dataSource = nil;
sellersTableView.delegate = nil;
[self.navigationController popToRootViewControllerAnimated:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}

I don't think that setting the tableView (or it's delegate) to nil is the issue. You should be able to perform both dismissViewControllerAnimated or popToRootViewController individually without having to modify the tableView in this way.
So the issue is most likely due to calling both of these methods at the same time (and with animated = YES), and in doing so asking your viewController setup to do something unnatural.
Looks like upon tapping a "close" button you are both popping to a rootViewController of a UINavigationController, as well as dismissing a modal viewController.
In doing so, you're dismissing a modal viewController which is likely presented by the topViewController of the navigationController (so top vc is holding a reference to modal vc). AND you're trying to kill the top vc via the popToRootViewController method call. And you're doing both of these things using animated = YES, which means they take some time to complete, and you can't be sure when each finishes (ie you can't be sure when dealloc will be called).
Depending on your needs you could do one of several things.
Consider adding a delegate property to your modal vc. Dismiss the modal vc, and in the completionBlock of the modal vc tell its delegate that it's finished dismissing. At that point call popToRootViewController (because at this point you can be sure that the modal is gone and scrolling wasn't interrupted).
If it's your navController that's been presented modally, then do this in the opposite order. Notifying the delegate that the pop operation has completed, and do the modal dismissal then.

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));
}

How can I alternate view controller and not lose data

I have two UIViewControllers, one is a UIPickerViewController, the Other a UITableViewController. Ideally the Picker should get a request from the user to add x amount of some item to the tableView. The Picker gets user inputs and assigns them to variables val1, val2, val3, where val1 is the number of items (number of rows) and val2 is the name or label for the item.
PickerViewController.m
- (IBAction)add:(id)sender
{
TableViewController *tvc = [[TableViewController alloc] init];
[tvc setValues:self.val1 :self.val2 :self.val3];
[self presentViewController:tvc animated:YES completion:nil];
}
TableViewController.m
-(void)setValues:(NSString *)newVal1 :(NSString *)newVal2 :(NSString *)newVal3
{
self.val1 = newVal1;
self.val2 = newVal2;
self.val3 = newVal3;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"UITableViewCell"];
// This is just a header which holds my "Add" button
UIView *header = self.headerView;
[self.tableView setTableHeaderView:header];
[self addNew:self.val1 :self.val2 :self.val3];
}
- (void)addNew:(NSString *)newVal1 :(NSString *)newVal2 :(NSString *)newVal3
{
if(!self.numberOfRows){
NSLog(#"Initially no of rows = %d", self.numberOfRows);
self.numberOfRows = [self.val1 intValue];
NSLog(#"Then no of rows = %d", self.numberOfRows);
}
else
{
self.numberOfRows = self.numberOfRows + [newVal1 intValue];
NSLog(#"New no rows = %d", self.numberOfRows);
}
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.numberOfRows inSection:0];
// Only run when called again .. not initially
if(self.run != 0){
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationBottom];
self.run ++;
[self.tableView endUpdates];
}
}
// "ADD" button which should go back to the picker and get new items to add to the table
- (IBAction)testAdd:(id)sender
{
PickerViewController *pvc = [[PickerViewController alloc] init];
[self presentViewController:pvc animated:YES completion:nil];
}
Now, I realize every time I call the next view controller I am creating a new instance of it, but I don't know how else to do it, I figure this is the main problem. As of right now, I expect when I leave the tableview for the picker view and return the console should log "New no of rows = x" but that doesn't happen.
I know val3 isn't used and my addNew: may not be the best, but I just need it to handle the basic logging mentioned above and I should be able to take it from there.
Been stuck on this for days
Create a property for TableViewController, and only create it the first time you present it,
- (IBAction)add:(id)sender {
if (! self.tvc) {
self.tvc = [[TableViewController alloc] init];
}
[self.tvc setValues:self.val1 :self.val2 :self.val3];
[self presentViewController:self.tvc animated:YES completion:nil];
}
It's not entirely clear from you question, whether it's this presentation or the one you have in the table view class that you're talking about. It also looks like you're doing something wrong in terms of presentation -- you're presenting the picker view from the table view controller, and also presenting the table view controller from the picker. That's not correct, you should present which ever controller you want to appear second, and that controller should use dismissViewControllerAnimated to go back, not present another controller.
In testAdd you don't need to create a new instance and present it. If you want to go back to the presentingViewController, just use dismissViewControllerAnimated .
And you will go one controller up in the stack.

iOS: Strange warnings and strange back on second select on table view

I am having a strange warning when I segue to one of my ViewControllers. Here is the warning:
Action connections from <UIView: 0x792b2a0; frame = (0 0; 320 568); autoresize = LM+RM+TM+BM; layer = <CALayer: 0x79e1ad0>> are not supported.
An other problem is, I am using a second UITableView to select a value from a list as dropdown list. In didSelectRowAtIndexPath I am doing this:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.tergetTextField.text = self.items[indexPath.row];
[self performSelector:#selector(returnToParent) withObject:nil afterDelay:0.5];
}
- (void) returnToParent {
[self.navigationController popViewControllerAnimated:NO];
}
When I select a value from UITableView I am getting following warning:
Unbalanced calls to begin/end appearance transitions for <HMXSelectViewController: 0x7bb0350>
I have seen similar questions about this warning but given solutions did not worked for me. "performSelector afterDelay" was one of the solution adviced.
And the last strange problem is when I seque to UITableView for second time it returns after second time I select an item not on first.
Here is my codes: http://pastebin.com/TVn51ppD
And as a side question, what would you advice for selecting an item between 3 to 5 items, like a dropdown list?
UPDATE:
I have solved the Unbalanced calls and return on second select on UITableView problems with following:
- (IBAction)scaleEditingDidBegin:(id)sender {
selectionSource = 0;
[self.view endEditing:YES];
[self performSegueWithIdentifier:#"select" sender:self];
}
- (IBAction)statusEditingDidBegin:(id)sender {
selectionSource = 1;
[self.view endEditing:YES];
[self performSegueWithIdentifier:#"select" sender:self];
}
The reason Unbalanced calls to begin/end appearance transitions for* error is, when user touches UITextField, in scaleEditingDidBegin or statusEditingDidBegin function whe go to other UIView before Editing Did End event fired. To fix this add [self.view endEditing:YES] to both functions:
- (IBAction)scaleEditingDidBegin:(id)sender {
selectionSource = 0;
[self.view endEditing:YES];
[self performSegueWithIdentifier:#"select" sender:self];
}
- (IBAction)statusEditingDidBegin:(id)sender {
selectionSource = 1;
[self.view endEditing:YES];
[self performSegueWithIdentifier:#"select" sender:self];
}

UIButton EXC_BAD_ACCESS on setTitle:forState

I have a view controller that's instantiated from IB. It contains a UIButton whose action creates a UIPopoverController whose delegate updates the title of the UIButton through:
- (void) popoverSelected:(NSString*)string {
[self.sortButton setTitle:string forState:UIControlStateNormal];
[self.sortPickerPopover dismissPopoverAnimated:YES];
}
popoverSelected is a delegate method for the UIPopoverController, which contains a simple UITableView.
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *selectedSort = [_sortTypes objectAtIndex:indexPath.row];
if (_delegate != nil) {
[_delegate popoverSelected:selectedSort];
}
}
The popover is instantiated by the TouchUpInside action on the self.button through:
- (IBAction)sortButtonPressed:(id)sender {
if (_sortPicker == nil) {
// Create the picker view controller
_sortPicker = [[SortPickerViewController alloc] initWithStyle:UITableViewStylePlain];
// Set this as the delegate
_sortPicker.delegate = self;
}
if (_sortPickerPopover == nil) {
// The colour picker popover is not showing. Show it
_sortPickerPopover = [[UIPopoverController alloc] initWithContentViewController:_sortPicker];
[_sortPickerPopover presentPopoverFromRect:_sortButton.frame
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
} else {
// if it's showing, we want to hide it
[_sortPickerPopover dismissPopoverAnimated:YES];
_sortPickerPopover = nil;
}
}
This has no issues the first time the button's title is updated, but second time around I get an EXC_BAD_ACCESS when executing setTitle: in popoverSelected.
I can't see anywhere that I'm releasing the button accidentally (and the object definitely still exists at this point). The project is using ARC.
With NSZombies I've occasionally reached [__NSArrayI valueRestriction] unrecognised selector sent to instance which makes even less sense.
Are there any obvious approaches I can take to debug this further?
Instead of checking _sortPickerPopover == nil to know whether to show it, you should check [_sortPickerPopover isPopoverVisible]. Also, I would put the construction code into autoloaders.
- (UIPopoverController *)sortPickerPopover
{
if (!_sortPickerPopover) {
_sortPickerPopover = [[UIPopoverController alloc] initWithContentViewController:self.sortPicker];
}
return _sortPickerPopover;
}
- (SortPickerViewController *)sortPicker
{
if (!_sortPicker) {
_sortPicker = [[SortPickerViewController alloc] initWithStyle:UITableViewStylePlain];
// Set this as the delegate
_sortPicker.delegate = self;
}
return _sortPicker;
}
- (IBAction)sortButtonPressed:(UIButton *)sender
{
if ([self.sortPickerPopover isPopoverVisible]) {
[self.sortPickerPopover dismissPopoverAnimated:YES];
} else {
[self.sortPickerPopover presentPopoverFromRect:sender.frame
inView:sender
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
/***
* NOTE: Delegate methods should always pass the calling object as the first
* object. Additionally, the name is not very descriptive of what is actually
* being performed and does not use should/will/did naming conventions.
* You should consider changing this method to something like:
* - (void)sortPickerViewController:(SortPickerViewController *)sortPicker
* didSelectSortMethod:(NSString *)sortMethod
**/
- (void)popoverSelected:(NSString *)string
{
[self.sortButton setTitle:string forState:UIControlStateNormal];
[self.sortPickerPopover dismissPopoverAnimated:YES];
}
Once these changes are made, the only other possible source of problems is the implementation of your SortPickerViewController. I'll look that over for you if you can post that view controller as well.

UIView animation in UITableViewCell works only once

Here's a strange fact :
I have a custom UITableViewCell in which I have an animation (an image moves).
It works fine on the creation of the cell.
But once I scroll to hide the cell and then go back on it, the animation has stopped. That, I can understand. But I'm calling my animation method in my viewWillAppear too. And the wired part is that the method is called, I've put a breakpoint in it, nothing is desallocated...
My UITableViewCell is kept strongly (there's a music player in it, the music continues to play). I really don't get it.
Here's my code :
#property (nonatomic, strong) DWPlayerCellVC *playerView;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"player"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"player"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
NSArray *viewsToRemove = [cell.contentView subviews];
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
}
if(!self.playerView){
self.playerView = [[DWPlayerCellVC alloc] init];
}
[cell.contentView addSubview:self.playerView.view];
self.playerView.model = self.model[indexPath.row];
return cell;
DWPlayerCellVC :
#implementation DWPlayerCellVC
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self zoomIn];
}
- (void)setModel:(id)model
{
_model = model;
// ...
[self zoomIn];
}
- (void)zoomIn
{
UIViewAnimationOptions options =
UIViewAnimationOptionBeginFromCurrentState |
UIViewAnimationOptionCurveEaseInOut |
UIViewAnimationOptionRepeat |
UIViewAnimationOptionAutoreverse;
[UIView
animateWithDuration:10.f
delay:0.f
options:options
animations:^{
CGFloat scale = 1.4f;
self.imageCoverView.transform = CGAffineTransformMakeScale(scale, scale);
} completion:NULL];
}
If you have any ideas...
Thanks a lot !
It looks like the problem is because zoomIn will only ever be called by setModel. This is because UITabelViewCells are not expected to managed displaying UIViewControllers so viewWillAppear: will never be called.
There are two options that I can think of.
The first would be to set up your root view controller to be a containment view controller and add the DWPlayerCellVC as a childViewController. This option does involve some work to get it all running, I suggect reading Creating Custom Container View Controllers to see whets required to get it working.
The second (and the one I would use) would be to create a UITableViewCell subclass that handles running the animation. Then you can just implement the method prepareForReuse to restart the animation. That method is automatically called on a cell when you use dequeueReusableCellWithIdentifier:.
Your UITableViewCell will remember it's state from before. When it's put into the queue (when it scrolls out of view), it doesn't revert back to a pristine state. When you call your ZoomIn method, I think what's happening is that it's zooming in again, but you can't tell because it's already zoomed in.
Try - before you zoom in, ensure that it's zoomed out. So in ViewWillAppear, to a quick "Reset" of the Cell, and then call zoomIn, and see if that fixes the issue.

Resources