skip to Main Content

I am attempting to create an EPUB reader. Since the chapters in an EPUB are represented as whole HTML files with no page breaks, I will display one page at a time in a WKWebView, loading the string with loadHTMLString. Then, when the user swipes to the next page of the UIPageViewController, I will make a new WKWebView and scroll down a page using the setContentOffset method. However, the setContentOffset method has no effect on the displayed content. tldr – how can I tell when this setContentOffset method is safe to call?

Here is my page view controller:

class MyPageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
    var page = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        dataSource = self
        delegate = self
        
        setViewControllers([Page(page: 0)], direction: .forward, animated: false, completion: nil)
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        return nil
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        return Page(page: page + 1)
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed {
            page += 1
        }
   }
}

and here is my Page class:

class Page: UIViewController, WKNavigationDelegate, WKUIDelegate {
    var page: Int
    var webView: WKWebView!
    
    let testString = "<h1>00000000000000000000000000000000000000000000000000</h1><br><h1>11111111111111111111111111111111111111111111111111</h1><br><h1>22222222222222222222222222222222222222222222222222</h1><br><h1>33333333333333333333333333333333333333333333333333</h1><br><h1>44444444444444444444444444444444444444444444444444</h1><br><h1>55555555555555555555555555555555555555555555555555</h1><br><h1>66666666666666666666666666666666666666666666666666</h1><br><h1>77777777777777777777777777777777777777777777777777</h1><br><h1>88888888888888888888888888888888888888888888888888</h1><br><h1>99999999999999999999999999999999999999999999999999</h1>"

    init(page: Int) {
        self.page = page
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        webView = WKWebView(frame: view.bounds)
        webView.navigationDelegate = self
        
        view.addSubview(webView)
        
        load()
    }
    
    func load() {
        webView.loadHTMLString(testString, baseURL: nil)
    }
    
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        self.scroll()
    }
    
    func scroll() {
        let scrollPoint = CGPoint(x: 0, y: 100 * CGFloat(page))
        webView.scrollView.setContentOffset(scrollPoint, animated: false)
    }
}

I want the scroll to be instantaneous so that the user cannot tell it is scrolling, hence the animated: false. However, this simply does not change the view in any way. If I use animated: true it works fine, and if I wrap self.scroll() in a DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) it works as well. But the user can see a bit of a jarring scroll if I do this. I have tried calling .setNeedsLayout() and .layoutIfNeeded() to no avail; the view still does not change.

So clearly there is something that is not ready to scroll the webView, even after the didFinish delegate method is called. How can I tell when this webView is actually ready to scroll? Or is there another reason that animated: false is not working?

2

Answers


  1. Tldr; I don’t have an actual answer, but here is some more info

    Trying to figure out what is going on, I made de Page class the delegate for the webiew scrollView. When doing that, you can see something weird happen when putting a print in the delegates scrollViewDidScroll event:

    onSetContentOffset
    didScroll (0.0, 100.0)
    didScroll (0.0, 0.0)
    didScroll (0.0, -102.0)
    didScroll (0.0, 0.0)
    didScroll (0.0, 518.3333333333334)
    didScroll (0.0, 398.0)
    didScroll (0.0, 0.0)
    

    initially the offset does get set to 100, but then something weird happens and the offset goes all over the place before eventually going back to 0.

    A similiar thing seems to happen if you set animated to true, but then because it is animated it gets fixed automatically

    onSetContentOffset
    didScroll (0.0, -118.0)
    didScroll (0.0, 0.0)
    didScroll (0.0, 0.6666666666666666)
    didScroll (0.0, 0.0)
    didScroll (0.0, 518.3333333333334)
    didScroll (0.0, 398.0)
    didScroll (0.0, 0.0)
    didScroll (0.0, 2.0)
    didScroll (0.0, 5.666666666666667)
    didScroll (0.0, 10.333333333333334)
    didScroll (0.0, 16.333333333333332)
    didScroll (0.0, 23.666666666666668)
    didScroll (0.0, 31.333333333333332)
    didScroll (0.0, 39.666666666666664)
    didScroll (0.0, 48.333333333333336)
    didScroll (0.0, 57.0)
    didScroll (0.0, 65.66666666666667)
    didScroll (0.0, 73.66666666666667)
    didScroll (0.0, 80.66666666666667)
    didScroll (0.0, 87.0)
    didScroll (0.0, 92.0)
    didScroll (0.0, 96.0)
    didScroll (0.0, 98.33333333333333)
    didScroll (0.0, 99.33333333333333)
    

    I don’t actually know what causes this and don’t have time to investigate further. My best advice is to accept the animated scroll or delaying it for now. Perhaps you could hide the webView behind a loading view until everything is loaded if you really don’t want people to see the behavior.

    Login or Signup to reply.
  2. After researching the issue, I came to a similar conclusion as https://stackoverflow.com/a/76145772/8010366. It appears that there is an issue with the initial scrolling.


    However, if you still need to manually scroll without the animation, you can use the following low-level code:

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        let yOffset = 20 * page
        webView.evaluateJavaScript("document.body.scrollTop = (100 * yOffset)")
    }
    

    This executes a JavaScript code to scroll the HTML document. Additionally, you should add more content to enable scrolling.

    Hope this helps!


    References:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search