scrollToRowAtIndexPath not scrolling to inactive/unloaded cells - iphone

I've noticed that scrollToRowAtIndexPath:atScrollPosition:animated: doesn't scroll to cell that are not currently in view, so If I have 100 cells and I need to get to the one at 70, the call to that selector will do nothing.
Is there a way I can get that cell into memory? I already have the cell's index path...
I need to scroll to that position in my app when the user would want to go there.
Thanks for any thoughts!
EDIT: #dasblinkenlight
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide) name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillHide
{
//Load remote cell here then scroll
// :( dont know how to load remote cell yet
}
- (void)keyboardWillShow
{
//Load remote cell here then scroll
// :( dont know how to load remote cell yet
//_cellIndexPath gets assigned on didSelectRowAtIndexPath:
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:_cellIndexPath.row inSection:_cellIndexPath.section] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
EDIT2:
- (void)keyboardWillShow
{
//Load remote cell here then scroll
[NSThread detachNewThreadSelector:#selector(keyboardWillShowThreaded) toTarget:self withObject:nil];
}
- (void)keyboardWillShowThreaded
{
[NSThread sleepForTimeInterval:2.0];
[self performSelectorOnMainThread:#selector(keyboardWillShowMainThread) withObject:nil waitUntilDone:YES];
}
- (void)keyboardWillShowMainThread
{
//Get the cell
//_textFieldThatHasFirstResponder is a subview in the cell
//This returns null, probably because the cell is not loaded into memory
UITableViewCell *cell = [_textFieldThatHasFirstResponder superview];
NSLog(#"CELL TO SCROLL TO: %#",cell);
NSIndexPath *indexPathForCell = [self.tableView indexPathForCell:cell];
[self.tableView scrollToRowAtIndexPath:indexPathForCell atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}

OK, I've got the cause of this, see, you have:
NSIndexPath *indexPathForCell = [self.tableView indexPathForCell:cell]; // nil here
[self.tableView scrollToRowAtIndexPath:indexPathForCell atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
When you send indexPathForCell: for an out-of-view cell it returns nil, so tableView doesn't know where to scroll to.

you can implement a delegate so that you can call it from the class where you are in, so that is can update the position

"I've noticed that scrollToRowAtIndexPath:atScrollPosition:animated:
doesn't scroll to cell that are not currently in view, so If I have
100 cells and I need to get to the one at 70, the call to that
selector will do nothing. "
No. it is not true. I have just tried with a tableview with 100 rows. The following code works well.
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:78 inSection:0]
atScrollPosition:UITableViewScrollPositionNone animated:YES];

I don't think adding sleep is changing anything. It just delays execution but does not affect the order. Can you check if the index your are passing to scrollToRowAtIndexPath is valid? I remember seeing the same problem myself but it was related to invisible cell. It was impossible to retrieve invisible cell (tableView returned nil) and therefore its index path was nil and thus scrolling failed.
You could store locations of all cells or compute it on the fly and then pass it to
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated;
It's the only solution I can imagine.

Related

scrolling other UITableview at the same time as another UItableview

I have a quite complicated view. What I basicly have is the following
You can see I have a vertical tableview. In each cell I have a horizontal tableview. What I want to do now is when I scroll one horizontal tableview, every other horizontal tableview should scroll also.
In my subClass of the vertical tableviewCell I have the following.
for(HorizontalTableCell *cell in mainTable.subviews){
if([cell isKindOfClass:[HorizontalTableCell class]]){
for(UITableView *cellTable in cell.subviews){
if([cellTable isKindOfClass:[UITableView class]]){
NSLog(#"cell table is table %#",cellTable);
[cellTable setContentOffset:CGPointMake(scrollView.contentOffset.x, 0) animated:NO];
}
}
}
}
But this is not working OK. Can anybody help me with this?
You will need to use the scroll view delegate methods. I'd suggest posting a Notification and then picking it up in the cells like so...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"CellScrolledHorizontally" object:self.tableView];
}
Then in the horizontal cells you can observe the notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(scrolled:) name:#"CellScrolledHorizontally" object:nil];
- (void)scrolled:(NSNotification *)notification
{
UITableView *notificationTableView = notification.object;
if (notificationTableView == self.tableView)
return;
[self.tableView setContentOffset:notificationTableView.contentOffset];
}
Alternatively, use a UICollectionView.
Can u try this logic:
Instead of Horizontal tableviews use a UIScrollView. Now the base is a UITableView on which the horizontal scrollviews. Now use UIScrollview delegate methods for event handling.
Although this is not the solution but you can look at EASYTABLEVIEW

Enable all textfields in UITableViewCell when in Edit mode

I have a bunch of custom UITableViewCells with a label and textbox. I have the textbox disabled but I want to make it so when the user taps the Edit button it will make the textboxes editable. How can I do this so that ALL the UITextFields in the UITableView become enabled?
I have
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[self.navigationItem setHidesBackButton:editing animated:YES];
if (editing) {
}
}
but cannot add the textbox enable in there since I don't have access to all the textfields. Would I need to add code to grab all the cells and loop through them and enable the textfields?
I would do this by setting a isEditing BOOL on your UITableViewDelegate in the setEditing:animated: method and just updating visible cells when the value is changed.
NSArray *visibleCells = [myTable visibleCells];
for (MyTableViewCell *cell in visibleCells)
cell.textField.enabled = isEditing;
Then, using your UITableViewDelegate again, update new cells as they appear in tableView:willDisplayCell:forRowAtIndexPath:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.textField.enabled = isEditing;
}
Edit your subclass of UITableViewCell and register your instances for an editing notification in your subclass's viewDidLoad or init method:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(disableTextBox) name:#"EditingIsEnabled" object:nil];
And implement a method called disableTextBox that disables the text box for that cell.
Then in your setEditing:animated method, post the notification when you want to start editing:
[[NSNotificationCenter defaultCenter] postNotificationName:#"EditingIsEnabled" object:self];
Override the method dealloc in your UITableViewCell and remove yourself as an observer, or you'll crash:
[[NSNotificationCenter defaultCenter] removeObserver:self];
If you're not using ARC, make sure to call [super dealloc]. If you're using ARC, do not call super.
You can do the same thing when you want to disable all the cells, just post a notification with a different name like EditingIsDisabled.
Let me know if you need me to flesh out the code a bit more.
Edit: I like DBD's method better in this situation.

Help With NSNotifcation and Asynchronous Downloading

I am sending a notification from one view to another view. My problem is that the notification in the view that I am calling in my cellForRowAtIndexPath method is only getting sent when the tableview is scrolling. How can I stop this and make it send the notification once the images have downloaded? Here is my code: https://gist.github.com/756302
Thanks
MKDev
as far as I understand your code, the message will trigger the reload of the whole table. That should lead to a refresh of the cells.
Thus, you'll need to check in line 76, if the cell is being drawn because a reload was triggered from the finish-message (and the image is now ready to display) or if you need to start the asynchronous download of the image.
The first thing which comes into my mind to check this is to set a property in reloadTableView:
- (void)reloadTableView
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"aaa"];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"name" object:nil];
NSLog(#"removeobserver");
loadImageFinished = YES;
// if your table has several sections you'll need to adopt the section number
NSIndexSet *indices = [[NSIndexSet alloc] initWithIndex:0];
[self.tableView reloadSections:indices withRowAnimation:UITableViewRowAnimationFade];
[indices release];
}
and then to add in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
if (loadImageFinished) {
...
} else {
[asyncImage loadImageFromURL:[NSURL URLWithString:pathImage]];
}
...
}
Note that there could be other reasons why the table is being reloaded - the view could have been disappeared or unloaded and you might not wish to trigger your asynnchronous loading several times.
Your code should work right, when the connectionDidFinishLoading, you call the NSNotificationCenter to send the notification, there is no post method in cellForRowAtIndexPath

