Dismiss WKWebView in SwiftUI - swift

I'm new to Swift and building an application using SwiftUI.
I'm displaying a WKWebView using code that's similar to what I've written below.
struct SomeView: View {
#State var showWebView = false
var body: some View {
ZStack {
Button(action: {
self.showWebView.toggle()
}) {
Text("Go To WebView")
.padding()
.foregroundColor(.black)
.font(.title)
}
.sheet(isPresented: $showWebView, content: {
WebView(url: "https://www.apple.com/")
})
}
}
}
struct WebView: UIViewRepresentable {
var url: String
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
return webView
}
func updateUIView(_ uiView: WebView.UIViewType, context: UIViewRepresentableContext<WebView>) {
let urlRequest = URLRequest.init(url: URL.init(string: self.url)!)
uiView.navigationDelegate = context.coordinator
uiView.load(urlRequest)
}
}
class Coordinator: NSObject, WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url?.absoluteString {
if url.contains("apple") {
let someViewModel = SomeViewModel()
someViewModel.processRequest(url: url)
}
}
decisionHandler(.allow)
}
}
The code works as expected. I'm able to display a WKWebView showing the desired website. However, I'm struggling to figure out how to dismiss the WKWebView and navigate to another page once I've invoked someViewModel.processRequest(). Could you please assist?

I would do the whole WebView thing a bit different, which should (as far as I understand your problem) solve your situation:
import SwiftUI
import SafariServices
struct SomeView: View {
#State var showWebView = false
var body: some View {
ZStack {
Button(action: {
self.showWebView.toggle()
}) {
Text("Go To WebView")
.padding()
.foregroundColor(.black)
.font(.title)
}
.sheet(isPresented: $showWebView, content: {
WebView(url: URL(string: "https://www.apple.com/")!) })
}
}
}
struct WebView: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context: UIViewControllerRepresentableContext<WebView>) -> SFSafariViewController {
return SFSafariViewController(url: url)
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: UIViewControllerRepresentableContext<WebView>) {
}
}

Related

SwiftUI WebView: This method should not be called on the main thread as it may lead to UI unresponsiveness

I'm trying to have a list of NavigationLinks that show a website in full screen. But when I tap on the NavigationLink I get the warning: "This method should not be called on the main thread as it may lead to UI unresponsiveness."
Here is a small example to reproduce the error:
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink("Apple") {
WebViewScreen(url: URL(string: "https://www.apple.com")!)
}
}
}
}
}
struct WebViewScreen: View {
let url: URL
#State private var isWebViewLoading = true
var body: some View {
ZStack {
WebViewRepresentable(
isLoading: $isWebViewLoading,
url: url
)
if isWebViewLoading {
ProgressView()
.scaleEffect(1.5)
.tint(.accentColor)
}
}
}
}
struct WebViewRepresentable: UIViewRepresentable {
#Binding var isLoading: Bool
let url: URL
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView(frame: .zero)
webView.navigationDelegate = context.coordinator
let request = URLRequest(url: url)
webView.load(request)
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {}
func makeCoordinator() -> WebViewCoordinator {
WebViewCoordinator(isLoading: $isLoading)
}
}
class WebViewCoordinator: NSObject, WKNavigationDelegate {
#Binding var isLoading: Bool
init(isLoading: Binding<Bool>) {
_isLoading = isLoading
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
isLoading = true
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
isLoading = false
}
}
The warning does not appear without if I only show the WebViewScreen.

How to search a custom text from textfield in webkit swiftUI

