custom tableView's header view overlap the table view cells - ios

I designed a custom view as my UITableView's header view. just like this
(I just put image link here instead of image since I don't have 10 reputations.)
http://i.stack.imgur.com/KhNbE.png
Then in my UITableViewController I use this view as tableHeaderView
override func viewDidLoad() {
tableView.tableHeaderView = headerView!
//...other things
}
I got text from a JSON to fulfill the ContentLabel. If the text is long, the headerView will overlap cells just like below image.(short text is OK)
http://i.stack.imgur.com/gtO2g.png
Section is visible but two lines of cell have been overlapped by the headerView.I'm not sure if I did wrong constraints or code on ContentLabel. Below is the code I configured the contentLabel in TopicHeaderView.swift
var content: String? {
didSet {
self.contentLabel.text = content!
self.setNeedsUpdateConstraints()
self.updateConstraintsIfNeeded()
self.setNeedsLayout()
self.layoutIfNeeded()
}
}
func setFrameHeight(height: CGFloat) {
var frame = self.frame
frame.size.height = height
self.frame = frame
}
override func layoutSubviews() {
super.layoutSubviews()
self.contentLabel.preferredMaxLayoutWidth = self.contentLabel.alignmentRectForFrame(contentLabel.frame).width
self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.bounds.size.width
self.authorLabel.preferredMaxLayoutWidth = self.authorLabel.bounds.size.width
self.setFrameHeight(CGRectGetMaxY(contentLabel.frame) + 8)
}
I browsed similar questions in SO but seems I can't find a solution to fix my problem. Can anyone help on this?
EDITED:
I logged the origin CGPoint of my first tableView cell and headerView's height. It shows the right number which means the first cell is right next to the header view vertically. There is a 22 points gap because of the height of section of course.
headerheight:600.0
first cell's y: 622.0
So maybe it's the label problem that its height is too big to exceed the bounds of TableView headerView? I'm not sure.
EDITED:
Strange things happen. I logged the y value of headerView's bottom,contentLabel's bottom and first UITableViewCell's origin. Please see the image from the link in the question comment below(still need 10 reputation)
As you can see, from the value in console, the view sequence from top should be "contentLabel's bottom(value:224) - headerView's bottom bounds(value: 232) - first cell's origin(value:254)". But in simulator, the sequence is totally messed up.It turns "headerView's bottom bounds - first cell's origin - contentLabel's bottom"
I really appreciate if anyone can help on this.

Problem is, that UITableView does not automatically change positions of cells when its headerView's height changes. Thus you need to reload UITableView every time TopicHeaderView.content changes.

Select that header view, or imageView what you have there, and check Clip Subviews in Attributes Inspector tab.
This worked for me.

Related

Self sizing tableview

First of all this is not a question about how to automatically size the cells inside the tableview, moreover how to automatically resize the entire tableview.
So I have a scrollview which has a tableview and 2 other views as its subviews. The tableview's cells already automatically resize itself, again this question is not about the individual cells. However, the tableview does not resize at all.
What I have done:
1) Set up the tableview to have a top, bottom, leading and trailing constraint
2) Set the cells up to have auto layout enabled
3) * I do not know the cell size at build time
4) I have disabled scrolling mode on tableview
So long story short, how can I go along to get the tableview to resize itself?
Edit
The cells contain a label which can have various lines of text, so therefore the cells, which use auto layout, should then determine the height of the table view.
The following images show how the view is set up:
As you can see the tableview is only a small part of the view and since the scrollview from the tableview is deactivated there should, and aren't, any scrolling problems.
EDIT 2
This is actually how it should end however, i am calculating this on my own and everytime I want to make a small change to the cells the whole code, which calculates the height of the cell, needs to be rewritten and it is quite difficult for me to get the height just right.
Edit 3
Until now I had a height constraint on the tableview which I calculated manually, however removing this constraint and trying to let auto layout handle the tableview height size creates the following error:
Scroll View
Need constraint for: Y position or height
I can conclude therefore that the tableview does not know how to automatically calculate the height based on its cells with autolayout.
You don't need to create a height constraint or set frame whatsoever. Create a subclass of UITableView and recalculate its intrinsicContentSize every time its contentSize changes aka new data added or removed. Here is all you needed:
class SelfSizingTableView: UITableView {
override var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
setNeedsLayout()
}
}
override var intrinsicContentSize: CGSize {
let height = min(.infinity, contentSize.height)
return CGSize(width: contentSize.width, height: height)
}
}
You can change your UITableView's frame by using tableview.frame = CGRect(x: <some_x>, y: <some_y>, width: <some_width>, height: <some_height>)
If your UITableViewCells use auto layout then they should resize when the UITableView's frame changes.

