Delegate methods in UIScrollViewDelegate don't respond to table view scrolling event - ios

I'm trying to do pull to refresh and infinite scrolling to table view without the need for external libraries
I was setting the delegate correctly:
tableView.delegate = self
But when scrolling inside tableview, scroll methods do not respond to scrolling action for example:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if scrollView == self.tableView {
let currentOffset: Float = Float(scrollView.contentOffset.y)
if currentOffset < 25 {
//refresh content
}
let offsetY = tableView.contentOffset.y
let contentHeight = tableView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height + 25 {
// load more
}
}
}
This case happened when I changed the scheme build configuration to "Release", but when getting back to "Debug" it's working correctly.
My deployment target is: 11.0, and XCode Version 10.2 (10E125)

After a lot of search it might be a Swift 5 compiler problem (I found a similar bug reported there):
The solution is to add #objc to each method:
#objc func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if scrollView == self.tableView {
let currentOffset: Float = Float(scrollView.contentOffset.y)
if currentOffset < 25 {
//refresh content
}
let offsetY = tableView.contentOffset.y
let contentHeight = tableView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height + 25 {
// load more
}
}
}

Related

Swift -How to programmatically access collectionView's targetContentOffset.pointee.y

When a user swipes, I run some code in scrollViewWillEndDragging and I need the targetContentOffset.pointee.y to do it:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let yAxis = targetContentOffset.pointee.y
let visibleItem = Int(yAxis / collectionView.frame.height)
// do something with visibleItem based on cell's current vertical scroll position...
}
The issue is I also do some autoscrolling, where the user doesn't have to swipe and the cv auto scrolls. I still need access to the targetContentOffset.pointee.y so I can do something with with the visibleItem but I the method it's in doesn't trigger on autoscroll.
How can I programmatically access the targetContentOffset.pointee.y?
You can try
let index = IndexPath(item: xyz, section: 0)
collectionView.scrollToItem(at:index, at: .centeredVertically, animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if let cell = collectionView.cellForItem(at:index) {
let frame = collectionView.convert(cell.frame,nil)
}
}

Which page of UICollectionView is visible?

Right now I have a UICollectionView set up with horizontal paging. I have sized my cells to fit the screen so that there is only ever one visible(unless you are scrolling).
What I wish to do is get the index path of the cell that is in view once you have stopped scrolling. This is because other parts of the view are dependent on the information that is currently visible.
I have set up a function in the scrollViewWillBeginDecelerating function and I am close but I still don't seem to understand how it is grabbing the cells.
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
if( lastContentOffset > scrollView.contentOffset.x && lastPointVisited > 0)
{
lastPointVisited -= 1
print("Scrolling Right")
}
else if(lastContentOffset < scrollView.contentOffset.x && lastPointVisited < myPoints.count - 1)
{
lastPointVisited += 1
print("Scrolling Left")
}
lastContentOffset = scrollView.contentOffset.x
}
This gets me accurate results if I am very careful about scrolling. Going fast or only half scrolling a page so that it snaps back seems to throw everything off.
You can use following code to get page number
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = scrollView.frame.size.width
let page = Int(floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1)
print("Page Number : \(page)")
}
It works for me If I put the same code in these two delegates at same time
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//code here
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
//code here
}
instead of writing it in
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView)
It gives me the exact value of index whether it is scrolling fast or half scrolled.
Hope it helps!
try this,
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
let cellObj = collectionViewDemo.visibleCells[0]
print(collectionViewDemo.indexPath(for: cellObj))
}
You can use the below code it will be always effective to find the visible collectionView page
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = scrollview.frame.width
let pageIndex = scrollview.frame.width
let indexTemp = Int((scrollview.contentOffset.x +
pageWidth / 2) / pageWidth)
print(pageIndex)
}

Easiest way to disable back scrolling in scrollview

