SwiftUI WKWebView 内容高度问题
SwiftUI WKWebView content height issue
我被这个问题困扰了一个星期
我有我的自定义 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)
}
}
它是这样呈现的
这里的问题是 webview 缺少高度。
除非我添加硬编码 frame
值,否则它不会出现在我的视图中
我的内容被截断的地方。
Webview()
.frame(height:300)
我几乎遇到过类似的问题,但没有帮助:/
SwiftUI 中的 ScrollView
和 UIWebView
内部 UIScrollView
试图从父视图获取大小的内容令人困惑...循环.
所以这是可能的方法.. 将确定的大小从 Web 视图传递到 SwiftUI 世界,因此不使用硬编码并且 ScrollView
表现得像具有平面内容。
第一次演示结果,据我理解和模拟...
这里是完整的demo模块代码。 测试并在 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
没有占用任何 space 的最可能原因是您将这些项目放在了 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
中)作为 Web 视图上方的 sibling。
- 将
webView.scrollView.contentInset.top
设置为您来自 #2 的内容的大小
- 实施
UIScrollViewDelegate.scrollViewDidScroll()
- 在
scrollViewDidScroll
中修改您的内容的位置以匹配 webview 的 contentOffset
。
这是一种可能的实现方式:
网络视图:
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
}
}
}
内容视图。注意:我使用 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)
}
}
我被这个问题困扰了一个星期
我有我的自定义 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)
}
}
它是这样呈现的
这里的问题是 webview 缺少高度。
除非我添加硬编码 frame
值,否则它不会出现在我的视图中
我的内容被截断的地方。
Webview()
.frame(height:300)
我几乎遇到过类似的问题,但没有帮助:/
SwiftUI 中的 ScrollView
和 UIWebView
内部 UIScrollView
试图从父视图获取大小的内容令人困惑...循环.
所以这是可能的方法.. 将确定的大小从 Web 视图传递到 SwiftUI 世界,因此不使用硬编码并且 ScrollView
表现得像具有平面内容。
第一次演示结果,据我理解和模拟...
这里是完整的demo模块代码。 测试并在 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
没有占用任何 space 的最可能原因是您将这些项目放在了 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
中)作为 Web 视图上方的 sibling。 - 将
webView.scrollView.contentInset.top
设置为您来自 #2 的内容的大小
- 实施
UIScrollViewDelegate.scrollViewDidScroll()
- 在
scrollViewDidScroll
中修改您的内容的位置以匹配 webview 的contentOffset
。
这是一种可能的实现方式:
网络视图:
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
}
}
}
内容视图。注意:我使用 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)
}
}