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.
Related
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".
I have a horizontal UIScrollView with paging enabled containing 3 view controllers. The middle view contains touch handling that sometimes gets interpreted as scrolling and runs the user experience. I have navigation buttons for moving to page 1 and 3 but want to preserve the scrolling abilities when in page 1 and 3 since they don't have multitouch/complex touch behaviors.
You can achieve this by implement the delegate method func scrollViewDidScroll(_ scrollView: UIScrollView)
here are some example code in Swift:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x == view.frame.width {
scrollView.isScrollEnabled = false
} else {
scrollView.isScrollEnabled = true
}
}
How can I get the length of scroll by dragging in a tableview, I want to execute a function inside scrollViewWillBeginDragging but only when the user scroll long, not when the user just touch the screen and the scroll bar moves by 1 mm, I need to know how match the scroll bar move so I can put a condition on that
scrollViewWillBeginDragging only fires once at the start of the gesture. scrollViewDidScroll continuously gets called as the user scroll. Just store the initial content offset of your tableview on scrollViewWillBeginDragging and check with the current offset in scrollViewDidScroll and perform you codes once there is enough movement.
var currentPosition : CGFloat!
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
currentPosition = tableView.contentOffset.y
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
let amoutOfScroll = abs(tableView.contentOffset.y - currentPosition) // abs return absulote value, just to avoid getting nagative values
let scrollLenght = 25 // the length of scroling I need
if amoutOfScroll > scrollLenght {
// code
}
}
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.