iOS7: UICollectionView appearing under UINavigationBar - ios

I've been building on iOS 7 for a while now but I've yet to get this solved, I have a number of views with autolayout enabled that were created in Storyboard and are displayed with a standard UINavigationController. The majority are fine, but the ones based on UICollectionView always place themselves under the navigation bar, unless I set the translucency to NO. I've tried the edgesExtended trick but that doesn't seem to solve it, I don't necessarily mind having the translucency off but I'd like to solve it cleaner.

FYI if your UICollectionView is the root view in your view controller's hierarchy and your view controller has automaticallyAdjustsScrollViewInsets set to YES (this is the default), then the contentInset should update automatically.
However, the scrollview's contentInset is only automatically updated if your scrollview (or tableview/collectionview/webview btw) is the first view in their view controller's hierarchy.
I often add a UIImageView first in my hierarchy in order to have a background image. If you do this, you have to manually set the edge insets of the scrollview in viewDidLayoutSubviews:
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGFloat top = self.topLayoutGuide.length;
CGFloat bottom = self.bottomLayoutGuide.length;
UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
self.collectionView.contentInset = newInsets;
}

I had this problem before, just set the edge insents of the collection view with a top margin:
[self.myCollectionVC.collectionView setContentInset:UIEdgeInsetsMake(topMargin, 0, 0, 0)];
Where topMargin is the size of the nav bar, or whatever point you want the collection to start scrolling.
In this way, your collection view will start scrolling just below the navigation bar, and at the same time it will fill the whole screen and you will see it if your nav bar is translucent.

I had this problem after ios 11, just set the contentInsetAdjustmentBehavior of UICollectionView to never:
self.collectionView.contentInsetAdjustmentBehavior = .never

I am using swift and xcode 7.3.1. I solved it by going to story board and selecting my Navigation Controller and then unchecking "Extend Edges" "Under Top Bards".

-(void) viewDidLoad{
[super viewDidLoad];
self.automaticallyAdjustsScrollViewInsets = NO; //added important
}
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGFloat top = self.topLayoutGuide.length;
CGFloat bottom = self.bottomLayoutGuide.length;
UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
self.collectionView.contentInset = newInsets;
}

Related

iOS11 customize navigation bar height

