In my app I frequently get the warning
CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.
when I'm transitioning from a View Controller with a MKMapView to a TableViewController. Every once and a while this also throws the following exception:
Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <CALayerArray: 0x198e81f0> was mutated while being enumerated.'
My issue is that I never actually call anything from the CoreAnimation library directly in my code so the solution offered here wouldn't work for me.
I can't show you the exact code I'm using for confidentiality reasons, but here is the gist of what my code looks like for the transition:
MKMapView Side
UITabBarController *tbc = mapViewController.tabBarController;
UINavigationController *nav = [[tbc viewControllers]objectAtIndex:1];
ViewController *anotherController = [[nav viewControllers]objectAtIndex:0]; // not the final destination
[nav popToRootViewControllerAnimated:NO]; // pops to root without animation
[anotherController setSelectedEvent:event]; // sets which event to select before the segue
[tbc setSelectedIndex:1]; // changes the selected tab
[anotherController performSegueWithIdentifier:#"identifier" sender:nil]; // segues to the TableView
Segue from anotherController to tableViewController
if ([[segue identifier]isEqualToString:#"identifier"]) {
TableViewController *vc = segue.destinationViewController;
[vc setEvent:selectedEvent];
[vc.navigationItem setTitle:#"Title"];
[vc performSelectorInBackground:#selector(searchForAndScrollToEvent:) withObject:_selectedEvent];
}
Final Destination
-(void)searchForAndScrollToEvent:(Events*)event {
scroll_to_event_waiting_for_table_to_load = YES;
while (scroll_to_event_waiting_for_table_to_load) {
usleep(1000);
}
NSLog(#"segue completed");
EventListVirtualTable* virtualTable = [[EventListVirtualTable alloc]initWithViewController:self];
NSIndexPath *indexPath = [virtualTable indexPathOfEvent:event];
if (/*indexPath is bad*/) {
NSLog(#"NOT scrolling to: row %i section %i",indexPath.row,indexPath.section);
return;
}
[self performSelectorOnMainThread:#selector(scrollToAtIndexPath:) withObject:indexPath waitUntilDone:YES];
while (![[_tableView indexPathsForVisibleRows] containsObject:indexPath]) {
usleep(100);
}
[self performSelectorOnMainThread:#selector(selectCellAtIndexPath:) withObject:indexPath waitUntilDone:YES];
usleep(delay);
[self performSelectorOnMainThread:#selector(decselectCellAtIndexPath:) withObject:indexPath waitUntilDone:YES];
}
-(void)scrollToAtIndexPath:(NSIndexPath*)indexPath {
if(_dataController == nil){return;}
if (_tableView.numberOfSections == 0 || [self numberOfRowsInSection:indexPath.section] == 0 ) {
return;
}
[_tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
NSLog(#"scrolling to: row %i section %i",indexPath.row,indexPath.section);
}
Thanks everyone for taking the time to offer any responses or useful hints!
Everybody knows not to modify UIView objects from another thread. I was activating UIActivityIndicator from another thread. That was causing the exceptions. I'm still getting the warnings, but the warnings happen now when I cancel threads that I've called.
Related
So i am showing a model controller on top of a view controller. And i have texts in the model controller, but somehow the texts are not visible. I tried everything but somehow labels are not visible. But of you stay on the page for like 30 -40 sec the text shows up. Also this model controller is called from main view controller after a successful service(REST) call. If i call the model without making the service call then labels are visible in simulator/iPad both. But if i call it after service call inside success block then labels are not visible. I tried adding the text programmatically but still same issue. I tried debugging using Color blended layers, but the label is not at all visible in the view somehow. :(
[self.serviceManager getCustDetails:account successBlock:^(NSDictionary * successDict) {
[self hideLoadingAnimation];
NSDictionary *custData = [[successDict objectForKey:#"txnData"] objectForKey:#"custData"];
self.showCurrYear = [iraContribData objectForKey:#"showCurrYear"];
if ([self.showCurrYear isEqual: #"true"]){
[self performSegueWithIdentifier:#"CSegue" sender:self];
}
} failureBlock:^(NSDictionary * failureDict) {
[self hideLoadingAnimation];
NSLog(#"Failiure Dict %#",failureDict);
}];
And this prepareForSegue method, -
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"CSegue"]) {
CustViewController *cVC = segue.destinationViewController;
cVC.delegate = self;
[cVC setModalPresentationStyle:UIModalPresentationFormSheet];
cVC.preferredContentSize = CGSizeMake(800,750);
}
}
Below is my screen in storyboard, but in simulator the label is not visible, only continue and close button is visible.
Please help!, any suggestions are most welcome. Thanks!
It is possible that the delay is due to a user interface update not made on the main thread.
Try to make sure that your code is executed on the main thread using dispatch_async like this :
[self.serviceManager getCustDetails:account successBlock:^(NSDictionary * successDict) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLoadingAnimation];
NSDictionary *custData = [[successDict objectForKey:#"txnData"] objectForKey:#"custData"];
self.showCurrYear = [iraContribData objectForKey:#"showCurrYear"];
if ([self.showCurrYear isEqualToString:#"true"]){
[self performSegueWithIdentifier:#"CSegue" sender:self];
}
});
} failureBlock:^(NSDictionary * failureDict) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLoadingAnimation];
NSLog(#"Failiure Dict %#",failureDict);
});
}];
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.
I'm writing an iOS application that populates an array using the data retrieved from the server and displays it in a picker view.
Everything goes smoothly the first time the view is displayed; However, when switching to another view which uses a camera to scan stuff and switch back using a slide-out menu (built using SWRevealViewController), it fails to populate the array. When we access it on the UI thread after the background task has finished to retrieve the records, an NSRangeException is thrown with the error index 0 beyond bounds for empty array.
I'm pretty sure the background task is being run and the data is being retrieved successfully as I log every single request to the server.
I believe it might be an issue with concurrency and the background thread not updating the variable.
As far as we have tested, this issue is only present on iOS 6, and does not happen, or at least has not yet, on iOS 7.
This is the code used to retrieve and set the array:
dispatch_queue_t queue = dispatch_queue_create("com.aaa.bbb", NULL);
dispatch_async(queue, ^{
_events = [_wi getEvents:_auth_token];
dispatch_async(dispatch_get_main_queue(), ^{
// code to be executed on the main thread when background task is finished
[_mPicker reloadComponent:0];
// Set the default on first row
[self pickerView:_mPicker didSelectRow:0 inComponent:0];
[pDialog dismissWithClickedButtonIndex:0 animated:YES];
});
});
This is the prepareforSegue method in my SidebarViewController that is responsible for switching between views when an item is selected from the slide-out menu.
- (void) prepareForSegue: (UIStoryboardSegue *) segue sender: (id) sender
{
// Set the title of navigation bar by using the menu items
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
UINavigationController *destViewController = (UINavigationController*)segue.destinationViewController;
destViewController.title = [[_menuItems objectAtIndex:indexPath.row] capitalizedString];
if ( [segue isKindOfClass: [SWRevealViewControllerSegue class]] ) {
SWRevealViewControllerSegue *swSegue = (SWRevealViewControllerSegue*) segue;
swSegue.performBlock = ^(SWRevealViewControllerSegue* rvc_segue, UIViewController* svc, UIViewController* dvc) {
UINavigationController* navController = (UINavigationController*)self.revealViewController.frontViewController;
[navController setViewControllers: #[dvc] animated: NO ];
[self.revealViewController setFrontViewPosition: FrontViewPositionLeft animated: YES];
};
}
}
The views are linked together from the storyboard for switching.
The error occurs when I try to retrieve a specific entry from my events array in the pickerView:didSelectRow:inComponent: method :
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
// Set the current id
_currentId = [NSString stringWithFormat:#"%d", row];
_currentName = [[_events objectAtIndex:row] objectForKey:#"event_name"];
-----------^
...
Here's the list of running threads and the call stack.
From my experience in Android, I think that this might have to do something with me not finishing the background task properly the first time, or somehow the code that is supposed to be run after the background task, is run alongside it the second time.
I would appreciate any suggestions that might help me with this issue!
Thanks
The variable _events is accessed from different threads without any synchronization mechanism.
Try this:
dispatch_queue_t queue = dispatch_queue_create("com.aaa.bbb", NULL);
dispatch_async(queue, ^{
NSArray* events = [_wi getEvents:_auth_token];
dispatch_async(dispatch_get_main_queue(), ^{
_events = [events copy];
// code to be executed on the main thread when background task is finished
[_mPicker reloadComponent:0];
// Set the default on first row
[self pickerView:_mPicker didSelectRow:0 inComponent:0];
[pDialog dismissWithClickedButtonIndex:0 animated:YES];
});
});
Also _events should be an NSArray, not NSMutableArray, to avoid such problems.
I am using the following code to switch views in ViewDeck, it was completely fine in iOS5 and 6 but 7, when I try to pop to an exist view. The screen became all white/black.
-(void)switchViewWithViewController:(UIViewController*)viewControllerToSwitch
{
if (viewControllerToSwitch)
{
// Reset Menu Button
[self.viewDeckController closeLeftViewAnimated:YES completion:^(IIViewDeckController *controller)
{
[((BaseViewController*)viewControllerToSwitch) closeMenu];
}];
#try
{
[((UINavigationController*)self.viewDeckController.centerController) pushViewController:viewControllerToSwitch animated:NO];
}
#catch (NSException * ex)
{
//“Pushing the same view controller instance more than once is not supported”
NSRange range = [ex.reason rangeOfString:#"Pushing the same view controller instance more than once is not supported"];
if([ex.name isEqualToString:#"NSInvalidArgumentException"] && range.location != NSNotFound)
{
//view controller already exists in the stack - just pop back to it
if (!IS_IOS7)
{
[((UINavigationController*)self.viewDeckController.centerController) popToViewController:viewControllerToSwitch animated:NO];
}
else
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void)
{
[((UINavigationController*)self.viewDeckController.centerController) popToViewController:viewControllerToSwitch animated:NO];
});
}
}
}
}
}
I did try to add a delay 0.1 but it is not going to help.
From the console, I found that it was popping two VCs at the same time.
Unbalanced calls to begin/end appearance transitions for <GameViewController: 0x15ef5630>.
-[BaseViewController viewDidAppear:] [Line 49] VC is showing: GameViewController
-[BaseViewController viewDidAppear:] [Line 49] VC is showing: HomePageViewController
I admit that using try and catch is not a good practice. As the question is regarding to ViewDeck, so I just simply replace the centerViewController on the fly, and avoid the push pop stack error from UINavigationController.
Here is the code. Hope it can help someone.
-(void)switchViewWithViewController:(BaseViewController*)viewControllerToSwitch
{
if (viewControllerToSwitch)
{
// Reset Menu Button
[self.viewDeckController closeLeftViewAnimated:YES completion:^(IIViewDeckController *controller)
{
[viewControllerToSwitch closeMenu];
}];
UINavigationController *navVC = [[UINavigationController alloc] initWithRootViewController:viewControllerToSwitch];
self.viewDeckController.centerController = navVC;
}
}
hope you had fixed this problem.
1, I don't think using #try #catch to determine push or pop is an good idea.
2, I had this "Unbalanced calls to ...blabla" things too in iOS7. I my case ,I need force-rotate the screen then pop to last viewcontroller. In iOS 5 or 6, just force rotate in -(void)viewWillDisappear, it works fine but iOS7 crashed. So in iOS7 I made force rotate first then pop.
Depending on this https://stackoverflow.com/a/17440074/1343200,
"An animation is started before the last related animation isnt done."
I have the following code to display a magazine type app. When the app is rotated it runs this code. I made sure that it is only run when rotated to supported orientations. When this function returns, the app fails with a SIGABRT. There is no other indication as to why.
I know it's this function because when I remove it the program does not fail.
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
//If portrait mode, change to single page view
if(UIInterfaceOrientationIsPortrait(orientation)){
UIViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObject:currentViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
self.pageViewController.doubleSided = NO;
return UIPageViewControllerSpineLocationMin;
//If landscape mode, change to double page view
}else{
//Get current view
UIViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
//Create an array to store, views
NSArray *viewControllers = nil;
NSUInteger currentIndex = self.currentPage;
//Conditional to check if needs page before or after
if(currentIndex == 1 || currentIndex %2 == 1){
UIViewController *nextViewController = [self pageViewController:self.pageViewController viewControllerAfterViewController:currentViewController];
viewControllers = [NSArray arrayWithObjects:currentViewController,nextViewController, nil];
}else{
UIViewController *previousViewController = [self pageViewController:self.pageViewController viewControllerBeforeViewController:currentViewController];
viewControllers = [NSArray arrayWithObjects:previousViewController, currentViewController, nil];
}
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
return UIPageViewControllerSpineLocationMid;
}
//return UIPageViewControllerSpineLocationMid;
}
Alas, borrrden is probably right. One of your IBOutlets is probably missing from your XIB. Make sure ALL of your IBs are connected properly, and if the problem continues, say so.
Well you didn't provide the output from the console, which would be nice. Giving the code a quick look I would guess that one of your controllers (next or previous) is nil, and since you can't insert nil into an NSArray (except as the last object) it is throwing an error.
EDIT Well, my guess was wrong. The error message is saying that the UIViewControllers you are giving to it do not support the orientation that the page controller needs. This is because you have a method called shouldRotateToInterfaceOrientation: in your child UIViewControllers, and they are returning no for (in this case) left landscape.
I was getting the same error
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'All provided view controllers ((
"<ContentViewController: 0x6a7eac0>",
"<ContentViewController: 0x6d89f10>"
)) must support the pending interface orientation (UIInterfaceOrientationLandscapeLeft)'
Adding the following to my PageModel class, where the page layout is designed worked for me:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}