我快要进入应用程序的最后阶段了,该阶段显示了公共汽车的实时地图。因此,基本上,我有一个计时器,该计时器从xml表中定期获取总线的纬度和经度,该xml表提供了总线的实时位置。我能够设置xml解析器,为公共汽车的运动设置动画,并为公共汽车设置一个自定义(箭头)图像。
但是,问题是,从多个总线的阵列中,我只能使单个总线旋转。查看xml数据,它总是xml表中正在旋转的第一条总线。早些时候,我甚至在旋转单个总线时都遇到了麻烦,因此用户“ Good Doug”帮助了我,并使它正常运行。您可以在此处看到该帖子:自定义注释图像仅在程序(Swift-iOS)的开头旋转。我试图通过为每条总线制作MKAnnotationView数组来使用相同的解决方案。我不确定这是否是正确的方法。如果有人可以帮助我,我会很高兴:)
首先,这是XML工作表的外观(在此示例中,有两种工具,因此我们只需要跟踪其中两种):
<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文件中)。这可能需要一些改进。
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
s数组。也许,我对注释视图如何在iOS中工作的理解是错误的。我很高兴能有人帮我解决这个问题,因为我已经坚持了一段时间。即使整个实现需要更改,我也很乐意尝试一下。这是问题的屏幕截图。
再次请注意,所有的公交车都出现在正确的位置并平稳地移动,但实际上只有其中一辆在旋转。提前致谢。
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.
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
}
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)
}
}
}
}
}
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
将在可视的情况下在地图视图中直观地表示。
并且,是一个使用此方法的简单示例:
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示例,请参见此答案的先前版本。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句