Hi all. Came here as a last resort. All the samples provided that I came across are not in SwiftUI.
Google's documentation is also outdated since they do not provide any samples or tutorial to do so in SwiftUI.
While showing the map in SwiftUI requires you to extend a UIViewRepresentable, i have no clue how to apply the custom info window with it.
Anyone got suggestions?
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
The title might be confusing but here is an example data to give a clear understanding
struct City {
var name: String?
var businesses: [Business]?
}
So if i have a city array, i wish to merge the businesses array based on the city name since the city may have duplicates e.g.
[
{
name: "CIty 1"
businesses: [
{ name: "Business 1" },
{ name: "Business 2" },
]
},
{
name: "CIty 2"
businesses: [
{ name: "Business 1" },
{ name: "Business 2" },
]
}
{
name: "CIty 1"
businesses: [
{ name: "Business 3" },
{ name: "Business 4" },
]
}
]
In the data example above, there are 2 entries of City1. i am looking for a way to not have to use a dictionary, if it is possible to use the existing array and output it such that there is only 1 entry of City but will contain 4 entries of business.
Thoughts?
Hi. Please see screenshot. Currently the icon placeholder is small. What I am trying to replicate is to have an area (the one in red) where the place holder icon is in the middle horizontally and vertically.
That same red area is where the downloaded image will be drawn. I think the term for this is aspect ratio if i am not mistaken? That the height should be proportionate based on the width.
I am using Kingfisher to load the image. This is what I have.
KFImage.url(URL(string: ""))
.placeholder {
HStack {
Image(systemName: "photo")
.resizable()
.frame(width: 50, alignment: .center)
.background(.red)
}
.background(.yellow)
.scaledToFit()
}
.fade(duration: 0.25)
.onFailure { error in print("Load image error : \(error) = \(earthquakeDetail.imgUrl)")
}
.resizable()
.aspectRatio(contentMode: .fill)
Based on the screenshot image, the area for HStack is so thin with its height being small. My goal is to create an area like the one in red border.
Thoughts?
All posts i came across instructs the user to import the objective c files into the swift project so that a bridging header file can be created to expose it.
Does this mean i have to instead download the objective c library, extract and copy all the files to the swift project? where the prompt to create the bridging header will be triggered?
Or is there a way to install the objective c library via cocoapods and manually trigger to create the bridging header to make it usable?
I prefer the latter, if it is possible?
Hi, asking this here because this is not about google maps ios sdk issue but regarding the use of binding.
My map view currently looks simple.
import SwiftUI
import GoogleMaps
import GoogleMapsUtils
struct MapView: UIViewRepresentable {
var geoJsonParser: GMUGeoJSONParser?
func makeUIView(context: Self.Context) -> GMSMapView {
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: GMSCameraPosition())
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: Context) {
mapView.clear()
if let geoJsonParser {
print(geoJsonParser.features)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(owner: self)
}
}
class Coordinator: NSObject, GMSMapViewDelegate {
let owner: MapView
init(owner: MapView) {
self.owner = owner
}
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
print("tapped at coordinate")
print(owner)
}
}
class Coordinator: NSObject, GMSMapViewDelegate {
let owner: MapView
init(owner: MapView) {
self.owner = owner
}
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
print("tapped at coordinate")
print(owner.geoJsonParser)
}
}
My question is when i tap on the map, the mapView didTapAtCoordinate gets called but the geoJsonParser variable is always nil. So I thought i would use Binding in this case since when the MapView struct is used, I pass a @State geoJsonParser to its parameter.
My question is, from within the MapView struct, I am not sure what else I could be lacking so that print(owner.geoJsonParser) will not return nil
I tried to set the variable to var geoJsonParser: Binding? = nil
But after tapping on the map and the mapView didTapAtCoordinate is called, print(owner.geoJsonParser) returns an error
Cannot convert value of type 'Binding<[any GMUGeometryContainer]>' to expected argument type '[any GMUGeometryContainer]'
Same error message in the print statement in updateUIView(). Any idea what could be wrong here?
My goal is to have owner.geoJsonFeature not be nil.
A sample struct code
struct Test {
@Binding var name: String?
}
Why does it force me to have to instantiate it with
Test(name: "blabla")
Is there a way to not have to initialize this in the param? since It is an optional type.
Binding<String?> works differently from @Binding, so ive read. Though one can do Binding<String?>? or is this the only possible way to do it?
Hi. I am not sure why i get the error message AttributeGraph: cycle detected through attribute
but this does happen i noticed if i have a Text component render html.
Here is the code
import SwiftUI
struct ContentView: View {
@State private var isShowingAlertSheet = false
@State private var alertTitle: String?
@State private var alertDescription: String?
var body: some View {
VStack {
Button(
"click me", action: {
alertTitle = "TITLE HERE"
alertDescription = "<b>hey</b>"
isShowingAlertSheet = true
}
)
}
.sheet(isPresented: $isShowingAlertSheet) {
DetailView(alertTitle: $alertTitle, alertDescription: $alertDescription)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct DetailView: View {
@Binding var alertTitle: String?
@Binding var alertDescription: String?
var body: some View {
VStack {
Text(alertTitle ?? "")
.font(.system(size: 20))
.padding(.top, 10)
.padding(.bottom, 0)
.presentationDetents([.height(250)])
Text(fixTextSize(alertDescription?.htmlMutableAttributedString))
.frame(maxWidth: .infinity)
.padding(.top, 10)
Spacer()
}
.padding()
}
func fixTextSize(_ mutableAttributedString: NSMutableAttributedString?) -> AttributedString {
if let mutableAttributedString {
mutableAttributedString.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 16), range: NSMakeRange(0, mutableAttributedString.length))
return AttributedString(mutableAttributedString)
}
return AttributedString()
}
}
extension String {
var htmlMutableAttributedString: NSMutableAttributedString? {
if let attributedString = try? NSMutableAttributedString(data: Data(self.utf8), options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor(.black), range: NSMakeRange(0, attributedString.length))
return attributedString
}
return nil
}
}
Thoughts?
I wish to ask suggestion on how to avoid this.
i dont want every tab switch to reload the component..worse, fetch from url
s this where mvvm comes in? so that even if component is reloaded, the mvvm data will be used? i think it is overkill though.
my first and 3rd tab fetches data and loads markers in a map. my 2nd tab is a wkwebview that loads a url
i noticed that whej i switch to the 3rd tab, the 1st tab gets reloaded still. thoughts on how to avood reloading?
also tried setting tag to each tab item and setting a state variable to tab view to keep track of tab switching. to no avail.
thoughts?
I have managed to do tasks when an info window of a GMSMarker is clicked. This is where the magic happens
func mapView(_ mapView: GMSMapView, didTapInfoWindowOf marker: GMSMarker)
However, this time, I wish to open a custom view as a new window. I have not seen any helpful posts so I decided to try and ask the question here if anyone has experienced doing so.
My goal is when the info window is clicked, i wish to open my custom view as a new window.
Currently, I only know NavigationView and NavigationLink when opening up new windows but I am not sure how to use this in a GMSMarker info window click event.
Thoughts?
struct Test {
var body: some View {
AsyncView(
operation: { try await getData("https://localhost") },
content: { json
Text("data retrieved")
}
)
}
func getData(_ url: String) async throws -> String {
return try await getRequest(url)
}
}
static func getRequest(_ sourceData: String?, _ useWindowsChar1252: Bool = false) async throws -> String {
guard let sourceData else {
return ""
}
var request = URLRequest(url: URL(string: sourceData.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)!)
request.httpMethod = "get"
let (data, _) = try await URLSession(configuration: .ephemeral).data(for: request)
return useWindowsChar1252 ?
String(data: data, encoding: .windowsCP1252)! :
String(decoding: data, as: UTF8.self)
}
You can just have one tab item in the TabView and the http request keeps on looping. any idea?
struct MyTabView: View {
var body: some View {
TabView {
Test()
.tabItem {
Label("test", systemImage: "leaf")
}
}
}
}
Trying to understand the cause. Because If I put the AsyncView, say inside a ZStack, it works ok.
Thoughts?
Hi. My view gets animated with a call frmo a block of code withAnimation.{ }
how can i place this inside the view so that the binding variable will be the one to trigger the animation. Same as how sheets work.
THis is my sample view
import SwiftUI
struct GradientLegendView: View {
@Binding var isShowingLegend: Bool
var labels: [String]?
var colors: [Color]?
var width: Float
var height: Float
var viewRect = Rectangle()
var body: some View {
if isShowingLegend {
HStack {
VStack {
if let labels {
ForEach(labels, id:\.self) { label in
Text(label)
.frame(maxWidth: .infinity, alignment: .leading)
.font(.system(size: 14))
.foregroundColor(.black)
.backgroundStyle(.red)
.padding(0)
if label != labels.last {
Spacer()
}
}
}
}
.frame(maxHeight: .infinity)
.padding(8)
Rectangle()
.foregroundColor(.clear)
.background(LinearGradient(gradient: Gradient(colors: colors ?? []), startPoint: .top, endPoint: .bottom))
.padding(8)
.frame(width: 30)
}
.frame(width: CGFloat(width), height: CGFloat(height))
.background(.white.opacity(0.7))
.cornerRadius(5)
.shadow(color: Color.gray.opacity(0.7), radius: 8, x: 0, y: 0)
.padding()
.transition(.move(edge: isShowingLegend ? .leading : .trailing))
}
}
}
struct GradientLegendView_Previews: PreviewProvider {
static var previews: some View {
GradientLegendView(
isShowingLegend: .constant(true),
labels: SampleData.createHeatmapLegendLabelArray(),
colors: SampleData.createHeatmapLegendColorArray(),
width: 120,
height: 200
)
}
}
struct Sample {
static func createHeatmapLegendLabelArray() -> [String] {
return ["Great", "Major", "Strong", "Moderate", "Small"]
}
func createHeatmapLegendColorArray() -> [Color] {
return [.red, .purple, .orange, .green]
}
}
And i use it here and run the animation using withAnimation { }
@State private var isShowingLegend = true
var body: some View {
Text("Hey")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
.task {
setupMenuItems()
}
.overlay(
HStack {
GradientLegendView(
isShowingLegend: $isShowingLegend,
labels: SampleData.createHeatmapLegendLabelArray(),
colors: SampleData.createHeatmapLegendColorArray(),
width: 120,
height: 200)
Spacer()
},
alignment: .bottom
)
}
Now, say i also have a button instead of Text("hey") where the action code is
withAnimation(isShowingLegend ? .easeIn(duration: 0.5) : .easeOut(duration: 0.5)) {
isShowingLegend.toggle()
}
Is it possible that this withAnimatoin be placed inslide GradientLegendView and will execute if the isShowingLegend value is change?
Ive read posts where you can pass 2 .environmentObject()
but what i noticed is that if i call object1.objectWillChange.send() , object2 also is affected
is this normal? or supposedly only published properties of object1 only gets affected?
discussion quwstion. thoughts?
Hi, i wish to ask for suggestions. I am in a dilemna using an environment object. Please see code.
struct ContentView: View {
@State var show = false
@StateObject var appData = AppData()
@StateObject var toolbarData = ToolbarData()
var body: some View {
NavigationStack(path: $appData.path) {
ZStack(alignment: .leading) {
VStack(spacing: 0) {
HStack {
Button(action: {
withAnimation(.default) {
show.toggle()
}
}) {
Image(systemName: "line.3.horizontal")
}
Text("Home")
Spacer()
ToolbarView()
OverflowMenu()
.padding([.leading], Default.instance.PADDING_BETWEEN_MENU_ITEM)
}
.padding()
.foregroundColor(.primary)
.overlay(Rectangle().stroke(Color.primary.opacity(0.1), lineWidth: 1).shadow(radius: 3).edgesIgnoringSafeArea(.top))
MainContent(navigationManager: navigationManager)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.environmentObject(toolbarData)
.background(Color.primary.opacity(self.show ? (self.dark ? 0.05 : 0.2) : 0))
}
.navigationBarHidden(true)
}
.navigationViewStyle(.stack)
.environmentObject(appData)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ToolbarView: View {
@Binding var toolbarData: ToolbarData
var body: some View {
HStack {
if let item = toolbarData.toolbarItem, item.visible {
Button {
item.action()
} label: {
if let iconLink = item.iconLink {
NavigationLink(destination: iconLink) {
Image(systemName: item.icon)
}
}
else {
Image(systemName: item.icon)
}
}
.disabled(item.disabled)
.opacity(item.opacity)
.padding([.leading, .trailing], Default.instance.PADDING_BETWEEN_MENU_ITEM)
}
}
}
final class ToolbarData: ObservableObject {
@Published var toolbarItem: ToolbarItemProperties?
}
final class ToolbarItemProperties {
var disabled = false
}
In the MainContent , my code runs this
toolbarData.toolbarItemProperties.disabled = false
I do not want MainContent to be rebuilt. Inside MainContent, I modify toolbarItemProperties (from an environment object). And i call objectWillChange.send(). Problem is, it will rebuild the ContentView widget including MainContent.
What can be the correct approach to having to eb able to change the value of toolbarItemProperties and rebuild it but ignoring MainContent to rebuild again.
Thoughts?
I have a model struct
struct Temp {
var cityUrlId: String {
get {
print(_cityUrlId)
print("gettting="+StringTool.nullToString(_cityUrlId).trim())
return _cityUrlId
}
set(newValue) {
print("newvalue="+newValue)
_cityUrlId = _cityUrlId
print("_cityUrlId="+_cityUrlId!)
}
}
}
The parent view has a state variable e.g.
@State var temp: Temp
and a child view as a new window with @Binding var temp: Temp
My question is that when the temp in child view sets a value for cityUrlId property, it's ok. but once i set a new value in the parent view, the getter returns nil. why is that?
is the solution for this have to be a class for Temp instead of a struct?
struct TideForecastInfoView: View {
var geometryProxy: GeometryProxy
var body: some View {
VStack {
getHeaders(geometryProxy)
TideForecastEntryView(geometryProxy: geometryProxy)
.padding(2)
}
.background(.white)
.clipShape(RoundedRectangle(cornerRadius: 14))
.shadow(radius: 8)
}
@ViewBuilder
func getHeaders(_ geometryProxy: GeometryProxy) -> some View {
HStack {
HStack {
Text("tide")
.frame(maxWidth: geometryProxy.size.width * 0.25)
Text("time")
.frame(maxWidth: geometryProxy.size.width * 0.5)
Text("height")
.frame(maxWidth: geometryProxy.size.width * 0.25)
}
.padding(8)
}
.frame(maxWidth: .infinity)
.background(.gray)
}
}
struct TideForecastEntryView: View {
var geometryProxy: GeometryProxy
var body: some View {
HStack {
Text("High Tide")
.frame(maxWidth: geometryProxy.size.width * 0.25)
Text("1:08 AM (Tue 03) January")
.frame(maxWidth: geometryProxy.size.width * 0.5, alignment: .leading)
Text("1.31 m (4.3 ft)")
.frame(maxWidth: geometryProxy.size.width * 0.25)
}
}
}
Result looks like this
03 should be on the first line and maybe parts of the January word. is there something else missing in the Text view option that i need to declare?
Also how to vertical align top for text High Tide. it is always vertically centered. I tried to set alignment: .top in the frame but doesnt do anything.
Thoughts?