How to scroll a UITableView to the tableFooterView

I have a UITableView whose contents are dynamically changing, like a FIFO stack. Cells are added to the bottom and removed from the top.
This works beautifully, and I can scroll to the indexPath so that the newest message always scrolls down to the bottom (Like a chat application).
Now.. I want to add a footer to that table section. Instead of using
SrollToRowAtIndexPath
I would like to be able to scroll to the tableFooterView.
Any ideas how I can do that would be appreciated.
I am using this to scroll to the footer view of a tableView:
[self.tableView scrollRectToVisible:[self.tableView convertRect:self.tableView.tableFooterView.bounds fromView:self.tableView.tableFooterView] animated:YES];
The best way to scroll a UITableView to the bottom of it's footerView is to simply set the content offset. You can calculate the bottom using the contentSize and the current bounds
Here is the way I do it.
CGPoint newContentOffset = CGPointMake(0, [self.instanceOfATableView contentSize].height - self.instanceOfATableView.bounds.size.height);
[self.instanceOfATableView setContentOffset:newContentOffset animated:YES];
Thanks to iphone_developer here is what I did :
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[tableView numberOfRowsInSection:0]-1 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
Then while I'm adding rows, I'm calling this and my tableView's footer view keeps being visible
Swift Version:
tableView.scrollToRow(at: IndexPath(row: self.tblview.numberOfRows(inSection: 0) - 1, section: 0), at: .top, animated: true)
So many poor answers. :-)
The BEST way:
[self.tableView scrollRectToVisible:
self.tableView.tableFooterView.frame animated:YES
];
Maybe something like:
[tableView setContentOffset:CGPointMake(0, tableView.contentSize.height) animated:YES];
Since UITableView is a subclass of UIScrollView, you can scroll to wherever you like using the UIScrollView method
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
Just set the rect so that when it's visible the footer is visible, and you'll have your solution (you can use the footer's rect or something else, just so long as you get the right behavior all the time).
My late answer is for developer, who need to show footer when the keyboard is shown.
The right solution is to consider contentInset property (which can be changed after keyboard is shown), so it's super easy:
- (void)scrollToFooter {
UIEdgeInsets tableInsets = self.tableView.contentInset;
CGFloat tableHeight = self.tableView.frame.size.height - tableInsets.bottom - tableInsets.top;
CGFloat bottom = CGRectGetMaxY(self.tableView.tableFooterView.frame);
CGFloat offset = bottom - tableHeight;
if(offset > 0.f) {
[self.tableView setContentOffset:CGPointMake(0, offset) animated:YES];
}
}
I must notice, that in my case tableView was added to my own ViewController and one of cells have UITextField which become first responder. To move show footer when keyboard is shown you need to register keyboard did shown notification and (on iOS7) perform this method in the end of current run loop, coz in this case iOS7 automatically perform scrollToRowAtIndexPath after our method and footer will not be shown.
-(void)registerKeyboardNotification {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShown:)
name:UIKeyboardDidShowNotification object:nil];
}
- (void)keyboardDidShown:(id)notification {
//move to the end of run loop
[self performSelector:#selector(scrollToFooter) withObject:nil afterDelay:.0];
}
The easiest way to do this is to use UITableViewScrollPositionTop on the LAST ROW in the LAST SECTION. This works very well for me...
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:LAST_ROW inSection:LAST_SECTION] atScrollPosition:UITableViewScrollPositionTop animated:YES];
Make sure your Table Footer View is well spaced out at the bottom and it should sit nicely animated inside the view...
Hope this helps...
This good work for me!
CGRect footerBounds = [addCommentContainer bounds];
CGRect footerRectInTable = [tableview convertRect:footerBounds fromView:addCommentContainer];
[tableview scrollRectToVisible:footerRectInTable animated:YES];
This works for me in Swift 4
func addRow() {
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: array.count - 1, section: 0)], with: .automatic)
tableView.endUpdates()
DispatchQueue.main.async {
self.scrollToBottom()
}
}
func scrollToBottom() {
let footerBounds = tableView.tableFooterView?.bounds
let footerRectInTable = tableView.convert(footerBounds!, from: tableView.tableFooterView!)
tableView.scrollRectToVisible(footerRectInTable, animated: true)
}
Great idea, was looking for this myself :) Here's sample code, which would do the trick:
[tableView reloadData];
NSIndexPath *index = [NSIndexPath indexPathForRow:0 inSection:1];
[tableView scrollToRowAtIndexPath:index
atScrollPosition:UITableViewScrollPositionBottom animated:YES];
You MUST have at least one cell in your tableView footer, the problem is that it's going to visible. Didn't have time to test, but I'd guess you could make it really small?
Additionally you must implement correct stuff inside numberOfSectionsInTableView (at least one for table and one for footer), numberOfRowsInSection (at least one for footer, your last section), viewForHeaderInSection (nil except for your cell), heightForHeaderInSection (maybe if you set this as zero), cellForRowAtIndexPath (add special case for your cell in footer)...
That should be enough.
I'm using this:
- (void)scrollToBottom{
[self.myTable scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.fetchedResultsController.fetchedObjects count] -1 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
This work to me:
- (void)tableViewScrollToBottomAnimated:(BOOL)animated {
NSInteger numberOfRows = [self.tableView numberOfRowsInSection:0];
if (numberOfRows) {
if (self.tableView.tableFooterView) {
[self.tableView scrollRectToVisible:
self.tableView.tableFooterView.frame animated:YES
];
} else {
[self.tableView scrollToRowAtIndexPath:
[NSIndexPath indexPathForRow:numberOfRows-1 inSection:0]
atScrollPosition:UITableViewScrollPositionBottom
animated:animated
];
}
}
}
I haven't tried, but what happens when you scroll to the last row+1?
Another idea would be to always add a dummy entry at the end and make it have a different look so it's seen as a footer. Then you can always scroll to that.

How to know when a UITableView animation is completed?

I want to do a series of things in reaction to the end of certain UITableView animations. For example, I want the table view cell to highlight (via selectRowAtIndexPath) after it has been scrolled to via scrollToRowAtIndexPath.
How can this be done?
Basic template:
[UIView animateWithDuration:0.2 animations:^{
//do some animations, call them with animated:NO
} completion:^(BOOL finished){
//Do something after animations finished
}];
Example: Scroll to row 100. When finished, get the cell at this row and make the cell content view with tag=1 to the firstResponder:
[UIView animateWithDuration:0.2 animations:^{
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:100 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];
} completion:^(BOOL finished){
//Do something after scrollToRowAtIndexPath finished, e.g.:
UITableViewCell *nextCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:100 inSection:0]];
[[nextCell.contentView viewWithTag:1] becomeFirstResponder];
}];
I realize this an old post but I was having a similar problem and created a solution that worked well for me. I applied the techniques used on NSCookBook for creating UIAlertViews with blocks. The reason I went for this was because I wanted to use the built-in animations rather than UIView's + animateWithDuration:animations:completion:. There is a larger difference between these animations with the change to iOS 7.
You create a category for UITableView and in the implementation file you create an inner private class that will callback the block by assigning it as your tableview's delegate. The catch is that until the block is called, the original delegate will be "lost" so to speak, since the new delegate is the object that will call the block. That is why I put a notification to send a message when the block has been called to reassign the original UITableViewDelegate. This code has been tested and is working on my end.
// Header file
#interface UITableView (ScrollDelegateBlock)
-(void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath
atScrollPosition:(UITableViewScrollPosition)scrollPosition
animated:(BOOL)animated
scrollFinished:(void (^)())scrollFinished;
#end
// Implementation file
#import "UITableView+ScrollDelegateBlock.h"
#import <objc/runtime.h>
NSString *const BLOCK_CALLED_NOTIFICATION = #"BlockCalled";
#interface ScrollDelegateWrapper : NSObject <UITableViewDelegate>
#property (copy) void(^scrollFinishedBlock)();
#end
#implementation ScrollDelegateWrapper
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
if (self.scrollFinishedBlock) {
[[NSNotificationCenter defaultCenter] postNotificationName:BLOCK_CALLED_NOTIFICATION object:nil];
self.scrollFinishedBlock();
}
}
#end
static const char kScrollDelegateWrapper;
static id<UITableViewDelegate>previousDelegate;
#implementation UITableView (ScrollDelegateBlock)
-(void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath
atScrollPosition:(UITableViewScrollPosition)scrollPosition
animated:(BOOL)animated
scrollFinished:(void (^)())scrollFinished {
previousDelegate = self.delegate;
ScrollDelegateWrapper *scrollDelegateWrapper = [[ScrollDelegateWrapper alloc] init];
scrollDelegateWrapper.scrollFinishedBlock = scrollFinished;
self.delegate = scrollDelegateWrapper;
objc_setAssociatedObject(self, &kScrollDelegateWrapper, scrollDelegateWrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(blockCalled:)
name:BLOCK_CALLED_NOTIFICATION
object:nil];
}
/*
* Assigns delegate back to the original delegate
*/
-(void) blockCalled:(NSNotification *)notification {
self.delegate = previousDelegate;
[[NSNotificationCenter defaultCenter] removeObserver:self
name:BLOCK_CALLED_NOTIFICATION
object:nil];
}
#end
You can then call the method like any other with a block:
[self.tableView scrollToRowAtIndexPath:self.currentPath
atScrollPosition:UITableViewScrollPositionMiddle
animated:YES
scrollFinished:^{
NSLog(#"scrollFinished");
}
];
Well if you want to perform an action once the scrollToRowAtIndexPath has been fired.
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated
You need to create a CAAnimation pointer like
CAAnimation *myAnimation;
Then set the delgate to self
myAnimation.delegate = self;
Once you do that, these following delegate methods should activate where you can put your code:
- (void)animationDidStart:(CAAnimation *)theAnimation
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag