goForward and goBack does not work in swiftui - xcode

I am trying to add the back and forward button in the swiftui but the buttons do not work. The URL comes from a firebase.
I followed this question How to access goBack and goForward via UIViewRepresentable. If anyone help me, I am very thankful to you.
Here is the source code
import SwiftUI
import WebKit
import Firebase
struct WebView: View {
#State var websiteURL: String
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
ZStack{
Color.init(red: 172/255, green: 198/255, blue: 224/255).edgesIgnoringSafeArea(.all)
VStack{
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image("back").renderingMode(.original).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 60, alignment: .leading).padding()
}
Webview(web: nil, req: URLRequest(url: URL(string: websiteURL)!))
HStack{
Button(action: {
//go to back
Webview(web: nil, req: URLRequest(url: URL(string: websiteURL)!)).goBack()
}) {
Image(systemName: "arrow.left").foregroundColor(.black)
}
Spacer()
Button(action: {
// go forwared
Webview(web: nil, req: URLRequest(url: URL(string: websiteURL)!)).goForward()
}) {
Image(systemName: "arrow.right").foregroundColor(.black)
}
}.padding([.leading,.trailing],20)
}.navigationBarTitle("")
.navigationBarHidden(true)
}.onAppear{
self.loadURL()
}
}
func loadURL(){
//For Admin
let rootRef = Database.database().reference().child("Admin").child(websiteURL)
rootRef.observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let getURL:String = value?["value"] as? String ?? ""
self.websiteURL=getURL
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
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 WebView_Previews: PreviewProvider {
static var previews: some View {
WebView(websiteURL: "")
}
}

I stand by my comment, you create 3 different Webview. This is one way to make it work.
Copy and paste this code and let me know if that works for you.
import SwiftUI
import WebKit
import Firebase
struct WebView: View {
#State var websiteURL: String
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
let websiteView = Webview()
var body: some View {
ZStack{
Color.init(red: 172/255, green: 198/255, blue: 224/255).edgesIgnoringSafeArea(.all)
VStack{
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image("back").renderingMode(.original).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 60, alignment: .leading).padding()
}
self.websiteView
HStack{
Button(action: {
// go to back
self.websiteView.webview.goBack()
}) {
Image(systemName: "arrow.left").foregroundColor(.black)
}
Spacer()
Button(action: {
// go forwared
self.websiteView.webview.goForward()
}) {
Image(systemName: "arrow.right").foregroundColor(.black)
}
}.padding([.leading,.trailing],20)
}.navigationBarTitle("")
.navigationBarHidden(true)
}.onAppear{
self.loadURL()
}
}
func loadURL() {
//For Admin
let rootRef = Database.database().reference().child("Admin").child(websiteURL)
rootRef.observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let getURL:String = value?["value"] as? String ?? ""
self.websiteURL = getURL
// new code <-----
self.websiteView.loadRequest(request: URLRequest(url: URL(string: self.websiteURL)!))
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
struct Webview : UIViewRepresentable {
#State var request: URLRequest?
let webview = WKWebView()
func makeUIView(context: Context) -> WKWebView {
return webview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
if request != nil { uiView.load(request!) }
}
func loadRequest(request: URLRequest) {
self.request = request
webview.load(request)
}
func goBack(){
webview.goBack()
}
func goForward(){
webview.goForward()
}
}

Related

Back Button not working quickly, Take 4-5 Second Webview to back, How to get Progress SwiftUI

I'm writing a Webapp in swiftUI with xcode13.1 I've a question "How can I add a progress bar that shows me the loading of page when user click on back button?
import SwiftUI
import WebKit
struct WebViewForCommunityTab : 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 goHomeScreen(){
webview?.load(URLRequest(url: (URL(string: DefaultUrlString.CommunityWebLink)!)))
}
}
struct WebContentView: View {
let webview = WebViewForHomeTab(web: nil, req: URLRequest(url: URL(string: DefaultUrlString)!))
var body: some View {
ZStack {
VStack {
VStack (spacing: 80) {
webview
}
HStack (alignment: .center) {
Group {
Spacer()
CustomButtonForWebView(btnImageTitle: ArrowLeftCircleImg, action: {
self.webview.goBack()
})
CustomButtonForWebView(btnImageTitle: ArrowRightCircleImg, action: {
self.webview.goForward()
})
Spacer()
}.padding(.bottom,10)
}.frame(width: UIScreen.screenWidth, height: 40, alignment: .center)
}.hiddenNavigationBarStyle().background(Color.white)
}
}
}
Question: How to get Progress in the WKWebView when user click on back button?
Can someone please explain to me How to get Progress?
Any help would be greatly appreciated.
Thanks in advance.

How to invoke a method on a NSViewRepresentable from a View?

I'm having a hard time to understand how NSViewRepresentable interacts with a SwiftUI view.
For example, in the example below, how can I load a different URL when the button in the view is clicked?
import Cocoa
import SwiftUI
import WebKit
import PlaygroundSupport
struct InternalBrowser: NSViewRepresentable {
func makeNSView(context: Context) -> WKWebView {
let browser = WKWebView()
browser.load(URLRequest(url: URL(string: "https://www.google.com")!))
return browser
}
func updateNSView(_ nsView: WKWebView, context: Context) {
}
}
struct Browser: View {
var body: some View {
GeometryReader { g in
VStack {
InternalBrowser().frame(width: 400, height: 400)
Button(action: {
// how to tell InternalBrowser to load an URL here?
}) {
Text("Load Different URL")
}
}
}
}
}
PlaygroundPage.current.setLiveView(Browser().frame(width: 500, height: 500))
how can I load a different URL when the button in the view is clicked?
Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4
struct InternalBrowser: NSViewRepresentable {
#Binding var url: URL?
func makeNSView(context: Context) -> WKWebView {
return WKWebView()
}
func updateNSView(_ browser: WKWebView, context: Context) {
if let url = self.url, url != browser.url {
browser.load(URLRequest(url: url))
}
}
}
struct Browser: View {
#State var resourceURL = URL(string: "https://www.google.com")
var body: some View {
VStack {
Button(action: {
self.resourceURL = URL(string: "https://www.stackoverflow.com")
}) {
Text("Load Different URL")
}
Divider()
InternalBrowser(url: self.$resourceURL).frame(width: 400, height: 400)
}
}
}

How to display an image where the user taps? SwiftUI

I am making a mining tapping game and I want to display a hammer wherever the user taps.
I mean, wherever the user taps the hammer image will stay on for one second.
Is there a way to do it?
My example code is below:
struct Level1: View {
#State var tapScore = 0
#State var showingMinedHammer = false
func showMinedHammer() {
self.showingMinedHammer = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.showingMinedHammer = false
}
}
func mine() {
tapScore += 1
showMinedHammer()
}
var body: some View {
GeometryReader { geometryProxy in
ZStack {
Image("mine1").resizable().frame(width: UIScreen.main.bounds.height * 1.4, height: UIScreen.main.bounds.height)
.onTapGesture {
self.mine()
}
if self.showingMinedHammer {
Image(systemName: "hammer.fill")
.resizable()
.frame(width: 30, height: 30)
}
}
}.edgesIgnoringSafeArea(.all)
}
}
It just need to read location of tap and use it as position for hammer image, like below - all by SwiftUI
Tested with Xcode 11.4 / iOS 13.4
Here is modified only part
#State private var location = CGPoint.zero // < here !!
var body: some View {
GeometryReader { geometryProxy in
ZStack {
Image("mine1").resizable().frame(width: UIScreen.main.bounds.height * 1.4, height: UIScreen.main.bounds.height)
.gesture(DragGesture(minimumDistance: 0).onEnded { value in
self.location = value.location // < here !!
self.mine()
})
if self.showingMinedHammer {
Image(systemName: "hammer.fill")
.resizable()
.frame(width: 30, height: 30)
.position(self.location) // < here !!
}
}
}.edgesIgnoringSafeArea(.all)
}
To get the location of where you tapped, you can do something like this:
import SwiftUI
struct ContentView: View {
#State var points:[CGPoint] = [CGPoint(x:0,y:0), CGPoint(x:50,y:50)]
var body: some View {
ZStack{
GetTapLocation {
// tappedCallback
location in
self.points.append(location)
print(self.points)
}
}
}
}
struct GetTapLocation:UIViewRepresentable {
var tappedCallback: ((CGPoint) -> Void)
func makeUIView(context: UIViewRepresentableContext<GetTapLocation>) -> UIView {
let v = UIView(frame: .zero)
let gesture = UITapGestureRecognizer(target: context.coordinator,
action: #selector(Coordinator.tapped))
v.addGestureRecognizer(gesture)
return v
}
class Coordinator: NSObject {
var tappedCallback: ((CGPoint) -> Void)
init(tappedCallback: #escaping ((CGPoint) -> Void)) {
self.tappedCallback = tappedCallback
}
#objc func tapped(gesture:UITapGestureRecognizer) {
let point = gesture.location(in: gesture.view)
self.tappedCallback(point)
}
}
func makeCoordinator() -> GetTapLocation.Coordinator {
return Coordinator(tappedCallback:self.tappedCallback)
}
func updateUIView(_ uiView: UIView,
context: UIViewRepresentableContext<GetTapLocation>) {
}
}
There has to be a simpler implementation, but until then you can get the location where you tapped. I hope that helps :)

Why does my TabView change back to Home tab when image loads?

I created a TabView with two tabs. One is Home and the other loads text and an image from NASA pic of the day API. When I change to the NASA pic of the day, I see "Loading data" until the data loads. Once the data is loaded, for some reason the tab switches back to the "Home" tab. After this bug happens, I can switch back and forth between the two tabs normally and everything is loaded. Why does the tab get switched back to the home tab? Thank you!!
APIImageView Code:
import SwiftUI
struct ApiImageView: View {
#ObservedObject var apiImage = ApiImage()
var body: some View {
Group {
if apiImage.dataHasLoaded {
VStack {
Text(apiImage.title!)
.font(.largeTitle)
Image(uiImage: apiImage.image!).resizable()
.cornerRadius(10)
.padding()
ScrollView(.vertical, showsIndicators: false) {
Text(apiImage.explanation!)
.font(.subheadline)
.padding()
}
}
} else {
Text("Loading Data")
}
}.onAppear {
self.apiImage.loadImageFromApi(urlString: "https://api.nasa.gov/planetary/apod?api_key=eaRYg7fgTemadUv1bQawGRqCWBgktMjolYwiRrHK")
}
}
}
struct ApiImageView_Previews: PreviewProvider {
static var previews: some View {
ApiImageView()
}
}
APIImage Code:
import SwiftUI
class ApiImage: ObservableObject {
#Published var dataHasLoaded = false
#Published var image: UIImage? = nil
#Published var title: String? = nil
#Published var explanation: String? = nil
}
extension ApiImage {
func loadImageFromApi(urlString: String) {
let url = URL(string: urlString)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: parseJsonObject)
task.resume()
}
func parseJsonObject(data: Data?, urlResponse: URLResponse?, error: Error?) {
guard error == nil else {
print("\(error!)")
return
}
guard let content = data else {
print("No data")
return
}
let json = try! JSONSerialization.jsonObject(with: content)
let jsonmap = json as! [String : Any]
let titleText = jsonmap["title"] as! String
let explanationText = jsonmap["explanation"] as! String
let urlString = jsonmap["url"] as! String
print("\(urlString)")
print("\(titleText)")
print("\(explanationText)")
DispatchQueue.main.async {
self.title = titleText
self.explanation = explanationText
}
let url = URL(string: urlString)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: setImageFromData)
task.resume()
}
func setImageFromData(data: Data?, urlResponse: URLResponse?, error: Error?) {
guard error == nil else {
print("\(error!)")
return
}
guard let content = data else {
print("No data")
return
}
DispatchQueue.main.async {
self.image = UIImage(data: content)
self.dataHasLoaded = true
}
}
}
MainTabView Code:
import SwiftUI
struct MainTabView: View {
var body: some View {
TabView {
CategoryHome()
.tabItem {
Image(systemName: "house.fill")
Text("Landmarks")
.tag(0)
}
ApiImageView()
.tabItem {
Image(systemName: "flame.fill")
Text("NASA Pic")
//.tag(1)
}
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
}
}
Maybe not directly a solution, but also important: it seems that the MainTabView is not entirely correct (the .tag() should be outside the .tabItem closure). This would be a correct version:
import SwiftUI
struct MainTabView: View {
var body: some View {
TabView {
CategoryHome()
.tabItem {
Image(systemName: "house.fill")
Text("Landmarks")
}.tag(0)
ApiImageView()
.tabItem {
Image(systemName: "flame.fill")
Text("NASA Pic")
}.tag(1)
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
}
}
Maybe this is already the solution; if not, I hope it is still helpful! :)

UIView[Controller]Representable: SwiftUI Subview is shown in Debugger, but not when run

Because the ScrollView does not provide a function to set the contentOffset, I'm trying to use the UIScrollView as UIViewRepresentable. The attached code shows both, the caller and the definition of the view and the view controller.
When running the code in simulator or previews, just a blue area is shown. When debugging the display, the Text is shown, as expected.
If have no idea about the reason - is it because I'm doing something wrong, or because there's a bug in Xcode or SwiftUI?
Here the custom scroll view:
struct PositionableScrollView<Content>: UIViewRepresentable where Content: View {
var content: () -> Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
func makeUIView(context: UIViewRepresentableContext<PositionableScrollView<Content>>) -> UIScrollView {
let scrollViewVC = PositionableScrollViewVC<Content>(nibName: nil, bundle: nil)
scrollViewVC.add(content: content)
let control = scrollViewVC.scrollView
return control
}
func updateUIView(_ uiView: UIScrollView, context: UIViewRepresentableContext<PositionableScrollView<Content>>) {
// Do nothing at the moment.
}
}
The view controller:
final class PositionableScrollViewVC<Content>: UIViewController where Content: View {
var scrollView: UIScrollView = UIScrollView()
var contentView: UIView!
var contentVC: UIViewController!
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
self.view.addSubview(self.scrollView)
self.scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
self.scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
self.scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
self.scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
}
override func viewDidLoad() {
super.viewDidLoad()
debugPrint("self:", self.frame())
debugPrint("self.view:", self.view!.frame)
debugPrint("self.view.subviews:", self.view.subviews)
// debugPrint("self.view.subviews[0]:", self.view.subviews[0])
// debugPrint("self.view.subviews[0].subviews:", self.view.subviews[0].subviews)
}
func add(#ViewBuilder content: #escaping () -> Content) {
self.contentVC = UIHostingController(rootView: content())
self.contentView = self.contentVC.view!
self.scrollView.addSubview(contentView)
self.contentView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor).isActive = true
self.contentView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor).isActive = true
self.contentView.topAnchor.constraint(equalTo: self.scrollView.topAnchor).isActive = true
self.contentView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor).isActive = true
self.contentView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.widthAnchor.constraint(greaterThanOrEqualTo: self.scrollView.widthAnchor).isActive = true
}
}
extension PositionableScrollViewVC: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<PositionableScrollViewVC>) -> PositionableScrollViewVC {
let vc = PositionableScrollViewVC()
return vc
}
func updateUIViewController(_ uiViewController: PositionableScrollViewVC, context: UIViewControllerRepresentableContext<PositionableScrollViewVC>) {
// Do nothing at the moment.
}
}
The callers:
struct TimelineView: View {
#State private var posX: CGFloat = 0
var body: some View {
GeometryReader { geo in
VStack {
Text("\(self.posX) || \(geo.frame(in: .global).width)")
PositionableScrollView() {
VStack {
Spacer()
Text("Hallo")
.background(Color.yellow)
Spacer()
}
.frame(width: 1000, height: 200, alignment: .bottomLeading)
}
.background(Color.blue)
}
}
}
}
struct TimelineView_Previews: PreviewProvider {
static var previews: some View {
TimelineView()
}
}
The display, when run, and in debugger:

Resources