Swift .oberve(.childAdded)在.observeSingleEvent(of:.值)未激活时生成错误

Swift .oberve(.childAdded)在.observeSingleEvent(of:.值)未激活时生成错误,swift,xcode,firebase,Swift,Xcode,Firebase,现在我有了.observeSingleEvent(of:.value),它将我的所有注释加载到地图上: func loadAnnotations() { if let uid = Auth.auth().currentUser?.uid { uidRef.child(uid).child("annotations").observeSingleEvent(of: .value, with: { (snapshot) in for item in

现在我有了
.observeSingleEvent(of:.value)
,它将我的所有注释加载到地图上:

func loadAnnotations() {

    if let uid = Auth.auth().currentUser?.uid {
        uidRef.child(uid).child("annotations").observeSingleEvent(of: .value, with: { (snapshot) in

            for item in snapshot.children {

                // annotationListItem is a struct I created
                let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)

                let doubleLatitude = Double(annotationItem.mapViewLatitude!)
                let doubleLongitude = Double(annotationItem.mapViewLongitude!)
                let coordinate = CLLocationCoordinate2DMake(doubleLatitude!, doubleLongitude!)

                let annotation = MKPointAnnotation()
                annotation.coordinate = coordinate
                annotation.title = annotationItem.annotationTitle
                annotation.subtitle = annotationItem.annotationSubtitle
                self.mapView.addAnnotation(annotation)

            }
        }, withCancel: nil)

    }
}
现在,我希望每次用户添加新注释时地图都会更新,因此我输入了完全相同的代码,但使用了
。观察(.childAdded)

我得到的错误是:

无法将类型为“\uu NSCFString”(0x1060b84f0)的值强制转换为“NSDictionary”(0x1060b92d8)。 快照值的打印说明: ([String:AnyObject])快照值=变量不可用>

我如何解决这个问题

更新


。观察(.value)
工作。但是我仍然想知道为什么
.childAdded

这里的问题是
.observeSingleEvent(of:.value)
。observeSingleEvent(.childAdded)
不返回相同的内容

当您调用
.observe(.value)
时,它将返回事件发生时包含所有内容的整个节点

但是,当您使用
.observe(.childAdded)
时,它将只返回添加到指定路径的内容(即添加的子级)

通过在这两种方法中执行
print(snapshot)
,您可以看到这一点,并且您将很容易看到差异

因此,要使用.childAdded访问数据,不需要像使用.observeSingleEvent(of:.value)那样遍历所有子级。相反,你会:

        guard let uid = Auth.auth().currentUser?.uid else {
           return 
        }

        uidRef.child(uid).child("annotations").observe(.childAdded, with: { (snapshot) in

                let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)

                let doubleLatitude = Double(annotationItem.mapViewLatitude!)
                let doubleLongitude = Double(annotationItem.mapViewLongitude!)
                let coordinate = CLLocationCoordinate2DMake(doubleLatitude!, doubleLongitude!)

                let annotation = MKPointAnnotation()
                annotation.coordinate = coordinate
                annotation.title = annotationItem.annotationTitle
                annotation.subtitle = annotationItem.annotationSubtitle
                self.mapView.addAnnotation(annotation)
            }

        })
另外,我不建议您强制将您的物品像
物品那样强制转换为!DataSnapshot
因为如果你在数据库中遗漏了什么,应用程序就会崩溃

相反,我会这样做,使用guard语句:

guard let uid = Auth.auth().currentUser?.uid else {
     return 
}

uidRef.child(uid).child("annotations").observe(.childAdded, with: { [weak self] (snapshot) in

        let annotationKey = snapshot.key

        guard let longitude = snapshot.childSnapshot(forPath: "longitude").value as? NSNumber else { return }

        guard let latitude = snapshot.childSnapshot(forPath: "latitude").value as? NSNumber else { return }

        guard let title = snapshot.childSnapshot(forPath: "title").value as? String else { return }

        // Here it really depends on how your database look like.

        // ...

        // Create your annotation item using the values from the snapshot :

        let annotation = AnnotationListItem(id: annotationKey, title: title, longitude: longitue, latitude: latitude)

        // Finally add the data to an array :

        self?.annotationsArray.append(annotation)

        // Or directly to your mapView :

        self?.mapView.addAnnotation(annotation)

})
只要让我知道它是否有帮助;D

更新

例如,假设数据库中最初有3个子节点。它会这样做:

姓名首字母:观察员被呼叫:

你有一个孩子

你有孩子2

你有三个孩子

现在,如果添加了另一个子项:

新子级:观察员被调用:

你有四个孩子


等等。

这里的问题是,
.observeSingleEvent(of:.value)
.observe(.childAdded)
不返回相同的内容

当您调用
.observe(.value)
时,它将返回事件发生时包含所有内容的整个节点

但是,当您使用
.observe(.childAdded)
时,它将只返回添加到指定路径的内容(即添加的子级)

通过在这两种方法中执行
print(snapshot)
,您可以看到这一点,并且您将很容易看到差异

因此,要使用.childAdded访问数据,不需要像使用.observeSingleEvent(of:.value)那样遍历所有子级。相反,你会:

        guard let uid = Auth.auth().currentUser?.uid else {
           return 
        }

        uidRef.child(uid).child("annotations").observe(.childAdded, with: { (snapshot) in

                let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)

                let doubleLatitude = Double(annotationItem.mapViewLatitude!)
                let doubleLongitude = Double(annotationItem.mapViewLongitude!)
                let coordinate = CLLocationCoordinate2DMake(doubleLatitude!, doubleLongitude!)

                let annotation = MKPointAnnotation()
                annotation.coordinate = coordinate
                annotation.title = annotationItem.annotationTitle
                annotation.subtitle = annotationItem.annotationSubtitle
                self.mapView.addAnnotation(annotation)
            }

        })
