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

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

Related

setNeedsDisplay not working after adding subviews

I added about 500 views to my viewController.view.
This action took about 5 seconds on target.
Now I want the screen to refresh after each subview I'm adding, so the user will see them appears one by one on screen.
I tried this in my viewController:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
for(int i=0; i<500; i++)
{
//...Create aView
[self.view addsubview:aView];
[self.view setNeedsDisplay];
}
}
I run it and nothing happened for 5 seconds then all views appeared at once.
I made sure that [self.view setNeedsDisplay] called from the main thread context.
Any idea how to make those subviews appear one by one?
I found a simple solution. I added a new property to my viewController - 'subviewsCount' witch was initialised to 500. then called the following method from viewDidLoad:
-(void) addSubviewsToMotherView
{
self.subviewsCount -=1;
if (self.subviewsCount >= 0)
{
[UIView animateWithDuration:0.0
delay:0.0
options:UIViewAnimationOptionLayoutSubviews
animations:^{
[self methodToAddSubview];
}
completion:^(BOOL finished){
[self addSubviewsToMotherView];
}
];
}
}

popToRootViewController crashes when tableView is still scrolling

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.

Animation works if UITableView scrolled gently, doesn't work when scrolled fast

I'm blinking a label inside a custom UITableViewCell using this code:
-(void) startBlinker
{
[self stopBlinker];
self.edgeCaseLabelTimer = [NSTimer scheduledTimerWithTimeInterval:.5 target:self selector:#selector(blinkEdgeCaseLabel) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.edgeCaseLabelTimer
forMode:NSRunLoopCommonModes];
}
-(void) blinkEdgeCaseLabel
{
if (blinkCycle == 0)
{
[UIView transitionWithView:self.thisCustomCell.edgeCaseLabel duration:1.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
self.thisCustomCell.edgeCaseLabel.textColor = [UIColor clearColor];
} completion:nil];
blinkCycle = 1;
}
else if (blinkCycle == 1)
{
[UIView transitionWithView:self.thisCustomCell.edgeCaseLabel duration:1.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
self.thisCustomCell.edgeCaseLabel.textColor = [UIColor redColor];
} completion:nil];
blinkCycle = 0;
}
}
I expect there's more elegant and modern code around for this, but it works. At least it works as long as I don't scroll the TV hard. Gently, works fine. Fast, the label remains (when it comes to rest) in one (or the other) of the states and doesn't blink.
The only cells affected are the top and bottom cells (which are the only ones with blinking labels, not coincidentally).
I've tried using scrollViewDidEndScrollingAnimation and scrollViewDidEndDecelerating to bump it into action again, but no dice.
Any ideas?
Note: I'm testing this on simulator & not on a device.
Edit:
Following #Woodstock's suggestion I switched to a regular NSTimer. In addition, I incorporated this code:
-(BOOL)topOrBottomCellIsVisible
{
NSArray *indexes = [self.myTableview indexPathsForVisibleRows];
for (NSIndexPath *index in indexes)
{
if (index.row == 0)
{
return YES;
}
if (index.row == detailFRC.fetchedObjects.count - 1)
{
return YES;
}
}
return NO;
}
from #Devunwired, contributed in his answer to this question. I put this method in several critical locations related to appearing and scrolling.
Now things seem more robust, but I think #Fahim Parkar is probably onto something with it running on the sim instead of a device. I say "more robust" rather than "cured" because while the blinking seems more resistant to fast scrolling, it still doesn't start automatically upon initial appearance of the cell(s) in question. I'll report back when I try it on the device.

Can't reload a collection view - null

