Why does WKWebView call scrollviewDidScroll when initialised and how to prevent it? - ios

In a UIViewController, I instantiate a WKWebView and set the view controller as the WKWebiew scrollview's delegate:
class MyViewController: UIViewController, UIScrollViewDelegate {
var webView : WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: self.view.frame)
view = webView
webView.loadHTMLString("<h1>hello</h1>", baseURL: NSURL(string: "http://www.google.com")!)
webView.scrollView.delegate = self
}
func scrollViewDidScroll(scrollView: UIScrollView) {
println("didScroll")
}
The problem I have is that scrollViewDidScroll is called as soon as the WKWebView is created (ie without the user actually scrolling anything).
Why is that and how can I prevent it ?

Set a bool value to true in scrollViewWillBeginDragging method, which is called only when user scrolls and it is not automatically called by WKWebview
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
scrollByDragging = true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !scrollByDragging{
UIView.animate(withDuration: 0.01) {
scrollView.setContentOffset(.zero, animated: true)
}
}
}

If you were to use contentOffset, you would be able to detect when the user scrolls.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y >= 0 {
print("didScroll")
}
}
If your UIScrollView is inside a UINavigationController, contentOffset.y will start at -44, but practically 0 is fine to use.

Related

UIRefreshControl in UIScrollView Doesn't disappear on endRefreshing

I tried to use a UIRefreshControll in a UIScrollView but I had the problem that I needed to scroll a lot in order to get it to refresh. I needed to use both hands to make it work so I decided to write some scrolling logic to start the refreshing.
Now the problem is that sometimes the endRefreshing function doesn't make the UIRefreshControll view disappear and I end up having it there forever.
I have tried called endRefreshing in the main queue with a delay and it doesn't work. I have also tried setting isHidden to true and also removeFromSuperView. I've had no luck in making it work.
import UIKit
class RefreshableView : UIView {
#IBOutlet weak var scrollView: UIScrollView!
private let refreshControl = UIRefreshControl()
private var canRefresh = true
var refreshFunction: (() -> ())?
func setupUI() {
backgroundColor = ColorName.grayColor.color
scrollView.refreshControl = refreshControl
}
func endRefreshing() {
self.refreshControl.endRefreshing()
}
}
extension RefreshableView: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y < -100 {
if canRefresh && !self.refreshControl.isRefreshing {
self.canRefresh = false
self.refreshControl.beginRefreshing()
self.refreshFunction?()
}
} else if scrollView.contentOffset.y >= 0 {
self.canRefresh = true
}
}
}
The scroll view's refreshControl handles the pull-to-refresh feature automatically, so you don't need any of the scrollViewDidScroll() code.
Assuming your storyboard looks something like this, where:
the view with the Red background is an instance of RefreshableView class
it contains a scroll view (connected via #IBOutlet)
and some content in the scroll view (here I have just a single label)
Your code can be like this:
class RefreshableView : UIView {
#IBOutlet var scrollView: UIScrollView!
private let refreshControl = UIRefreshControl()
var refreshFunction: (() -> ())?
func setupUI() {
backgroundColor = .gray
refreshControl.addTarget(self, action: #selector(didPullToRefresh), for: .valueChanged)
scrollView.refreshControl = refreshControl
}
#objc func didPullToRefresh() {
print("calling refreshFunction in controller")
self.refreshFunction?()
}
func endRefreshing() {
print("end refreshing called from controller")
self.refreshControl.endRefreshing()
}
}
class RefreshTestViewController: UIViewController {
#IBOutlet var theRefreshableView: RefreshableView!
override func viewDidLoad() {
super.viewDidLoad()
theRefreshableView.setupUI()
theRefreshableView.refreshFunction = {
print("simulating refresh for 2 seconds...")
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
[weak self] in
guard let self = self else { return }
// do what you want to update the contents of theRefreshableView
// ...
// then tell theRefreshableView to endRefreshing
self.theRefreshableView.endRefreshing()
}
}
}
}

Assign function to UIScrollView when scrolling (Swift)

