How to resolve "AttributeGraph: cycle" warnings in the following code? - swift

For context, the goal of the code below is to intercept a particular type of link inside a webview and handle navigation across a tabview natively (to a separate webview displaying the desired page) rather let the webview navigate itself. However, when I attempt to change the currentSelection to the desired index, I get a long list of "===AttributeGraph: cycle...===" messages. Below is the entirety of the code needed to repro this behavior:
import SwiftUI
import WebKit
#main
struct AttributeGraphCycleProofApp: App {
var body: some Scene {
WindowGroup {
ContentView(theController: Controller())
}
}
}
struct ContentView: View {
#StateObject var theController: Controller
#State var currentSelection = 0
private let selectedBackgroundColor = Color.green
var body: some View {
VStack(spacing: 12.0) {
HStack(spacing: .zero) {
ForEach(Array(0..<theController.viewModel.menuEntries.count), id: \.self) { i in
let currentMenuEntry = theController.viewModel.menuEntries[i]
Text(currentMenuEntry.title)
.padding()
.background(i == currentSelection ? selectedBackgroundColor : .black)
.foregroundColor(i == currentSelection ? .black : .gray)
}
}
TabView(selection: $currentSelection) {
let menuEntries = theController.viewModel.menuEntries
ForEach(Array(0..<menuEntries.count), id: \.self) { i in
let currentMenuEntry = theController.viewModel.menuEntries[i]
WrappedWebView(slug: currentMenuEntry.slug, url: currentMenuEntry.url) { destinationIndex in
// cycle warnings are logged when this line is executed
currentSelection = destinationIndex
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
.padding()
.background(.black)
.onAppear { theController.start() }
}
}
class Controller: ObservableObject {
#Published var viewModel: ViewModel = ViewModel(menuEntries: [])
func start() {
// Represents network request to create dynamic menu entries
DispatchQueue.main.asyncAfter(deadline: .now().advanced(by: DispatchTimeInterval.seconds(1)), execute: { [weak self] in
self?.viewModel = ViewModel.create()
})
}
}
struct ViewModel {
let menuEntries: [MenuEntry]
static func create() -> Self {
return Self(menuEntries: [
MenuEntry(title: "Domain", slug: "domain", url: "https://www.example.com/"),
MenuEntry(title: "Iana", slug: "iana", url: "https://www.iana.org/domains/reserved"),
])
}
}
struct MenuEntry {
let title: String
let slug: String
let url: String
}
struct WrappedWebView: UIViewRepresentable {
var slug: String
var url: String
var navigateToSlug: ((Int) -> Void)? = nil
func makeCoordinator() -> WrappedWebView.Coordinator { Coordinator(self) }
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.navigationDelegate = context.coordinator
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
webView.isOpaque = false
if let wrappedUrl = URL(string: url), webView.url != wrappedUrl {
let request = URLRequest(url: wrappedUrl)
webView.load(request)
}
}
class Coordinator: NSObject, WKNavigationDelegate {
let parent: WrappedWebView
init(_ parent: WrappedWebView) {
self.parent = parent
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
let url = navigationAction.request.url
if url?.absoluteString == parent.url {
return .allow
}
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.parent.navigateToSlug?(1)
}
return .cancel
}
}
}
All instrumentation and memory graph debugging are failing me as they don't describe the moment the leak occurs, all I know is that the critical line causing the leaks is the assignment of navigationIndex to currentSelection.

Related

Calling swiftUI View from UIKIt and pass data with or without userdefaults

I have been trying to call SwiftUI view from UIViewController but i dont know the right way to do it.
I have trying using use Userdefaults but SwiftUI view complains that URL passed via userdefaults is nil
this is the view
import SwiftUI
import QuickLook
import UIKit
struct PreviewController: UIViewControllerRepresentable {
let url: URL
var error: Binding<Bool>
func makeUIViewController(context: Context) -> QLPreviewController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
controller.isEditing = false
return controller
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
func updateUIViewController(
_ uiViewController: QLPreviewController, context: Context) {}
class Coordinator: QLPreviewControllerDataSource {
var parent: PreviewController
init(parent: PreviewController) {
self.parent = parent
}
func numberOfPreviewItems(
in controller: QLPreviewController
) -> Int {
return 1
}
func previewController(
_ controller: QLPreviewController, previewItemAt index: Int
) -> QLPreviewItem {
guard self.parent.url.startAccessingSecurityScopedResource()
else {
return NSURL(fileURLWithPath: parent.url.path)
}
defer {
self.parent.url.stopAccessingSecurityScopedResource()
}
return NSURL(fileURLWithPath: self.parent.url.path)
}
}
}
struct ProjectDocumentOpener: View {
#Binding var open: Bool
#State var errorInAccess = false
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 0) {
let url = URL(string: UserDefaults.standard.string(forKey: "documentLink")!)
PreviewController(url: url!, error: $errorInAccess)
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarTitle(
Text(URL(string: UserDefaults.standard.string(forKey: "documentLink")!)?.lastPathComponent ?? "")
)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Done") {
self.open = false
}
}
}
}
}
This is how i have been calling it in UIViewController
UserDefaults.standard.set(docURL, forKey: "documentLink")
self.navigationController?.pushViewController(UIHostingController(rootView: ProjectDocumentOpener(open: .constant(true))), animated: true)
I tried to google and find a proper resource which explains how to use a SwiftUI view with UIViewController but cant find any resource suitable for my needs.

