Initializing issue between Binding (in WebView) and State (in View) - ios

This issue is an initialization problem encountered in compiling code to transport variables between web views and views. I've asked 3 months ago(previous question was asked here.). I haven't been able to solve it yet. I gave up on solving and found for other alternatives, but wanted to know the cause or solution to the issue, so I re-identified the issue and recreated the question.
I'm implementing a sheet between a Web view and a View. The values ​​in the sheet are always changing. The content of the sheet called from the view must be shared by the web view (or reverse). There is no problem when mounting the web view directly inside the view. But the issue occurs when using with 'let' expressions. The following error message appears right before compilation:
"...Cannot use instance member '$activeSheet' within property initializer; property initializers run before 'self' is available..."
I haven't only used init(){...} in the view and but also used various initializing methods, but the initialization error message does not disappear. The most cause of this issue seems to be a problem with init, which sends #State from the view to web view and requests the value of #Binding down from the Web view to View again.
Is there any way?
struct ItlnrView: View {
#State var activeSheet: **Bool** = false
.....
let webView = myWebView(web: nil, actSheet: **$activeSheet**, req: URLRequest(url: URL(string: "https://google.com")!)). <--- problem
var body: some View {
VStack {
webView
// myWebView(web: nil, actSheet: $activeSheet, req: URLRequest(url: URL(string: "https://google.com")!)) <--- no problem
}
.sheet(isPresented: $activeSheet) {
// code
}
}
}
struct myWebView: UIViewRepresentable {
let request: URLRequest
var webview: WKWebView?
#Binding var activeSheet: Bool
init(web: WKWebView?, actSheet: **Binding<Bool>**, req: URLRequest) {
self.webview = WKWebView()
self._activeSheet = actSheet
self.request = req
}
....
....
}
If I don't use let webView and run myWebView(web:,actSheet:,req:) in V/HStack directly, the sheet value is compatible/shared properly. But I must use/call by let webView because I have to use the 'go back' and 'reload' functions of Web view. So whenever I use let webView, the initialization error message occurs on $activeSheet.
Anyway, this Web view must be use instance member $activeSheet within property initializer through let webView....
I hope you to be understand my lacked question.

I solved it through Lorem Ipsum advised ('Put the webview in the body') and it works fine.
struct ItlnrView: View {
#State var activeSheet: **Bool** = false
.....
var body: some View {
let webView = myWebView(web: nil, actSheet: $activeSheet, req: URLRequest(url: URL(string: "https://google.com")!))
VStack {
webView
}
.sheet(isPresented: $activeSheet) {
// code
}
}
}
struct myWebView: UIViewRepresentable {
let request: URLRequest
var webview: WKWebView?
#Binding var activeSheet: Bool
init(web: WKWebView?, actSheet: **Binding<Bool>**, req: URLRequest) {
self.webview = WKWebView()
self._activeSheet = actSheet
self.request = req
}
....
....
}

Related

Swift Webkit Webview with WebRTC produces a Black Video Screen

