bbraunj
bbraunj

Reputation: 153

UITableView can't scroll to the bottom after keyboard dismisses

I am trying to create a copy of the Any.DO app. The tableHeaderView is a "NewTaskCell" view, which contains a with a UITextField. For some reason, the tableView isn't able scroll all the way to the bottom after the keyboard shows up. Here's what I've tried:

  1. Changing the frame of the tableView
  2. Changing the bounds of the tableView
  3. Changing the contentSize of the tableView
  4. Updating the footer view after the keyboard dismisses

It has to be something to do with either the keyboard or the textfield because it only happens if the keyboard shows up.

In the animation below, you can see that if I click the plus button and show the keyboard, then the tableView can't scroll to the bottom. After that, if I click the plus button and don't show the keyboard, then it can scroll to the bottom fine.

Here is the source code for the whole project: Any.DO

This is what's in my viewDidLoad method.

- (void)viewDidLoad
{
    [super viewDidLoad];

    [[self tableView] setAllowsSelection: YES];
    [[self tableView] setShowsVerticalScrollIndicator: NO];

    // Make sure there aren't any empty cells showing at the bottom
    // Caused initial error [[self tableView] setTableFooterView: [UIView new]];

    // Fixes error at first, until the keyboard shows up
    UIView *footer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 45)];
footer.backgroundColor = [UIColor clearColor];
self.tableView.tableFooterView = footer;

    // Make the separators look right
    [[self tableView] setSeparatorStyle: UITableViewCellSeparatorStyleSingleLine];
    [[self tableView] setSeparatorColor: [UIColor colorWithWhite: 0.95 alpha: 1]];

    UINib *nibTwo = [UINib nibWithNibName: @"TaskCell" bundle: nil];
    [[self tableView] registerNib: nibTwo forCellReuseIdentifier: @"TaskCell"];

    // Create the new task cell to go above the tableView
    // --------------------------------------------------
    NewTaskCell *myNewTaskCell = [[NewTaskCell alloc] init];

    // Need to access it later to show the keyboard
    [self setTheNewTaskCell: myNewTaskCell];

    [[theNewTaskCell view] setFrame: CGRectMake(0, 0, 320, 44)];
    [[[theNewTaskCell reminderButton] layer] setOpacity: 0];
    [[[theNewTaskCell locationButton] layer] setOpacity: 0];
    [[[theNewTaskCell addTaskButton] layer] setOpacity: 0];

    // Assign a method for each button
    [[theNewTaskCell reminderButton] addTarget:self action:@selector(addReminder)     forControlEvents: UIControlEventTouchUpInside];
    [[theNewTaskCell locationButton] addTarget:self action:@selector(addLocationReminder) forControlEvents: UIControlEventTouchUpInside];
    [[theNewTaskCell addTaskButton] addTarget:self action:@selector(addTaskToList) forControlEvents: UIControlEventTouchUpInside];

    // Separator view
    UIView *separatorView = [[UIView alloc] initWithFrame: CGRectMake(0, 43, 320, 1)];
    [separatorView setBackgroundColor: [UIColor colorWithWhite: 0.5 alpha: 0.2]];
    [[theNewTaskCell view] addSubview: separatorView];

    [[self tableView] setTableHeaderView: [theNewTaskCell view]];
    [[self tableView] setContentInset: UIEdgeInsetsMake(-44, 0, 0, 0)];
    // ---------------------------------------------------
    // For Reordering
    [[self tableView] setEditing: YES];

    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget: self action: @selector(longPress:)];
    [longPress setMinimumPressDuration: 0.3];
    [longPress setDelaysTouchesBegan: YES];

    [self setLongPressGesture: longPress];
    [[self tableView] addGestureRecognizer: longPress];
}

Code for when the + button is clicked

