scrollView fetching process - ios

I have rotated my collectionView upside down so it would load the content at the top instead of at the bottom and I added this:
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let currentOffset = scrollView.contentOffset.y
let maxOffset = scrollView.contentSize.height - scrollView.frame.size.height
if maxOffset - currentOffset <= 10{
fetch()
}
}
but nothing gets fetch even though on the console it's printing every time I go reach the bottom. fetch() works, calling it doesn't

I did the same thing using this and it works fine.
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.frame.height + 50 /* 50 its just example for your like */ {
DispatchQueue.main.async {
fetch()
}
}
}

Related

Detect when a user swipes past a certain point in a UICollectionView's scrollViewWillBeginDragging?

In my collectionView the cells are the size of the entire screen, I use vertical scrolling, and I have .isPagingEnabled = true. I needed to prevent the user from swiping fast. Changing the .decelerationRate didn't work. The only thing that I found was to disable/enable the scrollView as the user swipes which almost works fine.
In the pic below, when the swiping starts, I disable the scrollView and show a red label at the top that says "cv Disabled". When swiping ends because the user lifted their finger, I enable the scrollView and remove the label. But when I swipe a hairline bit and quickly lift my finger from the cell, the red label never disappears and the scroll stays disabled. The orange arrow points to the next cell with a beige background that's about to be shown when I subtly swiped.
In the code below using both tries everything works fine when scrolling past a certain point occurs like full screen or a quarter of the screen. The issue is if I subtly swipe a hairline bit, the scrollView stays disabled because the methods myScrollView?.isUserInteractionEnabled = true are in never gets fired.
1st try:
var myScrollView: UIScrollView?
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if myScrollView == nil {
myScrollView = scrollView
}
myScrollView?.isUserInteractionEnabled = false
showRedDisabledLabel()
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
myScrollView?.isUserInteractionEnabled = true
removeRedDisabledLabel()
}
2nd try:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if myScrollView == nil {
myScrollView = scrollView
}
myScrollView?.isUserInteractionEnabled = false
showRedDisabledLabel()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
NSObject.cancelPreviousPerformRequests(withTarget: self)
perform(#selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation), with: nil, afterDelay: 0.3)
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
myScrollView?.isUserInteractionEnabled = true
removeRedDisabledLabel()
NSObject.cancelPreviousPerformRequests(withTarget: self)
}
This problem literally only occurs when I swipe that hairline bit and quickly lift my finger, every other time the code from both tries works perfectly. The question is in scrollViewWillBeginDragging, how can I detect when a user swipes past a certain point so that I can then set myScrollView?.isUserInteractionEnabled = false
I couldn't find a way to to determine how much a user swiped in scrollViewWillBeginDragging but I did find a workaround to the actual problem of hairline scrolling. I put myScrollView?.isUserInteractionEnabled and the redLabel into their own functions, combined this answer and this answer, created an instance property named isScrolling, and use didSet to determine if it's scrolling or not and call the functions from there:
// Properties
var myScrollView: UIScrollView?
var isScrolling = false {
didSet {
if isScrolling {
disableScrollView()
} else {
enableScrollView()
}
}
}
// My 2 Functions
func disableScrollView() {
myScrollView?.isUserInteractionEnabled = false
showRedDisabledLabel()
}
func enableScrollView() {
myScrollView?.isUserInteractionEnabled = true
removeRedDisabledLabel()
}
// UIScrollView Delegate Methods
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if myScrollView == nil {
myScrollView = scrollView
}
isScrolling = true
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
isScrolling = false
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
isScrolling = false
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
NSObject.cancelPreviousPerformRequests(withTarget: self)
perform(#selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation), with: nil, afterDelay: 0.3)
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
isScrolling = false
NSObject.cancelPreviousPerformRequests(withTarget: self)
}

Swipe only to the left using a UIScrollView

