How to get the length of a scroll move - ios

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
}
}

Related

How do I control the whether a function will happen depending on the speed that a tableView is scrolled

I want an action to occur if a user scrolls down at any speed, or if a user scrolls up at a fast speed (or reaches the top of the view controller). I am using the following code below to sense any movement and implement the function where the "did move up" and "did move down" comments are, but I want to limit the did move up to occur only when scrolling fast or the user reached the top of the tableView. How do I do this?
// we set a variable to hold the contentOffSet before scroll view scrolls
var lastContentOffset: CGFloat = 0
// this delegate is called when the scrollView (i.e your UITableView) will start scrolling
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.lastContentOffset = scrollView.contentOffset.y
}
// while scrolling this delegate is being called so you may now check which direction your scrollView is being scrolled to
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.lastContentOffset < scrollView.contentOffset.y {
// did move up
// I want this to only occur is the user is scrolling fast
} else if self.lastContentOffset > scrollView.contentOffset.y {
// did move down
} else {
// didn't move
}
}
I suppose you can store the time when scrollViewWillBeginDragging is called and then measure up the time difference against a certain amount of time in scrollViewDidScroll
var timeScrollingBegan: Date?
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// ...
timeScrollingBegan = Date()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.lastContentOffset < scrollView.contentOffset.y {
// Calculate time difference in milliseconds
let timeDifference = Date().timeIntervalSince(timeScrollingBegan) * 1000
let movementDifference = scrollView.contentOffset.y - lastContentOffset
// If the movement difference is past a certain threshold
// in a certain amount of time, then it is too fast. It will
// take a bit of trial and error to determine the correct threshold
}
// ...
}
// You can probably use scrollViewDidEndDecelerating here instead
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
timeScrollingBegan = nil
}

Two simultaneous scroll views behaving weirdly

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.

Move SubView of ChildViewController using scrollViewDidScroll

I am using a viewController to handle two ChildViewControllers, each containing a UITableView. Would it be possible to set the the position y of a SubView of viewController (e.g. a UILabel) depending on the scrollView.contentOffset of the current ChildViewController?
It works fine with its own subviews already,..
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.testConstt.constant = scrollView.contentOffset.y
}
Thanks for helping!
Just observe the correct scroll view using a conditional statement. I assume the scroll views of the children are table views, so you may do something like this:
let tableViewA = UITableView(...)
let tableViewB = UITableView(...)
let someScrollView = UIScrollView()
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == tableViewA {
// observe a specific table view's scroll view and do something
} else if scrollView == someScrollView {
// observe a specific scroll view and do something
}
}
Remember, UITableView is a subclass of UIScrollView so they can be treated the same in scrollViewDidScroll(_ scrollView:).

UIScrollView recursion

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".

Swift: How to anchor view to the top when scrolling

I have an UIView in ScrollView superview. I want this UIView to stick to the top and stays there when users scrolls down.
Note that it should start in the middle of a screen and go up respectively when scrolling down.
I've seen many questions and answers but none of them solved my problem
iOS: Add subview with a fix position on screen
Simple way to change the position of UIView?
My code in scrollViewDidScroll method
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > anchor.frame.origin.y {
var fixedFrame:CGRect = self.anchor.frame
fixedFrame.origin.y = (self.navigationController?.navigationBar.frame.height)!
self.anchor.frame = fixedFrame
}
}
you can do this by saving a initial offset value of Y for your anchor view, then compare it in the scrollview delegate event.
self.initialOffSet = self.anchor.frame.origin.y
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var newFrame = self.anchor.frame
newFrame.origin.y = max(self.initialOffSet!, scrollView.contentOffset.y)
self.anchor.frame = newFrame
}
Why dont you put that view out of your scrollview?It will stay at the top only even when you will be scrolling your scrollview?

Resources