How to hide a UIView while scrolling? - ios

I am trying to achieve this effect.
I am trying to hide a view, as the user scrolls up, and reveal it as the user scrolls down.
I do not know how to go about this.
I have tried looking as the scrollView didScroll function, but I am lost as how to set this up. Any advice?

I solved like this:
private var lastContentOffset: CGFloat = 0
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
lastContentOffset = scrollView.contentOffset.y
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if lastContentOffset > scrollView.contentOffset.y {
UIView.animate(withDuration: 0.25, animations: { [weak self] in
self?.addDeviceButton.alpha = 1.0
self?.addDeviceButton.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
}, completion: nil)
} else if lastContentOffset < scrollView.contentOffset.y {
UIView.animate(withDuration: 0.25, animations: { [weak self] in
self?.addDeviceButton.alpha = 0
self?.addDeviceButton.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
}, completion: nil)
}
}

Hiding content on scroll
Please check this library it do exactly what your attached gif do.
The other way around would be from the gif file attached by you would be.
Add search bar on top. And make hiding part as a header and aligned the search bar and tableview to get that animation.

You need to set up the container view for the scroll view and the view you want to hide as the delegate for the scroll view, registering this class to conform to the UIScrollViewDelegate protocol.
You also need to keep track of the scroll view’s content offset property, and setvthis to the initial offset of the scroll view when it is created:
fileprivate var scrollOffset : CGPoint
Then, as you say, use the scrollViewDidScroll method:
internal func scrollViewDidScroll(_ scrollView: UIScrollView) {
let delta : CGPoint = CGPoint(x: scrollView.contentOffset.x - scrollOffset.x,
y: scrollView.contentOffset.y - scrollOffset.y)
if delta.y > 0, subviewToFade.isDescendant(of: self) {
// fade out subviews and remove
else if delta < 0, !subviewToFade.isDescendant(of: self) {
// add subviews and fade back in
}
scrollOffset = scrollView.contentOffset
}
Hope that helps.

here is my constraints
func scrollViewDidScroll(_ scrollView: UIScrollView) {
setPosition(scrollView)
}
func setPosition(_ scrollView:UIScrollView) {
if scrollView.contentOffset.y >= 20 {
self.parentTopViewcontraints.constant = -95 // set postition till you want to hide your view
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}else if scrollView.contentOffset.y <= 200 {
self.parentTopViewcontraints.constant = 0 //set back
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}
}

Related

Swipes/scroll down first will disappear the view

How can I make a UIScrollView start scrolling from top to bottom. When i swipe(down) it will scroll down first not up and disappear. What i have now is, i can scroll up first and then down, while scrolling down it's disappear.
Here is my code -
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//for toogle swipe view
if scrollView.contentOffset.y < 120 {
self.swipeMsgView.isHidden = false
}else {
self.swipeMsgView.isHidden = true
}
//for making bottom inset
if scrollView.contentOffset.y < 190 {
var contentInset:UIEdgeInsets = scrollView.contentInset
contentInset.bottom = scrollView.contentOffset.y-100
scrollView.contentInset = contentInset
}
//when swipe down
if scrollView.contentOffset.y == 0 {
if !isScrollDownFirstTime{
UIView.animate(withDuration: 0.5, animations: {
self.dismiss(animated: true, completion: nil)
})
}
}
//for tracking first time scrolling
if scrollView.contentOffset.y > 150 {
isScrollDownFirstTime = false
}
}
It works fine. But i want to disappear the view when a user swipes or scroll down first (swipe gesture is not working). Is there any elegant way to do this with this existing feature?Thank you.
#Serg Smyk, Here how I fixed the problem.
fileprivate var isScrollDownFirstTime = true
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("Content y offet : \(scrollView.contentOffset.y)")
//for toogle swipe view
if scrollView.contentOffset.y < 120 {
self.swipeMsgView.isHidden = false
} else {
self.swipeMsgView.isHidden = true
}
//for making bottom inset
if scrollView.contentOffset.y < 190 {
var contentInset:UIEdgeInsets = scrollView.contentInset
contentInset.bottom = scrollView.contentOffset.y-100
scrollView.contentInset = contentInset
}
//when swipe down
if scrollView.contentOffset.y == 0 {
print(CLASS_NAME+" -- scrollViewDidScroll() -- contentOffset.y = 0")
if !isScrollDownFirstTime{
UIView.animate(withDuration: 0.5, animations: {
self.dismiss(animated: true, completion: nil)
})
}
}
if scrollView.contentOffset.y < 1 {
print(CLASS_NAME+" -- scrollViewDidScroll() -- contentOffset.y<0")
UIView.animate(withDuration: 0.5, animations: {
self.dismiss(animated: true, completion: nil)
})
}
//for tracking first time scrolling
if scrollView.contentOffset.y > 150 {
isScrollDownFirstTime = false
}
}