I have a scrollView with the delegate method set.
private let scrollView: UIScrollView = {
let scrollView = UIScrollView(frame: .zero).usingAutoLayout()
scrollView.isPagingEnabled = true
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
return scrollView
}()
I'm trying to making only scroll to the left to mimic a "delete cell", like in the phone book. I don't want the user to be able to scroll to the right. I have this, which kinda works:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x < 0 {
scrollView.contentOffset.x = 0
}
The problem is that if I swipe fast the contentOffSet is set to positive values, which makes the scrollView scroll in the opposite direction. This usually happens after I finish the swipe gesture. This makes me think it has to do with the bounce, but even setting it to false, it still occurs.
Was able to come up with a solution:
extension SwipeableCollectionViewCell: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView.superview).x > 0 {
self.scrollDirection = .rigth
} else {
self.scrollDirection = .left
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.scrollDirection == .rigth {
scrollView.contentOffset.x = 0
}
}
}
private enum ScrollDirection {
case rigth
case left
}

iOS WKWebView detect when reaches bottom

I have a migration issue from UIViewView to WKWebView, detecting the Scroll View reached bottom when using WKWebView. Prior to WKWebView I used the UIScrollViewDelegate detecting wether the User had seen all of the content by scrolling till the end of the WebView. If he did, the "confirm" button was enabled. iPhone - knowing if a UIScrollView reached the top or bottom
Now with WKWebView this doesn't work anymore. I guess the reason is, when using a WKWebView and load a html string, it scales the view down for full visiblity of the content. So I had to set the viewport by appending it to the html string. This displays the content in the same way, the UIWebView did, providing the html string, without setting a viewport.
But now the UIScrollViewDelegate on load always tells that the bottom already reached. I guess, that the WKWebView loads the html, scales it at full visiblity, the scrollViewDelegate recognizes, that the content was fully visible, after that the viewport comes in and scales the page up, so a vertical scroll is needed to display the full content. But at this time, my "confirm" button is already enabled.
Code Snippet
override func scrollViewDidScroll(_ scrollView: UIScrollView){
let scrollViewHeight = scrollView.frame.size.height;
let scrollContentSizeHeight = scrollView.contentSize.height;
let scrollOffset = scrollView.contentOffset.y;
if (scrollOffset + scrollViewHeight == scrollContentSizeHeight)
{
self.confirmButton.isEnabled = true;
}
}
With WKWebView, the scrollContentSizeHeight always is the same as scrollViewHeight on load, but after the scrollViewDidScroll delegate function invokes mulitple times (without scrolling) the scrollContentSizeHeight is larger than the scrollViewHeight at real size.
But now the UIScrollViewDelegate on load always tells that the bottom
already reached.
The issue in the particular case is UIScrollView delegate is getting called before the WKWebView is loaded completely.
Take one private instance variable to check if the URL is loaded completely or not.
var isURLLoaded = false
Confirm WKWebView delegates to your viewController.
webView.scrollView.delegate = self
webView.navigationDelegate = self
And override these delegate methods:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if isURLLoaded {
if scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height) {
confirmButton.isEnabled = true
} else if scrollView.contentOffset.y < scrollView.contentSize.height {
confirmButton.isEnabled = false
}
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
isURLLoaded = true
}
I wrote scrollview extension for Wkwebview using to read javascript code.through this you can use to check webview reaches to end of page or not. Before use, Set webview scroll delegate.
extension BaseWebViewController: UIScrollViewDelegate {
func scrollViewDidEndDragging(_ scrollView: UIScrollView,
willDecelerate decelerate: Bool) {
webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in
let bodyScrollHeight = height as! CGFloat
var bodyoffsetheight: CGFloat = 0
var htmloffsetheight: CGFloat = 0
var htmlclientheight: CGFloat = 0
var htmlscrollheight: CGFloat = 0
var wininnerheight: CGFloat = 0
var winpageoffset: CGFloat = 0
var winheight: CGFloat = 0
//body.offsetHeight
self.webView.evaluateJavaScript("document.body.offsetHeight", completionHandler: { (offsetHeight, error) in
bodyoffsetheight = offsetHeight as! CGFloat
self.webView.evaluateJavaScript("document.documentElement.offsetHeight", completionHandler: { (offsetHeight, error) in
htmloffsetheight = offsetHeight as! CGFloat
self.webView.evaluateJavaScript("document.documentElement.clientHeight", completionHandler: { (clientHeight, error) in
htmlclientheight = clientHeight as! CGFloat
self.webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (scrollHeight, error) in
htmlscrollheight = scrollHeight as! CGFloat
self.webView.evaluateJavaScript("window.innerHeight", completionHandler: { (winHeight, error) in
if error != nil {
wininnerheight = -1
} else {
wininnerheight = winHeight as! CGFloat
}
self.webView.evaluateJavaScript("window.pageYOffset", completionHandler: { (winpageOffset, error) in
winpageoffset = winpageOffset as! CGFloat
let docHeight = max(bodyScrollHeight, bodyoffsetheight, htmlclientheight, htmlscrollheight,htmloffsetheight)
winheight = wininnerheight >= 0 ? wininnerheight : htmloffsetheight
let winBottom = winheight + winpageoffset
if (winBottom >= docHeight) {
print("end scrolling")
}
})
})
})
})
})
})
})
}
})
}
}
After scroll reaches to end you will see in console "end scrolling"
Create a ScrollView inside the class, WKWebView has a scrollview embedded inside, make the scrollview you created relative with the embedded scrollview and implemente the method :
func scrollViewDidScroll(_ scrollView: UIScrollView){
if (scrollView.contentOffset.y + 1) >= (scrollView.contentSize.height - scrollView.frame.size.height) {
//bottom reached
your code here
}
}
and then, make your class inherit from UIScrollViewDelegate :
class : UIViewController,
func updateButtonState(_ scrollView : UIScrollView){
if isURLLoaded {
if scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height) {
confirmButton.isEnabled = true
} else if scrollView.contentOffset.y < scrollView.contentSize.height {
confirmButton.isEnabled = false
}
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateButtonState(scrollView);
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
isURLLoaded = true
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500), execute:{
self.updateButtonState(webView.scrollView)
});
}
Unfortunatley I can't find a callback where the scrollView is ready to evaluate, so I set a timeout of 500ms, with this modification it works as expected

