Floating UIButton over UICollectionView - ios

I have a UIButton, which is fixed in position, over a UICollectionView, not part of the actual UICollectionView view hierarchy.
Is there a way I am able to, on vertical scroll of the UICollectionView, to dynamically adapt the scroll length where the last row of the UICollectionView always appears above the floating button?
Haven't found anything to reference and am admittedly lost on implementation. Hope for any thoughts..
Below is the desired functionality:

You can make it so that your UICollectionView scrolls above your button by setting a contentInset to account for the height of your button (plus the buffer above and below the button. That could look something like this:
collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: button.bounds.size.height + buffer, right: 0)
"buffer" is a placeholder for whatever you want to add to account for the space above and below the button.

If I understood your problem, you could do something like that:
yourButton.zPosition = 100
And your button should appear above your collection view.

Related

Add space between tableviewcell and tableview

What's the proper way to set left an right margins for a tableviewcell within a tableview?
Here's an image of what Im going for:
And here's what I actually have:
I can get the spacing on the left, but not on the right. I've set the contentInsent to this:
self.tableView.contentInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
but it's not working either. Instead it's allowing a horizontal scroll on the tableview.
If I wanted to achieve the interface you're showing, this would have nothing to do with the table view insets. I would draw the cells to have that appearance. This would be a matter of the cell's background view. Each cell would fill the space, as intended, but the cell would draw an inner rounded rect within itself.

Setting a minimum content size on a UITableView

I have a view at the top of a view controller, and a tableview underneath it.
I've made it such that as the tableview is scrolled up the top view scrolls up too, up to a maximum amount, of lets say 50 points.
The tableview also has a top inset of 50:
tableView = UIEdgeInsets(top: 50, left: 0, bottom: 0, right: 0)
... so that it's cells start below the top view.
And in the scrollview delegate there is some code along the lines of:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scrollViewYOffset = ...
topViewHeightConstraint.constant = max(minTopViewHeight, minTopViewHeight - scrollViewYOffset)
}
This ensures as the user drags down on the tableview the top view 'sticks' to it, and is also pushed up when the user pushes the table view up.
I've drawn this picture to better describe what the Storyboard looks like:
So far so good. When there are a lot of cells and the user scrolls the table view up, the top view remains at it's minimum height nicely.
But if you are scrolled up - so the top view is at it's minimum - then the number of cells are reduced, the top view pings back down to it's maximum height.
This is because the actual content size of the tableview has dropped below its bounds height, and so as a scrollview it brings the top of the content to the top of the scrollview again (plus the 50 point top inset of course).
I would like to be able to scroll the tableview up, so the top view remains at its minimum height, regardless of the number of cells it contains - i.e. regardless of it's content size.
Can anyone think of a clever way to set a minimum content size on the table view?
(So far I've tried messing around with the footer, having a cell at the bottom that is essentially a spacer - this messes up the tableview's logic and some reordering code I have in there. I've attempted to coerce the offers etc. to my will, but haven't quite worked out how to achieve this.)
I would greatly appreciate some UI genius to point me in the right direction :) Thank you.
UPDATE:
Thank you for all the answers and comments.
After trying various types of footer and header views, tweaking constraints & layout priorities on scroll, adding spacer cells, putting the tableview inside a scrollview, etc. - it finally occurred to me I was making this more complicated than I needed to, and should just update the cause of the problem on the scrollViewDidScroll, the contentInset value.
See the answer below for a code example that achieves the behaviour I was looking for.
As the contentInset is what is causing the tableview to ping back to the wrong point, I simply needed to adjust the content inset as the tableview was scrolled up/down.
Here is some example code of what I did:
let maxPointsTopViewCanMoveUp: CGFloat = 50
let topInset = abs(min(max(-maxPointsTopViewCanMoveUp, scrollView.contentOffset.y), 0))
scrollView.contentInset = UIEdgeInsets(top: topInset, left: 0, bottom: 0, right: 0)
scrollView.scrollIndicatorInsets = UIEdgeInsets(top: topInset, left: 0, bottom: 0, right: 0)
let amountToMoveTopViewUp = maxPointsTopViewCanMoveUp - topInset
topViewToSuperviewTopConstraint.constant = amountToMoveTopViewUp
This is called from the scrollViewDidScroll of the tableview.
It means that when there are too few cells in the tableview to fill the content, the top the tableview sticks in the place it had been scrolled up to (i.e. the amount it had pushed up the top view).
Some ideas that you can try:
Create a "dummy" row on index 0 and make it's height be 50 (it will be hidden below the top view). Maybe you can leave that dummy row in section 0 and the rest of your data in section 1, so you don't have to think about it when deleting your data.
Or, instead of a dummy row, you can set the height of the tableView's header to be 50
Maybe set a minimum height constraint on the tableView, and keep it's content compression resistance priority high.

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

How to programmatically change the UICollectionView Cell location

How to change spacing between the navigation bar and the first collectionViewCell?
I want to be aligned in the middle. In this case, the only way to calculate the screen size?
If you want to add space at the top of your collection view you can use contentInset. There are probably other ways to meet your requirement but this is the easiest way I can think of.
collectionView.contentInset = UIEdgeInsets(top: 100, left: 0, bottom: 0, right: 0)
I don't understand what you are trying to do. Can you explain more?

TableView ContentInset does not shrink cell width / Add horizontal padding to TableView contents

I'd like to have a UITableView which is full screen. But the content of the UITableView should have a padding on the left and right.
So I tried to set ContentInset. But now the cells are as wide as the UITableView and the UITableView scrolls horizontally.
Is there a way to say that the UITableView content's width should become narrowed by the horizontal content insets? Or do I have to add the padding to all cells and header/footer views?
I don't want to narrow the table view itself, because the scroll indicator should stay at the right side of the screen and not in the middle.
The here (How to set the width of a cell in a UITableView in grouped style) suggested solution seems to be not as generic as i'd love to, beacuse the cells and header and footer views have to know about the padding (at least 3 places to maintain instead of one)
I don't want to narrow the table view itself, because the scroll
indicator should stay at the right side of the screen and not in the
middle.
This makes you happy?
_tableView.clipsToBounds = NO;
_tableView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, 0, -30.f);
If you don't like clipsToBounds = NO effects, you can embed the tableView in container view which is clipsToBounds = YES.
Set the layout margins of the table view. For this to work make sure your constraints in the cells are set relative to the superview margin.
tableView.layoutMargins = UIEdgeInsets(top: 0, left: 40, bottom: 0, right: 40)

Resources