How to change UIScrollView with isPagingEnabled autoscroll drag trashhold - ios

I have horizontal UIScrollView with isPagingEnabled = true containing few pages. If I drag scrollview content and release finger it scrolls to next page only if I dragged at least 50% of scrollview width. I want to auto scroll to next/previous page if drag distance is more than 25%.
I was able to achieve it by overriding scrollViewDidEndDragging in UIScrollViewDelegate, calculating drag distance and calling scrollView.setContentOffset. But issue is that if distance > 25 and < 50 then it scrolls back automatically probably because scrollview calls it's default implementation.
Any idea how can I achieve this? Thanks

I would use the following delegate's callback by modifying offset to desired page
// called on finger up if the user dragged. velocity is in points/millisecond.
// targetContentOffset may be changed to adjust where the scroll view comes to rest
#available(iOS 5.0, *)
optional func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>)

Related

UITableView content offset of y position always return 0

In scrollview delegate method table content offset while scrolling always return 0 for it's y position.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(items:String(format:"Offset %d",scrollView.contentOffset.y))
}
I have also printed the myTableView.contentOffset.y
But it also prints the same result.
ContentOffset doesn't change when you scrolling. It's origin of your content in scrollView. It defines the point in the content view that is visible at the top left of the scroll view bounds. We can use this property to scroll programmatically .
There is more about it
https://developer.apple.com/documentation/uikit/uiscrollview/1619404-contentoffset
https://www.objc.io/issues/3-views/scroll-view/
Try to track contenView.bounds.origin.y for tracking y-position

UITableView tap not working first time after constraints change

IMPORTANT: My problem is not that I'm implementing didDeelectRowAt instead of didSelectRowAt. Already checked that :)
I have a UITableView that is shown on part of the screen in a modally presented view controller. When the user is dragging it resizes to full screen and back to some defined min height. I'm doing this by implementing the following methods from the UIScrollViewDelegate:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard !scrollView.isDecelerating else { return }
let contentOffset = scrollView.contentOffset.y
if tableViewHeightConstraint.constant < view.frame.height && contentOffset > 0.0 {
tableViewHeightConstraint.constant = min(contentOffset + tableViewHeightConstraint.constant, view.frame.height)
scrollView.contentOffset.y = 0.0
return
}
if tableViewHeightConstraint.constant > minViewHeight && contentOffset < 0.0 {
tableViewHeightConstraint.constant = max(tableViewHeightConstraint.constant + contentOffset, minViewHeight)
scrollView.contentOffset.y = 0.0
}
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Here I have some calculations if depending the dragging end position and the velocity the end size should be full screen or `minViewHeight`
// After calculating what the end size should be I'm animating the size change
heightConstraint.constant = newConstraintHeight
UIView.animate(withDuration: TimeInterval(calculatedAnimationDuration), delay: 0.0, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
Everything about the resizing and the scrolling works fine, but there is a problem that I cannot figure out why it's happening. It's the following:
When the view controller with the table view is shown for the first time with the min height and I tap on a cell it works fine.
If I drag to expand the table view to full screen height and tap on a cell, again it works fine.
If I drag to expand the table view to full screen height and then drag again to return it to the min height and then tap on a cell, nothing is happening, no UIScrollViewDelegate or UITableViewDelegate method is called at all. If I tap once more on a cell everything works fine.
One thing that I noticed is that after dragging the table view back to the min height the scroll indicator does not hide. On the first tap it hides, and on the second tap the didSelectRowAt is called.
UPDATE:
Here is a test repo for the problem: https://github.com/nikmin/DragTest
Please don't mind if the dragging doesn't work perfectly, I just put something so anyone can try it out, but I think the problem is easily reproducible.
Also one more thing... If you drag from full size all the way to the bottom so the table view reaches min height and you continue dragging so the content offset is < 0 and the you release, the problem is not happening.
Drag TableView to return it to the min height and then tap on a cell, nothing is happening because:
When you drag to expand the table view to full screen, scrollView.isDecelerating is true. So the code inside scrollViewDidScroll method will run.
But when you drag TableView to return it to the min height, scrollViewDidScroll is false. So the code inside scrollViewDidScroll method won't run. It's make the first tap do nothing.
Simply remove guard !scrollView.isDecelerating else { return } from scrollViewDidScroll. You will tap cell normally after drag TableView down.
But you need change logic a little, animation will go wrong after remove above line.
Hope it can help you ;)
After trying to figure out a solution to this without result, we (me and a UX designer) decided to change the behaviour a bit.
So in the real scenario in the app I'm implementing this in, the table view is inside another view that has also a title label and some other views above the table view. We decided to add a pan gesture recognizer to this root view and disable the scrolling of the table view when the view has the min size. This way the pan gesture recognizer will take over whenever the user tries to drag anywhere inside the view (including the table view), so the expanding of the view works. And the tap in the cell still works.
When the view has the max height the table view scroll is enabled so the user can scroll. The downside of this approach is that when the user scrolls to the top of the table view and continues scrolling the view will not decrease the size. But he still has the option to drag it down by dragging any of the views above the table view. When dragging down in this way, only the size of the table view changes, and the content offset isn't, which is the root of the problem (changing both at the same time).
It looks like incorrectly set content offset. The first touch cancels incorrect position(unscroll), this is why it is not registered. It might be better if we got an access to the full code to check it, because I can't tell you where exactly the problem lies, but I guess it is in method scrollViewDidScroll.

