I have an iOS app and after adding address autocomplete, the app started lagging.
I added the bundle and the sdk manually: https://developers.google.com/maps/documentation/places/ios-sdk/config#install-manually.
Around 14mb:
Can the fact that adding the sdk manually(just drag and drop) be a problem and affecting performance?
After that I created the button that opens the autocompleter sheet:
import SwiftUI
struct ProfileUserView: View {
#State var openPlacePicker = false
#State var address = ""
var body: some View {
NavigationView{
VStack{
VStack {
Text(address)
Button {
openPlacePicker.toggle()
} label: {
Text("Schimba adresa")
}
}.sheet(isPresented: $openPlacePicker) {
PlacePicker(address: $address)
}
}
}
And the autocompleter:
import SwiftUI
import GooglePlaces
struct PlacePicker: UIViewControllerRepresentable {
func makeCoordinator() -> GooglePlacesCoordinator {
GooglePlacesCoordinator(self)
}
#Environment(\.presentationMode) var presentationMode
#Binding var address: String
func makeUIViewController(context: UIViewControllerRepresentableContext<PlacePicker>) -> GMSAutocompleteViewController {
GMSPlacesClient.provideAPIKey("xxxxx")
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = context.coordinator
let fields: GMSPlaceField = GMSPlaceField(rawValue:UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue) |
UInt(GMSPlaceField.coordinate.rawValue) |
GMSPlaceField.addressComponents.rawValue |
GMSPlaceField.formattedAddress.rawValue)
autocompleteController.placeFields = fields
let filter = GMSAutocompleteFilter()
filter.type = .address
autocompleteController.autocompleteFilter = filter
return autocompleteController
}
func updateUIViewController(_ uiViewController: GMSAutocompleteViewController, context: UIViewControllerRepresentableContext<PlacePicker>) {
}
class GooglePlacesCoordinator: NSObject, UINavigationControllerDelegate, GMSAutocompleteViewControllerDelegate {
var parent: PlacePicker
init(_ parent: PlacePicker) {
self.parent = parent
}
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
DispatchQueue.main.async {
print(place.description.description as Any)
self.parent.address = place.name!
self.parent.presentationMode.wrappedValue.dismiss()
print("latitude: \(place.coordinate.latitude)")
print("longitude: \(place.coordinate.longitude)")
}
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
print("Error: ", error.localizedDescription)
}
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
parent.presentationMode.wrappedValue.dismiss()
}
}
}
Any issue of how I do the autocomplete in SwiftUI.
It starts lagging whenever I open the sheet of the PlacePicker.
Related
I have place picker sheet and whenever I select a place the keyboard remains stuck on the main screen, I can t hide
my place picker view:
struct PlacePicker: UIViewControllerRepresentable {
func makeCoordinator() -> GooglePlacesCoordinator {
GooglePlacesCoordinator(self)
}
#Environment(\.presentationMode) var presentationMode
#Binding var address: String
#Binding var latitude: Double
#Binding var longitude: Double
func makeUIViewController(context: UIViewControllerRepresentableContext<PlacePicker>) -> GMSAutocompleteViewController {
GMSPlacesClient.provideAPIKey("xxxxx")
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = context.coordinator
let fields: GMSPlaceField = GMSPlaceField(rawValue:UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue) |
UInt(GMSPlaceField.coordinate.rawValue) |
GMSPlaceField.addressComponents.rawValue |
GMSPlaceField.formattedAddress.rawValue)
autocompleteController.placeFields = fields
let filter = GMSAutocompleteFilter()
filter.type = .address
autocompleteController.autocompleteFilter = filter
return autocompleteController
}
func updateUIViewController(_ uiViewController: GMSAutocompleteViewController, context: UIViewControllerRepresentableContext<PlacePicker>) {
}
class GooglePlacesCoordinator: NSObject, UINavigationControllerDelegate, GMSAutocompleteViewControllerDelegate {
var parent: PlacePicker
init(_ parent: PlacePicker) {
self.parent = parent
}
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
DispatchQueue.main.async {
print(place.description.description as Any)
self.parent.address = place.formattedAddress ?? "adresa gresita"
self.parent.presentationMode.wrappedValue.dismiss()
self.parent.latitude=place.coordinate.latitude
self.parent.longitude=place.coordinate.longitude
}
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
print("Error: ", error.localizedDescription)
}
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
parent.presentationMode.wrappedValue.dismiss()
}
}
The way that I call the sheet:
#State var addressString: String = ""
#State var address = ""
#State var openPlacePicker = false
var body: some View{
HStack{
TextField("\(address)", text: $addressString)
.onTapGesture {
openPlacePicker.toggle()
}
}.sheet(isPresented: $openPlacePicker) {
PlacePicker()
}
}
}
How I can hide the keyboard after the placePicker selection of the location is done?
Can I add something to the PlacePicker class? Whenever the selection is done, to dismiss the keyboard or something?
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.
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.
I am integrating Google Places in my SwiftUI app. I have searched a lot but didn't get any proper result for SwiftUI. I tried the solution in the comments of place autocomplete but didn't get my desired work.
I want:
I tried the following solution which helped me a lot to get my desired work but there is an issue with this is that for every location I searched, I got that location but the latitude and longitude always comes -180 for all locations.
Here is my code for View:
import SwiftUI
import GooglePlaces
struct MyGooglePlace: View {
#State var openPlacePicker = false
#State var address = ""
var body: some View {
VStack {
Text(address)
Button {
openPlacePicker.toggle()
} label: {
Text("open place picker")
}
}.sheet(isPresented: $openPlacePicker) {
PlacePicker(address: $address)
}
}
}
And the PlacePicker is:
import Foundation
import UIKit
import SwiftUI
import GooglePlaces
struct PlacePicker: UIViewControllerRepresentable {
func makeCoordinator() -> GooglePlacesCoordinator {
GooglePlacesCoordinator(self)
}
#Environment(\.presentationMode) var presentationMode
#Binding var address: String
func makeUIViewController(context: UIViewControllerRepresentableContext<PlacePicker>) -> GMSAutocompleteViewController {
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = context.coordinator
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue))
autocompleteController.placeFields = fields
let filter = GMSAutocompleteFilter()
filter.type = .address
autocompleteController.autocompleteFilter = filter
return autocompleteController
}
func updateUIViewController(_ uiViewController: GMSAutocompleteViewController, context: UIViewControllerRepresentableContext<PlacePicker>) {
}
class GooglePlacesCoordinator: NSObject, UINavigationControllerDelegate, GMSAutocompleteViewControllerDelegate {
var parent: PlacePicker
init(_ parent: PlacePicker) {
self.parent = parent
}
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
DispatchQueue.main.async {
print(place.description.description as Any)
self.parent.address = place.name!
self.parent.presentationMode.wrappedValue.dismiss()
print("latitude: \(place.coordinate.latitude)")
print("longitude: \(place.coordinate.longitude)")
}
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
print("Error: ", error.localizedDescription)
}
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
parent.presentationMode.wrappedValue.dismiss()
}
}
}
If I searched for some place in USA, it gives the coordinates -180, -180, and if I search for some location in Pakistan, it still gives -180, -180 coordinates.
Does anyone knows how to get the exact coordinates of the searched location?
Here I am posting a complete solution.
import SwiftUI
import GooglePlaces
struct PlacesViewControllerBridge: UIViewControllerRepresentable {
var onPlaceSelected: (GMSPlace) -> ()
//var selectedPlaceByFilter: GMSPlace
func makeUIViewController(context: UIViewControllerRepresentableContext<PlacesViewControllerBridge>) -> GMSAutocompleteViewController {
let uiViewControllerPlaces = GMSAutocompleteViewController()
uiViewControllerPlaces.delegate = context.coordinator
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue))
uiViewControllerPlaces.placeFields = fields
return uiViewControllerPlaces
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
func makeCoordinator() -> PlacesViewAutoCompleteCoordinator {
return PlacesViewAutoCompleteCoordinator(placesViewControllerBridge: self)
}
final class PlacesViewAutoCompleteCoordinator: NSObject, GMSAutocompleteViewControllerDelegate {
var placesViewControllerBridge: PlacesViewControllerBridge
init(placesViewControllerBridge: PlacesViewControllerBridge) {
self.placesViewControllerBridge = placesViewControllerBridge
}
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace)
{
print("Place name: \(place.name ?? "Default Place")")
print("Place ID: \(place.placeID ?? "Default PlaceID")")
print("Place attributions: \(String(describing: place.attributions))")
viewController.dismiss(animated: true)
self.placesViewControllerBridge.onPlaceSelected(place)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error)
{
print("Error: ", error.localizedDescription)
}
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
print("Place prediction window cancelled")
viewController.dismiss(animated: true)
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
}
Below is the usage in the custom text field.
CustomTextFieldWithEditing(
placeHolderText: "Enter your Location",
text: $tempLocation,
errorMessage: $isCsLocationMessage,
isError: $isCsLocationError,
isSecure: false,
action: {
isPresent = true
}
).textFieldStyle(CustomTextFieldStyle(icon: Image(systemName: "location.circle.fill")))
.sheet(isPresented: $isPresent) {
PlacesViewControllerBridge(onPlaceSelected: {
place in
tempLocation = place.name ?? "Please select your location"
selectedGMSPlace = place
isCsLocationError = false
})
}
I have been having a hard time trying to figure out how to authenticate with the Box API using SwiftUI.
As far as I understand, SwiftUI does not currently have the ability to satisfy the ASWebAuthenticationPresentationContextProviding protocol required to show the Safari OAuth2 login sheet. I know that I can make a UIViewControllerRepresentable to use UIKit within SwiftUI, but I can't get this to work.
I have figured out how to get the OAuth2 login sheet for Dropbox to appear and authenticate the client using SwiftUI.
The trick is to use a Coordinator to make the UIViewControllerRepresentable satisfy a protocol.
import SwiftUI
import BoxSDK
import AuthenticationServices
var boxSDK = BoxSDK(clientId: "<Client ID>", clientSecret: "<Client Secret>")
var boxClient: BoxClient
struct BoxLoginView: View {
#State var showLogin = false
var body: some View {
VStack {
Button {
showLogin = true
} label: {
Text("Login")
}
BoxView(isShown: $showLogin)
// Arbitrary frame size so that this view does not take up the whole screen
.frame(width: 40, height: 40)
}
}
}
/// A UIViewController that will present the OAuth2 Safari login screen when the isShown is true.
struct BoxView: UIViewControllerRepresentable {
typealias UIViewControllerType = UIViewController
let letsView = UIViewController()
#Binding var isShown : Bool
// Show the login Safari window when isShown
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
if(isShown) {
getOAuthClient()
}
}
func makeUIViewController(context _: Self.Context) -> UIViewController {
return self.letsView
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
func getOAuthClient() {
boxSDK.getOAuth2Client(tokenStore: KeychainTokenStore(), context:self.makeCoordinator()) { result in
switch result {
case let .success(client):
boxClient = client
case let .failure(error):
print("error in getOAuth2Client: \(error)")
}
}
}
class Coordinator: NSObject, ASWebAuthenticationPresentationContextProviding {
var parent: BoxView
init(parent: BoxView) {
self.parent = parent
}
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return parent.letsView.view.window ?? ASPresentationAnchor()
}
}
}