I am building a small browser in swift ui i have build a struct to represent the wkwebview and i want to enter any text in a textfield and search on the internet using wkwebview and a google query i tried to reinstantiate the Webview(web: nil, req: URLRequest(url: URL(string: searchText)!)) but its not working for me? how can i achieve this i am pretty new to SwiftUI.Please help?
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()
}
func refresh(){
webview?.reload()
}
}
import SwiftUI
struct ContentView: View {
var webview = Webview(web: nil, req: URLRequest(url: URL(string: "https://www.apple.com")!))
#State private var searchText = ""
#State private var txt = ""
var body: some View {
ZStack {
HStack {
TextField("Search", text: $searchText,onCommit: {
print(searchText)
}
)
.keyboardType(.URL)
.frame(width: UIScreen.main.bounds.size.width * 0.75 )
}
}
webview
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button(action: {
webview.goBack()
}) {
Image(systemName: "arrow.left")
}
Spacer()
Button(action: {
webview.goForward()
}) {
Image(systemName: "arrow.right")
}
Spacer()
Button(action: {
webview.refresh()
}) {
Image(systemName: "arrow.clockwise")
}
}
}
}
}
The way that you have things set up right now (attempting to store a reference to a UIViewRepresentable) is going to lead to problems later on (like this, in fact).
I suggest you set up an interim object of some sort that can be used to store data and communicate imperatively to the WKWebView while still retaining the ability to lay things out declaratively. In my example, WebVewManager is that interim object that both the parent view and the UIViewRepresentable have access to:
class WebViewManager : ObservableObject {
var webview: WKWebView = WKWebView()
init() {
webview.load(URLRequest(url: URL(string: "https://apple.com")!))
}
func searchFor(searchText: String) {
if let searchTextNormalized = searchText.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "https://google.com/search?q=\(searchTextNormalized)") { self.loadRequest(request: URLRequest(url: url))
}
}
func loadRequest(request: URLRequest) {
webview.load(request)
}
func goBack(){
webview.goBack()
}
func goForward(){
webview.goForward()
}
func refresh(){
webview.reload()
}
}
struct Webview : UIViewRepresentable {
var manager : WebViewManager
init(manager: WebViewManager) {
self.manager = manager
}
func makeUIView(context: Context) -> WKWebView {
return manager.webview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
struct ContentView: View {
#StateObject private var manager = WebViewManager()
#State private var searchText = ""
#State private var txt = ""
var body: some View {
ZStack {
HStack {
TextField("Search", text: $searchText,onCommit: {
print(searchText)
manager.searchFor(searchText: searchText)
})
.keyboardType(.URL)
.frame(width: UIScreen.main.bounds.size.width * 0.75 )
}
}
Webview(manager: manager)
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button(action: {
manager.goBack()
}) {
Image(systemName: "arrow.left")
}
Spacer()
Button(action: {
manager.goForward()
}) {
Image(systemName: "arrow.right")
}
Spacer()
Button(action: {
manager.refresh()
}) {
Image(systemName: "arrow.clockwise")
}
}
}
}
}
I'm doing some simple percent encoding on the search string -- you may need to do more testing to make sure that google always accepts the search query, but this looks like it's working to me.

How do I display a MathJax String in a SwiftUI ScrollView?

I receive a MathJax String from an API response which needs to be shown in a WKWebView in SwiftUI. Here's a sample String
"<img alt=\"\" height=\"2305\" src=\"https:\/\/s3-us-west-2.amazonaws.com\/infinitestudent-images\/ckeditor_assets\/pictures\/37732\/content_types_of_combustion.jpg\" width=\"800\" \/>"
Here's the code that I've tried:
import SwiftUI
import WebKit
struct Model: Codable {
var mathjaxBody: String?
var answer: Answer
}
struct MathJaxView: View {
let model: Model
var body: some View {
ScrollView {
AnswerSectionView(answer: model.answer)
HTMLView(htmlString: model.mathjaxBody)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
}
}
}
struct HTMLView: UIViewRepresentable {
var htmlString: String?
func makeUIView(context: Context) -> WKWebView {
WKWebView(frame: .zero)
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard let string = htmlString else { return }
uiView.loadHTMLString(string, baseURL: nil)
}
}
It need to be in a ScrollView with a few different details on top of it, which is displayed properly in the ScrollView. But, the HTMLView doesn't show up. I tried adding .frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity) But still doesn't show up.
The solution was to simply calculate the required height from evaluateJavaScript("document.body.scrollHeight") method and apply it to the WebView component with a few other methods.
struct WebView: UIViewRepresentable {
var htmlString: String?
#EnvironmentObject var viewModel: WebViewModel
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView(frame: .zero)
webView.backgroundColor = .clear
webView.navigationDelegate = context.coordinator
webView.scrollView.isScrollEnabled = false
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard let string = htmlString else { return }
uiView.loadHTMLString(string, baseURL: nil)
}
class Coordinator: NSObject, WKNavigationDelegate {
private var viewModel: WebViewModel
init(viewModel: WebViewModel) {
self.viewModel = viewModel
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.readyState") { (complete, error) in
if complete != nil {
webView.evaluateJavaScript("document.body.scrollHeight") { (height, error) in
if let height = height as? CGFloat, self.viewModel.contentHeight == 0 {
self.viewModel.contentHeight = height * 0.4
}
}
}
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(viewModel: viewModel)
}
}
class WebViewModel: ObservableObject {
#Published var contentHeight: CGFloat = 0
}
struct MathJaxView: View {
let model: Model
var body: some View {
ScrollView {
AnswerSectionView(answer: model.answer)
WebView(htmlString: model.mathjaxBody)
.environmentObject(webViewModel)
.frame(height: webViewModel.contentHeight)
.frame(maxWidth: .infinity)
.fixedSize(horizontal: false, vertical: true)
}
}
}

Sheet and alert keeps refreshing my page for WKWebView SwiftUI

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")!)
}
}

Why does my WKWebView not show up in a swiftUI view?

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.