UITableView Footer Resets on Scrolling - ios

I have a simple UITableView setup and have a footer view which has just a UIActivityIndicatorView in it.
In viewDidLoad I call:
[_tableView registerNib:[UINib nibWithNibName:#"FetchingResultsFooterView" bundle:nil] forHeaderFooterViewReuseIdentifier:#"TableViewFooterFetchingResultsView"];
Then I add the tableview methods for my footer:
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return [self tableViewFooterHeight];
}
-(CGFloat)tableViewFooterHeight
{
return 88;
}
-(UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
static NSString *headerReuseIdentifier = #"TableViewFooterFetchingResultsView";
APFetchingResultsFooter *footer = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
[footer setTag:kTableViewFooterTag];
//Centre my activity indicator on screen
CGFloat aiInset = ((_tableViewContainer.frame.size.width /2) - (footer.activityIndicator.frame.size.width /2));
CGFloat aiOffset = _tableViewContainer.contentOffset.x + aiInset;
[footer.activityIndicator setFrame:CGRectMake(aiOffset, ([self tableViewFooterHeight] /2) - (footer.activityIndicator.frame.size.height /2), footer.activityIndicator.frame.size.width, footer.activityIndicator.frame.size.height)];
return footer;
}
This displays my footer more or less as expected, it sits there with my activity indicator.
In case it confuses you, because I have a very wide 'UITableView' inside a 'UIScrollView' I simply centre my activity indicator on screen, rather than the centre of the tableview by offsetting it.
Which leads to this code which just updates the position of the activity indicator as I scroll, so it stays centred (allowing the tableview wider than the screen to use the footer).
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//Synchronise scrolling
if (scrollView == _headerView) [_tableViewContainer setContentOffset:_headerView.contentOffset];
else if (scrollView == _tableViewContainer) [_headerView setContentOffset:_tableViewContainer.contentOffset];
//because the tableview can be wider than the screen, we adjust the footer contents to be centered as we scroll
if (scrollView == _tableViewContainer)
{
APFetchingResultsFooter *footer = (APFetchingResultsFooter *)[_tableView viewWithTag:kTableViewFooterTag];
CGFloat aiInset = ((_tableViewContainer.frame.size.width /2) - (footer.activityIndicator.frame.size.width /2));
CGFloat aiOffset = _tableViewContainer.contentOffset.x + aiInset;
[footer.activityIndicator setFrame:CGRectMake(aiOffset, ([self tableViewFooterHeight] /2) - (footer.activityIndicator.frame.size.height /2), footer.activityIndicator.frame.size.width, footer.activityIndicator.frame.size.height)];
}
}
So, that is all pretty simple. Now my problem:
As I scroll vertically, when the footer is offscreen and then brought back on-screen, the footer is reset. So the activity indicator is not centred.
I have tried placing the frame update code in cellForRowAtIndexPath which kind of works, but as you scroll the footer on-screen, there is a delay and you see the indicator jump from its default frame to its new centred one.
How do I prevent this, so that as it is scrolled on screen (or as the footer is created), have the indicator view updated with a new frame before any jumping can be seen?
Have I just taken the wrong approach?

Related

How to keep cell's content on screen while scrolling until the cell runs out of available space?

The questions itself does not say much, but what I am trying to say is: how can one move the cell's content (let say a UILabel), so the label can be seen on screen while the collection view is being scrolled, until the collectionView's cell runs out of available space.
But, a picture is worth a thousand words:
I have something like this, a UICollectionViewCell with horizontal scroll.
Normally, when I scroll this is what it will happen:
But, what I would like to achieve is the names in the firsts cells to scroll to the right while there is space available in the cell, without forgetting that when there's no more space available it will start to truncate the tail, like: Keaton Pickett -> Keaton Pick... -> Keaton... -> etc,
I've been thinking about nesting the cell's content inside a UIScrollView and then scroll it by code when the collectionView scrolls.
I also thought changing the CGRect property of the cell's contents (in this case, the UILabel) while the view is scrolling (playing with the width and minX properties.
However, detecting when the, in this case, UILabel is starting to be out of the screen (like Kea in the first cell of the second picture) is turning to be a struggle.
In my real case scenario I have the UICollectionView (horizontal scroll) nested in UITableView (vertical scroll).
I will thank and appreciate your help in this predicament.
Best regards!
I prefer you to use the code
Objective C
- (void)checkVisibilityOfCell:(MyCustomUITableViewCell *)cell inScrollView:(UIScrollView *)aScrollView {
CGRect cellRect = [aScrollView convertRect:cell.frame toView:aScrollView.superview];
if (CGRectContainsRect(aScrollView.frame, cellRect))
[cell notifyCompletelyVisible];
[cells objectAtIndex:i].hidden= YES; // MAKE THE CELL VISIBLE
else
[cell notifyNotCompletelyVisible];
[cells objectAtIndex:i].hidden= NO; // MAKE THE CELL UNVISIBLE
}
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
NSArray* cells = myTableView.visibleCells;
NSUInteger cellCount = [cells count];
if (cellCount == 0)
return;
// Loop through All cells
for (NSUInteger i = 1; i < cellCount - 1; i++)
[[cells objectAtIndex:i] notifyCompletelyVisible];
}

