I want to stop backward scrolling on ScrollView after user scrolls to the next page. How can I do that.
I tried the following two codes, but the first one does not have any effect
self.scrollView.contentOffset = CGPointMake(self.view.frame.width,0)
and the second only disables the forward scrolling.
self.scrollView.contentSize = CGSizeMake( 2 * scrollWidth, scrollHeight);
To disable scrolling in one direction you implement the UIScrollViewDelegate method scrollViewDidScroll and put your logic there. For instance this TableViewController can only ever scroll down, because if the user tries to scroll up, we just overwrite the contentOffset, effectively undoing their scroll before they see it.
class ViewController: UITableViewController {
var lastScrollPosition = CGPoint.zero
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.y > lastScrollPosition.y else {
scrollView.setContentOffset(lastScrollPosition, animated: false)
return
}
lastScrollPosition = scrollView.contentOffset
}
}
If your cell is equal in size to your screen, you can apply the following option, which is very smooth:
var lastScrollPosition = CGPoint.zero
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x == lastScrollPosition.x + UIScreen.main.bounds.width {
lastScrollPosition.x += UIScreen.main.bounds.width
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.x > lastScrollPosition.x else {
scrollView.setContentOffset(lastScrollPosition, animated: false)
return
}
}
Related
I have a UIScrollView which scrolls automatically by setting its content offset within via a UIViewPropertyAnimator. The auto-scrolling is working as expected, however I also want to be able to interrupt the animation to scroll manually.
This seems to be one of the selling points of UIViewPropertyAnimator:
...dynamically modify your animations before they finish
However it doesn't seem to play nicely with scroll views (unless I'm doing something wrong here).
For the most part, it is working. When I scroll during animation, it pauses, then resumes once deceleration has ended. However, as I scroll towards the bottom, it rubber bands as if it is already at the end of the content (even if it is nowhere near). This is not an issue while scrolling towards the top.
Having noticed this, I checked the value of scrollView.contentOffset and it seems that it is stuck at the maximum value + the rubber banding offset. I found this question/answer which seems to be indicate this could be a bug with UIViewPropertyAnimator.
My code is as follows:
private var maxYOffset: CGFloat = .zero
private var interruptedFraction: CGFloat = .zero
override func layoutSubviews() {
super.layoutSubviews()
self.maxYOffset = self.scrollView.contentSize.height - self.scrollView.frame.height
}
private func scrollToEnd() {
let maxOffset = CGPoint(x: .zero, y: self.maxYOffset)
let duration = (Double(self.script.wordCount) / Double(self.viewModel.wordsPerMinute)) * 60.0
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.scrollView.contentOffset = maxOffset
}
animator.startAnimation()
self.scrollAnimator = animator
}
extension UIAutoScrollView: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// A user initiated pan gesture will begin scrolling.
if let scrollAnimator = self.scrollAnimator, self.viewModel.isScrolling {
self.interruptedFraction = scrollAnimator.fractionComplete
scrollAnimator.pauseAnimation()
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if let scrollAnimator = self.scrollAnimator, self.viewModel.isScrolling {
scrollAnimator.startAnimation()
}
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if let scrollAnimator = self.scrollAnimator, self.viewModel.isScrolling {
scrollAnimator.startAnimation()
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
switch scrollView.panGestureRecognizer.state {
case .changed:
// A user initiated pan gesture triggered scrolling.
if let scrollAnimator = self.scrollAnimator {
let fraction = (scrollView.contentOffset.y - self.maxYOffset) / self.maxYOffset
let boundedFraction = min(max(.zero, fraction), 1)
scrollAnimator.fractionComplete = boundedFraction + self.interruptedFraction
}
default:
break
}
}
}
Is there anywhere obvious I'm going wrong here? Or any workarounds I can employ to make the scroll view stop rubber banding on scroll downwards?
You can add tap Gesture Recognizer and call this function,
extension UIScrollView {
func stopDecelerating() {
let contentOffset = self.contentOffset
self.setContentOffset(contentOffset, animated: false)
}
}
I have a chat app and I'm trying to show a custom view that I've made when the user scrolls to the top, also hide it if it's on the bottom of tableview. (like whatsapp does it)
To be honest I'm struggling with the logic of show/hide button.
Tried to save the contentOffset.y of my tableview right after I reload the data so I'll know that's the bottom, and if it's smaller to show the custom view, but mainTableView.contentOffset.y it's always 0.
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if (scrollView == mainTableView) {
print(mainTableView.contentOffset.y)
if let point = startingPointForView {
//where var startingPointForView: CGFloat?
// and tried to save it after I reload the data
//self.startingPointForView = self.mainTableView.contentOffset.y
// but it's always 0
}
// Show and hide button logic
}
}
An image of what I m trying to achieve: https://imgur.com/ZkYEi2P
try this code to hide/show custom view according to UIscrollview contentOffset
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scrollViewContentHeight = scrollView.contentSize.height
let scrollViewHeight = scrollView.frame.height
if scrollView.contentOffset.y < (scrollViewContentHeight - scrollViewHeight){
//Custom view show
}else{
//Custom view Hide
}
}
May be this code will help you
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView).y > 0 {
// down
button.isHidden = false
} else {
// up
button.isHidden = true
}
}
For someone who is looking to hide a button when tableview is scrolling can use below code:
var previousContentOffset: CGFloat = CGFloat()
extension YourViewController: UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.yourTableView{
let currentContentOffset = scrollView.contentOffset.y
if (currentContentOffset > previousContentOffset) {
// scrolling towards the bottom
if scrollView.contentOffset.y > 50 {
self.yourButton.isHidden = true
} else {
self.yourButton.isHidden = false
}
} else if (currentContentOffset < previousContentOffset) {
// scrolling towards the top
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
// Change 10.0 to adjust the distance from bottom
if maximumOffset - currentContentOffset <= 10.0 {
self.yourButton.isHidden = true
} else {
self.yourButton.isHidden = false
}
}
previousContentOffset = currentContentOffset
}
}
}
I have a view, with four scrollviews inside of it. What I'd like to do is make all these scrollviews respond when one of them is scrolled. I tried to do that through this code:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.hpcScrollView.setContentOffset(scrollView.contentOffset, animated: true)
self.parScrollView.setContentOffset(scrollView.contentOffset, animated: true)
self.scoreScrollView.setContentOffset(scrollView.contentOffset, animated: true)
self.holeScrollView.setContentOffset(scrollView.contentOffset, animated: true)
}
however, when I apply this, the scrolling "behavior" is taken away. It does still scroll, but there's no bounce, and when I take my finger off the scrollview, it immediately stops scrolling (see gif below)
Is there a way to make this feel like normal scrolling?
Do not apply the contentOffset to the scrollView which is currently scrolling. Changing the contentOffset on a currently scrolling view would cause it to stop scrolling and immediately jump to the offset.
let scrollableViews = [hpcScrollView, parScrollView, scoreScrollView, holeScrollView]
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollableViews.forEach { if $0 != scrollView { $0.setContentOffset(scrollView.contentOffset, animated: true) } }
}
EDIT:
The scrollViewDidScroll is getting called whenever we're setting the content offset, which causes the weird scrolling behaviors shown in the gif.
try this instead
let scrollableViews = [hpcScrollView, parScrollView, scoreScrollView, holeScrollView]
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollableViews.forEach { if $0 != scrollView {
let scrollBounds = $0.bounds
scrollBounds.origin = scrollView.contentOffset
$0.bounds = scrollBounds
} }
}
The problem is that you reset the originating scroll view, which makes it stop. You need to keep track of the current scroll view the user is interacting with and only update the other scroll views. Otherwise they will all generate scroll events and end up updating each other.
// currentScrollView is an object var
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
currentScrollView = scrollView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == currentScrollView {
if currentScrollView != self.hpcScrollView {
self.hpcScrollView.setContentOffset(scrollView.contentOffset, animated: false)
}
if currentScrollView != self.parScrollView {
self.parScrollView.setContentOffset(scrollView.contentOffset, animated: false)
}
if currentScrollView != self.scoreScrollView {
self.scoreScrollView.setContentOffset(scrollView.contentOffset, animated: false)
}
if currentScrollView != self.holeScrollView {
self.holeScrollView.setContentOffset(scrollView.contentOffset, animated: false)
}
}
}
Note that you must set the animated flags to false.
Right now I have a UICollectionView set up with horizontal paging. I have sized my cells to fit the screen so that there is only ever one visible(unless you are scrolling).
What I wish to do is get the index path of the cell that is in view once you have stopped scrolling. This is because other parts of the view are dependent on the information that is currently visible.
I have set up a function in the scrollViewWillBeginDecelerating function and I am close but I still don't seem to understand how it is grabbing the cells.
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
if( lastContentOffset > scrollView.contentOffset.x && lastPointVisited > 0)
{
lastPointVisited -= 1
print("Scrolling Right")
}
else if(lastContentOffset < scrollView.contentOffset.x && lastPointVisited < myPoints.count - 1)
{
lastPointVisited += 1
print("Scrolling Left")
}
lastContentOffset = scrollView.contentOffset.x
}
This gets me accurate results if I am very careful about scrolling. Going fast or only half scrolling a page so that it snaps back seems to throw everything off.
You can use following code to get page number
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = scrollView.frame.size.width
let page = Int(floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1)
print("Page Number : \(page)")
}
It works for me If I put the same code in these two delegates at same time
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//code here
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
//code here
}
instead of writing it in
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView)
It gives me the exact value of index whether it is scrolling fast or half scrolled.
Hope it helps!
try this,
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
let cellObj = collectionViewDemo.visibleCells[0]
print(collectionViewDemo.indexPath(for: cellObj))
}
You can use the below code it will be always effective to find the visible collectionView page
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = scrollview.frame.width
let pageIndex = scrollview.frame.width
let indexTemp = Int((scrollview.contentOffset.x +
pageWidth / 2) / pageWidth)
print(pageIndex)
}
I am trying to set the content offset in my uiscrollview. I have tried the following, and none of it works. None of the following affect my scrollView in any way, it simple appears on the screen as normal.:
let point = CGPoint(x: 0, y: self.view.frame.height / 2)
self.scrollView.setContentOffset(point, animated: true)
and:
self.scrollView.contentOffset.y = self.view.frame.size.height / 2
and:
self.scrollView.contentOffset = CGPointMake(0, self.view.frame.size.height / 2)
and:
self.scrollView.contentOffset = CGPointMake(0, scrollView.frame.size.height / 2)
I have a scroll view that is 2 times the height of my view. They are essentially separated into 2 different view that I can page between. I want the scrollview to start on the bottom part.
You should try them in the viewDidLayoutSubviews
Ref: iOS 9 scroll UITextView to top
I was really scratching my head when I found that setting scrollView.contentOffset to any value was not working. Eventually I realized that I was calling the scrollView delegate message 'scrollViewDidScroll' to manage scroll dragging peculiarities.
It turns out that 'scrollViewDidScroll' is called whenever the scrollView contentOffset is changed, and my code to manage drag issues was changing the contentOffset. I just made a 'BOOL isDragging' variable to only make changes to the contentOffset when dragging:
private var isDragging : Bool = false
private var initialContentOffset = CGPoint.zero
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
isDragging = true
...
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
// need to keep track if we are dragging with isDragging
// since this 'scrollViewDidScroll' API is called with ANY contentOffset change
if (isDragging == true){
// do stuff to contentOffset to manage dragging peculiarities
...
}
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if (decelerate == false){
scrollViewDidEndScrollingAnimation(scrollView)
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollViewDidEndScrollingAnimation(scrollView)
}
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
isDragging = false
}