只有一个(自定义)注释从其他注释数组旋转 [英] Only one (custom) annotation rotating from an array of other annotations

查看:89
本文介绍了只有一个(自定义)注释从其他注释数组旋转的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我快要进入应用程序的最后一个阶段,该阶段显示了公共汽车的实时地图.因此,基本上,我有一个计时器,该计时器从xml表中定期获取总线的纬度和经度,该xml表提供了总线的实时位置.我能够设置xml解析器,为公共汽车的运动设置动画,并为公共汽车设置一个自定义(箭头)图像.

I am almost towards the last phase of my app, which shows a live map of buses. So, basically, I have a timer which gets the latitude and longitude of a bus periodically from a xml sheet which provides real-time locations of the buses. I was able to setup the xml parser, animate the buses' movement and setup a custom (arrow) image for the buses.

但是,问题是,从多条总线的阵列中,我只能使一根总线旋转.查看xml数据,它总是xml表中正在旋转的第一条总线.早些时候,我甚至在旋转单个总线时都遇到了麻烦,因此用户"Good Doug"帮助了我,并使它正常运行.您可以在此处查看该帖子:自定义注释图像仅在程序(Swift-iOS)的开头旋转.我试图通过为每条总线制作一个MKAnnotationView数组来使用相同的解决方案.我不确定这是否是正确的方法.如果有人可以帮助我,我会很高兴:)

However, the problem is, from an array of multiple buses, I can only get a single bus to rotate. Looking at the xml data, it's always the first bus from the xml sheet which is rotating. Earlier, I was having trouble with rotating even a single bus, so user "Good Doug" helped me out and I was able to get it working. You can see the post here: Custom annotation image rotates only at the beginning of the Program (Swift- iOS). I tried to use the same solution by making an array of MKAnnotationView for each bus. I'm not sure if this is the right approach. I'd be glad if someone could help me out with this :)

首先,这是XML工作表的样子(在此示例中,有两辆车,因此我们只需要跟踪其中两辆车):

First of all, this is how the XML sheet looks like (In this example, there are two vehicles, so we need to track only two of them):

<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> 

这是我对单独的Bus类的实现(在Bus.swift文件中).这可能需要一些改进.

Here's my implementation of a separate Bus class (in Bus.swift file). This could use some improvement.

class Bus : MKPointAnnotation, MKAnnotation  {
    var oldCoord : CLLocationCoordinate2D!
    var addedToMap = false

    init(coord: CLLocationCoordinate2D) {
        self.oldCoord = coord
    }
}

这是我的ViewController.swift-

Here's the code from my 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中的工作方式的理解是错误的.我很高兴有人帮我解决这个问题,因为我已经坚持了一段时间.即使整个实现需要更改,我也很乐意尝试一下.这是问题的屏幕截图.

I am doubtful of my viewForAnnotation implementation. I am also unsure if it's okay have an array of MKAnnotationViews. Perhaps, my understanding of how annotation views work in iOS is wrong. I'd be glad if someone could help me out with this as I've been stuck on it for a while. Even if the overall implementation needs changing, I'd be glad to try it out. Here's a screenshot of the problem.

再次请注意,所有公交车均显示在正确的位置并平稳移动,但实际上只有其中一辆会旋转.提前致谢.

Once again, please note that all the buses appear on the correct positions and move smoothly, but just one of them actually rotate. Thanks in advance.

推荐答案

我认为解析代码直接操纵批注视图不合适.您不知道它们是否可见,是否已实例化等等.mapview负责管理注释视图,而不是您.

I don't think it's appropriate for the parsing code to manipulate annotation views directly. You don't know if they're visible, whether they've been instantiated yet, etc. The mapview is responsible for managing the annotation views, not you.

如果需要维护总线和注释之间的交叉引用,请执行此操作,但不要维护对注释视图的引用.您的应用与注释的交互应仅限于注释本身.因此,创建一个具有angle属性的注释子类.

If you need to maintain cross reference between busses and annotations, do that, but don't maintain references to annotation views. Your app's interaction with the annotations should be limited to the annotations themselves. So create an annotation subclass that has a angle property.

class MyAnnotation : MKPointAnnotation {
    @objc dynamic var angle: CGFloat = 0.0
}

然后,您可以让注释视图子类观察"自定义注释子类,并随着注释的angle更改而旋转.例如,在Swift 4中:

Then you can then have the annotation view subclass "observe" the custom annotation subclass, rotating as the annotation's angle changes. For example, in 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中:

Or in 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,并将在相应的地图视图中可视地表示.

Now, your app can maintain references to annotations that have been added to the map, and set their angle and this will be visually represented in the map view as appropriate.

还有一个使用它的简单而又肮脏的例子:

And, a quick and dirty example of using this:

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示例,请参见此答案的先前版本.

这篇关于只有一个(自定义)注释从其他注释数组旋转的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