Cells not visible on first screen are not tappable

I have used a UIScroll View. It has a contentView (of type UIView) which again has a questionView (UIView) and a _questionTableView (UITableView). I know this is not the right practice but the design required me to implement like this. Scroll is working perfectly fine.
I have a total of 8 cells and only 4 cells are visible when the screen is opened first. Upon tapping on any of these 4 cells
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
is called.
Upon scrolling down, rest of the cells become visible. But tapping on them doesn't trigger didSelectRowAtIndexPath
I read various stack overflow posts on this issue (to enable tapping on cells) where people suggested to increase the content view size/ scrollView's contentSize/ tableView's height. I tried all of them but no luck.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCell"];
_scrollView.contentSize = CGSizeMake(_questionTableView.frame.size.width, 1000);
_contentView.frame = CGRectMake(0, 0, _questionTableView.frame.size.width * 2, 1000);
_questionTableView.frame = CGRectMake(0, 0, _qTableView.frame.size.width , 500);
self.scrollView.delegate = self;
return cell;
}
Is there nay way to make the cells tappable?
EDIT: I experimented some more and I realized that after several taps on the cell, the method - didSelectRowAtIndexPath is called. But those are some random taps 5-6 times. Couldn't identify a pattern.
Try to deselect property "Delays Content Touches" in your parent scroll view. It may help!

Track coordinates of UITableViewCell subview

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. :)

Multiple Scrollviews?

I don't really know how to explain what I want, so here's an picture :
I have a view with a lot of subviews (gray lines). Then the background (blue) is a picture (UIImageView + Blur effect), so I need it to stay and not to scroll. Behind the background, there's a view (orange). I want the picture (blue) to scroll only when the subviews (gray) are at the bottom (3rd picture).
Should I use embed scrollviews, or can I get this effect with only one UIScrollView ? If multiple scrollviews, does someone have an example ?
Thanks a lot for your help
Use only one scroll view, and use its delegate method 'scrollViewDidScroll' to read the content offset. Use this value to translate your background image.
It's not necessary to use 2 scroll views. But I would use it since I prefer setting contentOffset of the background scroll view than setting it's frame directly.
The idea is that you implement scrollViewDidScroll: delegate method for the front scroll view. And track the contentOffset to check whether the scrolling has reached the end of it's content by checking whether scrollView.contentOffset.y + scrollView.bounds.size.height > scrollView.contentSize.height or not.
If it has, then you offset the location of the background view with the amount of offset that exceed the content size.
Please see the code below. You can skip all the code and only look at scrollViewDidScroll:.
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.backgroundScrollView.userInteractionEnabled = NO;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
cell.textLabel.text = [NSString stringWithFormat:#"Item %ld", (long)indexPath.row];
return cell;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat backgroundVerticalOffset = MAX(scrollView.contentOffset.y + scrollView.bounds.size.height - scrollView.contentSize.height, 0);
CGPoint backgroundContentOffset = CGPointMake(0.0, backgroundVerticalOffset);
self.backgroundScrollView.contentOffset = backgroundContentOffset;
// If you decide not to use two scroll views, then please use backgroundContentOffset to set the backgroundView.frame.origin.y instead
// e.g. backgroundView.frame.origin.y = -backgroundContentOffset;
}
The result shown below:
P.S. I used blue background view instead of an image view and everything else with clear background color.

Scrolling horizantal on UITableView is changing the position of the UIPageControl which is attached to a scrollView inside the TableView cell

I have a tableView with dynamically generated custom cells and one of the custom cell contains a scrollView that can scroll vertically and a page control is attached to it.
The user interface structure of the tableview can be seen in the photo.
I am refreshing the position of the page control in the scrollViewDidScroll method as follows.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSIndexPath *indexPathPageControl=[NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell * cell = (UITableViewCell*)[self.sonDakikaTableView cellForRowAtIndexPath:indexPathPageControl];
CGFloat xOffset = scrollView.contentOffset.x;
CGFloat frameWidth = scrollView.frame.size.width;
int page = floor((xOffset - frameWidth / 2) / frameWidth) + 1;
UIPageControl *pageControl = (UIPageControl *)[cell.contentView viewWithTag:14];
pageControl.currentPage = page; // assigning to pagecontroll
}
It is working very fine when the user is scrolling vertical in the scrollView.
However when the user scrolls down on the tableView and the cell with vertical scrollView is not visible anymore, the integer page variable is being updated with 0 and when the user scrolls up and table view cell is visible again, the location of page control is being reset.
I will appreciate if someone can give me an idea about how to prevent this.
Thanks in advance.
i added this code
(void)scrollViewDidScroll:(UIScrollView *)_scrollView
{
if (_scrollView == self.tableView) return;
and in your case
(void)scrollViewDidScroll:(UIScrollView *)_scrollView
{
if (_scrollView == self.sonDakikaTableView) return;

Resources