I am having an issue that only occurs with Swift, Webkit abd WebRTC. I am creating a webview of this url:
https://widgets.bingewave.com/webrtc/6b8bcc00-864d-4c97-8e15-0b3aed37946b
The complete code is here: https://github.com/BingeWave/bingewave-swift-video-app
The code for creating the Webview is this:
Webview
import SwiftUI
import WebKit
struct SwiftUIWebView: UIViewRepresentable {
let url: URL?
func makeUIView(context: Context) -> CustomWebView {
//Preferences
let prefs = WKWebpagePreferences()
prefs.allowsContentJavaScript = true
//Create a congif
let config = WKWebViewConfiguration()
//Add
config.defaultWebpagePreferences = prefs
return CustomWebView(
frame: .zero,
configuration: config
)
}
func updateUIView(_ uiView: CustomWebView, context: Context) {
guard let myURL = url else {
return
}
let request = URLRequest (url: myURL)
uiView.load(request)
}
CustomWebView.swift
//
// CustomWebView.swift
// BingWave Sample App Swift
//
// Created by Ayusma on 07/04/2022.
//
import WebKit
class CustomWebView: WKWebView {
//Change th Auth Token with that of a user
let AUTH_TOKEN = "xxxxxxx";
override func load(_ request: URLRequest) -> WKNavigation? {
guard let mutableRequest: NSMutableURLRequest = request as? NSMutableURLRequest else {
return super.load(request)
}
mutableRequest.setValue(AUTH_TOKEN, forHTTPHeaderField: "Authorization")
return super.load(mutableRequest as URLRequest)
}
}
ContentView
import SwiftUI
struct ContentView: View {
let BINGWAVE_URL = URL(string: "https://widgets.bingewave.com/webrtc/6b8bcc00-864d-4c97-8e15-0b3aed37946b")
var body: some View {
NavigationView{
SwiftUIWebView(url: BINGWAVE_URL)
.navigationTitle("BingWave Sample App")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The issue is the screen loads the view, I am able to join the video chat but my camera video black and if another user joins, their video is a black within the app. What is weird is they can see me and themsevles on their end, so the video is only black on the iOS app. And this works for other implements like a React Native Webview, it only fails for this implementation with Swift.
Am I missing anything?

How to make WKWebView the first responder and respond to keyboard shortcuts after scrolling?

I've laid out a couple WKWebViews in SwiftUI (using UIViewRepresentable) for an iPad app.
When I simply click on one those views, it gets focus, and I can use various keyboard shortcuts specific to the website (in the example code below the pages use these DuckDuckGo shortcuts - https://help.duckduckgo.com/duckduckgo-help-pages/features/keyboard-shortcuts/ ), as well as up/down keys to scroll etc.
However, if I just scroll a web view, then it doesn't get focus.
How would I make the page get focus (becomeFirstResponder in UIKit terms) as soon as the web page is scrolled. Additionally, how could I show the focused web view in the UI (for instance, by making the web views border red)?
My code:
import WebKit
struct ContentView: View {
var body: some View {
VStack {
Webview(url: URL(string:"https://duckduckgo.com/?q=stack+overflow&t=h_&ia=web")!)
Divider()
Webview(url: URL(string:"https://duckduckgo.com/?q=ipad&t=h_&ia=web")!)
}
}
}
struct Webview: UIViewRepresentable {
let url: URL
func makeUIView(context: UIViewRepresentableContext<Webview>) -> WKWebView {
let webview = WKWebView()
let request = URLRequest(url: self.url, cachePolicy: .returnCacheDataElseLoad)
webview.load(request)
return webview
}
func updateUIView(_ webview: WKWebView, context: UIViewRepresentableContext<Webview>) {
let request = URLRequest(url: self.url, cachePolicy: .returnCacheDataElseLoad)
webview.load(request)
}
}

How do I present an alert from a custom class in swiftui?

In SwiftUI, I have a network request running in scenedelegate, scenedidbecomeactive. I don't know which view the user will be on when the app becomes active, but I want to present an alert if the data in the network request changes. I simplified the code below, so it's easy to read...
func sceneDidBecomeActive(_ scene: UIScene) {
let customClass = CustomClass()
customClass.performNetworkRequest()
In CustomClass, i have...
func performNetWorkRequest() {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let d = data {
let response = try JSONDecoder().decode(DetailResponse.self, from: d)
DispatchQueue.main.async {
//Here is where I want to either present an alert, but I can't figure out how to.
//OR do i put a func in SceneDeletegate to present the alert on the window.rootviewcontroller and then just call that func from here?
}
Any help is much appreciated!
Paul has a point - here's a possible implementation:
// In CustomClass.swift
import Combine
class CustomClass : ObservableObject {
#Published var dataRecieved = PassthroughSubject<DetailResponse, Never>()
init() {
performNetWorkRequest()
}
func performNetWorkRequest() {
URLSession.shared.dataTask(with: url) { (data, response, error) in
let response = try JSONDecoder().decode(DetailResponse.self, from: data)
DispatchQueue.main.async {
self.dataRecieved.send(response)
}
}
.resume()
}
}
// In SomeView.swift
import SwiftUI
import Combine
struct ContentView: View {
#State var showAlert = false
var customClass = CustomClass()
var body: some View {
Text("Hello, World!")
.onReceive(customClass.dataRecieved) { _ in
self.showAlert = true
}
.alert(isPresented: $showAlert) {
// your alert
}
}
}
Notice I didn't mention the SceneDelegate in any of these - this approach (called MVVM) is more flexible, in my opinion - besides, the way it is set up, performNetWorkRequest() will be executed as soon as your view is initialized, anyway.
You can also tweak the PassthroughSubject - I didn't know if you needed the DetailResponse or not.
Hope this helped!
Edit:
I just reread your question and it seems that this implementation is at fault as you noted there was no way to know what view the user would be on in the case of a network change. In that case, you can feed the same instance of CustomClass in your SceneDelegate as an EnvironmentObject.

SwiftUI: Do not refresh a custom view MacOS app

Hello I want to create an application with several webview that once they have been loaded once, I don't want them to be recreated anymore I want them to keep their instance.
I create in my application A navigation view which includes a list of 10 sites. When I click on a item of the list I want to display the corresponding webview with NavigationLink. It works. But the problem is that if I click on another item of the list and I will return to the previous one it returns to the home page, the webview loads again. I want the webview to be created only once and always stay alive in as long as the app is alive. I know that swiftui always refresh views is the problem, how can I prevent my webview from being refreshed by swiftUI?
In uitkit it's simple I create an array of wkwebview at launch of the app, I load all my webview url, in a singleton class. And depending on the item selected from my tableview I display the corresponding wkwebview. And even if I change the item all my webview are alive even if we don't see them.
struct WebView : UIViewRepresentable {
let request: URLRequest
func makeUIView(context: Context) -> WKWebView {
let web = WKWebView()
web.load(request)
return web
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
Here is simplified demo of possible approach. The idea is to cache created WKWebView objects by some identifiers and just reuse them in created (or recreated) thin-wrapper SwiftUI view representable.
Tested & works with Xcode 11.2 / iOS 13.2
struct WebView: UIViewRepresentable {
private static var cache: [Int: WKWebView] = [:]
// the only allowed entry point to create WebView, so have control either
// to create new instance of WKWebView or provide already loaded
static func view(with id: Int, request: URLRequest) -> WebView {
var web = cache[id] // it is UI thread so safe to access static
if web == nil {
web = WKWebView()
cache[id] = web
}
return WebView(with: web!, request: request)
}
private init(with web: WKWebView, request: URLRequest) {
self.web = web
self.request = request
}
private let web: WKWebView
private let request: URLRequest
func makeUIView(context: Context) -> WKWebView {
if web.url == nil { // just created, so perform initial loading
web.load(request)
}
return web
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
// Just simple test view
struct TestOnceLoadedWebView: View {
private let urls: [String] = [
"http://www.apple.com",
"http://www.google.com",
"http://www.amazon.com"
]
var body: some View {
NavigationView {
List(0 ..< urls.count) { i in
NavigationLink("Link \(i)", destination:
WebView.view(with: i, request:
URLRequest(url: URL(string: self.urls[i])!)))
}
}
}
}

Navigate SwiftUI page from WKWebView operation

I am a newbie in IOS Programming and also SwiftUI. Coming from Java/Kotlin Android. I want to learn SwiftUI.
I have a WKWebView. I want to change SwiftUI page according to url changes in WKWebView. I have done a lot of work as you can see below. But I am struggled in navigation.
struct ContentView: View, Listener {
func onFetched() {
NavigationLink(destination: MainView()) {/* HERE this is not working.
App never goes to MainView.swift page.*/
Text("Show Detail View")
}
}
var body: some View {
NavigationView {
VStack {
WebView(authListener: self)
}.navigationBarTitle(Text("PEAKUP Velocity"))
}
}
}
struct WebView: UIViewRepresentable {
var listener: Listener
#ObservedObject var observe = observable()
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
observe.observation = uiView.observe(\WKWebView.url, options: .new) { view, change in
if let url = view.url {
self.observe.loggedIn = true // We loaded the page
self.listener.onFetched()
uiView.isHidden = true
}
}
uiView.load("https://google.com")
}
}
protocol Listener {
func onFetched()
}
Update: I tried this in onFetched():
NavigationLink(destination: MainView()) { Text("") }''''
And I tried this piece of code:
NavigationView {
NavigationLink(destination: SecondView()){
Text("Navigation Link")
}
}
Update 2: I tried this code also in onFetched:
self.presentation(Model(MainView(), onDismiss: nil))
Update 3: I tried this:
self.sheet(isPresented: $sayHello) {
MainView()
}
** Disclaimer I haven't used WKWebviews in the context of SwiftUI, but I assume same would be true. **
I would recommend looking into WKNavigationDelegate. Unless your class is of type WKWebView you need to ensure to assign a delegate to handle any navigation events.
Perhaps you'll find the below article helpful: https://www.hackingwithswift.com/articles/112/the-ultimate-guide-to-wkwebview

Resources