-(void)makeNewTask
{
    // Create the fromPosition and toPosition of the label animation
    CGRect fromPosition;
    CGRect toPosition = CGRectMake(todayDayLabel.frame.origin.x +     todayDayLabel.superview.frame.origin.x, todayDayLabel.frame.origin.y + todayDayLabel.superview.frame.origin.y, todayDayLabel.frame.size.width, todayDayLabel.frame.size.height);

    if ([self dayType] == TaskDayTypeToday)
        fromPosition = toPosition;
    else if ([self dayType] == TaskDayTypeTomorrow)
        fromPosition = CGRectMake(tomorrowDayLabel.frame.origin.x + tomorrowDayLabel.superview.frame.origin.x, tomorrowDayLabel.frame.origin.y + tomorrowDayLabel.superview.frame.origin.y, tomorrowDayLabel.frame.size.width, tomorrowDayLabel.frame.size.height);
    else if ([self dayType] == TaskDayTypeUpcoming)
        fromPosition = CGRectMake(upcomingDayLabel.frame.origin.x + upcomingDayLabel.superview.frame.origin.x, upcomingDayLabel.frame.origin.y + upcomingDayLabel.superview.frame.origin.y, upcomingDayLabel.frame.size.width, upcomingDayLabel.frame.size.height);
    else
        fromPosition = CGRectMake(somedayDayLabel.frame.origin.x + somedayDayLabel.superview.frame.origin.x, somedayDayLabel.frame.origin.y + somedayDayLabel.superview.frame.origin.y, somedayDayLabel.frame.size.width, somedayDayLabel.frame.size.height);
    // -------------------------------------------------------------

    isLoading = YES;

    [[self tableView] reloadData];

    // Make sure the scroller doesn't show up on the side
    [[self tableView] setShowsVerticalScrollIndicator: NO];

    // Create the label and animate it -------------------------
    UILabel *lbl = [[[[[NSBundle mainBundle] loadNibNamed: @"DayCell" owner: self options:nil] objectAtIndex: 0] subviews] objectAtIndex: 0];

    if ([self dayType] == TaskDayTypeToday)
        [lbl setText: @"TODAY"];
    if ([self dayType] == TaskDayTypeTomorrow)
        [lbl setText: @"TOMORROW"];
    if ([self dayType] == TaskDayTypeUpcoming)
        [lbl setText: @"UPCOMING"];
    if ([self dayType] == TaskDayTypeSomeday)
        [lbl setText: @"SOMEDAY"];

    [self setTheDayLabel: lbl];
    [theDayLabel setFrame: fromPosition];

    [theDayLabel setTranslatesAutoresizingMaskIntoConstraints: YES];
    [[self tableView] addSubview: theDayLabel];

    // animate it moving to the right position
    if (!animateContentInset) {
        [[self tableView] setContentInset: UIEdgeInsetsMake(0, 0, 0, 0)];
        [[self tableView] setScrollEnabled: NO];
    }
    else {
        [UIView animateWithDuration: 0.4 delay: 0 options: UIViewAnimationOptionCurveEaseOut animations: ^{
            [[self tableView] setContentInset: UIEdgeInsetsMake(0, 0, 0, 0)];
            [theDayLabel setFrame: toPosition];
        } completion: ^(BOOL done){
            [[self tableView] setScrollEnabled: NO];
        }];
    }
    // ----------------------------------------------------------
    // Create the UIButton to get back to the tableView
    UIButton *backToTableView = [UIButton buttonWithType: UIButtonTypeCustom];
    [backToTableView setFrame: CGRectMake(1, 45, 320, 480)];
    [backToTableView addTarget: self action: @selector(showTableView) forControlEvents: UIControlEventTouchUpInside];

    [[self tableView] addSubview: backToTableView];

    // Show the keyboard
    [[theNewTaskCell theNewTaskTextField] setDelegate: self];
    //[[theNewTaskCell theNewTaskTextField] becomeFirstResponder];
}

Code for the backToTableView button

-(void)showTableView
{
    [theDayLabel setHidden: YES];

    [[self tableView] setScrollEnabled: YES];

    [UIView animateWithDuration: 0.4 animations:^{
        // self.tableView.contentInset = UIEdgeInsetsZero;
        [[self tableView] setContentInset: UIEdgeInsetsMake(-REFRESH_HEADER_HEIGHT, 0, 0, 0)];

        // Get the newTaskCell view, then get the textfield from that view
        [[theNewTaskCell theNewTaskTextField] setText: @""];
        [[theNewTaskCell addTaskButton] setAlpha: 0];
        [[theNewTaskCell reminderButton] setAlpha: 0];
        [[theNewTaskCell locationButton] setAlpha: 0];

        [[theNewTaskCell theNewTaskTextField] resignFirstResponder];

        // Remove the button to go back to the tableView
        for (UIView *v in [[self tableView] subviews]) {
            if (v.frame.origin.x == 1)
                [v removeFromSuperview];
        }

        isLoading = NO;

        [[self tableView] reloadData];
    }];
}

This is what the issue looks like:

issue

Upvotes: 3

Views: 2877

Answers (7)

stonedauwg
stonedauwg

Reputation: 1407

Had a similar problem and solved it by removing code in viewWillLayoutSubviews that was setting the contentInset of the TableVC like this:

UIEdgeInsets insets = UIEdgeInsetsMake([self.topLayoutGuide length], 0, 0, 0);
[myTableViewController.view setContentInset:insets];

Thing I don't understand is, why would setting insets at all, especially when not setting the bottom one as shown above, make any difference? So by not setting them at all, presumably leaving the insets set to the default of UIEdgeInsetsZero, it somehow fixes this issue. Methinks this could be an iOS bug. I notice I only get this issue with 32bit devices.

Upvotes: 0

carbonr
carbonr

Reputation: 6067

For the future, on iOS7 anyone experiencing similar problem could do something like this

 [[self tableView] setContentInset: UIEdgeInsetsMake(0, 0, self.bottomLayoutGuide.length, 0)];

topLayoutGuide and bottomLayoutGuide is part of auto layout and length is available to both and its a great feature.

Upvotes: 0

bbraunj
bbraunj

Reputation: 153

I solved the problem by adding this code into the showTableView method:

[[self tableView] setContentInset: UIEdgeInsetsMake(REFRESH_HEADER_HEIGHT, 0, 0, 0)];
[[self tableView] setContentInset: UIEdgeInsetsMake(0, 0, 0, 0)];

Upvotes: 1

Nitin Alabur
Nitin Alabur

Reputation: 5812

Couldn't understand what you are trying to do in the code.

But, looking at the animation, I think, you just need to reduce the height of your tableview.frame by 10-20 points.

This behavior is usually seen when the content & frame heights are greater than the visible area. setting the frame height to match that of the visible area will make your table view scroll.

Upvotes: 1

h4cky
h4cky

Reputation: 894

If that offset is the problem - one option to solve it with going to full screen for you entire aplication ot just for that ViewController.

This question answer you how - Is this the right way to make an iPad app full screen?

Upvotes: 0

George Mitchell
George Mitchell

Reputation: 1168

It looks like your UITableView is being offset by 20px by the status bar. You should decrease the height of your UITableView by 20px and it'll be fixed.

Upvotes: 2

Burhanuddin Sunelwala
Burhanuddin Sunelwala

Reputation: 5343

You may be setting the tableView frame to entire screen which aslo includes status bar. Decrease the height of your tableView to 20px

[[self tableView] setFrame:CGRectOffset(self.tableView.frame, 0, -20)];

or set the frame to self.view.frame

Upvotes: -1

Related Questions