I have a UIViewController with a UIScrollView I've added in Interface Builder. I want to have scrollView functions so I can detect if it's been scrolled up or down. My scroll view is attached with an IBOutlet called "mainScroll." Here is my code:
var lastContentOffset: CGFloat = 0
#IBOutlet weak var mainScroll: UIScrollView!
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.lastContentOffset = mainScroll.contentOffset.y
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (self.lastContentOffset < mainScroll.contentOffset.y) {
// did move up
print("Did Move Up")
} else if (self.lastContentOffset > mainScroll.contentOffset.y) {
// did move down
print("Did Move Down")
} else {
// didn't move
}
}
I've added UIScrollViewDelegate to my class. I have a feeling I'm not using the scrollView delegate callbacks properly. How can I take these functions and assign them to my "mainScroll"?
May be you need this in viewDidLoad
mainScroll.delegate = self

How to show navigation bar on scroll iOS

I find a lot of resources on how to hide the navigation bar on scroll, but I would like to have the navigation bar hidden on start, and then appear when starting to scroll. Like this animation from Design+Code app: https://imgur.com/a/SqDRD
You can use UIScrollViewDelegate for that.
Here is example code for hide navigation bar and tool bar with scroll:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var toolBar: UIToolbar!
#IBOutlet weak var webV: UIWebView!
var lastOffsetY :CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
webV.scrollView.delegate = self
let url = "http://apple.com"
let requestURL = NSURL(string:url)
let request = NSURLRequest(URL: requestURL!)
webV.loadRequest(request)
}
//Delegate Methods
func scrollViewWillBeginDragging(scrollView: UIScrollView){
lastOffsetY = scrollView.contentOffset.y
}
func scrollViewWillBeginDecelerating(scrollView: UIScrollView){
let hide = scrollView.contentOffset.y > self.lastOffsetY
self.navigationController?.setNavigationBarHidden(hide, animated: true)
toolBar.hidden = hide
}
}
Using the willBeginDragging and didEndDragging you can accomplish what you want. Here there is a simplified version of it, you may need to modify it a little bit to obtain the desired effect you want, but it is an starting point.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
navigationController?.setNavigationBarHidden(false, animated: true)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
navigationController?.setNavigationBarHidden(true, animated: true)
}
}

How to detect scrolling on a WKWebView that shows an AMP (Accelerated Mobile Page)

I have a WKWebView and I need to know when it is scrolled, so I bound its scroll view's delegate to my controller in order to use UIScrollView delegate methods:
self.myWebView.scrollView.delegate = self
...
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Do stuff
...
}
It works well for almost every web pages but when the user does a google search, selects an AMP page and then scrolls up or down, I can't detect that scrolling.
Is there a way to know if an AMP page is scrolled?
I hope this works (its working for me) , storing lastContentOffset's Y position and comparing it with current.
private var lastContentOffset: CGFloat = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (self.lastContentOffset > scrollView.contentOffset.y) {
print("move up")
}
else if (self.lastContentOffset < scrollView.contentOffset.y) {
print("move down")
}
self.lastContentOffset = scrollView.contentOffset.y
}
class ViewController: WKNavigationDelegate, UIScrollViewDelegate
var webView: WKWebView!
override func loadView() {
super.loadView()
webView = WKWebView()
webView.navigationDelegate = self
webView.scrollView.delegate = self
view = webView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset.y)
}

passing tableview offset data to tableview cell using delegate

I don't know why but delegates have always hurt my brain.
I have the delegate like so:
protocol StylistControllerDelegate {
func userInteractsWithTableView(tableViewOffset: CGFloat)
}
It sits inside StylistController and is called when the user scrolls:
class StylistController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIScrollViewDelegate {
var delegate : StylistControllerDelegate?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
self.delegate?.userInteractsWithTableView(tableViewOffset: offset)
}
inside the cell I am attempting to set the CGFloat value to a variable:
class CollectionViewCell: UICollectionViewCell, StylistControllerDelegate {
//var stylistControllerTableViewOffset: CGFloat?
func userInteractsWithTableView(tableViewOffset: CGFloat) {
print(tableViewOffset)
if tableViewOffset > 100 {
collectionView.isScrollEnabled = true
} else {
collectionView.isScrollEnabled = false
}
}
}
I use the offset to enable and disable to ability to scroll of the collectionview INSIDE the cell. Once it gets to a certain value (in this case 100), the ability to scroll is enabled.

Resources