SwiftUI @Publish изнутри закрытия не работает

У меня есть этот код Swift ниже, который я использовал с раскадровками, и теперь хочу повторно использовать с SwiftUI. Он использует GeoFire для запроса местоположений в Firestore в пределах определенного радиуса radiusInM в соответствии с документацией Firebase.

Я хочу опубликовать «местоположения» массива в представлении, которое должно отображать их на карте, но в то время как массив заполняется местоположениями (с «self.locations.append (location)»), это не приводит к тому, что это наблюдается Вид.

Что является причиной этого? Есть ли лучший способ сделать это?

import Foundation
import CoreLocation
import FirebaseFirestore
import GeoFire

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

class LocationsViewModel: NSObject, ObservableObject {

    @Published var locations: [Location] = []
    
    // location
    var lat: Double = 0.0
    var lng: Double = 0.0
    
    // firestore
    var db: Firestore!

    // Location records from Firestore
    var matchingDocs = [QueryDocumentSnapshot]()
    
    func getLocationsOnButton() {
        
        getLocations(completion: {
            print("*** finished all queries and getting locations ***")

            for i in self.matchingDocs {

                self.lat = i["lat"] as! Double
                self.lng = i["lng"] as! Double
                
                let location = Location(name: "Test", coordinate: CLLocationCoordinate2D(latitude: self.lat, longitude: self.lng))

                self.locations.append(location)
            }
        })
    }

    func getLocations(completion: @escaping () -> Void) {
        
        db = Firestore.firestore()
        
        let center = CLLocationCoordinate2D(latitude: lat, longitude: lng)
        let radiusInM: Double = 1000
        
        var queriesFinished = 0
        matchingDocs.removeAll()

        // Each item in 'bounds' represents a startAt/endAt pair. We have to issue
        // a separate query for each pair. There can be up to 9 pairs of bounds
        // depending on overlap, but in most cases there are 4.
        let queryBounds = GFUtils.queryBounds(forLocation: center,
                                              withRadius: radiusInM)
        
        let queries = queryBounds.map { bound -> Query in
            return db.collection("places")
                .order(by: "geohash")
                .start(at: [bound.startValue])
                .end(at: [bound.endValue])
        }
        
        let numOfQueries = queries.count
        print("There are", numOfQueries, "queries")
        
        // Collect all the query results together into a single list
        func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
            guard let documents = snapshot?.documents else {
                print("Unable to fetch snapshot data. \(String(describing: error))")
                return
            }
            
            for document in documents {
                let lat = document.data()["lat"] as? Double ?? 0
                let lng = document.data()["lng"] as? Double ?? 0
                let coordinates = CLLocation(latitude: lat, longitude: lng)
                let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)

                // We have to filter out a few false positives due to GeoHash accuracy, but most will match
                let distance = GFUtils.distance(from: centerPoint, to: coordinates)
            
                if distance <= radiusInM {
                    print("appending ", document.documentID)
                    if !matchingDocs.contains(document) {
                        matchingDocs.append(document)
                    }
                }
            }
            
            queriesFinished += 1
            print("Finished query # ", queriesFinished)
            
            allDone()
        }
        
        // After all callbacks have executed, matchingDocs contains the result.
        for query in queries {
            print("Processing query")
            query.getDocuments(completion:getDocumentsCompletion)
        }
        
        func allDone() {
            if queriesFinished == numOfQueries {
                print("Completed all queries")
                completion()
            }
        }
    }
}

Это вид

struct MapView: View {
    @StateObject private var viewModel = MapViewModel()
    @StateObject private var locationsViewModel = LocationsViewModel()
    
    var body: some View {
        
        Map(coordinateRegion: $viewModel.region, showsUserLocation: true, annotationItems: locationsViewModel.locations) { location in
            MapMarker(coordinate: location.coordinate)
        }
        .ignoresSafeArea()
    }
}

Если я вручную заполню массив местоположений, как это

    @Published var locations: [Location] = [
        Location(name: "Test", coordinate: CLLocationCoordinate2D(latitude: 43.6541, longitude: -79.3780))
    ]

Маркер правильно отображается на карте.

Это представление, которое начинает получать местоположения из Firestore.

struct LocationView: View {
    
    let places = LocationsViewModel()
    
    var body: some View {
        
        MapView()
            .ignoresSafeArea()
   
        Spacer()
    
        HStack(alignment: .center) {

            Button {
                places.getLocationsOnButton()
            } label: {
                Image(systemName: "location")
            }
        }
    }
}

Вам нужно показать, как вы используете это в своем View

jnpdx 05.04.2023 02:12

Я отредактировал исходный пост.

user8865059 05.04.2023 03:35

Вы никогда не вызываете функцию получения местоположения.

jnpdx 05.04.2023 03:41

MapView объединяется с другим View, который вызывает getLocationsOnButton(). Я опубликую это через мгновение.

user8865059 05.04.2023 03:47

вам больше повезет, удалив объекты модели представления, в SwiftUI структура представления уже является моделью представления, и она различает эти структуры для автоматического создания/обновления/удаления объектов UIView для вас.

malhal 05.04.2023 15:10

Комментарии здесь сработали для меня, поэтому я отмечу это как ответ.

user8865059 14.04.2023 14:54
Интеграция Angular - Firebase Analytics
Интеграция Angular - Firebase Analytics
Узнайте, как настроить Firebase Analytics и отслеживать поведение пользователей в вашем приложении Angular.
0
6
67
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В LocationView у вас должно быть @StateObject var places = LocationsViewModel().

В MapView вы должны иметь @ObservedObject var locationsViewModel: LocationsViewModel и передать LocationsViewModel ему из LocationView, используя: MapView(locationsViewModel: places)

Другие вопросы по теме