Skip to content

Instantly share code, notes, and snippets.

@rodericj
Last active May 27, 2016 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rodericj/0bf0b70dc8a7faf0cce5a5c52b60d917 to your computer and use it in GitHub Desktop.
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
@patzearfoss
Copy link

patzearfoss commented May 20, 2016

I think you could combine these statements into one if...let

                        if let position = json["iss_position"]  {
                            if let latitude = position["latitude"], longitude = position["longitude"] {
                                self.completion(latitude as! Double, longitude as! Double)
                            }
                        }

In fact, in what whole section, depending on what you want to do with the error you could use try? ISO try.

    func stop() {
        if let timer = timer {
            timer.invalidate()
        }
    }

could be more succinctly

    func stop() {
        timer?.invalidate()
    }

What not just

    var title = "ISS"

@patzearfoss
Copy link

You could also use trailing closures on things like dispatch_async

@rodericj
Copy link
Author

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

@patzearfoss
Copy link

! 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()

@rodericj
Copy link
Author

Regarding the title, to conform to the MKAnnotation protocol it'd have to be var title: String? = "ISS". So thanks for making that tighter.

@adamstener
Copy link

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.

@rodericj
Copy link
Author

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.

@adamstener
Copy link

The selector syntax also has been updated:
selector: #selector(ISSFetcher.getNext)

@adamstener
Copy link

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.

@adamstener
Copy link

adamstener commented May 27, 2016

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment