SwiftUI: WebView doesn't load specific page - ios

I try to load a url in my WebView, it works well with "https://www.google.com", but it doesn't work with "https://www.sl.se".
It shows "Not Found, HTTP Error 404: The requested resource is not found."
The weird thing is that, if I google "sl.se" in Google page, click the sl.se, the the "www.sl.se" can be load the WebView. Does anyone know the reason?
ContentView.swift
// ContentView.swift
struct ContentView: View {
#State private var shouldRefresh = false
var body: some View {
VStack{
Button(action: {
self.shouldRefresh.toggle()
}){
Text("Reload")
}
WebView(url: nil, reload: $shouldRefresh)
}
}
}
WebView.Swift
// WebView.swift
import WebKit
import SwiftUI
struct WebView: UIViewRepresentable{
var url: URL? // optional, if absent, one of below search servers used
#Binding var reload: Bool
private let urls = [URL(string: "https://google.com/")!, URL(string: "https://www.sl.se")!]
private let webview = WKWebView()
fileprivate func loadRequest(in webView: WKWebView) {
if let url = url {
webView.load(URLRequest(url: url))
} else {
let index = Int(Date().timeIntervalSince1970) % 2
print("load: \(urls[index].absoluteString)")
webView.load(URLRequest(url: urls[index]))
}
}
func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
loadRequest(in: webview)
return webview
}
func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
if reload {
loadRequest(in: uiView)
DispatchQueue.main.async {
self.reload = false // must be async
}
}
}
}

There is nothing wrong with your code. You just need to remove the www from your URL. try https://sl.se and It'll work.

Related

Reload web view contents when SwiftUI button is pressed

Aim
To display a web view inside a SwiftUI view
Reload the contents of the web view when a button (SwiftUI) is pressed.
My Attempt
I have made an attempt to do the above.
Pass a Publisher (CurrentValueSubject) to the UIViewRepresentable
Observe for any new values from the publisher
If the value is true refresh the web view
Question:
Is there a better way to do this? or is my approach reasonable?
Code:
WebView
import SwiftUI
import WebKit
import Combine
struct WebView: UIViewRepresentable {
let url: URL
let shouldRefresh: CurrentValueSubject<Bool, Never>
func makeUIView(context: Context) -> some UIView {
let configuration = WKWebViewConfiguration()
let webView = WKWebView(frame: .zero, configuration: configuration)
let request = URLRequest(url: url)
context.coordinator.navigation = webView.load(request)
context.coordinator.webView = webView
context.coordinator.observeChanges(for: shouldRefresh)
return webView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
print("update view called")
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
}
extension WebView {
class Coordinator {
var navigation: WKNavigation?
var webView: WKWebView?
private var cancellable: AnyCancellable?
#MainActor
func observeChanges(for shouldRefresh: CurrentValueSubject<Bool, Never>) {
cancellable = shouldRefresh
.filter { $0 == true }
.sink { [weak self] newValue in
self?.navigation = self?.webView?.reload()
}
}
}
}
ContentView
import SwiftUI
import Combine
struct ContentView: View {
#StateObject private var model = Model()
var body: some View {
VStack {
Button("Reload") {
print("reload pressed")
reload()
}
WebView(url: URL(string: "https://www.apple.com")!,
shouldRefresh: model.shouldRefresh)
}
}
func reload() {
model.shouldRefresh.send(true)
}
}
extension ContentView {
class Model: ObservableObject {
var shouldRefresh = CurrentValueSubject<Bool, Never>(false)
}
}

WKWebView scrolls to the top after keyboard opens

I have a WKWebView app, and in the url it loads it's got editables (inputs) to type in. But in the ios app when you tap an input that's at the bottom of the page, it opens the keyboard as expected, scrolls down on the page so you can see the input as expected, then it scrolls back to the top after half a second. So I tried it in the browser, but it didn't scroll back up after a split second. Does anyone know why this is happening only in my ios app?
Here's my code:
ContentView.swift:
struct ContentView: View {
var body: some View {
WebView(url: URL(string: "https://www.mywebsitenamethatiwontshare.com")!)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
WebView.swift:
struct WebView: UIViewRepresentable {
var url: URL
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: url)
webView.load(request)
}
}
It is possible you might be causing a refresh of the view in your updateUIView function. Try removing the webView.load inside and try
struct WebView: UIViewRepresentable {
var url: URL
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
let request = URLRequest(url: url)
webView.load(request)
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
// DO NOTHING
}
}

