TonyLanglet TonyLanglet - 5 months ago 36
Swift Question

MapView isn't instanciated in function

I'm working on an app which uses MKMapView and I wish to place annotations based on a PHP file which loads data from a MySQL. I'm not that experienced with iOS programming but I'm learning and I can't put my head on the problem that occurs for me.

I've managed to get the data to a MSMutableArray and converts it to a NSDirctionary and are using an object class for each object. When I'm trying to place a pin (Annotation) on the map I get the error

fatal error: unexpectedly found nil while unwrapping an Optional value
I've looked through the debug and find that the mapView is nil and came to the conclusion that when I try to place a annotation the program don't have a reference to where it should place the annotation.

I've managed to get the annotations to show when I place the creation of the object and call for the mapView.addAnnotation(object) in the viewDidLoad() function/method. But whenever I'm nesting the mapView into another function within the class (ViewController) it breaks.

The forwardGeoCoding method is to convert the address which i get in plain text to coordinates to be able to place them correctly on the map. The setArray is fetching the data from the MySQL/PHP class and makes an array available in the ViewController (Bad name standard as the annotation takes place inside as well)





  • I've tried to send the mapView to the function without result

  • I've tried without and calling the mapView form the ViewController
    within the function
    self.mapView.addAnnotation(test)
    without
    result

  • I've tried doing it from the SQL retrieving class without result






Break point - The error occurs where I'm calling the forwardGeocoding function because the mapView control is nil.




ViewController.swift

// Created by Tony Langlet on 2016-05-18.
// Copyright © 2016 Tony Langlet. All rights reserved.
//

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController, MKMapViewDelegate, HomeModelProtocal {

var feedItems: NSArray = NSArray()
var selectedLocation : LocationModel = LocationModel()
var popViewController : PopUpViewControllerSwift! = PopUpViewControllerSwift(nibName: "PopUpViewController", bundle: nil)

@IBOutlet weak var mapView: MKMapView!

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

let deviceItem = UIScreen.mainScreen().traitCollection.userInterfaceIdiom

switch (deviceItem) {
case .Phone:
if UIScreen.mainScreen().scale == 3 {
popViewController = PopUpViewControllerSwift(nibName: "PopUpViewController_iPhone6Plus", bundle: nil)
} else {
popViewController = PopUpViewControllerSwift(nibName: "PopUpViewController_iPhone6", bundle: nil)
}
case .Pad:
popViewController = PopUpViewControllerSwift(nibName: "PopUpViewController_iPad", bundle: nil)
case .Unspecified:
popViewController = PopUpViewControllerSwift(nibName: "PopUpViewController", bundle: nil)
default:
popViewController = PopUpViewControllerSwift(nibName: "PopUpViewController", bundle: nil)
}

mapView.delegate = self

let homeModel = HomeModel()
homeModel.delegate = self
homeModel.downloadItems()

}

func itemsDownloaded(items: NSArray) {
feedItems = items

//self.listTableView.reloadData()
}

func setArray(array: NSMutableArray) {
var jsonElement: NSDictionary = NSDictionary()

for item in array {
jsonElement = item as! NSDictionary
let id = jsonElement["id"] as? String
let name = jsonElement["name"] as? String
let address = jsonElement["address"] as? String
let city = jsonElement["city"] as? String
let country = jsonElement["country"] as? String
let typ = jsonElement["typ"] as? String
let lastresult = jsonElement["lastresult"] as? String

let myaddress = ("\(address), \(city), \(country)")
//print(array)
forwardGeocoding(self.mapView, coordAddress: myaddress, id: id!, name: name!, address: address!, typ: typ!, lastresult: last result!) // <-- This is where it breaks.

}
}

func forwardGeocoding(mv: MKMapView, coordAddress: String, id: String, name: String, address: String, typ: String, lastresult: String) {

CLGeocoder().geocodeAddressString(coordAddress, completionHandler: { (placemarks, error) in
if error != nil {
print(error)
return
}

if placemarks?.count > 0 {
let placemark = placemarks?[0]
let location = placemark?.location
let coordinate = location?.coordinate
//print("\(name) \n\(address) \n\(typ) \n\(lastresult) \nlat: \(coordinate!.latitude), long: \(coordinate!.longitude)")

let test = restObject(id: id, name: name, address: address, coordinate: CLLocationCoordinate2D(latitude: (coordinate!.latitude), longitude: (coordinate!.longitude)), typ: typ, lastresult: lastresult)


mv.addAnnotation(test)

}
})
}


func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "restObject"

if annotation.isKindOfClass(restObject.self) {
if let annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier) {
annotationView.annotation = annotation
return annotationView
} else {
let annotationView = MKPinAnnotationView(annotation:annotation, reuseIdentifier:identifier)
annotationView.enabled = true
annotationView.canShowCallout = true

let btn = UIButton(type: .DetailDisclosure)
annotationView.rightCalloutAccessoryView = btn
return annotationView
}
}

return nil
}


func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let restaurant = view.annotation as! restObject
let name = restaurant.name
let address = restaurant.address
let typ = restaurant.typ
let lastresult = restaurant.lastresult

popViewController.title = title
popViewController.showInView(self.view, argStatusImg: UIImage(named: "icon-nogood"), argTitle: name, argAddress: address, argType: typ, argLastresult: lastresult, argDate: "2016-01-01", animated: true)

}
}


HomeModel.swift (Retrieving of the data from PHP/MYSQL)

// Created by Tony Langlet on 2016-06-10.
// Copyright © 2016 Tony Langlet. All rights reserved.
//

import Foundation

protocol HomeModelProtocal: class {
func itemsDownloaded(items: NSArray)
}

class HomeModel: NSObject, NSURLSessionDataDelegate {

weak var delegate: HomeModelProtocal!

var data : NSMutableData = NSMutableData()

let urlPath: String = "path to PHP file"

func downloadItems() {

let url: NSURL = NSURL(string: urlPath)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()

session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)

let task = session.dataTaskWithURL(url)

task.resume()
}

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data)
}

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?){
if error != nil {
print("Failed to download data")
} else {
print("Data downloaded")
var jsonResult: NSMutableArray = NSMutableArray()

do {
jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}

let VC = ViewController()
VC.setArray(jsonResult)

}
}
}


restObject.swift (Object class)

// Created by Tony Langlet on 2016-05-18.
// Copyright © 2016 Tony Langlet. All rights reserved.
//


import MapKit
import UIKit

class restObject: NSObject, MKAnnotation {
var id: String
var name: String
var address: String
var coordinate: CLLocationCoordinate2D
var typ: String
var lastresult: String

init(id: String, name: String, address: String, coordinate: CLLocationCoordinate2D, typ: String, lastresult: String) {

self.id = id
self.name = name
self.address = address
self.coordinate = coordinate
self.typ = typ
self.lastresult = lastresult

}
}

Answer

EDIT 2:

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?){
    if error != nil {
        print("Failed to download data")
    } else {
        print("Data downloaded")
        var jsonResult: NSMutableArray = NSMutableArray()

        do {
            jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
        } catch let error as NSError {
            print(error)
        }

        //let VC = ViewController()
        //VC.setArray(jsonResult)
        // Here you are creating new view controller, it's not same controller which you are seeing on screen, and in this init controllers view is not loaded from xib and that's why mapView is nil  
        delegate?.itemsDownloaded()

    }

EDIT: add breakpoint in the forwardGecoding and check all variables, coordinate might be nil, and there it crashed.

override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

You should pass the name of your xib file in this method so it loads view. super.init(nibName: NAME_OF_XIB, bundle: nibBundleOrNil)

Comments