I know that the sticky header is not a new thing to ask advice on, but still...
I am trying to create a sticky header (UIImageView) and the scrolling part (UIScrollView with a UIStackView in it)
Im using the scrollViewDidScroll method from the UIScrolLViewDelegate. The only problem is, that when I scroll the view up, I am not only decreasing the height of the header view, but also scrolling the content of the stack view. So when I scroll further up, you still can see the header view, but the top content of the scroll view disappears by scrolling.
Can this be solved somehow that when I scroll up, the content of the stack view is scrolling up and not also disappearing? And starts disappearing when the header view disappears?
Thank you
The easiest way to do this is to use a table view. Your sticky header is the table's first section header, while the scrolling part is the second section.
If you can't use a table, for whatever reason, then you have to mess around with the scroll view's content offset. When the contentOffset.y is growing (but not beyond your header's height), reset it to 0 and decrease the header's height accordingly. After the header's height is 0, stop messing with contentOffset - until you come back and the contentOffset.y wants to go into negatives.
P.S. the second solution requires you to enable bouncing on the scroll view. Otherwise, the header will be hidden, but won't show again (unless you reset the controller)
L.E. Some (old) code for my second solution:
let headerHeight = self.headerView.height
self.scrollHandler = {
var offset = self.detailsTable.contentOffset
self.headerTopConstraint.constant -= offset.y
if self.headerTopConstraint.constant < -headerHeight {
self.headerTopConstraint.constant = -headerHeight
let size = self.detailsTable.contentSize
if offset.y + self.detailsTable.frame.height > size.height {
offset.y = size.height - self.detailsTable.frame.height
}
self.detailsTable.contentOffset = offset
}
else {
if self.headerTopConstraint.constant > 0 {
self.headerTopConstraint.constant = 0
}
self.detailsTable.contentOffset = CGPointZero
}
}
Please note that my code moved the header upwards to hide it. As far as I understood, you just have to change the header's height (by the same amount I move it upwards).
Related
I have a UIScrollView with a couple of subviews, one of them is a UIStackView that can be hidden/displayed when a button is pressed.
When the UIStackView is hidden I want to disable scrolling on the UIScrollView since the content will never have enough height that justifies scrolling being enabled. To achieve that, I'm using:
isScrollEnabled.toggle()
Problem is, when doing this, the whole view gets dragged up, as if I was scrolling down, causing certain elements to be hidden behind the navigationBar and the statusBar.
I tried to fix that using the following piece of code:
if let navigationBarHeight = vcDelegate?.navigationController?.navigationBar.frame.height {
if let statusBarHeight = window?.windowScene?.statusBarManager?.statusBarFrame.height {
let totalHeight = navigationBarHeight + statusBarHeight
setContentOffset(.init(x: 0, y: -totalHeight), animated: true)
}
}
And that does actually work correctly when the contentOffset.y is >= a certain value that I can't precisely specify (because, for some reason, printing the value of contentOffset.y only shows a value that is != 0 when I scroll all the way to the bottom of the view.)
When the contentOffset.y is < than that mystery value, disabling the scroll will cause the following behavior:
The view will be dragged up
The view will be dragged down (because I call the setContentOffset function)
My bottom line question is: why does the view get dragged up when I disable scrolling? Could it be a constraint issue, some "background" offset change that I should be aware of or something else?
I am trying to achieve something similar to the parallax effect. Wont say exactly the parallax. I have a header view that is a part of the table view.
I did set the contentInset of the table view to change the location of the first cell.
The amount of the top contentInset added is the height of the header view as header view is placed in blank space created by changing the content inset.
As tableview has a default scrollview along with it so in viewDidScroll i added the following code to hide the header view as the scrollview scrolls down.
var scrollOffsetY = abs(scrollView.contentOffset.y);
scrollOffsetY -= headerView.bounds.size.height;
if(scrollView.contentOffset.y <= 0-self.topDistance && abs(scrollView.contentOffset.y) <= headerView.bounds.size.height ){
print("\(scrollOffsetY)");
//
self.headerView.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, 0, scrollOffsetY);
}
self.headerView.setNeedsLayout();
self.headerView.layoutIfNeeded();
}
Now the problem is, the as the scroll down sometimes the header view is not fully collapsed. Is there any other way or some modifications that can help to make the transition smooth.
I am sticking my UITableView header to the top when user scrolls down the UITableView. The header view itself is a UIButton which does something when clicked.
The button responds well to touches when contentOffset Y is 0. However when the user scrolls down, the button still sticks to the top but every touches "passes through" it.
Here is my code to stick the header to the top:
var offsetY = scrollView.contentOffset.y;
var headerContentView: UIView = self.tableView.tableHeaderView?.subviews[0] as UIView;
headerContentView.frame = CGRect(x: 0, y: max(0, offsetY), width: headerContentView.bounds.width, height: headerContentView.bounds.height);
Thanks.
If you're going to be moving the view around yourself, don't use tableHeaderView at all. Instead add it as a subview of the table view directly and keep a reference to it. Then in scrollViewDidScroll: layout the view's Y offset according to scrollView.contentOffset.y.
You may need to trigger this layout in viewDidLoad so that it appears properly before any scroll events happen. If the view shouldn't overlap the top cell when the table is scrolled to the top, set the view's height to the table's contentInset's top.
I have a UIScrollView A (in fact a UICollectionView) filling the screen inside a UINavigationController B. The controller B's adjustScrollViewInsets is set to true.
I want to hide the navigation bar when user scrolls up, and show it when down. Following is my code:
func scrollViewDidScroll(scrollView: UIScrollView) {
if (self.lastContentOffset < scrollView.contentSize.height - scrollView.frame.size.height && self.lastContentOffset > scrollView.contentOffset.y) {
// dragging down
if self.navigationController!.navigationBarHidden {
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
} else if (self.lastContentOffset > 0 && self.lastContentOffset < scrollView.contentOffset.y) {
// dragging up
if !self.navigationController!.navigationBarHidden {
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
}
self.lastContentOffset = scrollView.contentOffset.y
}
Now the problem is, since the screen of iPhone 6+ is too large, the contentSize of the scroll view A is smaller than its frame(i.e. the full screen frame) when the navigation bar is hidden. In such circumstance, the scroll view will not be scrollable, and the navigation bar will never be back again.
I want to manually maintain the height of the contentSize of A to screen at least height + 1, but don't know how to do this. Could anyone help? Or provide a better solution?
BTW, I am using iOS 8 and Swift.
Lets say you need to keep the minimum content size of scroll view to 100(of course this will be dynamic and vary according to device)
NSInteger minScrollViewContentHeight = 100;
After populating the scroll view with content, you need to check if the scroll view's content size is less than minimum required scroll views content size. If its lesser than the required content size than you need to set the minimum content size of the scroll view as follows -
if(scrollView.contentSize.height < minScrollViewContentHeight)
[scrollView setContentSize:CGSizeMake(scrollView.frame.size.width, minScrollViewContentHeight)];
Off the top of my head (I'm on a phone), contentSize is not read-only I think.
How about changing it manually to the desired amount depending on the circumstances of scrolling direction etc?
Something like:
IF navbar is hidden THEN contentSize = whatever
An option would be to use the appearance and disappearance of cells to trigger the show/hide.
Use the delegate methods collectionView:willDisplayCell:forItemAtIndexPath: and collectionView:didEndDisplayingCell:forItemAtIndexPath: to detect movement. You can work out the direction from the index change of the cells being shown or removed. If you cannot scroll off screen then nothing happens.
You have to change no offset (which is actually just scrolling position), but contentSize itself. That means, that when you hide navigation bar, increase contentSize by navigation height (don't remember numbers) and when you show navigation bar, decrease contentSize. Or... Use AutoLayout and layoutIfNeeded method after showing/hiding navigation bar.
I stumbled upon a similar problem. I needed a minimum scrollable area for a tableview i was using.
ScrollView might be a bit easier since you can directly modify the contentView size.
If you're using autoLayout, try adding equal heights constraint between the contentView and the scrollView itself. Something along the lines of contentView.height = scrollView.height + scrollMin;
For my tableView i had to subclass UITableView and override the contentSize setter.
#define MIN_SCROLL 60
- (void)setContentSize:(CGSize)contentSize {
//take insets into account
UIEdgeInsets insets = self.contentInset;
CGFloat minHeight = self.frame.size.height - insets.top - insets.bottom + MIN_SCROLL;
if(contentSize.height < minHeight) {
contentSize.height = minHeight;
}
[super setContentSize:contentSize];
}
Forgive me to the obtuse title, as I'm unsure how to describe this question.
Recently many iOS apps utilise a scrolling UI design pattern which helps to maximise screen real-estate, typically hiding the header when the user scrolls downwards.
For example, Instragram's main view has the Instragram header at the top. Scrolling upwards on this view keeps the header fixed at the top, and the view bounces back normally to the top of the content. But scroll down and the header acts as part of the content, making way for an extra 44 points of vertical space.
Its probably that I haven't done much iOS work in a while, but I can't easily figure out how best to impliment this? Apologies for the terrible description.
If the header stays put no matter what, use a separate view on top of the scroll view.
If you use UITableView, you can use section headers.
EDIT Use this code:
- (void)scrollViewDidScroll:(UIScrollView*) scrollView
{
CGPoint offset = scrollView.contentOffset;
CGRect headerFrame = _headerView.frame;
if(offset.y > 0){
headerFrame.origin.y = offset.y;
}
else{
headerFrame.origin.y = 0.0;
}
[_headerView setFrame:headerFrame];
}
(Assumes _headerView is your header, sitting on top of the scroll view, not inside it. Also, both scroll view and header begin at the top of their parent view, y==0. Also, your view controller must be set up as delegate of the scroll view)
I just wrote this code from memory; haven't tested it but at most it should only need tweaking.
I tried ranReloaded's answer above but it seems that calling setFrame: on a UIScrollView stops the view from bouncing when going beyond its bounds.
Instead I set the scroll view to fit inside another UIView called scrollerWrapper. Applying the calculated origin and height to this view gives me effect I'm after plus retains the bounce behaviour.
- (void)scrollViewDidScroll:(UIScrollView*) scrollView
{
CGPoint offset = scrollView.contentOffset;
CGRect headerFrame = header.frame;
CGRect wrapperFrame = scrollerWrapper.frame;
if(offset.y > 0){
headerFrame.origin.y = -offset.y;
wrapperFrame.origin.y = MAX(0, headerFrame.size.height - offset.y);
}
else{
headerFrame.origin.y = 0.0;
wrapperFrame.origin.y = headerFrame.size.height;
}
wrapperFrame.size.height = self.view.frame.size.height - wrapperFrame.origin.y;
[header setFrame:headerFrame];
[scrollerWrapper setFrame:wrapperFrame];
}