【问题标题】:Retrieve data from Firebase before adding to a map在添加到地图之前从 Firebase 检索数据
【发布时间】:2019-11-05 10:34:54
【问题描述】:

我正在构建一个应用程序,用户可以在其中存储公司信息(如名称、地址、纬度、经度到 Firebase 实时数据库)。

有了这些信息,这些公司就会以注释的形式出现在地图中。

我的 viewDidLoad 如下所示

override func viewDidLoad() {
    super.viewDidLoad()

    // Definitions
    let autenticacao = Auth.auth()
    let idUsuarioLogado = (autenticacao.currentUser?.uid)!
    let database = Database.database().reference()
    let usuarios = database.child("usuarios")
    let clinicas = database.child("clinicas")

    // Map - User location
    self.mapa.delegate = self
    self.gerenciadorLocalizacao.delegate = self
    self.gerenciadorLocalizacao.desiredAccuracy = kCLLocationAccuracyBest
    self.gerenciadorLocalizacao.requestWhenInUseAuthorization()
    self.gerenciadorLocalizacao.startUpdatingLocation()

    // retrieve user information
    usuarios.child(idUsuarioLogado).observe(DataEventType.value) { (snapshot) in
        let dados = snapshot.value as? NSDictionary

        let emailUsuario = dados?["email"] as! String
        let nomeUsuario = dados?["nome"] as! String
        let perfilUsuario = dados?["perfil"] as! String
        let idUsuario = snapshot.key

        let usuario = Usuario(email: emailUsuario, nome: nomeUsuario, uid: idUsuario, perfil: perfilUsuario)
        print("User profile \(perfilUsuario)")
    }

    // Clinicas listeners
    clinicas.observe(DataEventType.childAdded) { (snapshot) in
        let dados = snapshot.value as? NSDictionary
        //print("Dados na leitura \(dados)")
        let clinica = Clinica()
        clinica.identificador = snapshot.key
        clinica.nome = dados?["nome"] as! String
        clinica.endereco = dados?["endereco"] as! String
        clinica.cidade = dados?["cidade"] as! String
        clinica.cep = dados?["cep"] as! String
        clinica.estado = dados?["estado"] as! String
        clinica.latitude = dados?["latitude"] as! String
        clinica.longitude = dados?["longitude"] as! String
        clinica.urlImagem = dados?["urlImagem"] as! String
        clinica.idImagem = dados?["idImagem"] as! String
        self.clinicasR.append(clinica)
    }

    // add annotations to the map
    for oneObject in self.todasAnotacoes {
        print("Oneobj \(oneObject)")
        let umaAnotacao = MinhaAnotacao()
        var oneObjLoc: CLLocationCoordinate2D = CLLocationCoordinate2DMake(oneObject.objLat, oneObject.objLong)
        umaAnotacao.coordinate = oneObjLoc
        umaAnotacao.title = oneObject.objName
        umaAnotacao.subtitle = oneObject.objDesc
        umaAnotacao.category = oneObject.objCat
        self.anotacaoArray.append(umaAnotacao)
        self.mapa.addAnnotations(self.anotacaoArray)
    }
}

我的 viewDidLoad 在 4 个主要块中“结构化”(即使它们以不同的顺序运行,因为它们是异步的):

  1. 定义;
  2. 检索用户资料;
  3. 检索公司信息(名称、纬度、经度);
  4. 向地图添加注释。

由于这些是异步函数,它们以不同的顺序运行,这就是给我带来麻烦的原因。 注意:这里仍然缺少一件东西,就是将所有注释提供给变量 todasAnotacoes,一旦我可以在触发添加注释“块”之前检索数据,我就会这样做。

