'[CLPlacemark]?' is not convertible to '[CLPlacemark]' -> swift2 - ios

this is a part of code I found on StackOverflow.
It was working in Swift 1.2
Why this code is not working anymore in swift 2:
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
let placeArray = placemarks as [CLPlacemark] // !!! ERROR HERE !!!
// Place details
var placeMark: CLPlacemark!
placeMark = placeArray[0]
// Address dictionary
print(placeMark.addressDictionary)
// Location name
if let locationName = placeMark.addressDictionary["Name"] as? NSString {
print(locationName)
}
// Street address
if let street = placeMark.addressDictionary["Thoroughfare"] as? NSString {
print(street)
}
// City
if let city = placeMark.addressDictionary["City"] as? NSString {
print(city)
}
// Zip code
if let zip = placeMark.addressDictionary["ZIP"] as? NSString {
print(zip)
}
// Country
if let country = placeMark.addressDictionary["Country"] as? NSString {
print(country)
}
})
Error is GetLocationViewController.swift:67:41: '[CLPlacemark]?' is not convertible to '[CLPlacemark]'

Looks like you need to unwrap the placemarks array (implicitly or optional chaining) before assigning it to a type of [CLPlacemarks]
For your example, you should use optional chaining so
if let validPlacemark = placemarks?[0]{
let placemark = validPlacemark as? CLPlacemark;
}
Than place all your logic inside the braces so if it finds a valid placemark array, it will execute your desired commands. If not, it will do nothing or you can handle it however you please

placemarks is not guaranteed to have a value, you could do this:
let placeArray: [CLPlacemark] = placemarks as? [CLPlacemark] ?? []
Which reads as,
if placemarks can be casted as [CLPlacemark], then do so. Otherwise, assign an empty array.
Here's this code in practice:
I'm realizing now that you're on Xcode 7! It's even easier there, all you need is this:
let placeArray = placemarks ?? []

In Xcode 7.0, Objective-C has generic arrays, so your placemarks array is no longer [AnyObject]? but is now [CLLocation]?. You don't need to cast the array, you can just unwrap the optional. With the addition of guard statements, your completion block is now as simple as:
geocoder.reverseGeocodeLocation(location) { placemarks, error in
guard let placemarks = placemarks else { print(error); return; }
guard let placemark = placemarks.first else { return; }
// ...
}

if let pm = placemarks?[0]{
// Example: get the country
print("Your country: \(pm.country!)")
}

Related

Cannot force unwrap value of non-optional type 'AnyObject'

