So, I have a infinite scrolling tableView of same size cells. Each cell has just a single image and what I'm trying to achieve is that as soon as a scroll flick happens, I'm trying to determine at what indexPath it will arrive at and preload Images for that index only.
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let contentOffset = CGPoint(x: targetContentOffset.pointee.x, y: targetContentOffset.pointee.y)
let totalRowsVisible = Int(tableView.bounds.height / rowHeight) + 1
let rowNumber = tableView.indexPathForRow(at: contentOffset)?.row ?? Int((contentOffset.y + offSetParameter) / rowHeight)
}
Right now this is what I'm using to determine the indexPath of tableview as soon as dragging ends but this is giving me incorrect results many times (especially when I scroll really fast).
Also when I add more rows to the tableView (at the bottom), the contentOffset changes weirdly and targetContentOffset Parameter is loaded somewhere else.
I have not found any solution anywhere.
EDIT: So, in the comments people have asked my specific requirement. The requirement exactly is as stated, that whenever a scroll begins and dragging ends, I have to determine what destination the scroll will end at and begin fetching content for that row before reaching the destination. So no matter how fast or slow the scroll occurs I just download images for the indexPath
Related
I’m trying to implement a custom top bar that behaves similarly to the iOS 11+ large title navigation bar, where the large title section of the bar collapses when scrolling down the content:
The difference is that my bar needs a custom height and also a bottom section that doesn’t collapse when scrolled. I managed to get that part working:
The bar is implemented using a UIStackView & with some non-required layout constraints, but I believe its internal implementation is not relevant. The most important thing is that the height of the bar is tied to scrollview's top contentInset. These are driven by scrollview's contentOffset in UIScrollViewDelegate.scrollViewDidScroll method:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let topInset = (-scrollView.contentOffset.y).limitedBy(topBarHeightRange)
// changes both contentInset and scrollIndicatorInsets
adjustTopContentInset(topInset)
// changes top bar height
heightConstraint?.constant = topInset
adjustSmallTitleAlpha()
}
topBarHeightRange stores the minimum and maximum bar height
One thing that I'm having a problem with is that when the user stops scrolling the scrollview, it's possible that the bar will end up in a semi-collapsed state. Again, let's look at the desired behavior:
Content offset is snapped to either the compact or expanded height, whichever is "closer". I'm trying to achieve the same in UIScrollViewDelegate.scrollViewWillEndDragging method:
func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let targetY = targetContentOffset.pointee.y
// snaps to a "closer" value
let snappedTargetY = targetY.snappedTo([topBarHeightRange.lowerBound, topBarHeightRange.upperBound].map(-))
targetContentOffset.pointee.y = snappedTargetY
print("Snapped: \(targetY) -> \(snappedTargetY)")
}
The effect is far from perfect:
When I look at the printout it shows that the targetContentOffset is modified correctly. However, visually in the app the content offset is snapped only to the compact height but not to the expanded height (you can observe that the large "Title" label ends up being cut in half instead of back to the "expanded" position.
I suspect this issue has something to do with changing the contentInset.top while the user is scrolling, but I can't figure out how to fix this behavior.
It's a bit hard to explain the problem, so I hope the GIFs help. Here's the repo: https://github.com/AleksanderMaj/ScrollView
Any ideas how to make the scrollview/bar combo snap to compact/expanded height properly?
I took a look at your project and liked your implementation.
I came up with a solution in your scrollViewWillEndDragging method by adding the following code at the end of method:
if abs(targetY) < abs(snappedTargetY) {
scrollView.setContentOffset(CGPoint(x: 0, y: snappedTargetY), animated: true)
}
Basically, if the scroll down amount is not worth hiding the large title (it happens if targetY is less than snappedTargetY) then just scroll to value of snappedTargetY to show the large title back.
Seems to be working for now, but let me know if you encounter any bugs or find a way to improve.
Whole scrollViewWillEndDragging method:
func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let targetY = targetContentOffset.pointee.y
// snaps to a "closer" value
let snappedTargetY = targetY.snappedTo([topBarHeightRange.lowerBound, topBarHeightRange.upperBound].map(-))
targetContentOffset.pointee.y = snappedTargetY
if abs(targetY) < abs(snappedTargetY) {
scrollView.setContentOffset(CGPoint(x: 0, y: snappedTargetY), animated: true)
}
print("Snapped: \(targetY) -> \(snappedTargetY)")
}
In my UIViewController I have a UICollectionView. The delegate is set properly. It just works fine. isPagingEnabled is set to true. But now I want to change the paging-positions I tried it within scrollViewWillEndDragging, because in the documentation it says:
Your application can change the value of the targetContentOffset parameter to adjust where the scrollview finishes its scrolling animation.
This functions is called properly but the only thing happens when I want to set a new Endpoint, the UICollectionView scrolls to 0.
This is my code:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
print(scrollView.contentOffset, "actual Offset")
print(targetContentOffset.pointee, "future offset")
targetContentOffset.pointee = CGPoint(x: 100, y: 0)
print(targetContentOffset.pointee, "new future offset")
}
At the print("new future offset"), it prints the right value. So it seems the value is mutated after this function.
func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint,
withScrollingVelocity velocity: CGPoint) -> CGPoint
Description:
If you want the scrolling behavior to snap to specific boundaries, you
can override this method and use it to change the point at which to
stop. For example, you might use this method to always stop scrolling
on a boundary between items, as opposed to stopping in the middle of
an item.
Override this layout method instead of directly changing value of targetContentOffset.
If you want the scrolling behavior to snap to specific boundaries, you can override this method and use it to change the point at which to stop. For example, you might use this method to always stop scrolling on a boundary between items, as opposed to stopping in the middle of an item.
Docs:
https://developer.apple.com/documentation/uikit/uicollectionviewlayout/1617729-targetcontentoffset
I have a table view which I get the data from server. with the code bellow, I detect the bottom of scrollview and with loadMore() I get my data from server again!
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// UITableView only moves in one direction, y axis
let currentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
// Change 10.0 to adjust the distance from bottom
if maximumOffset - currentOffset <= 200.0 {
self.loadMore()
}
}
my problem is when I uncheck the bounce on Scroll off and my datas height are less than the view, I can't load more data anymore! but when the bounce is on, its working perfectly!
how can I solve this issue?!
can I set bounce only for the top of the view?!
or is there any other way to load data, with bounce uncheck?
I guess since you have less data than the actual height of the view, it should not load the data as well.
In case the data is updated dynamically and there is a chance the more data can be populated, it must be done through pull to refresh.
Currently tableview cells look like this: current cell image When my app first opens it looks like the picture on the left and when I scroll one page down it looks like the picture on the right.
However I want it to look like a carousel: like this
I want it so when it I scroll (paging enabled) the bottom cell comes to middle cell spot, and the new bottom cell is at the old bottom cell spot, and the middle cell is at the top cell spot.
Possible Solutions/Questions:
How would I make the UITableViewCells infinitely loop, and thus start off with the ability to scroll up and down, since the last cell would be above the first cell to begin with. Right now there is no cell above the first one, which poses a problem.
CODE:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
targetContentOffset.pointee.y = self.tableView.contentOffset.y + 100
}
I want to basically snap the bottom card/ top card that is swiping to the middle of the screen.
So I have a collection view horizontal and I want when the user scroll to set the scroll where I want.
#IBOutlet var joke_cards: UICollectionView!
extension ViewController: UIScrollViewDelegate{
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
joke_cards.contentOffset.x = scrollView.contentOffset.x + 1000
joke_cards.reloadData()
}
}
But this doesn't work, it scrolls normally, I want to specify how much to scroll, any suggestions?
So I think I need to be a bit more clear, what I want is to flip through some cards horizontally thats why I need when the user stars to swipe to show the next cell in the middle
If you want to manually scroll the user to a certain area you need first to define the area you need to scroll into view. This will depend a little bit on where exactly you are trying to scroll, but if the goal is just to scroll 1000 points to the right you can define the rect and scrolling like so:
let destinationRect = CGRect(x: scrollView.contentOffset.x + 1000, y: scrollView.contentOffset.y, width: 1, height: 1)
scrollView.scrollRectToVisible(destinationRect, animated: true)
Please note that scrolling will stop as soon as any part of the rect is visible, so if you want contentOffset.x + 1000 to be in the center you will need to do some more math to create the destinationRect.
The other option, since you are using a UICollectionView is to figure out which cell is at the point you want to scroll to, and scroll that cell to a certain position. In this example I safely unwrap the optional indexPath at the point you specified, and scroll that cell to be centered horizontally in the collectionView:
if let indexPath = self.joke_cards.indexPathForItem(at: CGPoint(x: scrollView.contentOffset.x, y: scrollView.frame.midY)) {
self.joke_cards.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}