I want to stop backward scrolling on ScrollView after user scrolls to the next page. How can I do that.
I tried the following two codes, but the first one does not have any effect
self.scrollView.contentOffset = CGPointMake(self.view.frame.width,0)
and the second only disables the forward scrolling.
self.scrollView.contentSize = CGSizeMake( 2 * scrollWidth, scrollHeight);
To disable scrolling in one direction you implement the UIScrollViewDelegate method scrollViewDidScroll and put your logic there. For instance this TableViewController can only ever scroll down, because if the user tries to scroll up, we just overwrite the contentOffset, effectively undoing their scroll before they see it.
class ViewController: UITableViewController {
var lastScrollPosition = CGPoint.zero
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.y > lastScrollPosition.y else {
scrollView.setContentOffset(lastScrollPosition, animated: false)
return
}
lastScrollPosition = scrollView.contentOffset
}
}
If your cell is equal in size to your screen, you can apply the following option, which is very smooth:
var lastScrollPosition = CGPoint.zero
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x == lastScrollPosition.x + UIScreen.main.bounds.width {
lastScrollPosition.x += UIScreen.main.bounds.width
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.x > lastScrollPosition.x else {
scrollView.setContentOffset(lastScrollPosition, animated: false)
return
}
}

content offset doesn't work

I am trying to set the content offset in my uiscrollview. I have tried the following, and none of it works. None of the following affect my scrollView in any way, it simple appears on the screen as normal.:
let point = CGPoint(x: 0, y: self.view.frame.height / 2)
self.scrollView.setContentOffset(point, animated: true)
and:
self.scrollView.contentOffset.y = self.view.frame.size.height / 2
and:
self.scrollView.contentOffset = CGPointMake(0, self.view.frame.size.height / 2)
and:
self.scrollView.contentOffset = CGPointMake(0, scrollView.frame.size.height / 2)
I have a scroll view that is 2 times the height of my view. They are essentially separated into 2 different view that I can page between. I want the scrollview to start on the bottom part.
You should try them in the viewDidLayoutSubviews
Ref: iOS 9 scroll UITextView to top
I was really scratching my head when I found that setting scrollView.contentOffset to any value was not working. Eventually I realized that I was calling the scrollView delegate message 'scrollViewDidScroll' to manage scroll dragging peculiarities.
It turns out that 'scrollViewDidScroll' is called whenever the scrollView contentOffset is changed, and my code to manage drag issues was changing the contentOffset. I just made a 'BOOL isDragging' variable to only make changes to the contentOffset when dragging:
private var isDragging : Bool = false
private var initialContentOffset = CGPoint.zero
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
isDragging = true
...
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
// need to keep track if we are dragging with isDragging
// since this 'scrollViewDidScroll' API is called with ANY contentOffset change
if (isDragging == true){
// do stuff to contentOffset to manage dragging peculiarities
...
}
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if (decelerate == false){
scrollViewDidEndScrollingAnimation(scrollView)
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollViewDidEndScrollingAnimation(scrollView)
}
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
isDragging = false
}

Swift - UITableView scroll event

I was wondering how to detect if the UITableView is scrolled (up or down).
I want to hide the keyboard when the UITableView is scrolled with self.view.endEditing(true).
Thanks in advance
You can set property of UITable view (XCode 7+)
In Storyboard:
in Code:
tableView.keyboardDismissMode = .onDrag
You can add UIScrollViewDelegate. After that you can implement scrollViewDidScroll method.
I believe the complete solution would be the following:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == feedTableView {
let contentOffset = scrollView.contentOffset.y
print("contentOffset: ", contentOffset)
if (contentOffset > self.lastKnowContentOfsset) {
print("scrolling Down")
print("dragging Up")
} else {
print("scrolling Up")
print("dragging Down")
}
}
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if scrollView == feedTableView {
self.lastKnowContentOfsset = scrollView.contentOffset.y
print("lastKnowContentOfsset: ", scrollView.contentOffset.y)
}
}
The previous answers weren't 100% accurate.
Explanation: scrollViewDidEndDragging will be called when the scrolling stops, therefore we save the last know offset. After that we compare it with the current offset in the delegate method scrollViewDidScroll.
override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if(velocity.y>0){
NSLog("dragging Up");
}else{
NSLog("dragging Down");
}
}

Resources