How to forbid WKWebView's horizontal bounce?

As the gif showed blow,I want to forbid the horizontal bounce,it's so ugly.
You can use this to stop bounces:
self.webView.scrollView.bounces = false
Update:
To disable only horizontal bounce, you can use this code:
First set the webview's scrollview delegate to self
self.webView.scrollView.delegate = self
and then implement it like this.
extension UIViewController: UIScrollViewDelegate{
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(scrollView.contentOffset.x < 0){
scrollView.setContentOffset(CGPoint.init(x: 0, y: scrollView.contentOffset.y), animated: false)
}else if (scrollView.contentOffset.x >= scrollView.contentSize.width - scrollView.frame.size.width) {
scrollView.setContentOffset(CGPoint.init(x: scrollView.contentSize.width - scrollView.frame.size.width, y: scrollView.contentOffset.y), animated: false)
}
}
}
Hope it helps.

UIScrollView snap-to-position while scrolling

I am trying to implement a scroll view that snaps to points while scrolling.
All the posts here I've seen about snapping to a point 'after' the user has ended dragging the scroll. I want to make it snap during dragging.
So far I have this to stop the inertia after dragging and it works fine:
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
targetContentOffset.memory = scrollView.contentOffset
}
I tried this but not working as desired:
var scrollSnapHeight : CGFloat = myScrollView.contentSize.height/10
scrollViewDidScroll:
func scrollViewDidScroll(scrollView: UIScrollView) {
let remainder : CGFloat = scrollView.contentOffset.y % scrollSnapHeight
var scrollPoint : CGPoint = scrollView.contentOffset
if remainder != 0 && scrollView.dragging
{
if self.lastOffset > scrollView.contentOffset.y //Scrolling Down
{
scrollPoint.y += (scrollSnapHeight - remainder)
NSLog("scrollDown")
}
else //Scrolling Up
{
scrollPoint.y -= (scrollSnapHeight - remainder)
}
scrollView .setContentOffset(scrollPoint, animated: true)
}
self.lastOffset = scrollView.contentOffset.y;
}
This approach is going to enable / disable scrollEnabled property of UIScrollView.
When scrollView scrolls outside the given scrollSnapHeight, make scrollEnabled to false. That will stop the scrolling. Then make scrolling enable again for the next drag.
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.y > lastOffset + scrollSnapHeight {
scrollView.scrollEnabled = false
} else if scrollView.contentOffset.y < lastOffset - scrollSnapHeight {
scrollView.scrollEnabled = false
}
}
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
guard !decelerate else {
return
}
setContentOffset(scrollView)
}
func scrollViewWillBeginDecelerating(scrollView: UIScrollView) {
setContentOffset(scrollView)
}
}
func setContentOffset(scrollView: UIScrollView) {
let stopOver = scrollSnapHeight
var y = round(scrollView.contentOffset.y / stopOver) * stopOver
y = max(0, min(y, scrollView.contentSize.height - scrollView.frame.height))
lastOffset = y
scrollView.setContentOffset(CGPointMake(scrollView.contentOffset.x, y), animated: true)
scrollView.scrollEnabled = true
}
Subclass UIScrollView/UICollectionView
This solution does not require you lift your finger in order to unsnap and works while scrolling. If you need it for vertical scrolling and not horizontal scrolling just swap the x's with y's.
Set snapPoint to the content offset where you want the center of the snap to be.
Set snapOffset to the radius you want around the snapPoint for where snapping should occur.
If you need to know if the scrollView has snapped, just check the isSnapped variable.
class UIScrollViewSnapping : UIScrollView {
public var snapPoint: CGPoint?
public var snapOffset: CGFloat?
public var isSnapped = false
public override var contentOffset: CGPoint {
set {
if let snapPoint = self.snapPoint,
let snapOffset = self.snapOffset,
newValue.x > snapPoint.x - snapOffset,
newValue.x < snapPoint.x + snapOffset {
self.isSnapped = true
super.contentOffset = snapPoint
}
else {
self.isSnapped = false
super.contentOffset = newValue
}
}
get {
return super.contentOffset
}
}
}

