Reputation: 169
I am loading Html content in my WebView.
When I load Html content inside WebView, it shows correctly. After that when I refresh WebView with new Html content the problem occurs.
For example: my first Html content's height is 900, it shows correctly and then I reload WebView with my new Html content which has a height is 400, the WebView is not shrinking and fitting to the content with new height. It still remains as long as 900 height.
So, it seems that WebView is able to re-size itself from less content which is previously loaded to more content but unable to re-size itself when the content is less than previously loaded content.
WebView
struct WebView: UIViewRepresentable {
@State var htmlString: String
@Binding var dynamicHeight: CGFloat
func makeUIView(context: Context) -> WKWebView {
let webView: WKWebView = WKWebView()
webView.navigationDelegate = context.coordinator
webView.scrollView.bounces = false
webView.backgroundColor = .clear
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.loadHTMLString(htmlString, baseURL: nil)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, WKNavigationDelegate {
var parent: WebView
init(_ parent: WebView) {
self.parent = parent
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (result, error) in
if let height = result as? CGFloat {
self.parent.dynamicHeight = height
}
})
}
})
}
}
}
Any Content which is I used WebView
struct WebTest: View {
@ObservedObject var viewModel = NewsVM()
@State var index: Int = 0
@State var newID: String = ""
let screen = UIScreen.main.bounds
let formatString = "<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0\"><script type=\"text/javascript\" src=\"https://platform.twitter.com/widgets.js\"></script><style>@media (prefers-color-scheme: dark) { body { background-color: #1c1c1e; color: white; }} img,iframe,blockquote {\n\t\t\tmax-width: \(UIScreen.main.bounds.width - 24);\n\t\t\theight: auto\n\t\t}\n\t\t</style></head><body><span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size:17\">%@</span></body></html>"
@State private var webViewHeight: CGFloat = .zero
init(id:String) {
viewModel.fetchNews(id: id)
self.newID = id
}
var body: some View {
Group{
if self.viewModel.news.count > 0 {
ModelPages(self.viewModel.news, currentPage: self.$index, wrap: false, hasControl: false) { pageIndex, new in
ScrollView(.vertical, showsIndicators: false){
Text("Height: \(self.webViewHeight)").padding()
WebView(htmlString: String(format: self.formatString, new.content), dynamicHeight: self.$webViewHeight)
.frame(width: self.screen.width - 16)
.frame(height: self.webViewHeight)
.tag(pageIndex)
.padding(.horizontal, 8)
}
}
}else{
Group{
if self.viewModel.error == true{
ConnectionError()
}else{
LoadingView()
}
}
}
}
}
}
Upvotes: 3
Views: 2174
Reputation: 747
I had a similar problem within collectionView included accordion, expandable web data in WKWebView. It was not scrollable to the bottom due to the last expandable web item and solved this problem using the following solution:
class WebViewCollectionViewCell: WKNavigationDelegate {
@IBOutlet private var webView: WKWebView!
private var cancellable: AnyCancellable?
var onWebViewFinished: ((CGFloat) -> Void)?
func webView(_: WKWebView, didFinish _: WKNavigation!) {
webView.contentHeight { [weak self] height in
self?.onWebViewFinished?(height)
UIView.animate(withDuration: 0.4) {
self?.webView.alpha = 1 // Fix white flashing
}
}
cancellable = webView?
.publisher(for: \.scrollView.contentSize)
.removeDuplicates()
.sink { [weak self] size in
self?.onWebViewFinished?(size.height)
}
}
}
extension WKWebView {
func contentHeight(completion: @escaping ((CGFloat) -> Void)) {
evaluateJavaScript("document.readyState") { [weak self] complete, _ in
if complete.exists {
self?.evaluateJavaScript("document.body.scrollHeight", completionHandler: { height, _ in
completion(height as? CGFloat ?? 0)
})
}
}
}
}
And the usage with UICollectionViewCell closure:
class ViewController:
UIViewController,
UICollectionViewDelegate,
UICollectionViewDataSource,
UICollectionViewDelegateFlowLayout {
private var webViewHeight: CGFloat = 1_000
private lazy var configurator: CollectionViewCell = {
cell.onWebViewFinished = { height in
self?.webViewHeight = height
self?.onWebViewFinished?()
}
return cell
}
Upvotes: 2
Reputation: 123
I solved this problem starting with this solution: https://stackoverflow.com/a/41511422/5965343
Monitoring the onresize event, but with the following additions.
Add/inject an <span id="endElement"></span>
at ondocument ready
event, end of body.
functions to inject with AddUserScript() onloadFunction
window.onload=function () {window.webkit.messageHandlers.sizeNotification.postMessage({justLoaded:true,height: document.body.scrollHeight,endElement:document.getElementById('endOfDocument').offsetTop});};
onresize
document.body.onresize=function() {window.webkit.messageHandlers.sizeNotification.postMessage({justLoaded:false,height: document.body.scrollHeight,endElement:document.getElementById('endOfDocument').offsetTop});};
INJECT span at the end
document.body.insertAdjacentHTML('beforeend',"<span style='height: 0;' id='endOfDocument'></span>" );
Upvotes: 1
Reputation: 11
Could you try wrapping the Webview within a parent UIView and load the page. Don't put a fixed height constraint for the UIView instead make the Webview define the height of the parent UIView. It might work.
Upvotes: 0