I create an application, with several tabs, for each tab there is a webview.
My webview:
struct WebView : UIViewRepresentable {
let request: URLRequest
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
}
The problem when im changing tab, the web view recreate again. I want create the webviews only once, then every time I change tab, it keeped state And webview will not recharge each time
My code:
struct ContentView: View {
var body: some View {
TabView {
WebView(request: URLRequest(url: URL(string: "https://www.google.com/")!))
.tabItem {
Image(systemName: "1.circle")
Text("Messenger")
}.tag(0)
WebView(request: URLRequest(url: URL(string: "https://facebook.com/login")!))
.tabItem {
Image(systemName: "2.circle")
Text("Trello")
}.tag(1)
}
}
}
Well, SwiftUI views are values, so they are always copied, but WKWebView are objects, so you don't have to re-create them (as you do in UIViewRepresentable) but you can keep reference somewhere once created.
Here is an example code that shows how this could be done. For simplicity of demo I used static, but the cache container can leave wherever suitable.
struct WebView : UIViewRepresentable {
// static is only for demo, this can be some external model
static var cache = [URL: WKWebView]()
let request: URLRequest
func makeUIView(context: Context) -> WKWebView {
guard let url = request.url else { fatalError() }
if let webView = WebView.cache[url] {
return webView
}
let webView = WKWebView()
WebView.cache[url] = webView
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
if uiView.url == nil {
uiView.load(request)
}
}
}
Related
I'm new to SwiftUI and trying to inject some custom CSS/JS into a page loaded with WKWebView:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: URL(string: "https://example.com")!)
WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{ })
webView.load(request)
webView.configuration.userContentController.addUserScript(WKUserScript( source: "alert('debug')", injectionTime: .atDocumentEnd, forMainFrameOnly: true))
}
}
Which is load like this:
struct ContentView: View {
var body: some View {
WebView()
}
}
Sadly, the code doesn't seem to actually inject anything. I've tried running it before webView.load as well. Having been googling quite a bit, I only see examples done in UIKit and unfortunately, I'm too inexperienced to wrap UIKit in a way that I can use with SwiftUI.
Any guidance would be greatly appreciated.
First of all try to avoid including business code in your views whenever you can. You may use two functions in the Webkit API if you want to include/inject JS to the webview content: EvaluateJS and AddUserScript. You may use "AddUserScript" before the "load" starts. Also please not that "alert" function in JS, would not work in current Mobile Safari. You should have see the text colors to appear in blue with the script below.
Result:
import SwiftUI
import WebKit
struct ContentView: View {
var body: some View {
VStack {
CustomWebview()
}
.padding()
}
}
struct SwiftUIWebView: UIViewRepresentable {
typealias UIViewType = WKWebView
let webView: WKWebView
func makeUIView(context: Context) -> WKWebView {
webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
final class SwiftUIWebViewModel: ObservableObject {
#Published var addressStr = "https://www.stackoverflow.com"
let webView: WKWebView
init() {
webView = WKWebView(frame: .zero)
loadUrl()
}
func loadUrl() {
guard let url = URL(string: addressStr) else {
return
}
webView.configuration.userContentController.addUserScript(WKUserScript( source: """
window.userscr ="hey this is prior injection";
""", injectionTime: .atDocumentStart, forMainFrameOnly: false))
webView.load(URLRequest(url: url))
// You will have the chance in 8 seconds to open Safari debugger if needed. PS: Also put a breakpoint to injectJS function.
DispatchQueue.main.asyncAfter(deadline: .now() + 8.0) {
self.injectJS()
}
}
func injectJS () {
webView.evaluateJavaScript("""
window.temp = "hey here!";
document.getElementById("content").style.color = "blue";
""")
}
}
struct CustomWebview: View {
#StateObject private var model = SwiftUIWebViewModel()
var body: some View {
VStack {
SwiftUIWebView(webView: model.webView)
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This is a web browser app for macOS. To keep things organized, I’m moving some bookmark buttons from ContentView to their own struct, but now the webview doesn’t load the site after clicking any of the buttons.
They only work if they’re stored inside ContentView and I'm not sure why.
struct bookmarkbuttons: View {
#State var webView: WebView?
var body: some View {
Button {
webView?.youtube()
}
...........
}
ContentView
#State var webView: WebView?
struct ContentView: View {
................
bookmarkbuttons()
webView
}
.onAppear {
webView = WebView(webModel: webModel) }
}
WebView
struct WebView: NSViewRepresentable {
#ObservedObject var webModel: WebStateModel
let wkWebview = WKWebView()
func makeNSView(context: Context) -> WKWebView {
if let theUrl = webModel.url {
let request = URLRequest(url: theUrl, cachePolicy: .returnCacheDataElseLoad)
wkWebview.load(request)
}
return wkWebview
}
}
func updateNSView(_ nsView: WKWebView, context: Context) {
if let theUrl = webModel.url {
let request = URLRequest(url: theUrl, cachePolicy: .returnCacheDataElseLoad)
nsView.uiDelegate = context.coordinator
nsView.load(request)
}
func youtube() {
wkWebview.load(URLRequest(url: (URL(string: "https://www.youtube.com")!)))
}
}
I can't see the buttons that should be on top of the web view that make it go back and forward. I don't know if this is a web view taking up the whole screen problem, or something else. I've tried making a new view with the buttons and putting the view in there. I don't know.
Here is my code:
import WebKit
import SwiftUI
struct Webview : UIViewRepresentable {
let request: URLRequest
var webview: WKWebView?
init(web: WKWebView?, req: URLRequest) {
self.webview = WKWebView()
self.request = req
}
func makeUIView(context: Context) -> WKWebView {
return webview!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
func goBack(){
webview?.goBack()
}
func goForward(){
webview?.goForward()
}
}
struct ContentView: View {
#State var searchURL = "https://google.com"
var body: some View {
VStack {
HStack {
//MARK: BACK BUTTON
Button(action: {
Webview(web: nil, req: URLRequest(url: URL(string: "\(searchURL.prefix(4) == "http" ? searchURL : "https://\(searchURL)")")!)).goBack()
}) {
Image("arrowshape.turn.up.backward")
}
//MARK: FOREWARD BUTTON
Button(action: {
Webview(web: nil, req: URLRequest(url: URL(string: "\(searchURL.prefix(4) == "http" ? searchURL : "https://\(searchURL)")")!)).goForward()
}) {
Image("arrowshape.turn.up.foreward")
}
}
Webview(web: nil, req: URLRequest(url: URL(string: "\(searchURL.prefix(4) == "http" ? searchURL : "https://\(searchURL)")")!))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The button action should not be a view in itself but should just update a variable that stores the URL to be displayed by the webview. However that is not required.
Take a look at this. Should cover all the scenarios you need without the need to manually manager URLs
I am currently using WKWebView and I added a sheet and an alert inside my wkwebview. However, every time I close the sheet or close the alarm, My WkWebView keeps navigating(loading) back to the origin domain that I specified (for this case, google.com). I an wondering how I can fix this issue.
WebView
import SwiftUI
import WebKit
struct WebView : UIViewRepresentable {
let request: URLRequest
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
}
Main View
struct MainView: View {
var webView: WebView = WebView(request: URLRequest(url: URL(string: "https://www.google.com")!))
var body: some View {
VStack(){
AlertView()
webView
SheetScreenView(webView: $webView)
}
}
AlertView()
...
Button(action: {
}, label: {}..alert(isPresented: $isBookmarked) {
Alert(title: Text(" \(webView.getURL())"), dismissButton: .default(Text("Ok")))
}
...
SheetScreenView
....
#State private var showingSheet = false
var body: some View {
Button("Show Sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
}
...
SheetView
struct SheetView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button("Press to dismiss") {
presentationMode.wrappedValue.dismiss()
}
.font(.title)
.padding()
.background(Color.black)
}
}
Your issue can be reproduced even without the sheet and alert by just rotating the device. The problem is that in updateView (which is going to get called often), you call load on the URLRequest. This is going to get compounded by the fact that you're storing a view in a var which is going to get recreated on every new render of MainView, since Views in SwiftUI are transient.
The simplest way to avoid this if your URL isn't going to change is to just call load in makeUIView:
struct WebView : UIViewRepresentable {
var request: URLRequest
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.load(request)
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
WebView(request: URLRequest(url:URL(string: "https://www.google.com")!))
}
}
If your URL may change, you need a way to compare the previous state with the new one. This seems like the shortest (but certainly not only) way to do this:
struct WebView : UIViewRepresentable {
var url: URL
#State private var prevURL : URL?
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
if (prevURL != url) {
let request = URLRequest(url: url)
uiView.load(request)
DispatchQueue.main.async { prevURL = url }
}
}
}
struct ContentView: View {
var body: some View {
WebView(url: URL(string: "https://www.google.com")!)
}
}
I got the following swiftUI code:
import SwiftUI
import WebKit
struct WebView: NSViewRepresentable {
func makeNSView(context: Context) -> WKWebView {
let view: WKWebView = WKWebView()
guard let url: URL = URL(string: "https://google.com") else { return view }
let request: URLRequest = URLRequest(url: url)
view.load(request)
return view
}
func updateNSView(_ view: WKWebView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
WebView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
The problem is that it just shows an empty view. How do I fix that?
You can use below code and it uses NSViewRepresentable
struct WebView: NSViewRepresentable {
let view: WKWebView = WKWebView()
var request: URLRequest {
get{
let url: URL = URL(string: "https://google.com")!
let request: URLRequest = URLRequest(url: url)
return request
}
}
func makeNSView(context: Context) -> WKWebView {
view.load(request)
return view
}
func updateNSView(_ view: WKWebView, context: Context) {
view.load(request)
}
}
struct ContentView: View {
var body: some View {
GeometryReader { g in
ScrollView {
WebView()
.frame(height: g.size.height)
}.frame(height: g.size.height)
}
}
}
If it still not loads then please go to capabilities and check incoming and out going connections.