只有一个(自定义)注释从一组其他注释中旋转
Only one (custom) annotation rotating from an array of other annotations
我的应用程序即将进入最后阶段,它显示实时公交车地图。所以,基本上,我有一个计时器,它定期从 xml sheet 获取公交车的纬度和经度,它提供公交车的实时位置。我能够设置 xml 解析器,为公交车的运动设置动画并为公交车设置自定义(箭头)图像。
但是,问题是,从多条总线的数组中,我只能得到一条总线来旋转。查看 xml 数据,它始终是 xml sheet 的第一辆公共汽车,它正在旋转。早些时候,我什至无法旋转单个总线,所以用户 "Good Doug" 帮助我解决了问题,我能够让它工作。您可以在此处查看 post:。我尝试通过为每条总线制作一个 MKAnnotationView 数组来使用相同的解决方案。我不确定这是否是正确的方法。如果有人能帮我解决这个问题,我会很高兴:)
首先,XML sheet是这样的(在这个例子中,有两辆车,所以我们只需要跟踪其中的两辆车):
<body>
<vehicle id="3815" routeTag="connector" dirTag="loop" lat="44.98068" lon="-93.18071" secsSinceReport="3" predictable="true" heading="335" speedKmHr="12" passengerCount="16"/>
<vehicle id="3810" routeTag="connector" dirTag="loop" lat="44.97313" lon="-93.24041" secsSinceReport="3" predictable="true" heading="254" speedKmHr="62" passengerCount="1"/>
</body>
这是我对单独总线 class 的实现(在 Bus.swift 文件中)。这可能需要一些改进。
class Bus : MKPointAnnotation, MKAnnotation {
var oldCoord : CLLocationCoordinate2D!
var addedToMap = false
init(coord: CLLocationCoordinate2D) {
self.oldCoord = coord
}
}
这是我的 ViewController.swift-
的代码
var busArray: [Bus!] = [] //Array to hold custom defined "Bus" types (from Bus.swift file)
var busViewArray : [MKAnnotationView?] = [nil, nil] //Array to hold MKAnnotationView of each bus. We're assuming 2 buses are active in this case.
var vehicleCount = 0 // variable to hold the number of buses
var vehicleIndex = 0 // variable to check which bus the xml parser is currently on.
var trackingBusForTheVeryFirstTime = true
// My xml parser function:
func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: NSDictionary!) {
if (elementName == "vehicle" ) {
let latitude = attributeDict["lat"]?.doubleValue // Get latitude of current bus
let longitude = attributeDict["lon"]?.doubleValue // Get longitude of current bus
let dir = attributeDict["heading"]?.doubleValue // Get direction of current bus
var currentCoord = CLLocationCoordinate2DMake(latitude!, longitude!) // Current coordinates of the bus
// Checking the buses for the VERY FIRST TIME. This is usually the start of the program
if (trackingBusForTheVeryFirstTime || vehicleCount == 0) {
let bus = Bus(coord: currentCoord)
self.busArray.append(bus) // Put current bus to the busArray
self.vehicleCount++
}
else { // UPDATE BUS Location. (Note: this is not the first time)
// If index exceeded count, that means number of buses changed, so we need to start over
if (self.vehicleIndex >= self.vehicleCount) {
self.trackingBusForTheVeryFirstTime = true
// Reset count and index for buses
self.vehicleCount = 0
self.vehicleIndex = 0
return
}
let oldCoord = busArray[vehicleIndex].oldCoord
if (oldCoord.latitude == latitude && oldCoord.longitude == longitude) {
// if oldCoordinates and current coordinates are the same, the bus hasn't moved. So do nothing.
return
}
else {
// Move and Rotate the bus:
UIView.animateWithDuration(0.5) {
self.busArray[self.vehicleIndex].coordinate = CLLocationCoordinate2DMake(latitude!, longitude!)
// if bus annotations have not been added to the map yet, add them:
if (self.busArray[self.vehicleIndex].addedToMap == false) {
self.map.addAnnotation(self.busArray[self.vehicleIndex])
self.busArray[self.vehicleIndex].addedToMap = true
return
}
if let pv = self.busViewArray[self.vehicleIndex] {
pv.transform = CGAffineTransformRotate(self.map.transform, CGFloat(self.degreesToRadians(dir!))) // Rotate bus
}
}
if (vehicleIndex < vehicleCount - 1)
self.vehicleIndex++
}
else {
self.vehicleIndex = 0
}
return
}
}
}
这是我实现的viewForAnnotation
:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
let reuseId = "pin\(self.vehicleIndex)"
busViewArray[self.vehicleIndex] = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if busViewArray[self.vehicleIndex] == nil {
self.busViewArray[self.vehicleIndex] = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
busViewArray[vehicleIndex]!.image = imageWithImage(UIImage(named:"arrow.png")!, scaledToSize: CGSize(width: 21.0, height: 21.0))
self.view.addSubview(busViewArray[self.vehicleIndex]!)
}
else {
busViewArray[self.vehicleIndex]!.annotation = annotation
}
return busViewArray[self.vehicleIndex]
}
我对我的 viewForAnnotation
实施表示怀疑。我也不确定是否可以使用 MKAnnotationView
数组。也许,我对注释视图在 iOS 中的工作方式的理解是错误的。如果有人可以帮助我解决这个问题,我会很高兴,因为我已经坚持了一段时间。即使整体实施需要更改,我也很乐意尝试一下。这是问题的屏幕截图。
再次请注意,所有公交车都出现在正确的位置并平稳移动,但只有其中一辆真正旋转。提前致谢。
我认为解析代码直接操作注释视图是不合适的。你不知道它们是否可见,它们是否已经被实例化等等。地图视图负责管理注释视图,而不是你。
如果您需要维护总线和注释之间的交叉引用,请这样做,但不要维护对注释视图的引用。您的应用程序与注释的交互应仅限于注释本身。因此,创建一个具有 angle
属性 的注解子类。
class MyAnnotation : MKPointAnnotation {
@objc dynamic var angle: CGFloat = 0.0
}
然后您可以让注释视图子类 "observe" 自定义注释子类,随着注释的 angle
变化而旋转。例如,在 Swift 4:
class MyAnnotationView : MKAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
addAngleObserver()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addAngleObserver()
}
// Remember, since annotation views can be reused, if the annotation changes,
// remove the old annotation's observer, if any, and add new one's.
override var annotation: MKAnnotation? {
willSet { token = nil }
didSet { addAngleObserver() }
}
// add observer
var token: NSKeyValueObservation!
private func addAngleObserver() {
if let annotation = annotation as? MyAnnotation {
transform = CGAffineTransform(rotationAngle: annotation.angle)
token = annotation.observe(\.angle) { [weak self] annotation, _ in
UIView.animate(withDuration: 0.25) {
self?.transform = CGAffineTransform(rotationAngle: annotation.angle)
}
}
}
}
}
或在Swift 3:
private var angleObserverContext = 0
class MyAnnotationView : MKAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
addAngleObserver()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addAngleObserver()
}
// add observer
private func addAngleObserver() {
if let annotation = annotation as? MyAnnotation {
transform = CGAffineTransform(rotationAngle: annotation.angle)
annotation.addObserver(self, forKeyPath: #keyPath(MyAnnotation.angle), options: [.new, .old], context: &angleObserverContext)
}
}
// remove observer
private func removeAngleObserver() {
if let annotation = annotation as? MyAnnotation {
annotation.removeObserver(self, forKeyPath: #keyPath(MyAnnotation.angle))
}
}
// remember to remove observer when annotation view is deallocated
deinit {
removeAngleObserver()
}
// Remember, since annotation views can be reused, if the annotation changes,
// remove the old annotation's observer, if any, and add new one's.
override var annotation: MKAnnotation? {
willSet { removeAngleObserver() }
didSet { addAngleObserver() }
}
// Handle observation events for the annotation's `angle`, rotating as appropriate
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &angleObserverContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
UIView.animate(withDuration: 0.5) {
if let angleNew = change![.newKey] as? CGFloat {
self.transform = CGAffineTransform(rotationAngle: angleNew)
}
}
}
}
现在,您的应用程序可以维护对已添加到地图的注释的引用,并设置它们的 angle
,这将在适当的时候在地图视图中直观地表示。
并且,使用这个的一个快速而肮脏的例子:
class ViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!
var annotation = MyAnnotation()
private let reuseIdentifer = Bundle.main.bundleIdentifier! + ".annotation"
private lazy var manager: CLLocationManager = {
let manager = CLLocationManager()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
return manager
}()
override func viewDidLoad() {
super.viewDidLoad()
mapView.register(MyAnnotationView.self, forAnnotationViewWithReuseIdentifier: reuseIdentifer)
manager.requestWhenInUseAuthorization()
manager.startUpdatingHeading()
manager.startUpdatingLocation()
mapView.addAnnotation(annotation)
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation { return nil }
return mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifer, for: annotation)
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last,
location.horizontalAccuracy >= 0 else {
return
}
annotation.coordinate = location.coordinate
}
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
guard newHeading.headingAccuracy >= 0 else { return }
annotation.angle = CGFloat(newHeading.trueHeading * .pi / 180)
}
}
有关 Swift 2 个示例,请参见 previous revision of this answer。
我的应用程序即将进入最后阶段,它显示实时公交车地图。所以,基本上,我有一个计时器,它定期从 xml sheet 获取公交车的纬度和经度,它提供公交车的实时位置。我能够设置 xml 解析器,为公交车的运动设置动画并为公交车设置自定义(箭头)图像。
但是,问题是,从多条总线的数组中,我只能得到一条总线来旋转。查看 xml 数据,它始终是 xml sheet 的第一辆公共汽车,它正在旋转。早些时候,我什至无法旋转单个总线,所以用户 "Good Doug" 帮助我解决了问题,我能够让它工作。您可以在此处查看 post:
首先,XML sheet是这样的(在这个例子中,有两辆车,所以我们只需要跟踪其中的两辆车):
<body>
<vehicle id="3815" routeTag="connector" dirTag="loop" lat="44.98068" lon="-93.18071" secsSinceReport="3" predictable="true" heading="335" speedKmHr="12" passengerCount="16"/>
<vehicle id="3810" routeTag="connector" dirTag="loop" lat="44.97313" lon="-93.24041" secsSinceReport="3" predictable="true" heading="254" speedKmHr="62" passengerCount="1"/>
</body>
这是我对单独总线 class 的实现(在 Bus.swift 文件中)。这可能需要一些改进。
class Bus : MKPointAnnotation, MKAnnotation {
var oldCoord : CLLocationCoordinate2D!
var addedToMap = false
init(coord: CLLocationCoordinate2D) {
self.oldCoord = coord
}
}
这是我的 ViewController.swift-
的代码var busArray: [Bus!] = [] //Array to hold custom defined "Bus" types (from Bus.swift file)
var busViewArray : [MKAnnotationView?] = [nil, nil] //Array to hold MKAnnotationView of each bus. We're assuming 2 buses are active in this case.
var vehicleCount = 0 // variable to hold the number of buses
var vehicleIndex = 0 // variable to check which bus the xml parser is currently on.
var trackingBusForTheVeryFirstTime = true
// My xml parser function:
func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: NSDictionary!) {
if (elementName == "vehicle" ) {
let latitude = attributeDict["lat"]?.doubleValue // Get latitude of current bus
let longitude = attributeDict["lon"]?.doubleValue // Get longitude of current bus
let dir = attributeDict["heading"]?.doubleValue // Get direction of current bus
var currentCoord = CLLocationCoordinate2DMake(latitude!, longitude!) // Current coordinates of the bus
// Checking the buses for the VERY FIRST TIME. This is usually the start of the program
if (trackingBusForTheVeryFirstTime || vehicleCount == 0) {
let bus = Bus(coord: currentCoord)
self.busArray.append(bus) // Put current bus to the busArray
self.vehicleCount++
}
else { // UPDATE BUS Location. (Note: this is not the first time)
// If index exceeded count, that means number of buses changed, so we need to start over
if (self.vehicleIndex >= self.vehicleCount) {
self.trackingBusForTheVeryFirstTime = true
// Reset count and index for buses
self.vehicleCount = 0
self.vehicleIndex = 0
return
}
let oldCoord = busArray[vehicleIndex].oldCoord
if (oldCoord.latitude == latitude && oldCoord.longitude == longitude) {
// if oldCoordinates and current coordinates are the same, the bus hasn't moved. So do nothing.
return
}
else {
// Move and Rotate the bus:
UIView.animateWithDuration(0.5) {
self.busArray[self.vehicleIndex].coordinate = CLLocationCoordinate2DMake(latitude!, longitude!)
// if bus annotations have not been added to the map yet, add them:
if (self.busArray[self.vehicleIndex].addedToMap == false) {
self.map.addAnnotation(self.busArray[self.vehicleIndex])
self.busArray[self.vehicleIndex].addedToMap = true
return
}
if let pv = self.busViewArray[self.vehicleIndex] {
pv.transform = CGAffineTransformRotate(self.map.transform, CGFloat(self.degreesToRadians(dir!))) // Rotate bus
}
}
if (vehicleIndex < vehicleCount - 1)
self.vehicleIndex++
}
else {
self.vehicleIndex = 0
}
return
}
}
}
这是我实现的viewForAnnotation
:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
let reuseId = "pin\(self.vehicleIndex)"
busViewArray[self.vehicleIndex] = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if busViewArray[self.vehicleIndex] == nil {
self.busViewArray[self.vehicleIndex] = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
busViewArray[vehicleIndex]!.image = imageWithImage(UIImage(named:"arrow.png")!, scaledToSize: CGSize(width: 21.0, height: 21.0))
self.view.addSubview(busViewArray[self.vehicleIndex]!)
}
else {
busViewArray[self.vehicleIndex]!.annotation = annotation
}
return busViewArray[self.vehicleIndex]
}
我对我的 viewForAnnotation
实施表示怀疑。我也不确定是否可以使用 MKAnnotationView
数组。也许,我对注释视图在 iOS 中的工作方式的理解是错误的。如果有人可以帮助我解决这个问题,我会很高兴,因为我已经坚持了一段时间。即使整体实施需要更改,我也很乐意尝试一下。这是问题的屏幕截图。
再次请注意,所有公交车都出现在正确的位置并平稳移动,但只有其中一辆真正旋转。提前致谢。
我认为解析代码直接操作注释视图是不合适的。你不知道它们是否可见,它们是否已经被实例化等等。地图视图负责管理注释视图,而不是你。
如果您需要维护总线和注释之间的交叉引用,请这样做,但不要维护对注释视图的引用。您的应用程序与注释的交互应仅限于注释本身。因此,创建一个具有 angle
属性 的注解子类。
class MyAnnotation : MKPointAnnotation {
@objc dynamic var angle: CGFloat = 0.0
}
然后您可以让注释视图子类 "observe" 自定义注释子类,随着注释的 angle
变化而旋转。例如,在 Swift 4:
class MyAnnotationView : MKAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
addAngleObserver()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addAngleObserver()
}
// Remember, since annotation views can be reused, if the annotation changes,
// remove the old annotation's observer, if any, and add new one's.
override var annotation: MKAnnotation? {
willSet { token = nil }
didSet { addAngleObserver() }
}
// add observer
var token: NSKeyValueObservation!
private func addAngleObserver() {
if let annotation = annotation as? MyAnnotation {
transform = CGAffineTransform(rotationAngle: annotation.angle)
token = annotation.observe(\.angle) { [weak self] annotation, _ in
UIView.animate(withDuration: 0.25) {
self?.transform = CGAffineTransform(rotationAngle: annotation.angle)
}
}
}
}
}
或在Swift 3:
private var angleObserverContext = 0
class MyAnnotationView : MKAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
addAngleObserver()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addAngleObserver()
}
// add observer
private func addAngleObserver() {
if let annotation = annotation as? MyAnnotation {
transform = CGAffineTransform(rotationAngle: annotation.angle)
annotation.addObserver(self, forKeyPath: #keyPath(MyAnnotation.angle), options: [.new, .old], context: &angleObserverContext)
}
}
// remove observer
private func removeAngleObserver() {
if let annotation = annotation as? MyAnnotation {
annotation.removeObserver(self, forKeyPath: #keyPath(MyAnnotation.angle))
}
}
// remember to remove observer when annotation view is deallocated
deinit {
removeAngleObserver()
}
// Remember, since annotation views can be reused, if the annotation changes,
// remove the old annotation's observer, if any, and add new one's.
override var annotation: MKAnnotation? {
willSet { removeAngleObserver() }
didSet { addAngleObserver() }
}
// Handle observation events for the annotation's `angle`, rotating as appropriate
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &angleObserverContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
UIView.animate(withDuration: 0.5) {
if let angleNew = change![.newKey] as? CGFloat {
self.transform = CGAffineTransform(rotationAngle: angleNew)
}
}
}
}
现在,您的应用程序可以维护对已添加到地图的注释的引用,并设置它们的 angle
,这将在适当的时候在地图视图中直观地表示。
并且,使用这个的一个快速而肮脏的例子:
class ViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!
var annotation = MyAnnotation()
private let reuseIdentifer = Bundle.main.bundleIdentifier! + ".annotation"
private lazy var manager: CLLocationManager = {
let manager = CLLocationManager()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
return manager
}()
override func viewDidLoad() {
super.viewDidLoad()
mapView.register(MyAnnotationView.self, forAnnotationViewWithReuseIdentifier: reuseIdentifer)
manager.requestWhenInUseAuthorization()
manager.startUpdatingHeading()
manager.startUpdatingLocation()
mapView.addAnnotation(annotation)
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation { return nil }
return mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifer, for: annotation)
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last,
location.horizontalAccuracy >= 0 else {
return
}
annotation.coordinate = location.coordinate
}
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
guard newHeading.headingAccuracy >= 0 else { return }
annotation.angle = CGFloat(newHeading.trueHeading * .pi / 180)
}
}
有关 Swift 2 个示例,请参见 previous revision of this answer。