iOS/Swift - Hide/Show UITabBarController when scrolling down/up

I'm quite new to iOS development. Right now i'm trying to hide my tabbar when I scroll down and when scrolling up the tabbar should appear. I would like to have this animated in the same way like the navigation bar. For the navigation bar I simply clicked the option in the Attributes Inspector. I saw some examples for the toolbar, but I cant adopt it the tabbar.
self.tabBarController?.tabBar.hidden = true just hides my tabbar, but its not animated like the navigation controller.
This is code that i'm actually using in a production app.
It's in Swift and it also updates UITabBar.hidden var.
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0{
changeTabBar(hidden: true, animated: true)
}
else{
changeTabBar(hidden: false, animated: true)
}
}
You can also use the other callback method:
func scrollViewDidScroll(scrollView: UIScrollView) {
...
}
but if you choose so, then you must handle multiple calls to the helper method that actually hides the tabBar.
And then you need to add this method that animates the hide/show of the tabBar.
func changeTabBar(hidden:Bool, animated: Bool){
var tabBar = self.tabBarController?.tabBar
if tabBar!.hidden == hidden{ return }
let frame = tabBar?.frame
let offset = (hidden ? (frame?.size.height)! : -(frame?.size.height)!)
let duration:NSTimeInterval = (animated ? 0.5 : 0.0)
tabBar?.hidden = false
if frame != nil
{
UIView.animateWithDuration(duration,
animations: {tabBar!.frame = CGRectOffset(frame!, 0, offset)},
completion: {
println($0)
if $0 {tabBar?.hidden = hidden}
})
}
}
Update Swift 4
func changeTabBar(hidden:Bool, animated: Bool){
guard let tabBar = self.tabBarController?.tabBar else { return; }
if tabBar.isHidden == hidden{ return }
let frame = tabBar.frame
let offset = hidden ? frame.size.height : -frame.size.height
let duration:TimeInterval = (animated ? 0.5 : 0.0)
tabBar.isHidden = false
UIView.animate(withDuration: duration, animations: {
tabBar.frame = frame.offsetBy(dx: 0, dy: offset)
}, completion: { (true) in
tabBar.isHidden = hidden
})
}
This answer is a slight modification to Ariel answer which adds animation while user scrolls.
extension ViewController:UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0{
//scrolling down
changeTabBar(hidden: true, animated: true)
}
else{
//scrolling up
changeTabBar(hidden: false, animated: true)
}
}
func changeTabBar(hidden:Bool, animated: Bool){
let tabBar = self.tabBarController?.tabBar
let offset = (hidden ? UIScreen.main.bounds.size.height : UIScreen.main.bounds.size.height - (tabBar?.frame.size.height)! )
if offset == tabBar?.frame.origin.y {return}
print("changing origin y position")
let duration:TimeInterval = (animated ? 0.5 : 0.0)
UIView.animate(withDuration: duration,
animations: {tabBar!.frame.origin.y = offset},
completion:nil)
}
}
Building on Ariel's answer, I have updated the code for Swift3. This worked great on my collection views.
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0 {
changeTabBar(hidden: true, animated: true)
}else{
changeTabBar(hidden: false, animated: true)
}
}
func changeTabBar(hidden:Bool, animated: Bool){
let tabBar = self.tabBarController?.tabBar
if tabBar!.isHidden == hidden{ return }
let frame = tabBar?.frame
let offset = (hidden ? (frame?.size.height)! : -(frame?.size.height)!)
let duration:TimeInterval = (animated ? 0.5 : 0.0)
tabBar?.isHidden = false
if frame != nil
{
UIView.animate(withDuration: duration,
animations: {tabBar!.frame = frame!.offsetBy(dx: 0, dy: offset)},
completion: {
print($0)
if $0 {tabBar?.isHidden = hidden}
})
}
}
You can control UITabBar precisly by setting up your class as delegate for scrollView and implementing scrolling in scrollViewDidScroll: method.
Here is an example how I do it my application. You can probably easily modify that for your needs. Some helper function to get UITabBar included.
#define LIMIT(__VALUE__, __MIN__, __MAX__) MAX(__MIN__, MIN(__MAX__, __VALUE__))
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat scrollOffset = scrollView.contentOffset.y;
CGFloat scrollDiff = scrollOffset - self.previousScrollViewYOffset;
CGFloat scrollHeight = scrollView.frame.size.height;
CGFloat scrollContentSizeHeight = scrollView.contentSize.height + scrollView.contentInset.bottom;
CGFloat scrollOffsetGlobal = scrollOffset + scrollView.contentInset.top;
[self updateUITabBarY:[self UITabBarView].frame.origin.y + scrollDiff];
self.previousScrollViewYOffset = scrollOffset;
}
- (UITabBar*) UITabBarView
{
for(UIView *view in self.tabBarController.view.subviews)
{
if([view isKindOfClass:[UITabBar class]])
{
return (UITabBar*) view;
}
}
return nil;
}
- (void) updateUITabBarY:(CGFloat) y
{
UITabBar* tabBar = [self UITabBarView];
if(tabBar)
{
CGRect frame = tabBar.frame;
frame.origin.y = LIMIT(y, [self UITabBarMiny], [self UITabBarMaxY]);
tabBar.frame = frame;
}
}
- (CGFloat) UITabBarMiny
{
return [UIScreen mainScreen].bounds.size.height - [self UITabBarView].frame.size.height - [[UIApplication sharedApplication] statusBarFrame].size.height + 20.0f;
}
- (CGFloat) UITabBarMaxY
{
return [UIScreen mainScreen].bounds.size.height;
}
Ariels answer works, but has some values that seem off. When you compare the y-value of the scrollView scrollView.panGestureRecognizer.translation(in: scrollView).y, "0" has the side effect, that the tabBar shows or hides when you stop scrolling. It calls the method one more time with a "0" value. I tried it with didEndDragging, didScroll and willBeginDragging with similar effects. And that feels very counter intuitive or buggy.
I used +/- 0.1 when comparing the y-value and got the desired effect, that it just shows and hides when you are really scrolling up or down.
Another thing that isn't mentioned is that the offset that you set with tabBar.frame = frame.offsetBy(dx: 0, dy: offset) will be reset when the app moves to the background. You scroll down, the tabBar disappears, you change the app, open it up again, the tabBar is still hidden but the frame is back to the old location. So when the function is called again, the tabBar moves up even more and you have a gap of the size of the tabBar.frame.
To get rid of this I compared the current frame location and animated the alpha value. I couldn't get the usual coming back up animation to work, maybe somebody will try, can't be that hard. But its okay this way, as it doesn't happen that often.
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let yValue = scrollView.panGestureRecognizer.translation(in: scrollView).y
if yValue < -0.1 {
//hide tabBar
changeTabBar(hidden: true, animated: true)
} else if yValue > 0.1 {
//show tabBar
changeTabBar(hidden: false, animated: true)
}
}
func changeTabBar(hidden:Bool, animated: Bool) {
guard let tabBar = self.tabBarController?.tabBar else {
return
}
if tabBar.isHidden == hidden{
return
}
let frame = tabBar.frame
let frameMinY = frame.minY //lower end of tabBar
let offset = hidden ? frame.size.height : -frame.size.height
let viewHeight = self.view.frame.height
//hidden but moved back up after moving app to background
if frameMinY < viewHeight && tabBar.isHidden {
tabBar.alpha = 0
tabBar.isHidden = false
UIView.animate(withDuration: 0.5) {
tabBar.alpha = 1
}
return
}
let duration:TimeInterval = (animated ? 0.5 : 0.0)
tabBar.isHidden = false
UIView.animate(withDuration: duration, animations: {
tabBar.frame = frame.offsetBy(dx: 0, dy: offset)
}, completion: { (true) in
tabBar.isHidden = hidden
})
}
According to #Ariel Hernández Amador answer for black screen after hiding Tabbar just use this line of code in your ViewDidLoad(). Working Superbly...I have posted this here as I am unable to comment over there.
viewDidLoad()
{
if #available(iOS 11.0, *) {
self.myScroll.contentInsetAdjustmentBehavior = .never
}
}
Here myScroll is the Scrollview I am using in my VC. Just replace it with your VC.

