I have a button in a UITableViewCell which is stored as an ivar called currentButton. When that button is clicked a UIView containing a UIPickerView and UIToolBar is invoked and shown from the bottom of the screen. However, I have looked at other posts for hours and this following code still doesn't work. The cells sometimes scroll down below the keyboard and the cells all the way at the bottom of the UITableView do not scroll up enough to go on top of the UIView backgroundPickerView. This is my code:
CGPoint center = currentButton.center;
CGPoint rootViewPoint = [currentButton.superview convertPoint:center toView:self.view];
NSIndexPath *indexPath = [measurementTableView indexPathForRowAtPoint:rootViewPoint];
CGRect cellRect = [measurementTableView rectForRowAtIndexPath:indexPath];
if (cellRect.origin.y + cellRect.size.height >= backgroundPickerView.frame.origin.y) {
[measurementTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
CGPoint offset = measurementTableView.contentOffset;
offset.y += ((cellRect.origin.y - cellRect.size.height) - backgroundPickerView.frame.origin.y);
[measurementTableView setContentOffset:offset animated:YES];
}
Does anyone see what I am doing wrong here?
UITableViewCell are reused,keeping a reference of a UITableViewCell's subView is not a good approach.
If the special UITableViewCell is not in UITableView's visibleCells ,its frame is undefined.
A solution is:
Create a custom UITableViewCell that has the same structure as you need.
Keep a reference of the indexpath for the custom UITableViewCell.
Use this indexpath to do the work .
I hope this will work:
[measurementTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
CGPoint offset = measurementTableView.contentOffset;
offset.y += backgroundPickerView.frame.height;
This code worked for me. Uses a different approach than the ones suggested here and it's good for what I need.
CGRect buttonFrame = [currentButton convertRect:currentButton.bounds toView:measurementTableView];
NSIndexPath *indexPath = [measurementTableView indexPathForRowAtPoint:buttonFrame.origin];
CGRect backPickFrame = [backgroundPickerView convertRect:backgroundPickerView.bounds toView:measurementTableView];
if (buttonFrame.origin.y + buttonFrame.size.height >= backPickFrame.origin.y) {
measurementTableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, backgroundPickerView.frame.size.height, 0.0);
[measurementTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
} else {
[measurementTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
First problem is that UITableView is partially hidden under a keyboard. So when you trying scroll to bottom rows table will bounce back and required content is not visible.
To solve this problem when keyboard appears/hides you have two choices.
update value of self.tableView.contentInstet.bottom to take into account keyboard size
update frame of table, if you are using auto-layout then best approach will be update constant part of bottom constraint (IMO this is better approach).
After you did that you can do this:
CGRect rectToBeVisible = [currentButton convertRect: currentButton.bounds
toView: self.tableView];
[self.tableView scrollRectToVisible: rectToBeVisible animated: YES];
I could messed up view to which convertion should be performed, so experiment a bit, but general concept should be clear.
Related
So I do this to program a scroll in my table view controller to my first cell.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionBottom
animated:YES];
Then I want to fetch the new frame of this view:
NSArray *a=[self.tableView visibleCells];
UIView *view = [a firstObject];
However, the frame that this view gives me (which is the first one in my array of cells) returns the position before the scroll occurred.
I have tried putting it in an animation block to make sure it has finished scrolling before it tries to find it's position
The frame is always related to the super view. If you are interested then debug (or print out) what the super view of the cell view is. It is not directly the table view. (Used to be until iOS 7 or 8 or so but now it is embedded in some view object that scrolls along with the cell.)
You could try fetching the frame of its super view.
However, you'd be saver comparing the origin of the cell with the base view (self.view from the UIViewController's point of view which is the UITableView in case of a standard UITableViewController). For doing so you can use the convertPoint:fromView or convertPoint:toView methods.
So it turns out it is because I had it wrapped in an animation within an animation and so the SECOND animation wasn't completing before it checked for the frame:
[UIView animateWithDuration:0.5 animations:^{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionBottom
animated:NO];
}completion:^(BOOL finished)
{
NSArray *a=[self.tableView visibleCells];
InstructionView *iv = [[InstructionView alloc] initWithTargetView:[a firstObject] andInstructionType:kNeedYouInstruction];
iv.delegate = self;
[iv show];
}];
changing the second animation to NO stopped my problem :D
I would like to fire an event when the subview of a UITableviewCell reaches a certain point on the screen, say for example when its origin.y reaches 44 points. It would also be nice to know if it was being scrolled up or down when it reached that point. I was playing with KVO on the frame of the subview but this seems fixed to the cell so no changes with that. Is this task possible?
Vertical position of UITableViewCell is defined by its frame property, which represents position and size of that cell within its superview, UITableView. Typically, the frame property of the cell is changing only once for every time that UITableView requests a cell from its delegate for specific index path. That's it, UITableView gets a cell, places it in itself and that cell just lays there unchanged until rectangle stored in bounds property of UITableView ceases to include rectangle stored in the frame property of that cell. In that case UITableView marks that cell as hidden and places it into the pool of reusable cells.
Since the process of scrolling in essence is not a repositioning of subviews – it is merely a curious illusion of shifting a bounds viewport of UITableView – constant observing of UITableViewCell's properties are pointless.
Moreover, the frame property of subview of UITableViewCell also represents a position and size of that subview within its container, UITableViewCell. It is also will not change on scroll.
You need to observe changes in UITableView bounds property, which is also represented by contentOffset by the way. UITableView happens to be a subclass of UIScrollView, so you can use its delegate methods, such as -scrollViewDidScroll:, like in this simple example:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UITableView *tableView = (UITableView *)scrollView;
// current position
CGFloat currentY = tableView.bounds.origin.y;
// current inset
CGFloat currentInset = tableView.contentInset.top;
// trigger line position
CGFloat triggerY = currentInset + currentY + kYourTriggerPosition;
// nice visual mark
UIView *line = [tableView viewWithTag:88];
if (!line) {
line = [[UIView alloc] initWithFrame:CGRectZero];
line.tag = 88;
line.backgroundColor = [UIColor redColor];
[tableView addSubview:line];
}
line.frame = CGRectMake(0, triggerY, tableView.bounds.size.width, 1);
// determine scroll direction
BOOL scrollingUp = currentY > self.previousY;
// all visible cells
NSArray *visibleCells = tableView.visibleCells;
[visibleCells enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger idx, BOOL *stop) {
// subview
UIView *subview = [cell viewWithTag:kYourSubviewTag];
// subview frame rect in UITableView bounds
CGRect subviewRect = [subview convertRect:subview.frame toView:tableView];
// trigger line within subview?
BOOL triggered = (CGRectGetMinY(subviewRect) <= triggerY) && (CGRectGetMaxY(subviewRect) >= triggerY);
if (triggered) {
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
NSLog(#"moving %#, triggered for cell at [%2d:%2d]", #[#"down", #"up"][scrollingUp], indexPath.section, indexPath.row);
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}];
// save current position for future use
self.previousY = currentY;
}
Reach that subview of UITableViewCell with cellForRowAtIndexPath or tableView.visibleCells, then call convertRectToView: on that subview.
convertRectToView: allows you to do translations on different coordinate systems. For example, you can detect where that subview appears on screen by translating its frame within its superview into viewController.view
For more: Apple Documentation
Since I can not comment I am writing as an Answer
Changing the answer for the requirement.
Here is how I think it can be done, you need to have your custom UITableViewCell which has a function which can take in co-ordinates (again based on your logic if you just want an intersection where a cell just touches a boundary or if it has to be at a precise position in a frame), so your function would take the co-ordinates and will return a true and a false if it will tell you if the condition is met, and in your cellForTable function you call the function of UITableView cell to check if your condition is met, if it is in your view you create a subview at the exact location. You can also modify the function to return you the exact frame-cordinates so you can use them to create a subview\
Here's a simple approach, which you can use if you have only one section without section header.
Add this to your implementation:
CGFloat lastContentOffSet;
And then add this delegate method of scrollview as tableview is also a scrollview.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat cellHeight = 50;
CGFloat touchingPoint = 44.0f;
NSInteger rowNo = floor(scrollView.contentOffset.y / cellHeight);
NSInteger startPoint = (rowNo * cellHeight);
if (scrollView.contentOffset.y > lastContentOffSet) {
NSLog(#"Row %ld scrolled down", (long)rowNo);
if (scrollView.contentOffset.y > startPoint + touchingPoint) {
// Do something here
NSLog(#"Do something here");
}
}
else {
NSLog(#"Row %ld scrolled up", (long)rowNo);
if (scrollView.contentOffset.y > startPoint + touchingPoint) {
// Do something here
NSLog(#"Do something here");
}
}
lastContentOffSet = scrollView.contentOffset.y;
}
Change value of the cellheight according to your tableview cell and the distance of that subview with the cell.
Let me know if this code helped. :)
I have a view with 11 rows which contains textfields, textViews
Total height of all the elements > ScreenHeight
I have a textfield as the second last element.
Empty row as the last element
To present this View, I used TableView
I use different identifier for different types of elements.
In CellForRowAtIndexPath, I return cell based on indexaPath.row
The view is displayed as expected.
I scroll and still rows are displayed as expected
Since Textfield is at the end and keyboard is presented, so
When textField is clicked
I move the tableView.frame up by keyboard.height
It moves up correctly.
I see cellForIndexPath called for last element (indexPath.row = 10)
Now the problem starts
textField.cell.row = 9 (10th Element)
To dismiss keyboard, I press on the cell below the "textFieldCell" (last element) which is expected to be a row with indexPath.row = 10
In textField.didEndEditing, I reset tableView.frame = originalFrame
I scrollToBottomAnimated
Now I see, iOS calling cellForIndexPath = 7,6,5,4
But didSelectRowAtIndexPath gives me the rowNumber = 7 which is not what I clicked.
I clicked row 11th or indexPath.row = 10
But due to resizing of tableView and scolling, things went wrong.
What am I doing wrong.
What do I need to do to get indexPath.row = 10 and not 7 when I click the last cell?
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
[UIView beginAnimations:#"moveView" context:nil];
[UIView setAnimationDuration:0.0];
self.frame=CGRectMake(0,66,ScreenWidth,ScreenHeight - keyboardFrame.size.height-66);
[UIView commitAnimations];
[self scrollToBottomRowAnimated:YES];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[textField resignFirstResponder];
[UIView beginAnimations:#"moveView" context:nil];
[UIView setAnimationDuration:0.0];
self.frame=CGRectMake(0,66,ScreenWidth,ScreenHeight - 66);
[UIView commitAnimations];
[self scrollToBottomRowAnimated:YES];
}
- (void)scrollToBottomRowAnimated:(BOOL)animated
{
long lastRowNumber = [self numberOfRowsInSection:0]-1;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lastRowNumber inSection:0];
[self scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionBottom animated:animated];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(indexPath.row);
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
}
I suspect (not sure though) the problem is you are mixing two types of animations really and in the approach to shrinking the table view to compensate for keyboard.
Do not use tableview frame change via committed animations. Instead I recommend using a trivial vertical content offset in the table view. UITableView is really a UIScrollView subclass, so it has all the methods for scrollview and it supports vertical content offset for its content.
In textfieldDidBeginEditing:
[self.tableView setContentOffset:CGPointMake(0, kKeyboardHeight) animated:YES];
and in textfieldDidEndEditing just get rid of the offset via
[self.tableView setContentOffset:CGPointZero animated:YES];
I have a UITableView where I add rows dynamically. Basically a chat app.
I need to scroll the UITableView to bottom after reloadData
My code is:
It scrolls to here:
https://dl-web.dropbox.com/get/Screen%20Shot%202014-12-05%20at%2013.04.23.png?_subject_uid=44249794&w=AADgJkJJOGK_ANH_cXWnxeXmhFmF5KHjZllg-kP66HARdw
When manually scrolling, I can scroll even further to here: https://dl-web.dropbox.com/get/Screen%20Shot%202014-12-05%20at%2013.05.19.png?_subject_uid=44249794&w=AADtQEfMHMy0Ounri5W3gBTLNgR4uckT_gBdYQel9vD1qQ
My code:
[_chatTable reloadData];
NSIndexPath* indexPath = [NSIndexPath indexPathForRow: [[AppData getChatLog]count]-1 inSection: 0];
[_chatTable scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
I need the UITableView to scroll all the way to the bottom.
Sorry,I couldn't open up the given links.Try this
UITableView *yourTableView = yourTableViewToBeSetHere;
CGRect lastCellRect = CGRectMake(0, CGFLOAT_MAX, 50, 50);
// Scroll to the particular rect
[yourTableView scrollRectToVisible:lastCellRect
animated:YES];
Good luck!
I'm trying to expand UITableView's tableHeaderView like how Twitter does it (well more like how they used to do it). As I was messing with it, I happened to implement -scrollViewDidScroll like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.tableView.contentOffset.y < 0) {
float offsetY = self.tableView.contentOffset.y;
CGRect tblFrame = self.tableView.tableHeaderView.frame;
[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
self.tableView.tableHeaderView.frame = CGRectMake(tblFrame.origin.x, offsetY, tblFrame.size.width, originalHeaderHeight-offsetY);
}
}
and it works almost exactly how I wanted it to. I went back to clean it up, and I took the [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; out, and it stopped working.
When I take out -cellForRowAtIndexPath: tableHeaderView's y origin does not get changed, so it expands, but it does not stay at the top of the view. Can someone help me with this?