I'm developing a simple app which downloads images from Dribbble, but I'm having problem reloading the data for my collection view. I have two views set up with ViewDeck, center is my main view which contains the collection view and another view contains table view with settings and from there I'm trying to call a method in the first view and reload data when item is tapped but it just doesn't work.
I tried to call the same method from the main window using button -> worked like a charm but from the second window it just doesn't update the data.
I tried to debug somehow and seems like my collection is null when the reload is called, no idea why.
SettingsViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tap");
JKViewController *appDelegate = [[JKViewController alloc] init];
appDelegate.dataHasChanged = YES;
[appDelegate refresh];
[self.viewDeckController closeLeftViewAnimated:YES];
}
MainView
- (void)refresh{
NSLog(#"refresh");
if(dataHasChanged)
{
switch (listType) {
case 0:
[self refreshWithList:SPListPopular];
break;
case 1:
[self refreshWithList:SPListEveryone];
break;
case 2:
[self refreshWithList:SPListDebuts];
break;
case 3:
[self refreshWithList:SPListPopular];
break;
default:
[self refreshWithList:nil];
break;
}
dataHasChanged = NO;
NSLog(#"Should refresh");
}
NSLog(#"%d", [self->shots count]);
NSLog(#"Collection view: %#",self.collectionView.description);
NSLog(#"self.list: %#",self.list);
NSLog(#"List type: %d", listType);
}
This doesn't work :/, but when I call it from button in the MainView it works.
- (IBAction)changeList:(id)sender {
[self refreshWithList:SPListDebuts];
}
Does anyone know what could be the issue?
Edit - Solved
Getting the right instance of the centerViewController
JKViewController *mainController = ((UINavigationController*)self.viewDeckController.centerController).visibleViewController.navigationController.viewControllers[0];
The reason that you are not seeing your data being updated is because you are creating a new view controller and telling that to refresh. This new view controller has been initialized but not added to your view hierarchy. What you want to do is message the existing view controller like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tap");
JKViewController *mainViewController = self.viewDeckController.centerViewController;
mainViewController.dataHasChanged = YES;
[mainViewController refresh];
[self.viewDeckController closeLeftViewAnimated:YES];
}
Also, please note that I have changed the variable name in my revision. Naming a UIViewController instance 'appDelegate' is very confusing.

Objective-C, how to Generally resignFirstResponder?

(my boss says) that I have to implement a "Done" button on a navBar so that the various items in the view (that contain an edit box) will dismiss their keyboard (if they were in focus).
It seems that I must iterate through all items and then call resignFirstResponder on each on the off-chance that one of them is in focus? This seems a bit messy (and hard to maintain if e.g. someone else adds more items in future) - is there a better way to do it?
I have found it!
Thanks to this
I discovered that all I need do is this:-
-(void) done {
[[self.tableView superview] endEditing:YES];
}
// also [self.view endEditing:YES]; works fine
[remark]
Also I learn how to do the equivalent of an "eventFilter" to stop UITableViewController from swallowing background touch events by intercepting them before they get there - from the same, wonderful post on that thread - see "DismissableUITableView".
[end of remark]
You don't have to iterate through the controls since only one can be first responder at the moment.
This will reset the responder to the Window itself:
[[self window] makeFirstResponder:nil]
One solution is to use a currentTextField Object,
In .h file have an instance variable as
UITextField *currentTextField;
Now in .m file.
Note : Dont forget to set the delegates of all the textField to this class
- (void)textViewDidBeginEditing:(UITextView *)textView
{
currentTextField = textField;
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
currentTextField = nil;
}
Now in your button action method
-(IBAction)buttonTap
{
if([currentTextField isFirstResponder])
[currentTextField resignFirstResponder];
}
This avoids iterating through all the text field.
I think best way to handle it by searching all subviews of main view with recursive function, check example below
- (BOOL)findAndResignFirstResponder {
if (self.isFirstResponder) {
[self resignFirstResponder];
return YES;
}
for (UIView *subView in self.subviews) {
if ([subView findAndResignFirstResponder]) {
return YES;
}
}
return NO;
}
and also you can put this method to your utility class and can use from tap gesture. All you have to do is simply adding to gesture to view.
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(hideEverything)];
[self.tableView addGestureRecognizer:gestureRecognizer];
and than you can call hideEverything method;
- (void) hideKeyboard {
[self.view findAndResignFirstResponder];
...
...
}

Resources