I've converted my iOS Project from swift 2.x to swift 3.x
There are now more then 50 errors in my code. One of the most common is this one "Cannot force unwrap value of non-optional type 'AnyObject'"
Here is a part of the code:
the line let documentURL = JSON[eachOne]["media"]!![eachMedia]["media_url"]! is producing the error
How can I resolve this problem? Thank you!
if let JSON = response.result.value as? [[String: AnyObject]]{
//print("JSON: \(JSON)")
myDefaultValues.userDefaults.setValue(JSON, forKey: "JSON")
for eachOne in 0 ..< (JSON as AnyObject).count{
// print("Cover: \(JSON[eachOne]["cover"])")
//Download all Covers
let documentURL = JSON[eachOne]["cover"]!
let pathus = URL(string: documentURL as! String)
if pathus != nil {
HttpDownloader.loadFileSync(pathus!, completion:{(path:String, error:NSError!) in
})
}
//Download all Media Files
if JSON[eachOne]["media"] != nil{
//print(JSON[eachOne]["media"]!)
//print(JSON[eachOne]["media"]!!.count)
let thisMediaView = JSON[eachOne]["media"]!.count
for eachMedia in 0 ..< thisMediaView!{
//print(JSON[eachOne]["media"]!![eachMedia]["media_url"])
**let documentURL = JSON[eachOne]["media"]!![eachMedia]["media_url"]!**
let pathus = URL(string: documentURL as! String)
if pathus != nil {
HttpDownloader.loadFileSync(pathus!, completion:{(path:String, error:NSError!) in
})
}
}
}
}
}
As a beginning Swift programmer you should pretend that the ! force unwrap operator doesn't exist. I call it the "crash if nil" operator. Instead, you should use if let or guard let optional binding. You cast your JSON object as a an array of dictionaries, so use the array directly:
for anObject in JSON {
guard let mediaArray = anObject["media"] as [[String: Any]] else
{
return
}
for aMediaObject in mediaArray {
guard let aMediaDict = aMediaObject as? [String: Any],
let documentURLString = aMediaDict["media_url"] as? String,
let url = URL(string: documentURLString ) else
{
return
}
//The code below is extracted from your question. It has multiple
//issues, like using force-unwrap, and the fact that it appears to be
//a synchronous network call?
HttpDownloader.loadFileSync(pathus!,
completion:{(path:String, error:NSError!) in {
}
}
}
That code might not be perfect, but it should give you the idea.
First of all, the following line in your code produces an Int, not an Array:
let thisMediaView = JSON[eachOne]["media"]!.count
Second, you could force-unwrap all your values, but that bring a lot of risk. You should not force-unwrap unless... no wait, just don't force-unwrap.
Your line here makes a lot of assumptions on the type of values that are in your JSON, without actually checking.
let documentURL = JSON[eachOne]["media"]!![eachMedia]["media_url"]!
To be a lot safer and more expressive, try to write it as follows:
if let value = JSON[eachOne] as? [String: Any],
let anotherValue = JSON[value] as? [String: Any],
...etc,
let documentURL = anotherValue["media_url"] as? String {
// do something with the values
} else {
// handle unexpected value or ignore
}

Trouble parsing Divvy bike data with json

Im trying to parse some data from the following link: https://feeds.divvybikes.com/stations/stations.json.
func getData() {
let url = URL(string:"https://feeds.divvybikes.com/stations/stations.json")
// Submit a request
let task = URLSession.shared.dataTask(with: url!) { (data,response,error) in
// print error if there is one
if error != nil {
print(error!)
self.alert(messageTitle: "Error", messageM: "An error occured")
return
} // end if
// if there is no error, fetch the json
if let content = data {
do {
let json = try JSONSerialization.jsonObject(with: content, options:JSONSerialization.ReadingOptions.mutableContainers ) as AnyObject
if let stationName = json["stationBeanList"] as? [String: AnyObject] {
for station in stationName{
if let lat1 = ["latitude"] as? CGFloat, let long1 = ["longitude"] as? CGFloat{
print(lat1,long1)
}
}
}
}
catch {
print(error)
self.alert(messageTitle: "Error", messageM: "An error occured")
}
}//end if
}//end task
task.resume()
//endqueue
}
I would like to grab the longitude and latitude from the link above and plot them on a mapview embedded in swift but I cant seem to get even the longitude and latitude to print out or store properly. Any help would be much appreciated.
i do call the getData function on load()
and nothing shows up in the debugger when I run the application.
In the code you are considering the latitude and longitude as String. But its value is not type String.
So print(lat1,long1) will never be called.
Solution: Consider it as CGFloat instead of String, it will work I think so
let url = URL(string:"https://feeds.divvybikes.com/stations/stations.json")
// Submit a request to get the JASON data
let task = URLSession.shared.dataTask(with: url!) { (data,response,error) in
// if there is an error, print the error and do not continue
if error != nil {
print(error!)
self.alert(messageTitle: "Error", messageM: "An error occured")
return
} // end if
// if there is no error, fetch the json formatted content
if let content = data {
do {
let jsonObject = try JSONSerialization.jsonObject(with: content, options: [] ) as AnyObject
if let JSON = jsonObject["stationBeanList"] as? [[String:AnyObject]] {
for stationInfo in JSON {
if let longitude = stationInfo["longitude"] as? Float64, let latitude = stationInfo["latitude"] as? Float64 {
print(longitude,latitude)
// self.objects.append(stationInfo(latitude:latitude,longitude:longitude))
}//end if
}//end if let
}//end do
}//end do
catch {
print(error)
self.alert(messageTitle: "Error", messageM: "An error occured")
}
}//end if
}//end task
task.resume()
}//endqueue
}
After going through your code what I found is you made a just minor mistake to get lat-long from the call of web service.
if you want to fetch value you need to go according to this after the for loop of stationName.
Here is sample snippet for more clarification.
for station in stationName{
print(station["latitude"] as! Double)
print(station["longitude"] as! Double)
}
Hope this helps you :)
There are many issues.
The main issue is that the value for stationBeanList is an array not a dictionary.
.mutableContainers is completely useless in Swift, omit the parameter.
A Swift 3+ JSON dictionary is [String:Any], an array is [[String:Any]].
The unspecified JSON type in Swift 3+ is Any, never AnyObject.
A standard JSON floating point value is Double.
Never cast a JSON result to Any if the expected type is more specific.
if let json = try JSONSerialization.jsonObject(with: content) as? [String: Any] {
if let stationBeanList = json["stationBeanList"] as? [[String: Any]] {
for stationInfo in stationBeanList {
let lat = stationInfo["latitude"] as! Double
let lng = stationInfo["longitude"] as! Double
print(lat, lng)
}
}
}

Swift 2: Error: Initializer for conditional binding must have optional type, not 'CLPlacemark' in function Geocoder

