I want to stop UIScrollView scrolling when it reached at specific point.
I thought it will be solve by below code.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scrollY: CGFloat = scrollView.contentOffset.y
stopScrollViewIfNeeded(by: scrollY)
}
func stopScrollViewIfNeeded(by scrollY: CGFloat) {
guard scrollY <= SpecificY else {
return
}
scrollView.isScrollEnabled = false
}
But I was wrong. because contentOffset.y be to 0 when scroll be disabled.
And I was improved my function like below code, but it still didn't work as I wanted.
func stopScrollViewIfNeeded(by scrollY: CGFloat) {
guard scrollY <= SpecificY else {
return
}
scrollView.setContentOffset(.init(x: 0, y: SpecificY), animated: false)
scrollView.isScrollEnabled = false
scrollView.isPagingEnabled = true
}
How do I improve stopScrollViewIfNeeded() to work as I wanted?
just set contentOffset as:
Just need to get the offset value at which you want it to be stopped and Assign values as Below
Horizontal ScrollView
UIView.animate(withDuration: 0.5) {
self.MainScollView.contentOffset.x = self.view.frame.size.width*2
}
or Vertical scrollView
UIView.animate(withDuration: 0.5) {
self.MainScollView.contentOffset.y = 90
}
You can override the scroll position in didScroll. Logic in the didScroll can effectively make the scroll stick for as long as you want.
Related
I have a ScrollView on a view, where the page control is sticky from the bottom.
The page control is animated in and shown when the page is scrolled to 48px from the bottom of the page and stays opacity 100% (shown) to the end of the page.
But I am unable to detect this behaviour. scrollView content Size is mentioned below.
ScrollView content Size:
width : 414.0
height : 852.0
I am using the below code.
//MARK: - ScrollView Delegate
extension RemoveViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let distanceFromBottom = scrollView.contentSize.height - scrollView.contentOffset.y
print(distanceFromBottom)
if distanceFromBottom >= 796 {
print(" you reached at desired bottom")
self.showPageControl(toShow: true)
} else {
self.showPageControl(toShow: false)
}
}
}
func showPageControl(toShow: Bool) {
if toShow {
if self.pageControl.alpha != 1 {
UIView.animate(withDuration: 0.300, animations: {
self.pageControl.alpha = 1
})
}
} else {
if self.pageControl.alpha != 0 {
UIView.animate(withDuration: 0.300, animations: {
self.pageControl.alpha = 0
})
}
}
}
Kindly let me know what I am doing incorrectly here.
You need to calculate the bottomOffset first, then calculate the difference between it and the contentOffset. If it's <= 56, then you reached a specific position.
let bottomOffset = scrollView.contentSize.height - scrollView.frame.size.height
let position = bottomOffset - scrollView.contentOffset.y
if position <= 56 {
print(" you reached at desired bottom")
self.showPageControl(toShow: true)
} else {
self.showPageControl(toShow: false)
}
I am looking for a way to implement a header view that automatically hides once you start scrolling down and immediately shows itself once the user starts scrolling up.
Usually, I always post some code, but now I am a little bit lost on how to implement such behaviour.
My view layout:
UICollectionViewController with paging enabled for horizontal
scrolling (has two items)
The UICollectionViewCell fills the entire vertical space. Each UICollectionViewCell hosts a UITableView for vertical scrolling. I assume that I have to use the UITableView vertical scrolling position to adjust the frame of the menu bar.
Video: https://imgur.com/a/Rdu3wko
What would be the best way to implement such a behaviour?
If you want to use a UICollectionView, just grab the delegate, see which direction the user is scrolling, and hide/show the header as needed. Here's an example to get you started:
class ViewController: UIViewController {
// Variable to save the last scroll offset.
private var lastContentOffset: CGFloat = 0
private lazy var header: UIView = {
let header = UIView()
header.translatesAutoresizingMaskIntoConstraints = false
header.backgroundColor = .red
header.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
header.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
return header
}()
private lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.delegate = self
collectionView.backgroundColor = .white
collectionView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: 2000.0)
// Setting bounces to false - otherwise the header will disappear when we go past the top and are sprung back.
collectionView.bounces = false
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(collectionView)
collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
collectionView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: 2000.0)
// Make sure you either add the header subview last, or call self.view.bringSubviewToFront(header)
self.view.addSubview(header)
// Constrain the header so it's just sitting on top of the view. To make it visible, we'll use a transform.
header.bottomAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
header.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
// Header starts visible.
header.layoutIfNeeded()
self.header.transform = CGAffineTransform(translationX: 0.0, y: header.frame.height)
}
func revealHeader() {
// Set the duration below to how quickly you want header to appear/disappear.
UIView.animate(withDuration: 0.3) {
self.header.transform = CGAffineTransform(translationX: 0.0, y: self.header.frame.height)
}
}
func hideHeader() {
UIView.animate(withDuration: 0.3) {
self.header.transform = .identity
}
}
}
extension ViewController: UICollectionViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (lastContentOffset > scrollView.contentOffset.y) {
// Scrolled up: reveal header.
revealHeader()
}
else if (lastContentOffset < scrollView.contentOffset.y) {
// Scrolled down: reveal header.
hideHeader()
}
lastContentOffset = scrollView.contentOffset.y
}
}
EDIT: Noticed the functionality of the Reddit header is a bit different. If you want the thing to scroll dynamically (i.e. by the amount you have scrolled down by as opposed to appear all at once) replace that delegate function with this:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (lastContentOffset > scrollView.contentOffset.y) {
// Scrolled up: reveal header.
let difference = lastContentOffset - scrollView.contentOffset.y
if header.transform.ty < (header.frame.height - difference) {
// Header hasn't been fully revealed yet, bring it down by the amount we've scrolled up.
self.header.transform = CGAffineTransform(translationX: 0.0, y: header.transform.ty + difference)
} else {
self.header.transform = CGAffineTransform(translationX: 0.0, y: header.frame.height)
}
}
else if (lastContentOffset < scrollView.contentOffset.y) {
// Scrolled down: reveal header.
let difference = scrollView.contentOffset.y - lastContentOffset
if header.transform.ty > difference {
self.header.transform = CGAffineTransform(translationX: 0.0, y: header.transform.ty - difference)
} else {
self.header.transform = CGAffineTransform(translationX: 0.0, y: 0.0)
}
}
lastContentOffset = scrollView.contentOffset.y
}
This functionality is possible in UITableView set parallax header otherwise UIScrollView parallax animation.
I made a UICollectionView with a horizontal scroll.
I want to scroll only one direction i.e right to left my cell view size is as full view. once I scrolling cell, it should not scroll left to right.
Please Try this,
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let row = scrollView.contentOffset.x / cellWidth
currentIndexShown = Int(row)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x < cellWidth * CGFloat(currentIndexShown){
scrollView.contentOffset = CGPoint(x: cellWidth * CGFloat(currentIndexShown), y: -20)
scrollView.bounces = false
} else {
scrollView.bounces = true
}
}
I made an UIScrollView in a XIB for my onboarding. The UIScrollView has 3 onboarding views. Long story short:
This works perfect. However I want the top left and right buttons (Overslaan - Volgende) to animate up / off the screen when the third/last page is on screen. My UIScrollView starts behaving weird when I animate the buttons off:
This is the code im using:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageIndex = Int(targetContentOffset.pointee.x / self.frame.width)
pageControl.currentPage = pageIndex
if stepViews[pageIndex] is OnboardingLoginView {
moveControlConstraintsOffScreen()
} else {
moveControlConstraintsOnScreen()
}
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
}
I debugged the code and it turns out that setting a new constant for the constraints causes the issue, regardless of the animation block. How do I make the buttons move up/off the screen without my scrollView behaving weird?
It looks like triggering a layout pass is interfering with your scroll view positioning. You could implement func scrollViewDidScroll(_ scrollView: UIScrollView) and try to update the button view on a per-frame basis, without changing Auto Layout constraints. I usually use the transform property for frame changes outside of Auto Layout, since any changes to frame or bounds are overwritten during the next layout pass.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard mustBeOnLastPage else {
buttonOne.tranform = .identity
buttonTwo.tranform = .identity
return
}
let offset = scrollView.contentOffset.x
buttonOne.tranform = .init(translationX: 0, y: offset)
buttonTwo.tranform = .init(translationX: 0, y: offset)
}
Old answer
This interpretation is a bit of tangent of what you're asking for.
Since it looks like you're using the scroll view in a paging context, I would approach this problem by using UIPageViewController. Since UIPageViewController uses UIScrollView internally, you can observe the contentOffset of the last view in the scroll view to determine how far along the page has scrolled.
Yes, this involves looking inside the view hierarchy, but Apple hasn't changed it for half a decade so you should be safe. Coincidentally, I actually implemented this approach last week and it works like a charm.
If you're interested, I can further expand on this topic. The post that pointed me in the right direction can be found here.
Here is what i do
I can't post image,you can look at this http://i.imgur.com/U7FHoMu.gif
and this is the code
var centerYConstraint: Constraint!
func setupConstraint() {
fadeView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
centerYConstraint = make.centerY.equalToSuperview().constraint
make.size.equalTo(CGSize(width: 50, height: 50))
}
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint,targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageIndex = Int(targetContentOffset.pointee.x / self.view.frame.width)
if pageIndex != 1 {
centerYConstraint.update(offset: self.view.frame.height)
} else {
centerYConstraint.update(offset: 0)
}
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
Based on #CloakedEddy's answer I made 2 changes:
1: It seems layoutSubviews is responsible for the weird behaviour. To fix this I prevent the scrollView from calling layoutSubviews all the time:
override func layoutSubviews() {
super.layoutSubviews()
if !didLayoutSubviews {
for index in 0..<stepViews.count {
let page: UIView = stepViews[index]
let xPosition = scrollView.frame.width * CGFloat(index)
page.frame = CGRect(x: xPosition, y: 0, width: scrollView.bounds.width, height: scrollView.frame.height)
scrollView.contentSize.width = scrollView.frame.width * CGFloat(index + 1)
}
didLayoutSubviews = true
}
}
2: If you want to update your views for device orientations:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
onboardingView.didLayoutSubviews = false
onboardingView.setNeedsLayout()
}
I'm trying to zoom my image view as I scroll the scrollView past the top of the screen. Here's my code:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
if (offset <= 0) {
let ratio: CGFloat = -offset*1.0 / UIScreen.main.bounds.height
self.coverImageView.transform = CGAffineTransform(scaleX: 1.0 + ratio, y: 1.0 + ratio)
}
}
This zooms the image as I scroll up, but because I am also scrolling up, my view goes down, and reveals the white background behind the image as it expands. How do I prevent that from happening?
It sounds like a scroll view is not really suited to what you are trying to do. How about using a gesture recognizer instead? Something along these lines:
override func viewDidLoad() {
super.viewDidLoad()
coverImageView.isUserInteractionEnabled = true
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan))
coverImageView.addGestureRecognizer(panGestureRecognizer)
}
func didPan(panGestureRecognizer: UIPanGestureRecognizer) {
let translation = panGestureRecognizer.translation(in: coverImageView)
if translation.y > 0 {
let zoomRatio = (translation.y * 0.1) + 1.0
coverImageView.transform = CGAffineTransform(scaleX: zoomRatio, y: zoomRatio)
}
}
You'll have to play around to get it to behave exactly how you want, but it should be enough to get you started.