UITableView contentOffset behaving oddly - ios

I have a UIViewController which has multiple UIViews that enter from off the screen. Rather than initializing and designing all those UIViews in the UIViewController, I created separate nib files with corresponding .h/.m files which just get preloaded.
In one of the custom UIViews, I have a UITableView with a header UIView. I'm trying to have this header hidden under another component I have in the original UIViewController that this UIView will slide into. I initially worked this out using a sample project with [self.tableView setContentOffset:CGPointMake(0, 44) animated:NO];, but that was just in a UITableViewController.
For some reason, this does not work for a subclassed UIView. I've tried setting the tableView's content offset in both - (id)initWithCoder:(NSCoder *)aDecoder and - (void)layoutSubviews and I've been checking whether the offset was actually set in tableView:cellForRowAtIndexPath: by
NSLog(#"tableView Content Offset: %#", NSStringFromCGPoint(self.tableView.contentOffset));
but it just returns
tableView Content Offset: {0, 0}
The interesting thing is that if I set the contentOffset in tableView:cellForRowAtIndexPath: it works! But I know I shouldn't be setting it there. Could someone explain to me where and how I should be implementing this when loading a UITableView from a custom nib which is only a subclassed UIView? I can post all the code if you think it'll help.
Additional Info:
Question: how are you loading the view from the nib ? – MAB
UIView *newView = [[[NSBundle mainBundle] loadNibNamed:#"ProfileCostTableViewController" owner:self options:nil] objectAtIndex:0];

I subclassed UITableView to see when the contentOffset is being rest to {0,0}. it seems its a call in ios7 from the private method : _adjustContentOffsetIfNecessary. Since this method is not documented I don't see how to prevent it from changing the offset, so I think the workaround is to change the offset after a delay or in - (int) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section.

You're adding your view using addSubview: which triggers the methods: - (void)willMoveToSuperview:(UIView *)newSuperview I think if you put your code there it should work.

Related

UiTableView header not disappearing

I have a UITableView configured as plain style so I can have the header views stuck on the top of the table until another header pulls it away.
The problem is: If I have a header stuck on the top of the screen, and I programmatically scroll to another part of the table (where that header should not appear at all), that UIView will not be dismissed. I.e. if I scroll again to that part of the table, a ghost of that header will be visible on that part of the table.
I've implemented the method - (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(nonnull UIView *)view forSection:(NSInteger)section to understand what is happening. I found that if I manually scroll until a header is pull away of the screen, this delegate is called. But if I scroll programmatically, the delegate is not called.
By the way, I tried scrolling programmatically using two different methods, and the problem is the same.
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;
One workaround that I can imagine is implementing - (void)scrollViewDidScroll:(UIScrollView *)scrollView;, filtering all the header views that are outside the visible screen, and removing them from superview. I can probably make it work, but I would like to know if there is any other better solution.
[EDIT] If I call - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; with animated = YES, the bug does not happen. I can go with this solution, but I really would like in some cases to scroll without animation.
Not entirely sure I understand your issue entirely but it seems that your header view(s) (some UIView) is/are not rendered correctly once you programmatically scroll away from this area / section and then return.
I'm not sure how you are filling your header view content but I have several applications running UITableView's with multiple section headers that require updating for scrolling / content offset's with no problem, as long as you "draw" your headers with this delegate:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
// Per section, simply return the appropriate header view
...
NSString *someIdentifier = [NSString stringWithFormat:#"sectionHeaderView:<some #, letter, or tag>", <SOMETHING UNIQUE ADD HERE>];
UITableViewHeaderFooterView *myHeaderView = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:someIdentifier];
if (!myHeaderView) {
// No header view found with that ID. Make a new one
}
...
return myHeaderViewForSection;
}
This way whether you finger scroll or programmatically set the content offset which ever way you like, your table view will know what to draw, when to draw it, and where to put it.
Using their delegates is a bit of a drag as it's slightly tedious at start, but using the viewForHeaderInSection proved the only way I ever obtained the results I (you) wanted.
Hope this helps - happy coding!
TL;DR
Do NOT explicitly scroll the table view between beginUpdates and endUpdates.
Explanation
I'm using NSFetchedResultsController to populate the table. These are my implementations for some of the methods of NSFetchedResultsControllerDelegate.
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[_conversationTableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[_conversationTableView endUpdates];
}
The problem is that endUpdates was making a chain of calls that ended calling my method [self scrollToBottom] (which was a very ugly code actually). This method, as the name says, calls - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; to scroll the table view to the bottom of the table.
The explicit scrolling of the table during a beginUpdates - endUpdates was the culprit of my whole problem.
Solution
Scrolling the table view only after finishing endUpdates.
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[_conversationTableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[_conversationTableView endUpdates];
[self scrollToBottom];
}
Side Note
This also fixed a problem where the table view was sometimes flickering when scrolling.
Manually set the sectionHeader height to 0 when it should not appear
tableView.sectionHeaderHeight = 0;

iOS viewForHeaderInSection covering other cells

I am trying to implement a header view in my application and have the following code to create the header view:
(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *view = [[[NSBundle mainBundle] loadNibNamed:#"FutureAppointmentsHeaderView" owner:self options:nil] lastObject];
return view;
}
I have also set the height of the headerView as follows:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 70;
}
When the page loads, the header view is in the correct position (see first image). However when I scroll the header covers the other cells (see second picture.)
I can't what I am doing wrong to get this behaviour. Any help much appreciated.
This doesn't sound like a wrong behavior. This is how the header should act. Take, for example, the Contacts app on your iPhone - when you scroll, the header with the current letter always snaps to the top. When you continue scrolling, it is replaced by the next header in line.
What you should do is add a background color to the header instead of leaving it transparent. I think everything will look better and easier to understand once you do that.
Looks like you're header view is see through. Have you tried to put a background color ?
UIView *view = [[[NSBundle mainBundle] loadNibNamed:#"FutureAppointmentsHeaderView" owner:self options:nil] lastObject];
view.backgroundColor = [UIColor whiteColor];
try to change your tableViewType.
UITableViewStyleGroup can let your header scroll when you scroll the tableView
This is the default feature of tableview, and is not the bug. Just verify the background color of headerview in xib. It seems to have clear color, set it to any other color and you could find the difference. Code seems fine.
You are adding a header for the section with
- tableView:viewForHeaderInSection:
. Section headers stay on top of the table view and will cover the cell(s) underneath it. If you would add another section to your table view there would be two identical header views for each section, because you are returning the view without checking the section index.
If you want a floating header use tableHeaderView.
You can assign a view to UITableView's property in viewDidLoad or by dragging a view to the table view in interface builder. See Table Header Views in StoryBoards.

Is it possible to have a fixed uitableview Header while using sections?

This question should not be mixed up with this here.. These are two different things.
There is a good example how to use a UITableView Header on SO.
This all works fine and the main header is fixed on top as long as the style is set to plain.
But if I use sections, the main header no longer sticks to top and moves away while scrolling to the bottom.
In this method, I am returning the header for each section.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
In this method I am setting the height for the header section above:
- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
In this method, I am setting the real table header.
- (void)viewWillAppear:(BOOL)animated {
...
self.recordTableView.tableHeaderView = headerView;
}
Is it even possible having a fixed table header, while using sections?
What is an alternative solution to this please?
If you want a UITableViewController (static cells/keyboard handling) and have a fixed header then you should use Containment. You can do this from a Storyboard by setting up a UIViewController with your fixed header and then using a Container View to embed the UITableViewController.
Once you have your containing view setup, you right-click drag from the Container View to the View Controller you want to embed - the UITableViewController in this case.
You can access and get a reference to the contained View Controller (the UITableViewController) from the Container View Controller by implementing the prepareForSegue:sender: method.
There’s no way to maintain the header of a tableView fixed, but
an useful approach when you need a unique header, is to use a UIViewController rather than a UITableViewController, and set the header (UIView) out from the tableView.
Something like this:
If you want to keep the class as a UITableViewController you can add your header as a subview to the tableview's superview. You will have to also push the tableview top inset down so your headerview doesnt hide the table.
Here is a sample code to put inside your tableViewController subclass (This example assumes your tableview controller is inside a navigation controller, so it pushes the view to below the navigation bar):
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.automaticallyAdjustsScrollViewInsets = NO;
}
-(void)addHeaderView{
CGFloat yPosition = self.navigationController.navigationBar.frame.origin.y + self.navigationController.navigationBar.frame.size.height;
mainHeaderView = [[UIView alloc] init];
const CGFloat mainHeaderHeight = 44;
[mainHeaderView setFrame:CGRectMake(0, yPosition, self.view.frame.size.width, mainHeaderHeight)];
mainHeaderView.backgroundColor = [UIColor redColor];
[self.tableView.superview addSubview:mainHeaderView];
[self.tableView setContentInset:UIEdgeInsetsMake(yPosition + mainHeaderHeight, self.tableView.contentInset.left, self.tableView.contentInset.bottom, self.tableView.contentInset.right)];
}
I haven't done this, but the first thing I would think to try is to place my tableview in a UIView and make my own header there in that UIView. Seems a trivial matter to make that view appear to be the header of the table and it would certainly stay put.

Getting all subviews for a UIScrollView

I need to get an array of all the subviews in a UIScrollView. Right now I'm using
NSArray *subviews = [myScrollView subviews];
but this seems to only be returning the subviews that are visible at the time the code is run. I need all the subviews in the whole extent of the UIScrollView, even those that are currently hidden (as in off screen). How would I get that?
Essentially, I'm looking for something like the contentSize property of a UIScrollView, except instead of returning just the size of the UIScrollView if it were big enough to display all of it's content, I want it to return the content itself.
EDIT: I think I've figured it out: the scroll view this isn't working for is actually a UITableView - and I think it's deque-ing the cells that are off screen on me, and that's why they aren't showing up. I'm going to do some testing to confirm.
Try with following code its working for me.
for(UIView * subView in myScrollView.subviews ) // here write Name of you ScrollView.
{
// Here You can Get all subViews of your myScrollView.
// But For Check subview is specific UIClass such like label, button, textFiled etc.. write following code (here checking for example UILabel class).
if([subView isKindOfClass:[UILabel class]]) // Check is SubView Class Is UILabel class?
{
// You can write code here for your UILabel;
}
}
tl;dr
It turns out that
NSArray *subviews = [myScrollView subviews];
will indeed return all the subviews in a UIScrollView *myScrollView, even if they are off-screen.
The Details
The problem I was actually having was that the scroll view I was trying to use this on was actually a UITableView, and when a UITableViewCell in a UITableView goes off-screen, it actually gets removed from the UITableView - so by the time I was calling subviews, the cells I was looking for were no longer in the scroll view.
My workaround was to build all of my UITableViewCells in a separate method called by my viewDidLoad, then put all of those cells into an array. Then, instead of using subviews, I just used that array. Of course, doing it this way hurts the performance a little (in cellForRowAtIndexPath you just return the cell from the array, which is slower than the dequeueReusableCellWithIdentifier method that is typically used), but it was the only way I could find to get the behavior I needed.

Change custom UIView to UIScrollView

In general:
I got a custom UIView with a xib file as subview on a UIViewController. I add the subview programmatically in my controller.
In my UIViewController:
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"MyCustomSubView" owner:self options:nil];
mySubView = [subviewArray objectAtIndex:0];
...
[self.view addSubview:mapView];
Because I need to scroll on this subview, which also have an UIImageView on it, I decided to change my custom UIView into an UIScrollView.
I did the following steps:
Changed the MyCustomSubView.xib, added an UIScrollView with Custom Class MyCustomSubView (and an UIImageView on it like before)
Changed my MyCustomSubView class to extend UIScrollView
Changed my MyViewController to implement UIScrollViewDelegate
set mySubView.delegate to self(the controller)
implemented (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView and returned the UIImageView of the subview
enabled user interaction on the imageview and on the uiscrollview
These were my total steps I think. But as example, the viewForZoomingInScrollView method wasn't called. Also I tried to zoom in, but nothing happened.
Anyone an idea, what could be wrong?
Looking briefly at the UIScrollView Class Reference page, I see the following:
The UIScrollView class can have a delegate that must adopt the UIScrollViewDelegate protocol. For zooming and panning to work, the delegate must implement both viewForZoomingInScrollView: and scrollViewDidEndZooming:withView:atScale:; in addition, the maximum (maximumZoomScale) and minimum ( minimumZoomScale) zoom scale must be different.
You mentioned implementing viewForZoomingInScrollView:, but not scrollViewDidEndZooming:atScale:, nor did you mention setting maximumZoomScale or minimumZoomScale. Try doing those things, and check back if you're still having issues.

Resources