how to know UIwebview is scrolling down side or up side? (swift)

in my app i am using small menu at the bottom of uiwebview. and i want to make like when user scroll downside that view must be hide. and when scrolling upside view must be unhide.
Like Safari.
this is what i tried
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
print("Going Down")
viewbottom.hidden = true
viewHieght.constant = 0
}
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
print("Going Up")
viewbottom.hidden = false
viewHieght.constant = 45
}
but by using this code its continuously showing up and down.
Use scroll view pan gesture recogniser to determine the direction.
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translationInView(scrollView.superview).y > 0 {
// scrolls down
} else {
// scrolls up
}
}
keep tracking the scrollView.contentOffset.y value and compare the last with the current value like so:
in your mainView add: var lastScrollOffset = CGFloat()
compare the last value with the current one in func scrollViewDidScroll(scrollView: UIScrollView)
func scrollViewDidScroll(scrollView: UIScrollView) {
let scrollOffset = scrollView.contentOffset.y
if lastScrollOffset < scrollOffset{
//scrolling down
}else if lastScrollOffset > scrollOffset {
//scrolling up
}else{
//going crazy
}
lastScrollOffset = scrollOffset
}

How can I refresh the table view but disable the bounce?

I want to disable my scrollview bounce when scroll down.
When I disable bounce vertically I can't refresh my table.
Any suggestion how to disable bounce, but enable refresh table?
I'm refreshing this way:
self.refreshControl = UIRefreshControl()
self.refreshControl.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refreshControl)
func refresh(sender:AnyObject) {
getJson()
self.tableView.reloadData()
self.refreshControl.endRefreshing()
}
Thanks.
Just did it this way:
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.y < 0.0 {
return
}
if (scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.size.height) {
scrollView.setContentOffset(CGPointMake(scrollView.contentOffset.x, scrollView.contentSize.height - scrollView.frame.size.height), animated: false)
}
}
Implement the scrollViewDidScroll method in the UIScrollViewDelegate
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > 0 {
scrollView.setContentOffset(CGPoint(x: scrollView.contentOffset.x, y: 0), animated: false)
}
}
}
This allows the table view to pull to refresh down, but restricts pulling up. This removes the bounce in the upwards direction.
Side-effect: this will not let you go beyond the cells already on view, so you can't scroll down to cells not visible.

Resources