-
-
Save rodericj/0bf0b70dc8a7faf0cce5a5c52b60d917 to your computer and use it in GitHub Desktop.
//: Playground - Track the ISS from a playground | |
import MapKit | |
import XCPlayground | |
class ISSFetcher: NSObject { | |
var completion: (Double, Double) -> Void | |
var timer: NSTimer? | |
init(inCompletion: (Double, Double) -> Void) { | |
completion = inCompletion | |
} | |
func getNext() { | |
guard let url = NSURL(string: "http://api.open-notify.org/iss-now.json") else { | |
print("we need a url") | |
return | |
} | |
let request = NSURLRequest(URL: url) | |
let session = NSURLSession.sharedSession() | |
let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) in | |
if let data = data, json = try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? [String: AnyObject], | |
position = json?["iss_position"], | |
latitude = position["latitude"], | |
longitude = position["longitude"] { | |
self.completion(latitude as! Double, longitude as! Double) | |
} | |
if let error = error { | |
print("error fetching new coordinates", error) | |
} | |
}) | |
task.resume() | |
} | |
func stop() { | |
timer?.invalidate() | |
} | |
func start() { | |
// kick things off | |
getNext() | |
// set it up so we do it again after 5 seconds | |
timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: #selector(getNext), userInfo: nil, repeats: true) | |
} | |
} | |
class ISSAnnotation: NSObject, MKAnnotation { | |
dynamic var coordinate: CLLocationCoordinate2D | |
var title: String? = "ISS" | |
init(lat: Double, lon: Double) { | |
coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon) | |
} | |
func update(newLatitude: Double, newLongitude: Double) { | |
coordinate = CLLocationCoordinate2D(latitude: newLatitude, longitude: newLongitude) | |
} | |
} | |
// This is our main() | |
let frame = CGRect(x: 0, y: 0, width: 400, height: 400 ) | |
let mapView = MKMapView(frame: frame ) | |
mapView.mapType = .Satellite | |
let annotation = ISSAnnotation(lat: 0,lon: 0) | |
mapView.addAnnotation(annotation) | |
// Create the fetcher object with completion block | |
let fetcher = ISSFetcher(inCompletion: {(lat, lon) in | |
dispatch_async(dispatch_get_main_queue(), { | |
UIView.animateWithDuration(3, animations: { | |
print("new coordinates", lat, lon) | |
annotation.update(lat, newLongitude: lon) | |
mapView.centerCoordinate = annotation.coordinate | |
}) | |
}) | |
}) | |
// start the fetcher | |
fetcher.start() | |
// view the map in the timeline! | |
XCPlaygroundPage.currentPage.liveView = mapView |
You could also use trailing closures on things like dispatch_async
@pzearfoss This:
func stop() {
timer?.invalidate()
}
vs:
func stop() {
if let timer = timer {
timer.invalidate()
}
}
It's definitely more concise. Is that what the kids are doing these days? I feel like I've been a fan of not using ?
and !
where possible, but maybe it's a misguided approach that I'm taking. It most certainly makes the code more verbose.
!
is the devil IMO. Most of my swift crashes are there.
?
in this case is a shorter way of doing the same thing. Semantically the same as sending a message to nil in that if timer is an optional it won't try to call invalidate()
Regarding the title, to conform to the MKAnnotation
protocol it'd have to be var title: String? = "ISS"
. So thanks for making that tighter.
as far as ! and ? go, I'm totally fine with ? wherever needed, and ! is in fact the devil. Having ? around fairly often is a lot better than if lets everywhere. Once your code grows, the if lets really can take over, and it gets unreadable.
Cool, I think I'm remembering back in swift 1, possibly before if let
where if let
was supposed to be the savior. I'm probably mis-remembering. But I'm definitely seeing the value of not having if let
.
The selector syntax also has been updated:
selector: #selector(ISSFetcher.getNext)
if let is more common for me to use where I need a non-optional value to call into something. So I need to make sure it's there and if it is, then I can use the non-optional thing to make the call and if it's not there I can make a different decision. In this case, if the timer is not there then there's no other decision to make. If it is there then it gets invalided and that's all there is to do.
This one is purely preference, but I like the look of this trailing closure syntax better (I think this is what pzearfoss was referring to, though there are many variations of syntax closure syntax). There are many many people who will say they like other ways better, because there are lots of ways of writing closure syntax, but the trailing ')' at the end of a method is just less appealing to me. The argument that most people have is that your current syntax looks more like a closure, so its' more obvious what is going on, I just think this way is cleaner, and I've gotten used to it. There is no performance benefit, strictly which syntax works better for whatever team/person is working on the code:
// Create the fetcher object with completion block
let fetcher = ISSFetcher() { lat, lon in
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(3) {
print("new coordinates", lat, lon)
annotation.update(lat, newLongitude: lon)
mapView.centerCoordinate = annotation.coordinate
}
}
}
let task = session.dataTaskWithRequest(request) { data, response, error in
if let data = data, json = try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? [String: AnyObject],
position = json?["iss_position"],
latitude = position["latitude"],
longitude = position["longitude"] {
self.completion(latitude as! Double, longitude as! Double)
}
if let error = error {
print("error fetching new coordinates", error)
}
}
I think you could combine these statements into one if...let
In fact, in what whole section, depending on what you want to do with the error you could use
try?
ISOtry
.could be more succinctly
What not just