I am creating a location based app where I want the user to be see details of their location. For showing their address details, I have added a new function, CLGeocoder, for finding the details. However, I get this error:
Initializer for conditional binding must have optional type, not
'CLPlacemark'.
I get the error in:
if let p = CLPlacemark(placemark: placemarks![0] as! CLPlacemark )
CLGeocoder().reverseGeocodeLocation(userLocation) { (placemarks, error) in
if (error != nil) {
print(error)
}
else {
if let p = CLPlacemark(placemark: placemarks![0] as! CLPlacemark ) {
}
}
}
You don't have to create the CLPlacemark instance explicitly, reverseGeocodeLocation returns [CLPlacemark]? on success.
CLGeocoder().reverseGeocodeLocation(userLocation) { (placemarks, error) in
if error != nil { // no parentheses in Swift!
print(error!)
}
else if let p = placemarks?.first {
// p is a non-optional CLPlacemark instance
// Do stuff
}
}
The expression inside the if-let needs to be optional. CLPlacemark initializer will never return an optional value. If you're unsure whether placemarks is nil or not, then something like
if let placemarkArray = placemarks {
let p = CLPlacemark(placemark: placemarkArray[0])
// Do stuff
}
}
Otherwise, you don't need the if let...just let.

Why is the method being run at the end?

I am trying to recover all the animal objects based on certain parameters. First I need to retrieve their location from parse as well as the name, but since I am importing more than one and using geocoder, I am using strings, and not an array. So instead of appending the imported information into an array, I am mutating a variable. What I though would happen is the query would go through the first object then run the retrieveLocation method, then proceed to the next object imported from parse, but instead it imports everything then runs the method, so in the end I only get 1 object instead of how many are supposed to be imported.
let query = PFQuery(className: "Animals")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) in
if(error == nil){
for object in objects!{
if let addressobj = object["Add"] as? NSDictionary{
if let address = addressobj["address"] as? String{
self.addr = address
print("sdfadsf \(self.addr)")
}
}
if let name = object["Name"] as? String{
self.impname = name
print("rturrty \(self.impname)")
self.retrieveLocation()
}
}
}
}
func retrieveLocation(){
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(addr, completionHandler: {(placemarks, error) -> Void in
if((error) != nil){
print("Error", error)
}
if let placemark = placemarks?.first {
let coordinates = PFGeoPoint(location: placemark.location)
if(whatever is true){
append the name and address into an array. This is the part where I just get repeats of the LATEST imported object.
}
}
})
}
This should work if you use a local variable and pass this local variable to an implementation of retrieveLocation that takes a string as a parameter retrieveLocation(address: String)
let query = PFQuery(className: "Animals")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) in
if(error == nil){
for object in objects!{
if let addressobj = object["Add"] as? NSDictionary{
if let address = addressobj["address"] as? String{
let newAddress = address
print("sdfadsf \(self.addr)")
}
}
if let name = object["Name"] as? String{
self.impname = name
print("rturrty \(self.impname)")
self.retrieveLocation(newAdress)
}
}
}
}
func retrieveLocation(address: String){
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address, completionHandler: {(placemarks, error) -> Void in
if((error) != nil){
print("Error", error)
}
if let placemark = placemarks?.first {
let coordinates = PFGeoPoint(location: placemark.location)
if(whatever is true){
append the name and address into an array. This is the part where I just get repeats of the LATEST imported object.
}
}
})
}
Problem seems to be that by the time self.addr is being used in the geocodeAddresString method, the for-loop has finished and thus overwritten all the previous values that were at one point individually held by self.addr. By using a local variable, it will be sure to use a unique value to geocodeAddressString each time it is executed

Error creating multiple map annotations from a parse class

I'm trying to create annotations from Parse backend but I get an error '[PFObject]?' is not convertible to '[PFObject]'
I based my code on a question i found here Query a GeoPoint from Parse and add it to MapKit as MKAnnotation?
Heres a pic of my code and the error. code photo
{
mapView.showsUserLocation = true
mapView.delegate = self
mapView.setUserTrackingMode(MKUserTrackingMode.Follow, animated: true)
MapViewLocationManager.delegate = self
MapViewLocationManager.startUpdatingLocation()
var annotationQuery = PFQuery(className: "Movers")
currentLoc = PFGeoPoint(location: MapViewLocationManager.location)
annotationQuery.whereKey("ubicacion", nearGeoPoint: currentLoc, withinKilometers: 10)
annotationQuery.findObjectsInBackgroundWithBlock {
(movers, error) -> Void in
if error == nil {
// The find succeeded.
print("Successful query for annotations")
let myMovers = movers as [PFObject]
for mover in myMovers {
let point = movers["ubicacion"] as PFGeoPoint
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(point.latitude, point.longitude)
self.mapView.addAnnotation(annotation)
}
}else {
// Log details of the failure
print("Error: \(error)")
}
}
}
Thanks in advance
myMovers is a [PFObject]?, an optional array of PFObjects (as in maybe an array of PFObjects or maybe nil). Because it is an optional, it's not directly convertible to the non optional because you can't convert nil to [PFObject]. So what you really want is to do a conditional cast here using as? and put it in an if let statement. Like so
if let myMovers = movers as? [PFObject] {
// Use myMovers to do what you want
}
That will execute what is in the braces only when movers is a [PFObject] and not nil.

Resources