scrolling/resizing UITableView

I'll get right to the point.
I have a UIViewController that has two subviews in it. The top one (let's call it HeaderView from now one) is a custom UIView and the bottom one is a UITableView.
I have set them up in InterfaceBuilder so that the HeaderView has 0 margin from the left, top and right, plus it has a fixed height.
The UITableView is directly underneath with 0 margin from all sides.
My goal is to achieve a behaviour such that when I start scrolling the UITableView's content the HeaderView will start shrinking and the UITableView becomes higher without scrolling. This should go on until the HeaderView has reached a minimum height. After that the UITableView should start scrolling as normal. When scrolling down the effect should be reversed.
I have initially started this out using a UIScrollView instead of the UITableView and I have achieved the desired result. Here is how:
connect the UIScrollView to the outlet
#IBOutlet weak var scrollView: UIScrollView!
set the UIScrollViewDelegate in the controller's viewDidLoad() method
self.scrollView.delegate = self
and declared the UIViewController to conform to the protocol
intercept when the UIScrollView scrolls:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.adjustScrolling(offset: scrollView.contentOffset.y, scrollView: scrollView)
}
in my adjustScrolling(offset:scrollView:) method the "magic" happens
Now let's look at what happens in this method.
private func adjustScrolling(offset: CGFloat, scrollView: UIScrollView) {
// bind value between 0 and max header scroll
let actualOffset: CGFloat = offset < 0 ? 0 : (offset >= self.maxHeaderScroll ? self.maxHeaderScroll : offset)
// avoid useless calculations
if (actualOffset == self.currentOffset) {
return
}
/**
* Apply the vertical scrolling to the header
*/
// Translate the header up to give more space to the scrollView
let headerTransform = CATransform3DTranslate(CATransform3DIdentity, 0, -(actualOffset), 0)
self.header.layer.transform = headerTransform
// Adjust header's subviews to new size
self.header.didScrollBy(actualOffset)
/**
* Apply the corrected vertical scrolling to the scrollView
*/
// Resize the scrollView to fill all empty space
let newScrollViewY = self.header.frame.origin.y + self.header.frame.height
scrollView.frame = CGRect(
x: 0,
y: newScrollViewY,
width: scrollView.frame.width,
height: scrollView.frame.height + (scrollView.frame.origin.y - newScrollViewY)
)
// Translate the scrollView's content view down to contrast scrolling
let scrollTransform = CATransform3DTranslate(CATransform3DIdentity, 0, (actualOffset), 0)
scrollView.subviews[0].layer.transform = scrollTransform
// Set bottom inset to show content hidden by translation
scrollView.contentInset = UIEdgeInsets(
top: 0,
left: 0,
bottom: actualOffset,
right: 0
)
self.currentOffset = actualOffset
}
If I haven't forgotten anything this should be enough to achieve the desired effect. Let me break it down:
I calculate the actualOffset binding it between 0 and self.MaxHeaderScroll which is just 67 (I think, it's calculated dynamically but this doesn't really matter)
If I see that the actualOffset hasn't changed since the last time this function was called I don't bother to aplly any changes. This avoids some useless calculations.
I apply the scrolling to the header by translating it up with a CATransform3DTranslate on just the y axis by negative actualOffset.
I call self.header.didScrollBy(actualOffset) so that the HeaderView can apply some visual changes internally. This doesn't concearn the question though.
I resize the scrollView so that it keeps 0 margin from top and bottom now that the HeaderView is higher up.
I translate down the scrollView's content by the same actualOffset amount to contrast the scrolling. This piece is essential to the correct visual effect that I want to achieve. If I didn't do this, the scrollView would still resize correctly but the content would start scrolling right away, which I don't want. It should only start scrolling once the HeaderView reaches it's minimum height.
I now set a bottom inset in the scrollView so that I am able to scroll it all the way to the end. Without this, the last part of the scrollView would be cut off since the scrollView itself would think it reached the end of it's content.
Lastly I store the actualOffset for later comparison
As I said, this works fine. The problem arises when I switch from a UIScrollView to a UITableView. I assumed it would work since UITableView inherits from UIScrollView.
The only piece of code that doesn't work is the number 6. I don't really know what is going wrong so I will just list everything I have found out and/or noticed. Hopefully someone will be able to help me out.
in the case of the UIScrollView, in point 6, the scrollView.subviews[0] refers to a view that holds all the content inside it. When I change to UITableView this subview seems to be of the type UITableViewWrapperView which I could not find any documentation about, nor does XCode recognize it as a valid class. This is already frustrating.
if in point 6 I also give some translation on the x axis (let's say of 50) I can see an initial very quick translation that is immediately brought back to 0. This only happens when the UITableView starts scrolling, it doesn't go on while scrolling.
I have tried changing the frame of the subview in point 6 to achieve the desired result. Although the scrolling is correct, the top cells start disappearing as I scroll the UITableView. I thin this is because I am using dequeueReusableCell(withIdentifier:for:) to instatiate the cells and the UITableView thinks that the top cells aren't visible when they actually are. I wasn't able to work around this problem.
I have tried setting the self.tableView.tableHeaderView to a UIView of the actualOffset height to contrast scrolling but this gave a weird effect where the cells would not scroll correctly and when the UITableView was brought back to the initial position, there would be a gap on top. No clue about this either.
I know there's a lot here so please don't hesitate asking for more details. Thank you in advance.
I made something like this recently, so heres how I achieved it:
Make a UIView with a height constraint constant and link this to your view/VC, have you UITableview constrained to the VC's view full screen behind the UIView.
Now set your UITableViews contentInset top to the starting height of your 'headerView' now, in the scrollViewDidScroll you adjust the constant until the height of the header is at its minimum.
Here is a demo
If you just run it, the blue area is your 'header' and the colored rows are just any cell. You can autolayout whatever you want in the blue area and it should auto size and everything

Dynamic UILabel size for view before uitableview

I am having abit of trouble here trying to make this post description label to grow and shrink based on content size on IOS9. I have a view (I will refer it topView) that I am using as a header for the tableview (So when I scroll up the header disappears). Inside the topView, there are a bunch of stack views. I wish to grow and shrink the post description label in height based on content size. I do know how to do it in simple case where everything is inside the prototype cell (i.e. set estimated row height and set uitableviewautomaticDimensions, set sizetoFit on label and change number of lines to 0). However, this is a different case because the post description label is not really inside the cell, it is in its view before the table view cells.
Note that all items in the view has static height except the postdescription label. Post description label is inside a stack view that is pinned only left and right (So that top and bottom would grow?). Also, the main stack view that contains all elements is pinned towards the four sides with the topview that contains the main stack view also pinned towards the four sides. With this setup, I would expect the topview to grow and shrink based on the content size. However, I do not see that in the output. I dont know if it is the stackview that is holding the label refusing to grow or the top view refusing to grow to allow more space for the stackview for the label. Thanks
UPDATE
Thanks Riadluke, I tried doing something as suggested which is resizing the headerview after calculating the required height. I have placed the following code in viewDidLayoutSubview and it works with an issue
postDescriptionLbl.sizeToFit()
let headerView = commentTableView.tableHeaderView!
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
let height = TopStackView.frame.size.height + ImageStackView.frame.size.height + postDescriptionLbl.frame.size.height + SpacerStackView.frame.size.height + BottomStackView.frame.size.height
headerView.frame.size.height = height
commentTableView.tableHeaderView = headerView
The issue I now have with this method is that when the view controller appears, I can physically see the postDescription label height grow from the default height in storyboard to the required height. For example, when the VC first appears, I see a line of label with some string being cut off, however after 0.5 second, the headerview and the label grow to the size that I wanted. I know this would be expected because I was calling the manipulation after the viewDidLayout Subview. I was wondering if there is a better way such that I dont see that transition and the view appears to be the right height straight away. Ie. let the view know exactly how high the label is to determine how high the headerview needs to be before appearing on screen?
I'm afraid the view set as tableHeaderView of a UITableView does not get resized automatically. Its height will be fixed to the height it had in IB.
What you have to do is set its size manually and then reassign it as the tableHeaderView so it is displayed in the height you want.
It could take only few lines since you're using autolayout.
You can try this code right after you've set the header view's contents:
//for the target size you have set the width as your tableView's width when it is already displayed on screen.
//note that when it is accesed inside viewDidLoad the tableView's bounds
//may be different to the actual bounds it will be displayed with,
//here I am just using the screen bounds
let targetSize = CGSize(width: UIScreen.mainScreen().bounds.width, height: 10000)
//set the tableHeader's size to its size after its layout constraints are resolved
tableHeader.bounds.size = tableHeader.systemLayoutSizeFittingSize(targetSize)
//reassign it as the tableHeaderView to update the height it will be displayed in
tableView.tableHeaderView = tableHeader
After many many attempts, I could not get anything to work with the original setup. The best I achieved was to resize it after view did appear which is not idea as you see the previous layout.
It is now working with a complete different approach. I have created two prototype cell and have one as "HeaderViewCell" and implemented the following functions
commentTableView.estimatedSectionHeaderHeight = 400
commentTableView.sectionHeaderHeight = UITableViewAutomaticDimension
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
Everything works like a charm after that.

How to make uitableview to fill all my view?

I am very new to iOS. Can anyone tell me how to fix this, to fill all my view with UITableView:
I am trying to do it with auto layout and constraints but I don't know why isn't working.
What you want to do is to have the cells at equal height so that all cells together fit exactly the height of the screen.
You cannot do that with auto layout. You have to tell the table view the height you'd like the rows to have like so:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let heightOfVisibleTableViewArea = view.bounds.height - topLayoutGuide.length - bottomLayoutGuide.length
let numberOfRows = tableView.numberOfRowsInSection(0)
tableView.rowHeight = heightOfVisibleTableViewArea / CGFloat(numberOfRows)
}
Note that this code assumes that you don't implement the method tableView(_:heightForRowAtIndexPath:).
To fill the whole view, with a table view, I use four constraints:
One. tableView centre = view centre.
Two. tableView width = view width.
Three. tableView vertical distance to top layout guide = 0
Four. tableView vertical distance to bottom layout guide = 0.
To fill everything with the same color, I set the backgroundColor of the tableView, then set the backgroundColor of the cells to [UIColor clearColor].

How to Add Margin Between UITableViewCells Without Being Hacky

A lot of the solutions I've seen here include changing the cell's background to an image and using sections for rows rather than just rows themselves. I'm looking to have only two sections and have each cell expand in height on tap, so neither of those solutions would work.
I saw one solution includes setting the frame of the cell in the layoutSubviews() function like so:
override func layoutSubviews() {
super.layoutSubviews()
self.frame = CGRectOffset(self.frame, 0, 10);
}
When I do this however, it only gives margin to one cell and that's only when I tap on the cell.
Is there a surefire way to add spacing in between UITableViewCells without being hacky and breaking the cell layouts in the process?
I did this yesterday pretty easily with auto layout.
I set the background of the cell and it's content view to clear, then I created a new view and setup constraints all around it and put my labels inside of it. The height changes dynamically based on the label so I needed to use UITableViewAutomaticDimension for the row height and give it an estimated row height as well.
I don't see why this wouldn't work for expanding it on a tap as well, you just might have to reload the cell.
make the cell and it's contentView transparent
contentView addSubview customContentView and layout your cell on customContentView
customContentView pin to contentView top leading trailing with offset 0 but pin to bottom with offset 10 //the margin height

Resources