Improve calculation of TokenScript view height, especially for heavy TokenScript views

pull/1603/head
Hwee-Boon Yar 5 years ago
parent 547bb46861
commit 45666c42e1
  1. 54
      AlphaWallet/Tokens/Views/TokenInstanceWebView.swift

@ -15,10 +15,19 @@ protocol TokenInstanceWebViewDelegate: class {
class TokenInstanceWebView: UIView {
private enum RenderingAttempt {
case first
case second
}
//Cache height given a piece of HTML (which might have different values loaded into it) to improve performance of heavy TokenScript views.
//TODO This can be inaccurate if the HTML height changes when applied with different values. eg. a line is added if a flag is true. Fix it so that caching is by contract + token ID, rather than by HTML-only
private static var htmlHeightCache: [Int: CGFloat] = .init()
//TODO see if we can be smarter about just subscribing to the attribute once. Note that this is not `Subscribable.subscribeOnce()`
private let server: RPCServer
private let walletAddress: AlphaWallet.Address
private let assetDefinitionStore: AssetDefinitionStore
private var hashOfCurrentHtml: Int?
lazy private var heightConstraint = heightAnchor.constraint(equalToConstant: 100)
lazy private var webView: WKWebView = {
let webViewConfig = WKWebViewConfiguration.make(forType: .tokenScriptRenderer, server: server, address: walletAddress, in: ScriptMessageProxy(delegate: self))
@ -157,19 +166,45 @@ class TokenInstanceWebView: UIView {
func loadHtml(_ html: String) {
webView.loadHTMLString(html, baseURL: nil)
let hash = html.djb2hash
hashOfCurrentHtml = hash
if let cachedHeight = TokenInstanceWebView.htmlHeightCache[hash] {
guard heightConstraint.constant != cachedHeight else { return }
heightConstraint.constant = cachedHeight
delegate?.heightChangedFor(tokenInstanceWebView: self)
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard keyPath == "estimatedProgress" else { return }
guard webView.estimatedProgress == 1 else { return }
//Needs a second time in case the TokenScript is heavy and slow to render, or if the device is slow. Value is empirical
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.makeIntroductionWebViewFullHeight()
self.makeIntroductionWebViewFullHeight(renderingAttempt: .first)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
self.makeIntroductionWebViewFullHeight(renderingAttempt: .second)
}
}
private func makeIntroductionWebViewFullHeight(renderingAttempt: RenderingAttempt) {
webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { height, _ in
guard let height = height as? CGFloat else { return }
self.cache(height: height, forRenderingAttempt: renderingAttempt)
self.heightConstraint.constant = height
self.delegate?.heightChangedFor(tokenInstanceWebView: self)
})
}
private func makeIntroductionWebViewFullHeight() {
heightConstraint.constant = webView.scrollView.contentSize.height
delegate?.heightChangedFor(tokenInstanceWebView: self)
private func cache(height: CGFloat, forRenderingAttempt renderingAttempt: RenderingAttempt) {
switch renderingAttempt {
case .first:
break
case .second:
//We should only cache if it's the 2nd pass. 1st pass might give the wrong height
guard let hash = hashOfCurrentHtml else { return }
TokenInstanceWebView.htmlHeightCache[hash] = height
}
}
}
@ -296,3 +331,14 @@ func wrapWithHtmlViewport(_ html: String) -> String {
"""
}
}
extension String {
//https://useyourloaf.com/blog/swift-hashable/
//http://www.cse.yorku.ca/~oz/hash.html
fileprivate var djb2hash: Int {
let unicodeScalars = self.unicodeScalars.map { $0.value }
return unicodeScalars.reduce(5381) {
($0 << 5) &+ $0 &+ Int($1)
}
}
}

Loading…
Cancel
Save