First of all, thank you for coming here and help solving my problem. Thank you!!!
In iOS11 beta6, sizeThatFits: seems to not work on UINavigationBar. I notice that UINavigationBar structure has changed by Reveal my app.
I have tried my best to change custom navigation bar's height. But it seems always to be 44, and it works before iOS11.
- (CGSize)sizeThatFits:(CGSize)size {
CGSize newSize = CGSizeMake(self.frame.size.width, 64);
return newSize;
}
Oddly, I just log its frame in didMoveToSuperview method, its height is 64, but I really see that is 44 in Reveal and app.
I have no idea about this... Help me please.. Thank you.
Update:
I found that about my custom navigation bar LayoutConstraints log in console like this :
"<NSAutoresizingMaskLayoutConstraint:0x604000495ae0 FDCustomNavigationBar:0x7fe2f01399d0.(null) == 42>",
"<NSAutoresizingMaskLayoutConstraint:0x604000495b30 FDCustomNavigationBar:0x7fe2f01399d0.height == 44>"`
bug I even no use auto layout in my navigation bar. What's wrong with it?
Update 8/28 :
I have set my custom navigation bar's subviews frame at navigation bar's -
layoutSubviewsmethod.
- (void)layoutSubviews {
[super layoutSubviews];
self.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), 64);
for (UIView *view in self.subviews) {
if([NSStringFromClass([view class]) containsString:#"Background"]) {
view.frame = self.bounds;
} else if ([NSStringFromClass([view class]) containsString:#"ContentView"]) {
CGRect frame = view.frame;
frame.origin.y = 20;
frame.size.height = self.bounds.size.height - frame.origin.y;
view.frame = frame;
}
}
}
but the navigation bar will cover view controller's view. How can I fix that?
Since iOS 11 UINavigationBar fully supports Auto Layout (this is the reason why you're seeing its constraints). I've opened a radar to Apple because I thought that setting a height constraint to the titleView would have adjusted the navigation bar height accordingly. However this is what Apple replied:
Full support for auto layout does not imply that your view can influence other aspects of the layout of the navigation bar – in particular, the navigation bar enforces its own height and does not allow the title view or other custom views to exceed the height of the navigation bar. We are continuing to work on this issue, and will follow up with you again.
As of today the radar is still open.
Hello I just experienced this same issue.
Now the top layout guide is deprecated on iOS 11. You have to reference the safeAreaLayoutGuide in your constraints.
Here's an example in swift
if #available(iOS 11, *) {
let guide = self.view.safeAreaLayoutGuide.topAnchor
let height = (self.navigationController?.navigationBar.frame.height)! - CGFloat(12)
NSLayoutConstraint.activate([
self.yourTableView.topAnchor.constraint(equalTo: guide, constant: height)
])
}
As you can see your view's top anchor should match the safeAreaLayoutGuide top anchor. In this example I'm using the variable height to create the new constraint. The variable height contains the navigation bar height minus a constant.
You should try by changing the height value.
Hope this helps you.

UICollectionVIew: How can I remove the space on top first cell's row?

I have this arrangement in Interface Builder, all properties are set to zero.
However, when I run it on both device and simulator it appears like this
Where is the space above the cells come from?
So I try to set these properties for UICollectionViewFlowLayout in code like this
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.headerReferenceSize = CGSizeZero;
layout.footerReferenceSize = CGSizeZero;
layout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0);
layout.minimumInteritemSpacing = 0;
layout.minimumLineSpacing = 0;
layout.itemSize = CGSizeMake(103, 119);
self.calendarView.collectionViewLayout = layout;
but I have no luck.
How can I get rid of that space?
UICollectionView is descendant of UIScrollView class which has contentInset property, setting -20 top inset fixes the problem
[self.calendarView setContentInset:UIEdgeInsetsMake(-20, 0, 0, 0)];
However, the problem comes from UIViewController's automaticallyAdjustsScrollViewInsets property.
By documentation:
Default value is YES, which allows the view controller to adjust its scroll view insets in response to the screen areas consumed by the status bar, navigation bar, and toolbar or tab bar. Set to NO if you want to manage scroll view inset adjustments yourself.
That's why we get adjusted content insets for status bar. It's better to disable automatically adjusting than manually set value which doesn't match in result picture.
[self setAutomaticallyAdjustsScrollViewInsets:NO];
Another way is to select your ViewController and uncheck the checkbox Adjust Scroll View Insets in your interface builder:
It is essentially the same as the following line of code. But you got to see your changes right away in the interface builder.
automaticallyAdjustsScrollViewInsets = false
iOS 11 deprecated the use of automaticallyAdjustsScrollViewInsets, so the use of collectionView.contentInsetAdjustmentBehavior = .never is advised.
Here is the answer in swift with a few adjustments:
I have a collection view that takes up a small portion of the view. I used:
self.automaticallyAdjustsScrollViewInsets = false
to remove the top spacing that was messing up my layout. This piece of code didn't work for me:
self.paperCollectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
And neither did this one:
self.paperCollectionView.contentInset = UIEdgeInsetsMake(-20, 0, 0, 0)
But that might be because I'm not using a UICollectionViewController, I'm just using a UICollectionView.
Here's a bigger portion of the code to give more context:
You can also go with
[self.calendarView setContentInset:UIEdgeInsetsMake(0, 0, 0, 0)];
[self setAutomaticallyAdjustsScrollViewInsets:NO];
There is by default collection view header scrolling space added on the collection view and you do not need to add -20 from top because it may reflect on device issue
You can do this in Interface Builder by going to the Scroll View section and changing the Content insets dropdown to "Never".
Swift 3:
self.automaticallyAdjustsScrollViewInsets = false
This answer is weird, but it works if you are working in Interface Builder and have a Collection View embedded in a View Controller that is under the control of a Tab Bar Controller that is the root view controller of a Navigation Controller.
Add a Toolbar to the View Controller that has the Collection View
Move the Toolbar in the hierarchy such that it is above the Collection View
If the Toolbar is above the Collection View, there will be no space from the top of the prototype Collection View Cell to the Collection View. If there is no Toolbar or the Toolbar is below the Collection View, then there will be space between the top of the Collection View and the Collection View Cell. This is true both in the Storyboard preview and while running the app. The same type of thing occurs for Table Views.
This was most recently tested with Xcode Version 8.3.3

iOS7 UIRefreshControl changes contentInset

I have a UINavigationController in my app. The UINavigationBar is set to opaque and all the scroll views do not overlap underneath the bar.
In one view I have a UITableView. The frame of the UITableView is (0 0; 320 504) on my iPhone 5. i.e. the height is 568 - 64 (the height of the nav bar and status bar).
The contentInset of the UITableView is (0, 0, 0, 0). When the table view first loads the contentOffset is (0, 0).
This is fine. Works brilliantly.
I added a UIRefreshControl to the table view. This works a couple of times but then after a few times of doing pull to refresh then the content at the top gets "stuck" under the nav bar.
When this happens I inspect the contentInset and it is now (-60, 0, 0, 0).
Is there any way to stop the UIRefreshControl from changing the contentInset?
This is probably the reason why UIRefreshControl is currently only supported on UITableViewController, rather than by addition to any scrollview (which you can get away with, in many cases).
The refresh control does its magic by tinkering with the content insets of the scrollview - particularly when it ends refreshing. Unfortunately the view controller is also tinkering with the content insets of the scroll view to fit it under the translucent nav and status bars. Fun ensues. Is this also an issue on iOS 6 (or, "good old iOS6" as I called it when dealing with the same issue).
The quickest solution is probably to add your table view as a child UITableViewController instead of a simple subview. I think that UITableViewController manages the insets for you at the end of the refresh. If that doesn't work, I've got workarounds for this but it will have to wait until I get back in the office.
I will add this answer here in case any one has problems with UIRefreshControl by changing the control properties (attributed title, tint, etc...):
Don't mess with the UIRefreshControl on -viewDidLoad:, use -viewDidAppear: instead.
Reset your table view contentInset.
-(void)pullToRefresh
{
[self.tableView reloadData];
[self.refreshControl endRefreshing];
[self.tableView setContentInset:UIEdgeInsetsMake(0, 0, 0, 0)];
}
You need override setContentInset: in you UICollectionView
- (void)setContentInset:(UIEdgeInsets)contentInset {
if (self.tracking) {
CGFloat difference = contentInset.top - self.contentInset.top;
CGPoint translation = [self.panGestureRecognizer translationInView:self];
translation.y -= difference * 3.0 / 2.0;
[self.panGestureRecognizer setTranslation:translation inView:self];
}
[super setContentInset:contentInset];
}

UITableView is starting with an offset in iOS 7

I have dragged a plain jane UITableView onto a UIViewController in iOS 7.
Now there is an vertical offset of space before the first cell starts. How do I get rid of it? I want the first line to be much closer to the top edge of where the UITableView actually starts. I did not ask for the large offset did I?
Any ideas?
The new iOS 7 implementation of UIViewController has a new set of options that allows the developer to choose if the system will automatically add insets for UIScrollView, UITableView and derivations.
To disable this behaviour uncheck these boxes for all your wanted UIViewControllers in InterfaceBuilder, on UIViewController selected object inspector:
For more details:
Submit your iOS 7 apps today.
iOS 7 UI Transition Guide > Appearance and Behavior
By default table view controllers will pad the content down under the nav bar so you could scroll the content under it and see it, in a blurred state, underneath the navbar/toolbar.
Looks like you're positioning it at 44 (maybe 64)px to move it out from under the nav bar, but it already compensates for this so you get a big gap.
Go to the storyboard/xib in IB and untick the show content under nav bar stuff.
From iOS7 transition guide:
If you don’t want a scroll view’s content insets to be automatically
adjusted, set automaticallyAdjustsScrollViewInsets to NO. (The default
value of automaticallyAdjustsScrollViewInsets is YES.)
self.automaticallyAdjustsScrollViewInsets = NO;
i had a similar problem, after dismissing a viewController, the contentOffset from my tableView was changed to (0, -64).
my solution was a little weird, i tried all the other answers but had no success, the only thing that fixed my problem was to switch the tableView position in the controls tree of the .xib
it was the first control in the parent View like this:
I moved the tableView right after the ImageView and it worked:
it seems that putting the table view in the first position was causing the trouble, and moving the table view to another position fixed the problem.
P.D. I'm not using autoLayout neither storyboards
hope this can help someone!
it resolve my similar problem:
if ([[UIDevice currentDevice].systemVersion floatValue] >= 7){
tableView.contentInset = UIEdgeInsetsMake(-20, 0, 0, 0);
}
Try using this
tableView.separatorInset = UIEdgeInsetsZero;
Obviously, if you're supporting anything less than iOS7 you will need to ensure that the object responds to this selector before calling it.
Seriously, changing contentOffset is not the solution. You're just patching a problem and not fixing the cause. I've been facing the same problem and it turns out that grouped tableViews have a padding on the top. In my case setting the type to plain has done the trick.
Hopefully it saves someone a few minutes.
Z.
With iOS 9, none of the other answers from this page worked for me (i.e. unchecking boxes in Storyboard, setting automaticallyAdjustsScrollViewInsets to NO).
My workaround I am really dissatisfied about was this:
- (void)viewDidAppear:(BOOL)animated {
self.tableView.contentOffset = CGPointMake(0.0, 0.0);
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
}
The same lines in viewWillAppear or viewDidLoad were ineffective.
Sometimes, I get a 64 height gap at the top of my Table View when the UIViewController is embedded inside a Navigation Controller.
In the past, I would just re-create everything, hoping that the constraints turn out correct after a clean slate.
TIL: If you don't want to make a vertical constraint to the Top Layout Guide, you can hold down the Option key to access the Container Margin.
Then make sure the Top Space to Superview constant is set to 0. This worked for me at least.
THis works for me:
- (void)loadView
{
[super loadView];
[self setAutomaticallyAdjustsScrollViewInsets:YES];
self.edgesForExtendedLayout = UIRectEdgeNone;
self.view.frame = CGRectZero;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
If you add an empty UIView before UITableView (or any view is scrollable such as ScrollView and TextView), you can have a luck.
I had a UITableViewController embedded in a container view. To get rid of the unwanted 64 points of vertical space, I needed to uncheck the 'Adjust Scroll View Insets' in Interface Builder and set the UITableView's contentInset in my UITableViewController's viewWillAppear as below.
The size of the vertical space appears to match the navigation bar's frame height and y offset. The problem only occurred on iOS 7.
- (void)viewWillAppear:(BOOL)animated;
{
[super viewWillAppear:animated];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
const CGRect navBarFrame = self.navigationController.navigationBar.frame;
const CGFloat blankVerticalSpace = navBarFrame.origin.y + navBarFrame.size.height;
self.tableView.contentInset = UIEdgeInsetsMake(-blankVerticalSpace, 0, 0, 0);
}
}
In Xamarin iOS, I had this issue occuring on a backgrounded UITableViewController just after the foreground modal dialog was being dismissed. In the process of moving into the foreground, the UITableViewController had insets set (somewhere by iOS):
This class solved it
public class UITableViewControllerWithBugFix : UITableViewController {
public UITableViewControllerWithBugFix(UITableViewStyle withStyle) : base(withStyle) {
}
public override void ViewWillLayoutSubviews() {
if (TableView.ContentInset.Top != 0.0f)
TableView.ContentInset = UIEdgeInsets.Zero;
if (TableView.ScrollIndicatorInsets.Top != 0.0f)
TableView.ScrollIndicatorInsets = UIEdgeInsets.Zero;
base.ViewWillLayoutSubviews();
}
}
From time to time I get back to this awful situation, and I notice that it's still quite unknown. So, for future memory...
Lately, I'm fixing with this workaround.
Add a subview at the top of the UITableView, with zero pixel height. Works with Autolayout or without.
If I feel confident :-) I remove this fake view, fix the constraints on the top, and magically it works.
Don't ask me why, I still think it's a bug deep in UIKit.
If you go to storyboard, you can change the offset by selecting the table and under the Table View section in the Attributes inspector you just change the Separator Insets on the left to 0.
I think the real solution is to set up your top and bottom constraints on the tableview be equal to the topMargin and bottomMargin. Not the top layout guide and bottom layout guide. This allows you to keep automaticallyAdjustsScrollViewInsets to be true.
Swift 3 solution:
class PaddingLessTableView : UITableView
{
override func headerView(forSection section: Int) -> UITableViewHeaderFooterView?
{
return nil
}
override func footerView(forSection section: Int) -> UITableViewHeaderFooterView?
{
return nil
}
}
If you embedded your TableViewController in a NavigationController or a ContainerView, You have to constraint to margins instead of top Layout guide in Storyboard. Check constraint to margins when you are doing the constraints No other way worked for me. I couldn't comment so I'm just reiterating on Robert Chens answer.
I tried several of the answers. Changing the settings in storyboard caused ripple problems with an overlay menu that pops in from the left.
I only have a blank UIViewController in storyboard, otherwise everything is programmatically generated.
I have same problem with a UITableView inside a UIView inside a UIViewController. Namely, the section headers start too far down when the UIViewController is embedded in a Navigation Controller. W/o the navigation controller everything works fine.
To fix the problem I created a UILabel and with constraints placed the UILabel bottom constraint = the top constraint of the UIView (so it does not show on the screen. Now with that additional control (the new Label) the TableView behaves properly.
inputsContainerView.addSubview(titleLabel)
inputsContainerView.addSubview(tableView)
// inputsContainerView
///////////////////////////////////////
inputsContainerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
inputsContainerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
inputsContainerView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -40).isActive = true
inputsContainerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.7).isActive = true
// tableView
///////////////////////////////////////
tableView.centerXAnchor.constraint(equalTo: inputsContainerView.centerXAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: inputsContainerView.topAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
tableView.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor).isActive = true
// titleLabel - inserted to stop bad section header behavior
///////////////////////////////////////
titleLabel.centerXAnchor.constraint(equalTo: inputsContainerView.centerXAnchor).isActive = true
titleLabel.bottomAnchor.constraint(equalTo: inputsContainerView.topAnchor).isActive = true
titleLabel.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
titleLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
I had a same problem in iOS 11 and xib, UITableviewController and I solved it as below
[self.tableView setContentInset:UIEdgeInsetsMake(-44,0,0,0)];
If none of the above answers work, try changing the table view style to plain from grouped

UIScrollView adjusts contentOffset when contentSize changes

I am adjusting a detail view controller's state, just before it is pushed on a navigationController:
[self.detailViewController detailsForObject:someObject];
[self.navigationController pushViewController:self.detailViewController
animated:YES];
In the DetailViewController a scrollView resides. Which content I resize based on the passed object:
- (void)detailsForObject:(id)someObject {
// set some textView's content here
self.contentView.frame = <rect with new calculated size>;
self.scrollView.contentSize = self.contentView.frame.size;
self.scrollView.contentOffset = CGPointZero;
}
Now, this all works, but the scrollView adjusts it's contentOffset during the navigationController's slide-in animation. The contentOffset will be set to the difference between the last contentSize and the new calculated one. This means that the second time you open the detailsView, the details will scroll to some unwanted location. Even though I'm setting the contentOffset to CGPointZero explicitly.
I found that resetting the contentOffset in - viewWillAppear has no effect. The best I could come up with is resetting the contentOffset in viewDidAppear, causing a noticeable up and down movement of the content:
- (void)viewDidAppear:(BOOL)animated {
self.scrollView.contentOffset = CGPointZero;
}
Is there a way to prevent a UIScrollView from adjusting its contentOffset when its contentSize is changed?
Occurs when pushing a UIViewController containing a UIScrollView using a UINavigationController.
iOS 11+
Solution 1 (Swift Code):
scrollView.contentInsetAdjustmentBehavior = .never
Solution 2 (Storyboard)
iOS 7
Solution 1 (Code)
Set #property(nonatomic, assign) BOOL automaticallyAdjustsScrollViewInsets to NO.
Solution 2 (Storyboard)
Uncheck the Adjust Scroll View Insets
iOS 6
Solution (Code)
Set the UIScrollView's property contentOffset and contentInset in viewWillLayoutSubviews. Sample code:
- (void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
self.scrollView.contentOffset = CGPointZero;
self.scrollView.contentInset = UIEdgeInsetsZero;
}
The cause of this problem remains unclear, though I've found a solution. By resetting the content size and offset before adjusting them, the UIScrollView won't animate:
- (void)detailsForObject:(id)someObject {
// These 2 lines solve the issue:
self.scrollView.contentSize = CGSizeZero;
self.scrollView.contentOffset = CGPointZero;
// set some textView's content here
self.contentView.frame = <rect with new calculated size>;
self.scrollView.contentSize = self.contentView.frame.size;
self.scrollView.contentOffset = CGPointZero;
}
I had the same issue with a UIScrollview, where the problem was caused by not setting the contentSize. After setting the contentSize to the number of items this problem was solved.
self.headerScrollView.mainScrollview.contentSize = CGSizeMake(320 * self.sortedMaterial.count, 0);
Here's what worked for me:
In the storyboard, in the Size Inspector for the scrollView, set Content Insets Adjustment Behavior to "Never".
Is your scrollView the root view of the DetailViewController? If yes, try wrapping the scrollView in a plain UIView and make the latter the root view of DetailViewController. Since UIViews don't have a contentOffset property, they are immune to content offset adjustments made by the navigation controller (due to the navigation bar, etc.).
I experienced the problem, and for a specific case - I don't adjust the size - I used the following:
float position = 100.0;//for example
SmallScroll.center = CGPointMake(position + SmallScroll.frame.size.width / 2.0, SmallScroll.center.y);
Same would work with y: anotherPosition + SmallScroll.frame.size.height / 2.0
So if you don't need to resize, this is a quick and painless solution.
I was experiencing a similar problem, where UIKit was setting the contentOffset of my scrollView during push animations.
None of these solutions were working for me, maybe because I was supporting iOS 10 and iOS 11.
I was able to fix my issue by subclassing my scrollview to keep UIKit from changing my offsets after the scrollview had been removed from the window:
/// A Scrollview that only allows the contentOffset to change while it is in the window hierarchy. This can keep UIKit from resetting the `contentOffset` during transitions, etc.
class LockingScrollView: UIScrollView {
override var contentOffset: CGPoint {
get {
return super.contentOffset
}
set {
if window != nil {
super.contentOffset = newValue
}
}
}
}
Adding to KarenAnne's answer:
iOS 11+
automaticallyAdjustsScrollViewInsets was deprecated
Use this istead:
Storyboards:
Code (Swift):
scrollView.contentInsetAdjustmentBehavior = .never

Resources