Edited at

WKWebViewのScrollView内にHeaderViewを追加したときのスクロール位置補正対策

More than 1 year has passed since last update.


はじめに

WKWebViewのScrollView内に自作のHeaderViewを追加してみたところ、

戻る、進む、更新などの操作で毎回HeaderViewの高さ分だけスクロール位置がずれてしまう問題が発生しました。

(iOS11以降の問題です。)

その問題の対策を纏めます。


開発環境

Category
Version

Swift
4.1

Xcode
9.3 (9E145)


対策前のコード

とりあえず、HeaderViewを作ってWKWebViewのScrollViewにaddSubviewしてみました。


CustomView.swift


final class CustomView: UIView {

private let headerViewHeight: CGFloat = 50.0

var webView: WKWebView?
var headerView: UIView?

// ...
// init時にWKWebViewのインスタンス生成(割愛)
// ...

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)

// headerViewをscrollView内に追加
setHeaderView()
}

func setHeaderView() {

headerView?.removeFromSuperview()

// 画面外に作成
headerView = UIView(frame: CGRect(x: 0,
y: -headerViewHeight,
width: superview?.frame.height ?? frame.height,
height: headerViewHeight))
headerView?.backgroundColor = .blue

// スクロール領域の拡張
webView?.scrollView.contentInset = UIEdgeInsetsMake(headerViewHeight, 0, 0, 0)
webView?.scrollView.addSubview(headerView ?? UIView())

// スクロール開始位置を変更
webView?.scrollView.setContentOffset(CGPoint(x: 0, y: -headerViewHeight), animated: false)
}
}



発生した問題

iOS11以降で戻る、進む、更新などの操作をすると、HeaderViewの高さ分だけスクロール位置がずれてしまいます。

スワイプによる戻る、進むも同様でした。

before.gif


対策

UIScrollViewDelegateでフラグを駆使して対応してみました。


CustomView.swift


final class CustomView: UIView {

private let headerViewHeight: CGFloat = 50.0
private var scrollingToTop = false
private var updatingContentOffsetY = false
private var isOverTheContentSizeHeight = false
private var isUnderTheStartingPosition = false

var webView: WKWebView?
var headerView: UIView?

// ...
}

// MARK: - UIScrollViewDelegate

extension CustomView: UIScrollViewDelegate {

func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
scrollingToTop = true
return true
}

func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
scrollingToTop = false
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {

if scrollView.frame.height + scrollView.contentOffset.y > scrollView.contentSize.height {
// bouncing
isOverTheContentSizeHeight = true
}

if scrollView.contentOffset.y < -headerViewHeight {
// bouncing
isUnderTheStartingPosition = true
}

// 以下の全てを満たす場合、webView.scrollViewのContentOffsetYを更新する
// - scrollView内部のドラッグをしていない
// - ステータスバータップによる一番上へのスクロールをしていない
// - webView.scrollViewのContentOffsetY更新中でない
// - scrollviewの一番下より下にいない (not bouncing)
// - scrollviewの一番上より上にいない (not bouncing)

let isUpdateableContentOffsetY = !scrollView.isDragging
&& !scrollingToTop
&& !updatingContentOffsetY
&& !isOverTheContentSizeHeight
&& !isUnderTheStartingPosition

if isUpdateableContentOffsetY {

updatingContentOffsetY = true

if #available(iOS 11.0, *) {
var newContentOffsetY: CGFloat
if scrollView.contentOffset.y < 0 {
newContentOffsetY = -headerViewHeight
} else {
newContentOffsetY = scrollView.contentOffset.y - headerViewHeight
}
print("newContentOffsetY: \(newContentOffsetY)")
webView?.scrollView.setContentOffset(CGPoint(x: 0, y: newContentOffsetY), animated: false)
}

updatingContentOffsetY = false
}
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
isOverTheContentSizeHeight = false
isUnderTheStartingPosition = false
}
}


iOS11未満では、そもそもこの問題が発生していなかったので余計なスクロール位置補正を実行しないように

if #available(iOS 11.0, *) { }

で囲っています。


対策後

iOS11以降で戻る、進む、更新やスワイプによる戻る、進むなどの操作をしたとき

HeaderViewの高さ分だけスクロール位置のずれが補正されて、想定のスクロール位置になりました。

after.gif


さいごに

webView.scrollViewのbouncesを切ってしまうという選択肢がある場合は、

bouncingのチェックを無くすことができるのでもう少しシンプルに実装できます。

作成したサンプルプロジェクトをGitHubにあげています。

https://github.com/stv-yokudera/ios-wkwebview-demo