Array Indexing occurs Fatal error: Index out of range

Problem
Hello. I'm studying SwiftUI.
I've tried to pick multiple photos from gallery using PHPickerController, and show up multiple views which represents each photo one by one. However, Fatal error occurs whenever I try to access any index of vm.images.
How could I solve this issue?
My source code is as follows
Solved
The problem comes from vm.images I thought that .onChange modifier operate after all images are saved into vm.images. But it didn't.
I solved this matter by adding if statement when calling PickerTabView; Quite Easy
// Added code
if let images = vm.images {
if images.count > 0 {
PickerTabView()
}
}
struct PickerTabView: View {
#EnvironmentObject var vm: ViewModel
var body: some View {
TabView {
if let images = vm.images{
ForEach(images, id: \.self) { image in
PickerSettingView(image: image)
}
}
}
.tabViewStyle(.page)
}
}
struct ImagesPicker: UIViewControllerRepresentable {
#Binding var selectedImages: [UIImage]?
//var selectionLimit: Int
//var filter: PHPickerFilter?
var itemProvider: [NSItemProvider] = []
func makeUIViewController(context: Context) -> some PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.selectionLimit = 20
configuration.filter = .images
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
func makeCoordinator() -> Coordinator {
return ImagesPicker.Coordinator(parent: self)
}
class Coordinator: NSObject, PHPickerViewControllerDelegate, UINavigationControllerDelegate {
var parent: ImagesPicker
init(parent: ImagesPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
if !results.isEmpty {
parent.itemProvider = []
parent.selectedImages = []
}
parent.itemProvider = results.map(\.itemProvider)
loadImage()
}
private func loadImage() {
for itemProvider in parent.itemProvider {
if itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { image, error in
DispatchQueue.main.sync {
if let image = image as? UIImage {
self.parent.selectedImages?.append(image)
}
}
}
}
}
}
}
}
struct PickerHomeView: View {
#EnvironmentObject var vm: ViewModel
#State private var isSelected = false
var body: some View {
NavigationView {
VStack {
NavigationLink("Tab View", isActive: $isSelected) {
PickerTabView()
}
.hidden()
HStack {
Button {
vm.showPicker()
} label: {
ButtonLabel(symbolName: "photo.fill", label: "Photos")
}
}
Spacer()
}
.sheet(isPresented: $vm.showPicker) {
ImagesPicker(selectedImages: $vm.images)
.ignoresSafeArea()
}
.onChange(of: vm.images, perform: { _ in
isSelected = true
})
}
}
}
struct PickerSettingView: View {
#EnvironmentObject var vm: ViewModel
var image: UIImage
let myImage = MyImage(category: Category.unCategorized)
#State private var selectedCategory: Category = Category.unCategorized
var body: some View {
VStack {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(minWidth: 0, maxWidth:.infinity)
SwiftUI.Picker("Category Picker", selection: $selectedCategory) {
Text("Formal").tag(Category.formal)
Text("Casual").tag(Category.casual)
Text("Semi Formal").tag(Category.semiFormal)
}
.pickerStyle(.segmented)
.padding([.leading, .trailing], 16)
HStack {
Button {
if vm.selectedImage == nil {
vm.addMyImage(category: selectedCategory, image: image)
} else {
vm.updateSelected()
}
} label: {
ButtonLabel(symbolName: vm.selectedImage == nil ? "square.and.arrow.down.fill" :
"square.and.arrow.up.fill",
label: vm.selectedImage == nil ? "Save" : "Update")
}
}
}
}
}
class ViewModel: ObservableObject {
#Published var images: [UIImage]?
#Published var showPicker = false
}
I think the image in your loadImage, is not being added to the self.parent.selectedImages
when it is nil, that is, when images in your ViewModel is nil, as it is at the start. So whenever you try to access any index of the images array in your vm.images, the app crashes.
You could try this in your loadImage (note also .async) to append the images:
DispatchQueue.main.async {
if let image = image as? UIImage {
if self.parent.selectedImages == nil { self.parent.selectedImages = [] }
self.parent.selectedImages!.append(image)
}
}

SwiftUI: Is it possible to let the user scale an image chosen with PHpicker?

I have an image picker created with PHPicker, and I was wondering if it is possible to let the user scale the chosen image?
This is not the entire code, but just the code for the makeUIViewController which I think is what is needed to solve this problem. I can of course provide the rest of the code if necessary.
This is what I'm looking for
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images
config.selectionLimit = 1
let controller = PHPickerViewController(configuration: config)
controller.delegate = context.coordinator
return controller
}
can use this one line after choose the image to fixed height and width of your image
Image(room.thumbnailImage)
.resizable()
.frame(width: 32.0, height: 32.0)
or here i am sharing my running work with you checkout function didFinishPicking and var body: some View
import SwiftUI
import PhotosUI
struct PhotoPickerDemo: View {
#State private var isPresented: Bool = false
#State var pickerResult: [UIImage] = []
var config: PHPickerConfiguration {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images //videos, livePhotos...
config.selectionLimit = 0 //0 => any, set 1-2-3 for har limit
return config
}
var body: some View {
ScrollView {
LazyVStack {
Button("Present Picker") {
isPresented.toggle()
}.sheet(isPresented: $isPresented) {
PhotoPicker(configuration: self.config,
pickerResult: $pickerResult,
isPresented: $isPresented)
}
ForEach(pickerResult, id: \.self) { image in
Image.init(uiImage: image)
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 250, alignment: .center)
.aspectRatio(contentMode: .fit)
}
}
}
}
}
struct PhotoPicker: UIViewControllerRepresentable {
let configuration: PHPickerConfiguration
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
/// PHPickerViewControllerDelegate => Coordinator
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.parent.pickerResult.append(newImage as! UIImage)
}
}
} else {
print("Loaded Assest is not a Image")
}
}
// dissmiss the picker
parent.isPresented = false
}
}
}
struct photoPickerDemo_Previews: PreviewProvider {
static var previews: some View {
PhotoPickerDemo()
}
}
or if you want to crop via user interface like attach picture
Step 1
Using Xcode 12, go to File -> Swift Packages -> Add Package Dependency and enter https://github.com/marshallino16/ImageCropper
Step 2
in your didFinishPicking method where you are receiving selected image pass it in this package using these lines
let ratio = CropperRatio(width: 1, height: 1)//square ratio for crop
ImageCropperView(image: Image(yourSelectedImageHere),cropRect: nil,ratio: ratio).onCropChanged { (newCrop) in
print(newCrop)//here you will receive cropped image
}
edited use of ImageCropperView
struct PhotoPicker: UIViewControllerRepresentable {
let configuration: PHPickerConfiguration
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
/// PHPickerViewControllerDelegate => Coordinator
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
if let error = error {
print(error.localizedDescription)
} else {
let ratio = CropperRatio(width: 1, height: 1)//square ratio for crop
ImageCropperView(image: Image(newImage),cropRect: nil,ratio: ratio).onCropChanged { (newCrop) in
print(newCrop)//here you will receive cropped image
}
}
}
} else {
print("Loaded Assest is not a Image")
}
}
// dissmiss the picker
parent.isPresented = false
}
}
}

