Проблема с высотой содержимого SwiftUI WKWebView
Уже неделю я застрял с этой проблемой, когда у меня есть свой собственный WKWebView
с UIViewRepresentable
struct Webview : UIViewRepresentable {
var webview: WKWebView?
init() {
self.webview = WKWebView()
}
class Coordinator: NSObject, WKNavigationDelegate {
var parent: Webview
init(_ parent: Webview) {
self.parent = parent
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("Loading finished -- Delegate")
webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (height, error) in
print(height)
webView.bounds.size.height = height as! CGFloat
})
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> WKWebView {
return webview!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.navigationDelegate = context.coordinator
let htmlStart = "<HTML><HEAD><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no\"></HEAD><BODY>"
let htmlEnd = "</BODY></HTML>"
let dummy_html = """
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ut venenatis risus. Fusce eget orci quis odio lobortis hendrerit. Vivamus in sollicitudin arcu. Integer nisi eros, hendrerit eget mollis et, fringilla et libero. Duis tempor interdum velit. Curabitur</p>
<p>ullamcorper, nulla nec elementum sagittis, diam odio tempus erat, at egestas nibh dui nec purus. Suspendisse at risus nibh. Mauris lacinia rutrum sapien non faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec interdum enim et augue suscipit, vitae mollis enim maximus.</p>
<p>Fusce et convallis ligula. Ut rutrum ipsum laoreet turpis sodales, nec gravida nisi molestie. Ut convallis aliquet metus, sit amet vestibulum risus dictum mattis. Sed nec leo vel mauris pharetra ornare quis non lorem. Aliquam sed justo</p>
"""
let htmlString = "\(htmlStart)\(dummy_html)\(htmlEnd)"
uiView.loadHTMLString(htmlString, baseURL: nil)
}
}
и это представлено так
и проблема здесь в том, что у веб-просмотра отсутствует высота. На мой взгляд, это не будет отображаться, если я не добавлю жестко запрограммированныйframe
значение, при котором мой контент отключается.
Webview()
.frame(height:300)
Я почти сталкивался с подобными вопросами, но это не помогло:/
2 ответа
Это сбивает с толку ScrollView
в SwiftUI, который ожидает заранее известный размер контента, и UIWebView
внутренний UIScrollView
, который пытается получить размер из родительского представления... езда на велосипеде.
Итак, вот возможный подход... передать определенный размер из веб-представления в мир SwiftUI, поэтому жесткое кодирование не используется и ScrollView
ведет себя как плоский контент.
Сначала демонстрация результата, как я понял и смоделировал...
Вот полный код модуля демонстрации. Протестировано и работает на Xcode 11.2 / iOS 13.2.
import SwiftUI
import WebKit
struct Webview : UIViewRepresentable {
@Binding var dynamicHeight: CGFloat
var webview: WKWebView = WKWebView()
class Coordinator: NSObject, WKNavigationDelegate {
var parent: Webview
init(_ parent: Webview) {
self.parent = parent
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (height, error) in
DispatchQueue.main.async {
self.parent.dynamicHeight = height as! CGFloat
}
})
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> WKWebView {
webview.scrollView.bounces = false
webview.navigationDelegate = context.coordinator
let htmlStart = "<HTML><HEAD><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no\"></HEAD><BODY>"
let htmlEnd = "</BODY></HTML>"
let dummy_html = """
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ut venenatis risus. Fusce eget orci quis odio lobortis hendrerit. Vivamus in sollicitudin arcu. Integer nisi eros, hendrerit eget mollis et, fringilla et libero. Duis tempor interdum velit. Curabitur</p>
<p>ullamcorper, nulla nec elementum sagittis, diam odio tempus erat, at egestas nibh dui nec purus. Suspendisse at risus nibh. Mauris lacinia rutrum sapien non faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec interdum enim et augue suscipit, vitae mollis enim maximus.</p>
<p>Fusce et convallis ligula. Ut rutrum ipsum laoreet turpis sodales, nec gravida nisi molestie. Ut convallis aliquet metus, sit amet vestibulum risus dictum mattis. Sed nec leo vel mauris pharetra ornare quis non lorem. Aliquam sed justo</p>
"""
let htmlString = "\(htmlStart)\(dummy_html)\(htmlEnd)"
webview.loadHTMLString(htmlString, baseURL: nil)
return webview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
struct TestWebViewInScrollView: View {
@State private var webViewHeight: CGFloat = .zero
var body: some View {
ScrollView {
VStack {
Image(systemName: "doc")
.resizable()
.scaledToFit()
.frame(height: 300)
Divider()
Webview(dynamicHeight: $webViewHeight)
.padding(.horizontal)
.frame(height: webViewHeight)
}
}
}
}
struct TestWebViewInScrollView_Previews: PreviewProvider {
static var previews: some View {
TestWebViewInScrollView()
}
}
Если я просто поставлю Webview
в VStack
размер соответствует ожидаемому. Здесь нет никакой реальной проблемы.
Хотя вы этого не заявили, наиболее вероятная причина вашего Webview
не занимая места - это то, что вы поместили эти предметы в ScrollView
. посколькуWKWebView
в основном (но не подкласс) UIScrollView
система не знает, как его размер, когда он содержится в другом прокручиваемом представлении.
Например:
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView { // THIS LINE means that...
VStack(spacing: 0.0) {
Color.red.aspectRatio(1.0, contentMode: .fill)
Text("Lorem ipsum dolor sit amet").fontWeight(.bold)
Webview()
.frame(height: 300) // ...THIS is necessary
}
}
.navigationBarItems(leading: Text("Item"))
.navigationBarTitle("", displayMode: .inline)
}
}
}
Вложенные прокрутки - это не то, что вы хотите для макета, который вы все равно показывали. Вы, несомненно, хотите, чтобы это изображение и текст вверху прокручивались в сторону, когдаWKWebView
свитки.
Техника, которую вам нужно использовать с UIKit или SwiftUI, будет аналогичной. На данный момент я не рекомендую делать это с помощью SwiftUI.
- Поместите
WKWebView
в контейнере (скорее всегоUIViewController.view
) - Разместите содержимое заголовка (изображение плюс текст, который может быть
UIHostingContainer
) в этом представлении как брат, стоящий над веб-просмотром. - Задавать
webView.scrollView.contentInset.top
к размеру вашего контента от #2 - Осуществлять
UIScrollViewDelegate.scrollViewDidScroll()
- В
scrollViewDidScroll
измените положение вашего контента, чтобы оно соответствовало веб-просмотруcontentOffset
.
Вот одна из возможных реализаций:
WebView:
import SwiftUI
import WebKit
struct WebView : UIViewRepresentable {
@Binding var offset: CGPoint
var contentInset: UIEdgeInsets = .zero
class Coordinator: NSObject, UIScrollViewDelegate {
var parent: WebView
init(_ parent: WebView) {
self.parent = parent
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var offset = scrollView.contentOffset
offset.y += self.parent.contentInset.top
self.parent.offset = offset
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> WKWebView {
let webview = WKWebView()
webview.scrollView.delegate = context.coordinator
// continue setting up webview content
return webview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
if uiView.scrollView.contentInset != self.contentInset {
uiView.scrollView.contentInset = self.contentInset
}
}
}
ContentView. Примечание: я использовал50
как константа вместо расчета размера. Фактический размер можно получить, используяGeometryReader
хотя.
struct ContentView: View {
@State var offset: CGPoint = .zero
var body: some View {
NavigationView {
WebView(offset: self.$offset, contentInset: UIEdgeInsets(top: 50, left: 0, bottom: 0, right: 0))
.overlay(
Text("Hello World!")
.frame(height: 50)
.offset(y: -self.offset.y)
, alignment: .topLeading)
.edgesIgnoringSafeArea(.all)
}
.navigationBarItems(leading: Text("Item"))
.navigationBarTitle("", displayMode: .inline)
}
}