由于注释信息来自 firebase 数据库,我应该只在 clinicas.observe(DataEventType.childAdded) { (snapshot) in 结束后执行它。

现在,Xcode 运行的顺序是:

  • 定义
  • 添加注释;
  • 从 Firebase 中检索包含公司详细信息的数据

我已经尝试将添加注释块添加到闭包中,添加一个调度队列,但这些都没有真正起作用。我还在 stackoverflow 中进行了很多操作系统搜索,但我找不到任何我可以使用的东西(或者我无法理解)。

所以,总而言之,我需要在从 Firebase 检索所有数据后运行添加注释。

关于如何做到这一点的任何想法?

编辑 1 - 带有建议更新的最终代码

override func viewDidLoad() {
    super.viewDidLoad()

    // Retrieving Logger user data and hidding "add" button if applicable
    ProgressHUD.show("Carregando...")
    let autenticacao = Auth.auth()
    let idUsuarioLogado = (autenticacao.currentUser?.uid)!
    let database = Database.database().reference()
    let usuarios = database.child("usuarios")
    var isUserLoaded = false
    var isClinicsLoaded = false

    usuarios.child(idUsuarioLogado).observe(DataEventType.value) { (snapshot) in
        let dados = snapshot.value as? NSDictionary

        let emailUsuario = dados?["email"] as! String
        let nomeUsuario = dados?["nome"] as! String
        let perfilUsuario = dados?["perfil"] as! String
        let idUsuario = snapshot.key

        let usuario = Usuario(email: emailUsuario, nome: nomeUsuario, uid: idUsuario, perfil: perfilUsuario)
        isUserLoaded = true
        if (isUserLoaded && isClinicsLoaded) {
            self.addAnnotationsToMap();
        }
        //print("\(usuario.email) e \(usuario.nome) e \(usuario.uid) e \(usuario.perfil)")
    }


    // Option to code above
    /*if Auth.auth().currentUser != nil {
     if let uid = (Auth.auth().currentUser?.uid) {

     let database = Database.database().reference()
     let usuarios = database.child("usuarios").child(uid)

     usuarios.observe(.value) { (snapshot) in
     let dados = snapshot.value as? NSDictionary

     let emailUsuario = dados?["email"] as! String
     let nomeUsuario = dados?["nome"] as! String
     let perfilUsuario = dados?["perfil"] as! String
     let idUsuario = snapshot.key

     let usuario = Usuario(email: emailUsuario, nome: nomeUsuario, uid: idUsuario, perfil: perfilUsuario)
     print("Got here \(usuario.email) e \(usuario.nome) e \(usuario.uid) e \(usuario.perfil)")
     if perfilUsuario != "admin" {
     self.navigationItem.rightBarButtonItems?.remove(at: 1)
     print("Disable + Button")
     }

     }
     }
     }*/

    // Map - User location
    self.mapa.delegate = self
    self.gerenciadorLocalizacao.delegate = self
    self.gerenciadorLocalizacao.desiredAccuracy = kCLLocationAccuracyBest
    self.gerenciadorLocalizacao.requestWhenInUseAuthorization()
    self.gerenciadorLocalizacao.startUpdatingLocation()
    let clinicas = database.child("clinicas")

    // Clinicas listeners
    clinicas.observe(DataEventType.value) { (snapshots) in
        for child in snapshots.children {
            let snapshot = child as! DataSnapshot

            print("Clinicas Mapeadas - end")
            let dados = snapshot.value as? NSDictionary
            //print("Dados na leitura \(dados)")
            let clinica = Clinica()
            clinica.identificador = snapshot.key
            clinica.nome = dados?["nome"] as! String
            clinica.endereco = dados?["endereco"] as! String
            clinica.cidade = dados?["cidade"] as! String
            clinica.cep = dados?["cep"] as! String
            clinica.estado = dados?["estado"] as! String
            clinica.latitude = dados?["latitude"] as! String
            clinica.longitude = dados?["longitude"] as! String
            clinica.urlImagem = dados?["urlImagem"] as! String
            clinica.idImagem = dados?["idImagem"] as! String

            self.clinicasR.append(clinica)
            self.todasAnotacoes.append((objLat: Double(clinica.latitude) as! CLLocationDegrees, objLong: Double(clinica.longitude) as! CLLocationDegrees, objName: clinica.nome, objDesc: clinica.endereco, objId: clinica.identificador))
        }
        isClinicsLoaded = true
        if (isUserLoaded && isClinicsLoaded) {
            self.addAnnotationsToMap();
        }
    }

    /* NOT IN USE FOR NOW
     let latitude = Double(-23.623558)
     let longitude = Double(-46.580787)
     let localizacao: CLLocationCoordinate2D = CLLocationCoordinate2D.init(latitude: latitude, longitude: longitude)
     let span: MKCoordinateSpan = MKCoordinateSpan.init(latitudeDelta: 0.01, longitudeDelta: 0.01)
     let regiao = MKCoordinateRegion.init(center: localizacao, span: span)
     self.mapa.setRegion(regiao, animated: true)*/

    ProgressHUD.dismiss()
}

// add annotations to the map
func addAnnotationsToMap() {
    anotacaoArray = []
    for oneObject in self.todasAnotacoes {
        for oneObject in self.todasAnotacoes {
            // print("Oneobj \(oneObject)")
            let umaAnotacao = MinhaAnotacao()
            var oneObjLoc: CLLocationCoordinate2D = CLLocationCoordinate2DMake(oneObject.objLat, oneObject.objLong)
            umaAnotacao.coordinate = oneObjLoc
            umaAnotacao.title = oneObject.objName
            umaAnotacao.subtitle = oneObject.objDesc
            umaAnotacao.identicadorMapa = oneObject.objId
            self.anotacaoArray.append(umaAnotacao)
            print("Annotation added \(todasAnotacoes.count) - end")

        }
        self.mapa.addAnnotations(self.anotacaoArray)
        self.todasAnotacoes = []
        self.anotacaoArray = []

        // print("Annotations added 2 - end")

    }
}

【问题讨论】:

    标签: swift firebase firebase-realtime-database


    【解决方案1】:

    代码中的第二个观察者使用.childAdded,这意味着闭包会为clinicas 下的每个单独的子节点调用。这使您很难知道您何时完成了诊所,因此我建议首先将该观察者切换到 .value,例如:

    clinicas.observe(DataEventType.value) { (snapshots) in
      for child in snapshots.children {
        let snapshot = child as! DataSnapshot
    
        let dados = snapshot.value as? NSDictionary
        //print("Dados na leitura \(dados)")
        let clinica = Clinica()
        clinica.identificador = snapshot.key
        clinica.nome = dados?["nome"] as! String
        clinica.endereco = dados?["endereco"] as! String
        clinica.cidade = dados?["cidade"] as! String
        clinica.cep = dados?["cep"] as! String
        clinica.estado = dados?["estado"] as! String
        clinica.latitude = dados?["latitude"] as! String
        clinica.longitude = dados?["longitude"] as! String
        clinica.urlImagem = dados?["urlImagem"] as! String
        clinica.idImagem = dados?["idImagem"] as! String
        self.clinicasR.append(clinica)
      }
      // At this point we're done with all clinics
    }
    

    您会注意到,这段代码现在使用循环遍历snapshots.children。所以我们在一个闭包中处理clinicas 的所有子节点,这样就更容易知道我们何时完成了。


    现在,通过上述更改,我们有两个单独调用的闭包,并且您有一些代码要在它们都完成后运行。有几种方法可以同步此代码。

    第一种方法是您已经尝试过的:将clinicas.observe 块放入usuarios.child(idUsuarioLogado).observe( 的闭包中。现在诊所观察员使用.value,您会发现这更容易上手。

    但我想在下面展示一个替代方案,我们使用两个简单的标志来确定两个闭包是否都已完成。为此,您必须将数据加载后运行的代码放入单独的函数中,如下所示:

    func addAnnotationsToMap() }
      for oneObject in self.todasAnotacoes {
        ...
      }
    }
    

    现在在两个闭包中,我们将在它们完成后设置一个标志。然后在每个闭包结束时,我们将检测是否设置了两个标志,然后如果我们有我们需要的所有数据,则调用新函数。

    所以在viewDidLoad 的开头我们定义了两个标志:

    let isUserLoaded = false
    let isClinicsLoaded = false
    

    然后在加载用户结束时:

    usuarios.child(idUsuarioLogado).observe(DataEventType.value) { (snapshot) in
        let dados = snapshot.value as? NSDictionary
    
        let emailUsuario = dados?["email"] as! String
        let nomeUsuario = dados?["nome"] as! String
        let perfilUsuario = dados?["perfil"] as! String
        let idUsuario = snapshot.key
    
        let usuario = Usuario(email: emailUsuario, nome: nomeUsuario, uid: idUsuario, perfil: perfilUsuario)
    
        isUserLoaded = true
        if (isUserLoaded && isClinicsLoaded) {
          addAnnotationsToMap();
        }
    }
    

    和类似的代码(设置isClinicsLoaded标志)到另一个闭包。

    有了这些,您将在代码运行时开始加载用户数据和诊所,然后无论其中哪一个最后完成,都会调用新的 addAnnotationsToMap 函数。


    另一种选择是使用DispatchGroup,这是一种Apple 特定的构造,它也可以使这变得更简单。在这个答案(以及那里的链接)中阅读更多关于它的信息:Wait until swift for loop with asynchronous network requests finishes executing

    【讨论】:

    • 嗨弗兰克...我非常感谢您的帮助 :-) 我按照您的建议添加了 2 个标志。
    猜你喜欢
    • 2018-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-07
    • 2019-05-03
    • 2018-11-07
    • 2021-08-04
    • 2021-12-07
    相关资源
    最近更新 更多