I'm building an app that displays the positions of buses on a map using the Google Maps SDK in Swift 2.0. I have an XML file that updates every 10 seconds and displays the lats and longs of all active shuttles. In my code I have marker objects that have fields for the lats, longs, IDs, and names of the buses.
I have placed the code that displays the map centered on the location where I want to show the buses AND the for loop that iterates through all the buses positions to display them on the map as markers in a function called realoadBuses(). In this function I also have the line of code that communicates with the XML file.
I use NSTimer() to call realoadBuses() every 10 seconds so the positions of the buses get updated accordingly.
The following is what my code looks like:
// runs reloadBuses() every 10 seconds
timer = NSTimer.scheduledTimerWithTimeInterval(10.0, target: self, selector: "reloadBuses", userInfo: nil, repeats: true)
func reloadBuses() {
// displays the map adjusted on Campus
let camera = GMSCameraPosition.cameraWithLatitude(37.0000,
longitude: -122.0600, zoom: 14)
let mapView = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
mapView.mapType = kGMSTypeNormal
mapView.myLocationEnabled = true
self.view = mapView
// XML file
parser = NSXMLParser(contentsOfURL:(NSURL(string:"http://link.to.xml.file.xml"))!)!
let coord = Coord2()
parser.delegate = coord
parser.parse()
print("coord has a count attribute of \(coord.count)")
print("coord has \(coord.markers.count) markers")
// loops through all the lats and lngs of the buses and produces a marker
// for them on our Google Maps app
for marker in coord.markers {
print("marker id=\(marker.id), lat=\(marker.lati), lng=\(marker.lngi), route=\(marker.route)")
// displays the buses
let buses = GMSMarker()
buses.position = CLLocationCoordinate2DMake(marker.lati, marker.lngi)
buses.title = marker.route
if buses.title == "UPPER CAMPUS" {
buses.icon = UIImage(named: "uppercampus")
} else if buses.title == "LOOP" {
buses.icon = UIImage(named: "innerloop")
}
buses.snippet = marker.id
buses.map = mapView
}
}
The problem I'm having is that the map gets refreshed along with the bus markers every time NSTimer() calls reloadBuses().
I know this happens because I have the code that displays the map inside the reloadBuses() function. I have tried to place that chunk of code somewhere before the call to NSTimer(), but then mapView is out of scope which is needed for the last line in the for loop inside of reloadBuses().
I then tried passing mapView into reloadBuses() by doing
// displays the map adjusted on Campus
let camera = GMSCameraPosition.cameraWithLatitude(37.0000,
longitude: -122.0600, zoom: 14)
let mapView = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
mapView.mapType = kGMSTypeNormal
mapView.myLocationEnabled = true
self.view = mapView
// runs reloadBuses() every 10 seconds
timer = NSTimer.scheduledTimerWithTimeInterval(10.0, target: self, selector: "reloadBuses(mapView)", userInfo: nil, repeats: true)
reloadBuses(map: GMSMapView){
...
for loop {
...
buses.map = map
}
and the code would build successfully and the map would display without any markers(buses), but then after about a few seconds the app would crash and I would get this error:
**2015-10-28 19:42:05.731 GeoBus[1926:107070] -[GeoBus.ViewController reloadBuses(self.view)]: unrecognized selector sent to instance 0x7f91784aebc0 2015-10-28 19:42:05.743 GeoBus[1926:107070] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[GeoBus.ViewController reloadBuses(mapView)]: unrecognized selector sent to instance 0x7f91784aebc0' *** First throw call stack: ( 0 CoreFoundation 0x000000010600bf65 __exceptionPreprocess + 165 1 libobjc.A.dylib 0x0000000107cd9deb objc_exception_throw + 48 2 CoreFoundation 0x000000010601458d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205 3 CoreFoundation 0x0000000105f61f7a ___forwarding___ + 970 4 CoreFoundation 0x0000000105f61b28 _CF_forwarding_prep_0 + 120 5 Foundation 0x00000001063f1671 __NSFireTimer + 83 6 CoreFoundation 0x0000000105f6c364 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20 7 CoreFoundation 0x0000000105f6bf11 __CFRunLoopDoTimer + 1089 8 CoreFoundation 0x0000000105f2d8b1 __CFRunLoopRun + 1937 9 CoreFoundation 0x0000000105f2ce98 CFRunLoopRunSpecific + 488 10 GraphicsServices 0x0000000109e03ad2 GSEventRunModal + 161 11 UIKit 0x0000000106828676 UIApplicationMain + 171 12 GeoBus 0x0000000103ca7bfd main + 109 13 libdyld.dylib 0x000000010880292d start + 1 14 ??? 0x0000000000000001 0x0 + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException**
Yep, the map gets recreated every time you call:
let mapView = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
mapView.mapType = kGMSTypeNormal
mapView.myLocationEnabled = true
self.view = mapView
I'd suggest you instead set up the map view in interface builder instead, and just get a binding to it through IB's ability to add outlets to your class.
As to moving markers around, what you need to do is to hang on to the markers in a data structure in your code and move them, instead of attempting to recreate the display on each data refresh from the server.