TonyLanglet
TonyLanglet

Reputation: 3

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)



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

    }
}

Upvotes: 0

Views: 233

Answers (1)

tbilopavlovic
tbilopavlovic

Reputation: 1098

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)

Upvotes: 0

Related Questions