Unexpectedly found Nil unwrapping an Optional value - ios

I am aware that there are many other questions like this however I assure you this is not a duplicate as far as I can tell. As you can see in the code below I have not marked any values as Optional however I keep getting this error. It crashes when I run the snapNext value highlighting the
viewMap.camera = newLocation
Here is the full code below, I have the viewMap linked to just a regular UIView
import UIKit
import MapKit
import GoogleMaps
class ViewController: UIViewController {
var camera = GMSCameraPosition.cameraWithLatitude(33.600727, longitude: -117.900840, zoom: 16.9)
#IBOutlet weak var viewMap: GMSMapView!
override func viewDidLoad() {
super.viewDidLoad()
viewMap.camera = camera
viewMap = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
viewMap.myLocationEnabled = true
viewMap.settings.myLocationButton = true
let marker = GMSMarker()
marker.position = CLLocationCoordinate2DMake(33.600727, -117.900840)
marker.title = "Newport Beach"
marker.snippet = "California"
marker.map = viewMap
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func snapNext(sender: AnyObject) {
let newLocation = GMSCameraPosition.cameraWithLatitude(33.622578, longitude: -117.911099, zoom: 16.9)
viewMap.camera = newLocation
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Your error is in the line:
viewMap = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
Here you are setting the map to a new anonymous object and because the map property is weak it will be set to nil automatically as soon as the anonymous object goes out of scope (i.e. when viewDidLoad completes).
Either you want to create the object in the storyboard; in which case leave it as a weak outlet, or you just want it as a 'normal' property; in which case remove the weak and IBOutlet.

import UIKit
import MapKit
import GoogleMaps
class ViewController: UIViewControllerGMSMapViewDelegate , CLLocationManagerDelegate {
var camera:GMSCameraPosition!
#IBOutlet weak var viewMap: GMSMapView!
override func viewDidLoad() {
super.viewDidLoad()
camera = GMSCameraPosition.cameraWithLatitude(33.600727, longitude: -117.900840, zoom: 16.9)
viewMap.camera = camera
viewMap = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
viewMap.myLocationEnabled = true
viewMap.delegate=self
viewMap.settings.myLocationButton = true
let marker = GMSMarker()
marker.position = CLLocationCoordinate2DMake(33.600727, -117.900840)
marker.title = "Newport Beach"
marker.snippet = "California"
marker.map = viewMap
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func snapNext(sender: AnyObject) {
camera = GMSCameraPosition.cameraWithLatitude(33.622578, longitude: -117.911099, zoom: 16.9)
viewMap.camera = camera
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

As you've already pointed out the viewMap is nil. Why it's getting released, I do not know (Need more info to know that). But I know that you can prevent it by removing the weak keyword.
#IBOutlet weak var viewMap: GMSMapView!
However, this might lead to leaks if you have retain cycles between your viewMap and your view controller.

Related

Passing a Double between ViewControllers Swift 3

Total noob here to Swift 3. All I need to do is pass a double value from one ViewController to another via the 2 user input text fields. I've tried numerous solutions and have read everything I can find on passing data between ViewControllers. I get a 'fatal error: unexpectedly found nil while unwrapping an Optional value'. I have a real hard time understand the wrapping and unwrapping of the variables and I'm sure it's something simple.
Here is my first ViewController:
import UIKit
var longitude: Double?
var latitude: Double?
class ViewController: UIViewController {
#IBOutlet var getLongitude: UITextField!
#IBOutlet var getLatitude: UITextField!
#IBOutlet var mapbutton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func MapBtn(_ sender: Any) {
performSegue(withIdentifier: "segue", sender: self)
longitude = Double(getLongitude.text!)!
latitude = Double(getLatitude.text!)!
}
}
And this is the SecondViewController:
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let camera = GMSCameraPosition.camera(withLatitude: latitude!, longitude: longitude!, zoom: 6.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
view = mapView
// Creates a marker in the center of the map.
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
marker.map = mapView
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Avoid global mutable state. Send data from one scene to another.
Try this:
class ViewController: UIViewController {
#IBOutlet weak var longitudeTextField: UITextField!
#IBOutlet weak var latitudeTextField: UITextField!
#IBOutlet weak var mapButton: UIButton!
#IBAction func didTapToMapButton(_ sender: UIButton) {
// I assume your storyboard' name is Main. If not, change it below accordingly
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
// I assume your destination view controller' identifier and type is SecondViewController. If not, change it below accordingly.
if let secondViewController = mainStoryboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController {
if let longitude = Double(longitudeTextField.text),
let latitude = Double(latitudeTextField.text) {
secondViewController.latitude = latitude
secondViewController.longitude = longitude
}
present(secondViewController, animated: true, completion: nil)
}
}
}
class SecondViewController: UIViewController {
var latitude: Double?
var longitude: Double?
override func viewDidLoad() {
super.viewDidLoad()
if let latitude = latitude,
let longitude = longitude {
let camera = GMSCameraPosition.camera(withLatitude: latitude, longitude: longitude, zoom: 6.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
view = mapView
// Creates a marker in the center of the map.
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
marker.map = mapView
}
}
}
1st, Unwrapping. when you try to unwrap the variable, rather forcely unwrap it, it's better test if it's nil, or you could use if let to safely unwrap it.
2nd, pass variable from one VC to another. As you know, in iOS, those VCs instances are like any other variable. All you need to do is trying to fetch a reference to that VC, then assign the Double to its accessible variable. Take a look at this link
You could create a segue between the two controllers and pass the variable by overriding prepare for segue.
Pass that variable to the new controller and load that information to its outlet on viewDidAppear or viewWillAppear.
self.prepare(for: "segueName", sender: self)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueName" {
if let vc = segue.destination as? DestinationViewController {
// Pass Data to New View Controller
}
}
}

Passing latitude and longitude coordinates into a second view controller

I wish to save latitude and longitude coordinates in two labels on a second view controller but I am having trouble getting it to work.
Here is my code:
First View Controller:
import UIKit
import CoreLocation
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet var map: MKMapView!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation: CLLocation = locations[0]
let latitude = userLocation.coordinate.latitude
let longitude = userLocation.coordinate.longitude
let latDelta: CLLocationDegrees = 0.05
let lonDelta: CLLocationDegrees = 0.05
let span = MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lonDelta)
let location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let region = MKCoordinateRegion(center: location, span: span)
self.map.setRegion(region, animated: true)
}
}
Second View Controller:
import UIKit
class AddSightingViewController: UIViewController {
var getCoordinates: ViewController!
#IBOutlet weak var latitudeLabel: UILabel!
#IBOutlet weak var longitudeLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
latitudeLabel.text = "\(getCoordinates.locationManager.location?.coordinate.latitude)"
longitudeLabel.text = "\(getCoordinates.locationManager.location?.coordinate.longitude)"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
when you push VC
objAddSightVC.location = CLLocationCoordinate2D(latitude: your_lat, longitude: your_long)
in AddSightingViewController
var location : CLLocationCoordinate2D!
and
latitudeLabel.text = String(describing: location.latitude)
longitudeLabel.text = String(describing: location.longitude)
on storyboard initiate or push VC
let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
let addSightingViewController = AddSightingViewController(location :coordinate)
in AddSightingViewController declare property location with init constructor
class AddSightingViewController: UIViewController {
let location:LLocationCoordinate2D
init(location:LLocationCoordinate2D!){
self.location = location
}
override func viewDidLoad() {
super.viewDidLoad()
latitudeLabel.text = "\(location?.coordinate.latitude)"
longitudeLabel.text = "\(location?.coordinate.longitude)"
}
}

Add markers from UITableViewController to UIViewController

I have a UIViewController, inside of this one in my storyboard I have a UITableViewController and a GMSMapView.
Well, I have a service to populate my UITableViewController, with some information and georeferences. The UITableViewController behavior is working perfectly, but when I try to access to my UIViewController the markers aren't added.
class RequestViewController: UIViewController {
#IBOutlet weak var mapView: GMSMapView!
var solicitudes = [SolicitudesModel]()
override func viewDidLoad() {
super.viewDidLoad()
let camera = GMSCameraPosition.camera(withLatitude: 83.4824182, longitude: -88.1776567, zoom: 15)
self.mapView.camera = camera
let marker = GMSMarker()
marker.position = CLLocationCoordinate2DMake(13.4824182, -88.1776567)
marker.title = "My location"
marker.map = self.mapView
}
}
The marker as this point was added.
class RequestTableViewController: UITableViewController {
var reqs = [RequestModel]()
#IBOutlet var RequestTable: UITableView!
override func viewDidLoad() {
var requestVC = RequestViewController()
Alamofire.request(UrlGlobals.retriveInformation()).responseJSON { response in
let json = JSON(response.result.value)
var i: Int = 0
self.reqs.removeAll()
for _ in json.array ?? [] {
//Some code to populate the table
let marker = GMSMarker()
marker.position = CLLocationCoordinate2DMake(CLLocationDegrees(json["lat"].float!), CLLocationDegrees(json["lng"].float!))
marker.title = "Some title"
marker.map = requestVC.mapView
}
self.RequestTable.reloadData()
}
}
}
The json retrieves all the information correctly. How I can access to my primary ViewController and add those google markers?
Right now your RequestViewController is an instance that exists only in viewDidLoad. Make it a class property and you will be able to access it anywhere.

Google Maps API IOS cannot load new coordinates with custom UIView

I have a problem with loading coordinates for a custom UIView that has been linked through IBOutlet and subclassed as GMSMapView. The mapView loads but it always shows the wrong location every time (always London as I presume that is the default). But if I change self.mapView to self.view, the coordinates load correctly within the map. I have no clue why loading coordinates with a custom UIView doesn't work while using the superview works. Thank you in advanced!
#IBOutlet var mapView: GMSMapView!
override func loadView() {
super.loadView()
let kCameraLatitude = 37.314617900000002
let kCameraLongitude = -121.7901318
let camera = GMSCameraPosition.cameraWithLatitude(kCameraLatitude,
longitude: kCameraLongitude, zoom: 1)
let newMapView = GMSMapView.mapWithFrame(self.mapView.frame, camera: camera)
self.mapView = newMapView
}
I had the same problem.
I fixed it by changing the view camera only as such:
#IBOutlet var mapView: GMSMapView!
override func loadView() {
super.loadView()
let kCameraLatitude = 37.314617900000002
let kCameraLongitude = -121.7901318
let camera = GMSCameraPosition.cameraWithLatitude(kCameraLatitude,
longitude: kCameraLongitude, zoom: 1)
self.mapView.camera = camera
}

Resetting saved map pins, iOS Swift

I am working on a small map application, so far I have the code to drop map pins and save them so they remain once the app is reopened, this class is for the main view controller:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UISearchBarDelegate, UIPopoverPresentationControllerDelegate {
var location: CLLocation!
let locationManager = CLLocationManager()
#IBOutlet weak var placesMap: MKMapView!
#IBOutlet weak var addButton: UIBarButtonItem!
#IBOutlet weak var moreStuff: UIButton!
// Popover button action
#IBAction func moreStuff(sender: AnyObject) {
self.performSegueWithIdentifier("showMoreStuff", sender:self)
moreStuff.adjustsImageWhenHighlighted = false
}
#IBAction func addButton(sender: AnyObject) {
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: self.placesMap.userLocation.coordinate.latitude, longitude: self.placesMap.userLocation.coordinate.longitude)
self.placesMap.addAnnotation(annotation)
self.locationManager.startUpdatingLocation()
}
// Location function
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.004, longitudeDelta: 0.004))
self.placesMap?.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()
let locationDictionary:[String:Double] = ["latitude":center.latitude,"longitude":center.longitude]
var locationArray = [[String:Double]]()
if NSUserDefaults.standardUserDefaults().objectForKey("locationArray") != nil {
locationArray = NSUserDefaults.standardUserDefaults().objectForKey("locationArray") as! [[String:Double]]
}
locationArray.append(locationDictionary)
NSUserDefaults.standardUserDefaults().setObject(locationArray, forKey: "locationArray")
NSUserDefaults.standardUserDefaults().synchronize()
}
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.placesMap?.showsUserLocation = true
if NSUserDefaults.standardUserDefaults().objectForKey("locationArray") != nil {
for dictionary in NSUserDefaults.standardUserDefaults().objectForKey("locationArray") as! [[String:Double]]{
let center = CLLocationCoordinate2D(latitude: dictionary["latitude"]!, longitude: dictionary["longitude"]!)
let annotation = MKPointAnnotation()
annotation.coordinate = center
self.placesMap?.addAnnotation(annotation)
}
}
}
I want to add a button which allows all pins (memories) to be reset at once. This button is located on a new scene and class, called "PopoverOptions". I have the following code from the class which should do this, but it does not seem to be functioning as no pins disappear from the map once it is pressed by the user!
#IBOutlet weak var resetMemories: UIButton!
#IBAction func resetMemories(sender: AnyObject) {
func removeStoredLocations(){
NSUserDefaults.standardUserDefaults().removeObjectForKey("locationArray")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
Any idea why the pins aren't being removed? I have ensured that the class is linked correctly as well as the buttons outlet / action.
You clear out your user defaults key, but don't change the map view on the view controller that shows those pins. You need to call removeAnnotations to remove the annotations from the map.
You could add a viewWillAppear method that detects that your annotations have been deleted and remove them. Something like this:
func viewWillAppear(_ animated: Bool)
{
if NSUserDefaults.standardUserDefaults().objectForKey("locationArray") == nil
{
if let annotations = self.placesMap.annotations
self.placesMap?.removeAnnotations(annotations)
}
}
The code above is written to only remove the annotations from the map if the entire annotations dictionary was deleted from userDefaults. That way you avoid reloading the annotations every time the view controller regains focus.
BTW, you said your new scene is called "PopoverOptions." If the scene is presented in a popover then the above might not work. (I don't think viewWillAppear gets called on a view controller when a popover is dismissed, but I don't remember for sure.)

Resources