QLPreviewController showing file then going blank in SwiftUI

I've added a UIViewControllerRepresentable for UIKit's QLPreviewController which I've found in a related question:
struct QuickLookView: UIViewControllerRepresentable {
var url: URL
var onDismiss: (() -> Void) = { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func updateUIViewController(_ viewController: UINavigationController, context: UIViewControllerRepresentableContext<Self>) {
(viewController.topViewController as? QLPreviewController)?.reloadData()
}
func makeUIViewController(context: Context) -> UINavigationController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
controller.reloadData()
return UINavigationController(rootViewController: controller)
}
class Coordinator: NSObject, QLPreviewControllerDataSource {
var parent: QuickLookView
init(_ qlPreviewController: QuickLookView) {
self.parent = qlPreviewController
super.init()
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
self.parent.url as QLPreviewItem
}
}
}
In my app, I download a file (jpg/png/pdf) via Alamofire:
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.documentsDirectory
.appendingPathComponent(document.id.string)
.appendingPathComponent(document.name ?? "file.jpg")
return (documentsURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF
.download(url, to: destination)
.responseURL { (response) in
guard let url = response.fileURL else { return }
self.fileURL = url
self.isShowingDoc = true
}
...and pass its local url to the QuickLookView to present it:
#State private var isShowingDoc = false
#State private var fileURL: URL?
var body: some View {
// ...
.sheet(isPresented: $isShowingDoc, onDismiss: { isShowingDoc = false }) {
QuickLookView(url: fileURL!) {
isShowingDoc = false
}
}
}
What happens is that the QuickLookView opens as sheet, the file flashes (is displayed for like 0.1 seconds) and then the view goes blank:
I checked the Documents folder of the app in Finder and the file is there and matches the url passed to the QuickLookView. I've noticed that when the view is open, and I then delete the file from the folder via Finder, then the view will throw an error saying there's no such file – that means it did read it properly before it was deleted.
Note: I read somewhere that the QL controller has had issues when placed inside a navigation controller. In my view hierarchy, my views are embedded inside a NavigationView – might that cause issues?
How do I solve this?
You just need to update the view before presenting the sheet otherwise it wont work. It can be the button title, opacity or anything. Although it looks like a hack it works fine. I will be very glad if someone explains why it happens and if there is a proper way to make it work without updating the view.
import SwiftUI
struct ContentView: View {
#State private var fileURL: URL!
#State private var isDisabled = false
#State private var isDownloadFinished = false
#State private var buttonTitle: String = "Download PDF"
private let url = URL(string: "https://www.dropbox.com/s/bxrhk6194lf0n73/macpro_mid2010-macpro_mid2012.pdf?dl=1")!
var body: some View {
Button(buttonTitle) {
isDisabled = true
buttonTitle = "Downloading..."
URLSession.shared.downloadTask(with: url) { location, response, error in
guard
let location = location, error == nil,
let suggestedFilename = (response as? HTTPURLResponse)?.suggestedFilename,
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
else { return }
fileURL = documentDirectory.appendingPathComponent(suggestedFilename)
if !FileManager.default.fileExists(atPath: fileURL.path) {
do {
try FileManager.default.moveItem(at: location, to: fileURL)
} catch {
print(error)
}
}
DispatchQueue.main.async {
isDownloadFinished = true
buttonTitle = "" // you need to change the view prefore presenting the sheet otherwise it wont work
}
}.resume()
}
.disabled(isDisabled == true)
.sheet(isPresented: $isDownloadFinished) {
isDisabled = false
isDownloadFinished = false
fileURL = nil
buttonTitle = "Download PDF"
} content: {
if isDownloadFinished {
PreviewController(previewItems: [PreviewItem(url: fileURL, title: fileURL?.lastPathComponent)], index: 0)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
import QuickLook
struct PreviewController: UIViewControllerRepresentable {
var previewItems: [PreviewItem] = []
var index: Int
func makeCoordinator() -> Coordinator { .init(self) }
func updateUIViewController(_ viewController: UINavigationController, context: UIViewControllerRepresentableContext<Self>) {
(viewController.topViewController as? QLPreviewController)?.reloadData()
}
func makeUIViewController(context: Context) -> UINavigationController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
controller.delegate = context.coordinator
controller.reloadData()
return .init(rootViewController: controller)
}
class Coordinator: NSObject, QLPreviewControllerDataSource, QLPreviewControllerDelegate {
let previewController: PreviewController
init(_ previewController: PreviewController) {
self.previewController = previewController
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
previewController.previewItems.count
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
previewController.previewItems[index]
}
}
}
class PreviewItem: NSObject, QLPreviewItem {
var previewItemURL: URL?
var previewItemTitle: String?
init(url: URL? = nil, title: String? = nil) {
previewItemURL = url
previewItemTitle = title
}
}
I finally got it to work – big thanks to Leo Dabus for his help in the comments.
Here's my currently working code:
#State private var isShowingDoc = false
#State private var isLoadingFile = false
#State private var fileURL: URL?
var body: some View {
Button {
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.documentsDirectory
.appendingPathComponent(document.id.string)
.appendingPathComponent(document.name ?? "file.jpg")
return (documentsURL, [.removePreviousFile, .createIntermediateDirectories])
}
isLoadingFile = true
AF
.download(url, to: destination)
.responseURL { (response) in
self.isLoadingFile = false
guard let url = response.fileURL else { return }
isShowingDoc = true
self.fileURL = url
}
} label: {
VStack {
Text("download")
if isLoadingFile {
ActivityIndicator(style: .medium)
}
}
}
.sheet(isPresented: $isShowingDoc, onDismiss: { isShowingDoc = false }) {
QuickLookView(url: fileURL!)
}
}
with this QuickLookView: (mostly unchanged)
struct QuickLookView: UIViewControllerRepresentable {
var url: URL
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func updateUIViewController(_ viewController: UINavigationController, context: UIViewControllerRepresentableContext<Self>) {
(viewController.topViewController as? QLPreviewController)?.reloadData()
}
func makeUIViewController(context: Context) -> UINavigationController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
controller.reloadData()
return UINavigationController(rootViewController: controller)
}
class Coordinator: NSObject, QLPreviewControllerDataSource {
var parent: QuickLookView
init(_ qlPreviewController: QuickLookView) {
self.parent = qlPreviewController
super.init()
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
self.parent.url as QLPreviewItem
}
}
}
As you can see, there's hardly any difference to my code from when I asked the question. Yesterday night, the fileURL was always nil for an unclear reason; yet, now it started working just fine. In exchange, the remote images in my list (not shown here) stopped working even though I haven't touched them, haha.
I don't know what's going on and what I even changed to make it work, but it works and I won't complain!

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.