另外,我不建议您强制将您的物品像
物品那样强制转换为!DataSnapshot
因为如果你在数据库中遗漏了什么,应用程序就会崩溃

相反,我会这样做,使用guard语句:

guard let uid = Auth.auth().currentUser?.uid else {
     return 
}

uidRef.child(uid).child("annotations").observe(.childAdded, with: { [weak self] (snapshot) in

        let annotationKey = snapshot.key

        guard let longitude = snapshot.childSnapshot(forPath: "longitude").value as? NSNumber else { return }

        guard let latitude = snapshot.childSnapshot(forPath: "latitude").value as? NSNumber else { return }

        guard let title = snapshot.childSnapshot(forPath: "title").value as? String else { return }

        // Here it really depends on how your database look like.

        // ...

        // Create your annotation item using the values from the snapshot :

        let annotation = AnnotationListItem(id: annotationKey, title: title, longitude: longitue, latitude: latitude)

        // Finally add the data to an array :

        self?.annotationsArray.append(annotation)

        // Or directly to your mapView :

        self?.mapView.addAnnotation(annotation)

})
只要让我知道它是否有帮助;D

更新

例如,假设数据库中最初有3个子节点。它会这样做:

姓名首字母:观察员被呼叫:

你有一个孩子

你有孩子2

你有三个孩子

现在,如果添加了另一个子项:

新子级:观察员被调用:

你有四个孩子


等等

谢谢你!为了清楚起见,我最初认为,
.childAdded
一次遍历所有现有的子级,然后充当每个添加的子级的侦听器。这不对吗?其次,我应该在
viewdiload
中调用我的
loadAnnotations
函数,然后在该函数下面调用
annotationChildAdded
?不客气@zachenn!您是对的,它确实在一开始会遍历所有现有的子项一次,然后会为添加的每个子项触发。但在这两种情况下,它将只返回特定的子节点,而不是返回包含其所有内容的整个父节点。我更新了我的答案,以便更容易理解。对于第二个问题,我建议您在viewDidAppear方法中编写添加子对象的方法,并确保删除viewDidAppear方法中的所有观察者。此外,在调用childAdded observer之前,您需要删除数组中的所有元素,以避免每次出现视图控制器时重复。My
AnnotationListItem
不是MKAnnotation。没事吧?我想出来了。感谢您的回答,我最终使用
.observeSingleEvent(of:.value)
来最初加载项目,然后调用您建议的更新的
.observe(.childAdded)
。我现在也了解了很多关于守卫声明的内容,如果让我说的话,因为是你提出来的。我非常感激:)谢谢你!为了清楚起见,我最初认为,
.childAdded
一次遍历所有现有的子级,然后充当每个添加的子级的侦听器。这不对吗?其次,我应该在
viewdiload
中调用我的
loadAnnotations
函数,然后在该函数下面调用
annotationChildAdded
?不客气@zachenn!您是对的,它确实在一开始会遍历所有现有的子项一次,然后会为添加的每个子项触发。但在这两种情况下,它将只返回特定的子节点,而不是返回包含其所有内容的整个父节点。我更新了我的答案,以便更容易理解。对于第二个问题,我建议您在viewDidAppear方法中编写添加子对象的方法,并确保删除viewDidAppear方法中的所有观察者。此外,在调用childAdded observer之前,您需要删除数组中的所有元素,以避免每次查看时重复