Its normal that no one wants to scroll unnecessarily once the scroll has reached its end. I have implemented the following code which works perfect when the scroll is stable at the top/bottom position.
func scrollViewDidScroll(scrollView: UIScrollView)
{
if scrollView.contentOffset.y <= -1
{
scrollView.scrollEnabled = false
self.scrollView.setContentOffset(CGPointMake(0, 0), animated: true)
scrollView.scrollEnabled = true
}
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height))
{
scrollView.scrollEnabled = false
scrollView.scrollEnabled = true
}
}
But if user drags rapidly to top or bottom, the scrolling immediately doesn't stop scrolling when it reaches to top/bottom, but rather takes some time to halt. Can we do something that scroll will halt immediately when scrolling reaches the ends and not accelerate further?
Related
I'm trying to implement a design like Twitter homepage on iOS, which can be simplified to something like the following:
Both scroll views receive gesture through the gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) method, which works well.
My scrolling logic is the following:
private func parentScrollViewDidScroll(_ scrollView: UIScrollView) {
if self.parentScrollViewLocked {
scrollView.contentOffset.y = threshold
self.childScrollViewLocked = false
return
}
if scrollView.contentOffset.y >= threshold {
scrollView.contentOffset.y = threshold
self.parentScrollViewLocked = true
self.childScrollViewLocked = false
}
}
private func childScrollViewDidScroll(_ scrollView: UIScrollView) {
if self.childScrollViewLocked {
scrollView.contentOffset.y = 0
} else if scrollView.contentOffset.y <= 0 {
self.childScrollViewLocked = true
self.parentScrollViewLocked = false
}
}
This actually works pretty well. The only problem though is when scrolling down slowly (but still with deceleration), there's a good chance the scroll ends at the threshold, without transitioning from parent scrolling to child scrolling smoothly. When scrolling fast enough, it's pretty smooth and can continue just fine.
What's intriguing is that when scrolling back up, no matter how slow I scroll I don't have any problem at all. Every time it's a smooth transition.
It turns out that for some reason, the child scroll view come to a stop signaled by scrollViewDidEndDecelerating(_ scrollView: UIScrollView) at the threshold - much earlier than the parent view. But the parent view is locked beyond the threshold so it doesn't move, and the child scroll view has already finished, so the scrolling will end there. When scrolling back up both scroll views behave normally, so my logic handles it fine.
What may be wrong here? Any pointer appreciated. Thank you!
I finally discovered the answer.
Basically, contentOffset = 0 is a magic number: at this value, the scroll view needs to pick up its velocity from 0 even if the gesture already has a velocity. Therefore, simply set the default offset to something like 1 will do:
private func childScrollViewDidScroll(_ scrollView: UIScrollView) {
if self.childScrollViewLocked {
scrollView.contentOffset.y = 1 // <- set it to 1 instead
} else if scrollView.contentOffset.y <= 1 {
self.childScrollViewLocked = true
self.parentScrollViewLocked = false
}
}
For perfectionists, set a corresponding content inset can offset this artificial offset (whelp!).
Now the two scroll views can hand off scrolling smoothly.
I wish to implement the animated hide and show like how the navigation bar hidesbaronswipe behaviour. I cant get any more information on the internet and I decided to consult here, kindly guide me on how to do that. Thanks.
P.S. I did the animate whole view by changing the height constant of the view but the whole animation is not smooth enough.
the code that I tried :
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if currentOffset > scrollView.contentOffset.y {
for button in topButton{
button.isHidden = false
}
self.topbuttonStackHeight.constant = 50
topButtonStack.isHidden = false
}
else if currentOffset < scrollView.contentOffset.y - 50{
self.topbuttonStackHeight.constant = 0
self.topButtonStack.isHidden = true
}
}
In the UIScrollViewDelegate scrollViewDidScroll(UIScrollView) function, if you change the frame of the delegate's UIScrollView, then scrollViewDidScroll(UIScrollView) gets called again. A common example of this is when you have a header that shrinks as the user scrolls up and expands as the user scrolls down.
Aside from an implementation in which you unset and reset the scrollView's delegate property like below, are there any ways to prevent recursion? The UIScrollView object has isTracking and isDragging properties that overlap, and also remain true in the below example.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let d = scrollView.delegate // Prevent recursion by setting the
scrollView.delegate = nil
// Do stuff
if someCondition == true {
scrollView.contentOffset = somePoint
scrollView.frame = someRect
}
scrollView.delegate = d // Restore the scrollView's delegate
}
NOTE: I DO NOT NEED A TUTORIAL FOR MAKING A COLLAPSIBLE HEADER, NOR DO I NEED A SIMPLE IF CHECK. I'm only specifically asking what options are available for breaking out of scrolling events based on their origin. For example, you could put this at the start of scrollViewDidScroll:
if(!scrollView.isTracking && !scrollView.isDragging) {
// Break out if we got here through anything but a touch event
return
}
I just tried it, and setting .contentOffset does not cause recursion.
As you drag the content, the delegate receives continuous scrollViewDidScroll messages... when you call:
scrollView.contentOffset = CGPoint(x: 0, y: 0)
you are still dragging, and the content keeps getting reset to 0,0.
Change your function to this:
var i = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
i += 1
print("did scroll", i)
scrollView.contentOffset = CGPoint.zero
}
and you will see it stops counting when you stop dragging - so it is not recursive.
Perhaps you really want to use scrollViewDidEndDragging or scrollViewDidEndDecelerating?
Edit:
Perhaps this will work better for you. Keep in mind, as long as you continue to drag inside the scroll view, scrollViewDidScroll() will continue to be called.
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
print("did end dragging", scrollView.contentOffset)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("did scroll", scrollView.contentOffset)
scrollView.setContentOffset(CGPoint.zero, animated: false)
}
If you look at the console output, you should never see more than 2 print()s after you "stop dragging".
There are two scrollView in the View: mainScrollView(red) and listTable(yellow). I want to set that mainScrollView scrolls first and does the following actions:
When the topView disappears and top of the listTable reaches top of the screen, the listTable scrolls up automatically.
If the top of listTable is at the top of screen, the mainScrollView scroll down first when user scrolls down.
func detectScrollView(_ scrollView: UIScrollView){
if scrollView.tag == 1{
if scrollView.contentOffset.y >= 100{
mainScrollView.isScrollEnabled = false
mainScrollView.contentOffset.y = 100
mainScrollView.panGestureRecognizer.isEnabled = false
listTable.isScrollEnabled = true
listTable.panGestureRecognizer.isEnabled = true
}
}else{
//.....
}
}
By now, I just try the codes of scrolling up action, and it can runs part of my need. Problem is that the listTable cannot scrolls automatically when it's top reaches top of the screen. I have to make it works by releasing my finger from the screen and starting a new drag.
How can I continue the panGesture using on the listTable?
======= Update 2017-7-3 =======
I temporarily use the following codes:
var lastContentOffset:CGFloat = 0
func detectScrollView(){
if (self.lastContentOffset > mainScrollView.contentOffset.y) {
// move up
listTable.isScrollEnabled = true
}
else if (self.lastContentOffset < mainScrollView.contentOffset.y) {
// move down
if mainScrollView.contentOffset.y >= 110{
mainScrollView.setContentOffset(CGPoint(x:0, y:110), animated: true)
listTable.isScrollEnabled = true
}else{
listTable.isScrollEnabled = false
}
}
// update the new position acquired
self.lastContentOffset = mainScrollView.contentOffset.y
}
Moving up is solved, and still moving down needs retouching the screen so as to scroll the listTable.
I'm trying to disable scrolling in a UICollectionView when the contentOffset is smaller or equal to zero.
While the scroll is happening.
Code:
extension MyController: UIScrollViewDelegate {
func scrollViewDidScroll(scrollView: UIScrollView) {
print("scrollView.contentOffset: \(scrollView.contentOffset)")
if scrollView.contentOffset.y <= 0 {
scrollView.userInteractionEnabled = false
} else {
scrollView.userInteractionEnabled = true
}
}
}
this only disables the interaction when the user stops scrolling.
How can i disable the interaction while the scroll is happening?
Thanks
I don't think you can do it that way.
You should look at the UIScrollViewDelegate : https://developer.apple.com/library/prerelease/ios//documentation/UIKit/Reference/UIScrollViewDelegate_Protocol/index.html#//apple_ref/occ/intfm/UIScrollViewDelegate and maybe try to set the contentOffset of your collectionview back to 0 when the user tries to scroll too far.