Swift - Shrink Navigation Bar on scroll

I'm really stuck here. I would like to shrink the Navigation Bar when I scroll down a UITableView and enlarge it again when scrolling up. I managed to alter the size of the Navigation Bar, but the title image is not shrinking with the Navigation Bar.
I want to do it exactly like Safari, and the problem is that the height of my TitleView is shrinking but the width never changes.
Here's the code I've used to get the height of the scrollbar to change.
func scrollViewDidScroll(scrollView: UIScrollView) {
var navbar = navigationController?.navigationBar
var dir:CGPoint = tableview.panGestureRecognizer.translationInView(self.tableview)
var scrollViewHeight = tableview.frame.size.height
var scrollContentSizeHeight = tableview.contentSize.height
var scrollOffset = tableview.contentOffset.y
if (dir.y > 0 && self.formernavstate == "small") {
self.formernavstate = "big"
UIView.animateWithDuration(0.5, delay:0.0, options: UIViewAnimationOptions.AllowAnimatedContent, animations: { () -> Void in
println("")
navbar?.frame.origin.y = 20
self.navigationItem.titleView?.transform = CGAffineTransformMakeScale(0.52, 0.6)
}, completion: nil)
}
if (dir.y < 0 && self.formernavstate == "big") {
self.formernavstate = "small"
navbar?.frame.origin.y = 0
navigationItem.titleView?.transform = CGAffineTransformMakeScale(0.0001, 0.2)
}
}
I implemented a Swift 3 version with a custom "header" and resizing it's height constraint based on #chauhan's answer:
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollViewOffset < scrollView.contentOffset.y {
shrinkHeader(shrink: true)
} else if scrollViewOffset > scrollView.contentOffset.y {
shrinkHeader(shrink: false)
}
}
func shrinkHeader(shrink: Bool) {
if shrink {
if self.headerContainerHeightConstraint.constant > CGFloat(minHeaderHeight) {
UIView.animate(withDuration: 0.1, animations: {
self.headerContainerHeightConstraint.constant = self.headerContainerHeightConstraint.constant - 2
self.view.layoutIfNeeded()
})
}
} else {
if self.headerContainerHeightConstraint.constant < CGFloat(maxHeaderHeight) {
UIView.animate(withDuration: 0.1, animations: {
self.headerContainerHeightConstraint.constant = self.headerContainerHeightConstraint.constant + 6
self.view.layoutIfNeeded()
})
}
}
}
}

Resources