How to open external links in a WebView Swift?

I've a question about opening external links in my webView of SwiftUI. The web page loaded properly, but I can't open external links from this website (for example: Facebook, YouTube, Mail, Phone etc). Please, who can give me a help on this one?
I added the code below:
You can call an url, like ShowWebpage("https://www.wordpress.com"), in the ContentView.
struct WebView: UIViewRepresentable {
let request: URLRequest
private var webView: WKWebView?
init(request: URLRequest) {
self.webView = WKWebView()
self.request = request
self.webView?.configuration.defaultWebpagePreferences.allowsContentJavaScript = true
}
func makeUIView(context: Context) -> WKWebView {
return webView!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
}
struct ShowWebpage : View {
//get URL Page
var urlpage: String
var body: some View {
let webview = WebView(request: URLRequest(url: URL(string: urlpage)!))
VStack{
//Load URL page
webview
}
}
}

SwiftUI View init called multiple times

I am pretty new to SwiftUI. I have a very simple view. It's just a root view that contains a WKWebView wrapped in a UIViewRepresentable. My problem is, that the init method of the UIViewRepresentable is called 6 times when the view is opened. Which means the WKWebView is initialised 6 times and all my initialisation code (setting JS callbacks, ...) is called 6 times. I added print statements to the init functions of the root view MyWebView and the subview WebView (the UIViewRepresentable). The root view init is only called once, but the subview's init is called 6 times. Is this normal? Or am I doing something wrong?
struct MyWebView: View {
#ObservedObject private var viewModel = WebViewModel()
init() {
print("root init")
}
var body: some View {
VStack(alignment: .leading, spacing: 0, content: {
WebView(viewModel: viewModel)
})
.navigationBarTitle("\(viewModel.title)", displayMode: .inline)
.navigationBarBackButtonHidden(true)
} }
struct WebView: UIViewRepresentable {
var wkWebView: WKWebView!
init(viewModel: WebViewModel) {
print("webview init")
doWebViewInitialization(viewModel: viewModel)
}
func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
let request = URLRequest(url: URL(string: "https://www.google.com")!, cachePolicy: .returnCacheDataElseLoad)
wkWebView.load(request)
return wkWebView
}
}
I'm not getting your issue of multiple calls to the init method of the UIViewRepresentable.
I modified slightly your WebView, and this is how I tested my answer:
import SwiftUI
import Foundation
import WebKit
#main
struct TestSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
MyWebView()
}.navigationViewStyle(StackNavigationViewStyle())
}
}
// for testing
class WebViewModel: ObservableObject {
#Published var title = ""
}
struct WebView: UIViewRepresentable {
let wkWebView = WKWebView()
init(viewModel: WebViewModel) {
print("\n-----> webview init")
// doWebViewInitialization(viewModel: viewModel)
}
func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
if let url = URL(string: "https://www.google.com") {
let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad)
wkWebView.load(request)
}
return wkWebView
}
func updateUIView(_ webview: WKWebView, context: UIViewRepresentableContext<WebView>) { }
}
struct MyWebView: View {
#ObservedObject private var viewModel = WebViewModel()
init() {
print("\n-----> root init")
}
var body: some View {
VStack(alignment: .leading, spacing: 0, content: {
WebView(viewModel: viewModel)
})
.navigationBarTitle("\(viewModel.title)", displayMode: .inline)
.navigationBarBackButtonHidden(true)
}
}
This leaves "doWebViewInitialization" with a possible problem spot.
You have to write your code assuming that the initializer of the View in SwiftUI will be called many times.
You write the initialization process in makeUIView(context:) in this case.
See:
https://developer.apple.com/documentation/swiftui/uiviewrepresentable/makeuiview(context:)
For example, I wrote the following code based on this answer. I added a toggle height button to this referenced code.
the -----> makeUIView log is only output once,
but the -----> webview init logs are output every time the toggle button is pressed.
import SwiftUI
import WebKit
struct ContentView: View {
var body: some View {
MyWebView()
}
}
class WebViewModel: ObservableObject {
#Published var title = ""
}
struct WebView: UIViewRepresentable {
let wkWebView = WKWebView()
init(viewModel: WebViewModel) {
print("\n-----> webview init")
}
func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
print("\n-----> makeUIView")
if let url = URL(string: "https://www.google.com") {
let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad)
wkWebView.load(request)
}
return wkWebView
}
func updateUIView(_ webview: WKWebView, context: UIViewRepresentableContext<WebView>) { }
}
struct MyWebView: View {
#State private var toggleHight = false
#ObservedObject private var viewModel = WebViewModel()
init() {
print("\n-----> root init")
}
var body: some View {
VStack {
WebView(
viewModel: viewModel
)
.frame(
height: { toggleHight ? 600 : 300 }()
)
Button(
"toggle",
action: {
toggleHight.toggle()
}
)
}
}
}
Furthermore, I realized after I wrote example code that WebView: UIViewRepresentable should not have an instance variable of wkWebView.
Please do it all(create instance and configuration) in makeUIView(context:), as shown below.
This is because instance variables are recreated every time the initializer is called.
import SwiftUI
import WebKit
struct ContentView: View {
var body: some View {
MyWebView()
}
}
class WebViewModel: ObservableObject {
#Published var title = ""
}
struct WebView: UIViewRepresentable {
init(viewModel: WebViewModel) {
print("\n-----> webview init")
}
func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
print("\n-----> makeUIView")
let wkWebView = WKWebView()
if let url = URL(string: "https://www.google.com") {
let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad)
wkWebView.load(request)
}
return wkWebView
}
func updateUIView(_ webview: WKWebView, context: UIViewRepresentableContext<WebView>) { }
}
struct MyWebView: View {
#State private var toggleHight = false
#ObservedObject private var viewModel = WebViewModel()
init() {
print("\n-----> root init")
}
var body: some View {
VStack {
WebView(
viewModel: viewModel
)
.frame(
height: { toggleHight ? 600 : 300 }()
)
Button(
"toggle",
action: {
toggleHight.toggle()
}
)
}
}
}
I struggled with this tight constraint when I was developing with UIViewControllerRepresentable. With the help of my colleagues, I managed to finish the code.
Your code has been called 6 times, so there may be some problem. but I cannot tell what the problem is from the code you provided.
It is common for init to be called multiple times in SwiftUI. We need to write code to deal with this. If your init is being called too often, you may want to look for the root cause. The code I referred to and the code I wrote are only once at startup.

