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.)
Related
I am using MapKit on Xcode to create a "nearby" hospital/mental health center/call center location app. I want the user to be able to filter the map's results. For instance, if I am looking for hospitals, I only want to see hospital locations appear on the map. So far, to do this, I created a sliding hamburger menu with checkbox-like buttons to use as filters. So for example, you tap the hamburger menu button and you check the box with hospitals so that you only get hospital results on the map. However, I must have messed it up somehow because when I get to the MapViewController, only a black screen appears. Please advise how I can fix this or maybe use a different method entirely to achieve the same goal.
import UIKit
import MapKit
class MapViewController: NSObject, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var trailingMap: NSLayoutConstraint!
#IBOutlet weak var leadingMap: NSLayoutConstraint!
func viewDidLoad() {
mapView.delegate = self as? MKMapViewDelegate
let locationManager = CLLocationManager()
let regionRadius:CLLocationDistance = 1000
locationManager.requestWhenInUseAuthorization()
mapView.showsUserLocation = true
locationManager.startUpdatingLocation()
}
var hamburgerMenuIsVisible = false
#IBAction func hamburgerMenu(_ sender: Any) {
if hamburgerMenuIsVisible {
leadingMap.constant = 150
trailingMap.constant = -150
hamburgerMenuIsVisible = true
} else {
leadingMap.constant = 0
trailingMap.constant = 0
hamburgerMenuIsVisible = false
}
}
var mapItems: [MKMapItem] = []
var place = MKLocalSearch.Request() //user's facility search options
var naturalLanguageQuery: String? {
place.naturalLanguageQuery = "hospital"
let place2 = MKLocalSearch.Request()
place2.naturalLanguageQuery = "Mental Health Facility"
let place3 = MKLocalSearch.Request()
place3.naturalLanguageQuery = "Women's Center"
let place4 = MKLocalSearch.Request()
place4.naturalLanguageQuery = "Call Center"
place.region = self.mapView.region
place2.region = self.mapView.region
place3.region = self.mapView.region
place4.region = self.mapView.region
return place.naturalLanguageQuery
}
#IBAction func hospitalButton(_ sender: Any) {
print(mapItems)
}
#IBAction func callCenterButton(_ sender: Any) {
print(mapItems)
}
}
Your ViewController is subclass of UIViewController, so you have to use inheritance from UIViewController instead of NSObject
So, replace this
class MapViewController: NSObject, CLLocationManagerDelegate {
with this
class MapViewController: UIViewController, CLLocationManagerDelegate {
also fix your viewDidLoad method by adding override keyword before func keyword of your viewDidLoad function. That's because you override function from UIViewController. You will have to call super.viewDidLoad() at the begining of this function. So viewDidLoad should look like this
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self as? MKMapViewDelegate
let locationManager = CLLocationManager()
let regionRadius:CLLocationDistance = 1000
locationManager.requestWhenInUseAuthorization()
mapView.showsUserLocation = true
locationManager.startUpdatingLocation()
}
I was wondering if anyone may know how to make Mapbox annotations be able show the current location information once dropped on a certain spot. Kind of like it does in Apple's maps when you tap on a pin or drop one and it then shows the locations name or address. I have the code for the pin being dropped but after that I don't know how to make it show information on that current location. Thank you for your feedback in advance.
import UIKit
import CoreLocation
import Mapbox
class SecondViewController: UIViewController, CLLocationManagerDelegate, MGLMapViewDelegate {
#IBOutlet var mapView: MGLMapView!
let manager = CLLocationManager()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func markStuff(_ sender: Any) {
manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
mapView.setCenter(center, zoomLevel: 15, animated: true)
let annotation = MGLPointAnnotation()
annotation.coordinate = location.coordinate
self.mapView.addAnnotation(annotation)
manager.stopUpdatingLocation()
}
}
You’ll want to reverse geocode the coordinate of the annotation. The Mapbox iOS SDK does not offer this capability out of the box, but we do provide a library called MapboxGeocoder.swift.
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
}
}
}
hey all i am brand new into coding iOS apps, and find it rather enjoyable so far..
I am having an issue assigning lat and lng to a text field. I have been following along in a tutorial but it crapped out and most examples i have found are in objective C and not swift.. and the ones in swift aren't all the best..
my ViewController is below:
import UIKit
import MapKit
class LocationVC: UIViewController, MKMapViewDelegate {
#IBOutlet weak var map: MKMapView!
#IBOutlet weak var latField: UITextField!
#IBOutlet weak var lngField: UITextField!
#IBOutlet weak var locateBtn: UIButton!
#IBOutlet weak var saveBtn: UIButton!
let regionRadius: CLLocationDistance = 500
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
map.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
override func viewDidAppear(animated: Bool) {
locationAuthStatus()
}
func locationAuthStatus() {
if CLLocationManager.authorizationStatus() == .AuthorizedWhenInUse {
map.showsUserLocation = true
} else {
locationManager.requestWhenInUseAuthorization()
}
}
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, regionRadius * 1, regionRadius * 1)
map.setRegion(coordinateRegion, animated: true)
}
func mapView(mapView: MKMapView, didUpdateUserLocation userLocation: MKUserLocation) {
if let loc = userLocation.location {
centerMapOnLocation(loc)
}
let latField = location.longitude
let lngField = location.latitude
}
}
So far the map is moving, works on my device but I have no idea how to get the coords to appear..
Please forgive me if this is a noob question, but I just cant get this damn thing to go..
Replace your mapView(_ :,didUpdatedUserLocation:) with this...
func mapView(mapView: MKMapView, didUpdateUserLocation userLocation: MKUserLocation) {
if let loc = userLocation.location
{
centerMapOnLocation(loc)
self.latField.text = "\(loc.coordinate.latitude)"
self.lngField.text = "\(loc.coordinate.longitude)"
}
}
Note: If you only need show the lat/lng could be a better idea to use an UILabel instead of UITextFiel
So I have a map view(mapkit based) and a slider in my storyboard. As the user slides, map zooms in or out based on their action. How do I implement that? I think that might have something to do with longitudeDelta and latitudeDelta.
Please help me.
import UIKit
import MapKit
import CoreLocation
class ConfigureViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapkitView: MKMapView!
#IBOutlet weak var travelRadius: UILabel!
#IBAction func button(sender: AnyObject) {
}
#IBAction func sliderChanged(sender: AnyObject) {
let sliderValue = lrintf(sender.value)
travelRadius.text = "\(sliderValue) mi."
let delta = Double(self.sliderChanged(sender.value))
var currentRegion = self.mapkitView.region
currentRegion.span = MKCoordinateSpan(latitudeDelta: delta, longitudeDelta: delta)
self.mapkitView.region = currentRegion
}
let locationManager = CLLocationManager()
#IBOutlet weak var mapKitView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapKitView.showsUserLocation = true
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
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: 1.5,
longitudeDelta: 1.5))
self.mapKitView.setRegion(region, animated: false)
self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
{
print ("Errors:" + error.localizedDescription)
}
}
Screenshot of my storyboard
You set the zoom via the region property of the mapView. It defines both the center point of the map and the span (i.e. zoom level)
Edit
Do yourself a favor and define a separate IBOutlet for the slider. It turns out that your slider is measured in miles, but the span is measured in degrees latitude and longitude. How long 1 degree of latitude/longitude is in terms of miles vary depend on your location on Earth. Wikipedia has some discussion on latitude and longitude. Assuming you are on the equator, the conversion is 69 miles per degree of both.
(Remember to connect the outlets)
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var slider: UISlider!
#IBOutlet weak var travelRadius: UILabel!
#IBOutlet weak var currentLocationLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
sliderChanged(self) // Set the correct zoom according to the slider initial value
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func sliderChanged(sender: AnyObject) {
let miles = Double(self.slider.value)
let delta = miles / 69.0
var currentRegion = self.mapView.region
currentRegion.span = MKCoordinateSpan(latitudeDelta: delta, longitudeDelta: delta)
self.mapView.region = currentRegion
travelRadius.text = "\(Int(round(miles))) miles"
let (lat, long) = (currentRegion.center.latitude, currentRegion.center.longitude)
currentLocationLabel.text = "Current location: \(lat), \(long))"
}
}