iOS Swift : ScrollView strict dragging

I have a scrollView(only horizontal scrolling) in which two collection views and want that if it is dragged/Swiped upto >= mid of screen then it should scroll forward i.e right side (show 2nd collection view) else it should be in left side(show first collection view). It should not stay half scrolled either full or none. How can i make this possible?
To achieve a scroll snap, you want to use this UIScrollViewDelegate method:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
You'll want to set the following within this method.
targetContentOffset.pointee.x =
Here's a tutorial link that may help explain.
Or just convert this ObjC SO answer to swift and change from vertical snapping to horizontal as it describes.

UICollectionView Scroll to next item

The idea is replacing PageController with UIScrollView, UIScrollView should only scroll to the next/previous item and stop. I've found a few solutions, but I've only understood that I've to use targetContentOffsetForProposedContentOffset, but have no idea what CGPoint should I return and how to stop on the next item even if user scrolls with high velocity.
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
return CGPoint(x: 0, y: 0)
}
Use below of code. Your problem will be solved.
YourScrollView.pagingEnabled = YES;
With UIScrollView, you can enable paging. The property is called pagingEnabled. This means if it will snap to full widths (or heights) providing you set the content size properly.
i.e. If you want 2 pages and have a width of 100 and height of 200. You set the content size to be CGSize 200 for width and 200 for height. The scroll view will handle the snapping and paging.
If you want to have a function to go to a page like goToPage(2). Then you just need to call scrollRectToVisible method in UIScrollView with a CGRect of (pageNumber * scrollView.width (or page width), 1 height)
It looks like you are using Swift, so your implementation may vary.

UIScrollView and PanGestureRecognizer

I’m have a view that contains a regular UIView and a UIScrollView. The UIView sits above the UIScrollView offscreen. The UIScrollView typically occupies the entire screen. (It should be noted that I’m not using Autolayout). When the user scrolls to the top of the scrollview content I would like the UIView to start appearing on the screen. And when it reaches a certain threshold have the UIView snap into place and occupy the screen.
My initial thought was to use the UIScrollView delegate method, and adjust the superview.frame.orgin.y value when the scrollview contentOffset.y value is negative.
func scrollViewDidScroll(scrollView: UIScrollView) {
pullDownInProgress = scrollView.contentOffset.y <= 0.0
if pullDownInProgress {
self.view. = (-self.view.height / 2) - scrollView.contentOffset.y
}
}
However, this creates a stretching between the UIView and the UIScrollView due the scrollview bounce setting. If I turn off the bounce setting then the scrollview.contentOffset is never less then zero, therefore my superview frame is never adjusted.
Any suggestions on how to accomplish this?
You don't need to change the superview.frame, instead move the offscreen view down by its height so that it can appears and any bouncing effect for the scroll view might be hidden by that view.Or you can even move both the scroll view and the offscreen view with the height of the offscreen view. It really depends whether your offscreen view is transparent or not

Resources