WKWebview Prevent updateUIView

When creating the WKWebView I pass the web page URL via #Binding webPageURL.
I also update the displayed website via webPageURL.
The problem here is that when I call a NavigationLink and navigate back from the NavigationLink, the updateUIView method is called and the WKWebView reloads the web page.
How do I prevent the WKWebView from calling the method updateUIView when I navigate back from the NavigationLink?
struct MainView:View {
#State private var isActive = false
#Binding var stateWebPageURL:String
var body: some View {
return VStack {
MyWKWebView(webPageURL:$stateWebPageURL)
NavigationLink(destination: SecoundDestination(),isActive: $isActive) {
Text("Do Something")
}
}
}
}
struct MyWKWebView: UIViewRepresentable {
#Binding var webPageURL:String
func updateUIView(_ uiView: WKWebView, context: Context) {
let pageURL = URL(string:webPageURL)
let urlRequest = URLRequest(url: pageURL!)
uiView.load(urlRequest)
}
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView(frame: .zero,configuration: webConfiguration)
let pageURL = URL(string:webPageURL)
let urlRequest = URLRequest(url: pageURL!)
webView.load(urlRequest)
return webView
}
func makeCoordinator() -> ContentController {
ContentController()
}
}
Provided code is not testable, so just by code reading, the idea is to make explicitly MyWKWebView equatable, as below
var body: some View {
return VStack {
MyWKWebView(webPageURL:$stateWebPageURL).equatable() // << here !!
and
struct MyWKWebView: UIViewRepresentable, Equatable {
static func == (lhs: MyWKWebView, rhs: MyWKWebView) -> Bool {
lhs.webPageURL == rhs.webPageURL
}
// ... other your code
SwiftUI should not update equal views, so should work.

Resources