Search code examples
swiftuiswiftui-layout

Looped value is showing the same result for tap


I have imported JSON for countries:

Countries.json (sample)

    [ 
       {
          display_name: "France",
          timezone: "placeholder",
          longitude: 13.33,
          latitude: 15.34
       },   
       {
          display_name: "California",
          timezone: "EST",
          longitude: 33.33,
          latitude: 12.34
       }, 
  ]

I have a function getAnnotated that iterates through the countries to make an array of AnnotatedItem. That is used in Map and gets looped through as item to actually create the MapAnnotation. Then item is passed into a helper function getCountry. I filter through countries to get the country that has the same display_name field as item.

The desired behavior is to have an annotation/marker over each country and tapping on that annotation will pop up a modal/sheet that gives info on the country.
My issue is that if I am zoomed in and on screen is only a single annotation/marker, the proper country is displayed when clicking on it.
If I zoom out on the map and there are multiples annotations, every annotation I tap pops up the same country info for each one. I assume there is something wrong with the way I am looping.

var countries = Bundle.main.decode("Countries.json")

struct AnnotatedItem: Identifiable {
    let id = UUID()
    var name: String
    var coordinate: CLLocationCoordinate2D
}

struct MapView: View {
    @State var showSheet = false
    
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(
            latitude: 25.7617,
            longitude: 80.1918
        ),
        span: MKCoordinateSpan(
            latitudeDelta: 10,
            longitudeDelta: 10
        )
    )
    
    func getAnnotated() -> [AnnotatedItem] {
        var pointsOfInterest = [AnnotatedItem]()

        for i in countries {
            pointsOfInterest.append(AnnotatedItem(name: i.display_name, coordinate: .init(latitude: i.latitude, longitude: i.longitude)))
        }
        
        return pointsOfInterest
    }

    func getCountry(newItem: AnnotatedItem) -> Country {
        let country = countries.filter{ $0.display_name == newItem.name }
        return country[0]
    }
    
    var body: some View {
        Map(coordinateRegion: $region, annotationItems: getAnnotated()) { item in
            MapAnnotation(coordinate: item.coordinate) {
                Button(action: {
                    showSheet.toggle()
                }){
                    Image(systemName: "airplane")
                        .foregroundColor(.white)
                        .padding()
                }
                
                .background(Circle())
                .foregroundColor(Color.green)
                .sheet(isPresented: $showSheet) {
                    SheetView(country: getCountry(newItem: item))
                }
                
            }
        }
    }

}


Solution

  • I would try something like this to achieve the desired behaviour:

    class SelectedCountry: ObservableObject {
        @Published var item: AnnotatedItem = AnnotatedItem(name: "no name", coordinate: CLLocationCoordinate2D())
    }
    
    struct MapView: View {
        @ObservedObject var selected = SelectedCountry()  // <--- here
        @State var showSheet = false
        
        ...
        
    
        var body: some View {
            Map(coordinateRegion: $region, annotationItems: getAnnotated()) { item in
                MapAnnotation(coordinate: item.coordinate) {
                    Button(action: {
                        selected.item = item  // <--- here
                        showSheet.toggle()
                    }){
                        Image(systemName: "airplane")
                            .foregroundColor(.white)
                            .padding()
                    }
                    .background(Circle())
                    .foregroundColor(Color.green)
                }
            }
            // ---> put the sheet here 
            .sheet(isPresented: $showSheet) {
                SheetView(country: getCountry(newItem: selected.item))
            }
        }