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
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: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 automaticallyI 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.
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:
This executes a JavaScript code to scroll the HTML document. Additionally, you should add more content to enable scrolling.
Hope this helps!
References: