I am using MapKit's local search feature to populate a bunch of pin annotations based on a user's search. When you click on a pin, in the callout you go to a detail viewcontroller of the location. When the user segues and clicks the back button on the toolbar from the detailviewcontroller to the previous viewcontroller, all the annotation pins are gone. Is there any way to avoid this?
here is my code for the localsearch:
#IBAction func returnText(sender: AnyObject) {
sender.resignFirstResponder();
attractionsMap.removeAnnotations(attractionsMap.annotations);
performSearch();
}
func performSearch() {
matchingItems.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchText.text;
request.region = attractionsMap.region;
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler({(response:
MKLocalSearchResponse!,
error: NSError!) in
if error != nil {
println("Error occured in search: \(error.localizedDescription)")
} else if response.mapItems.count == 0 {
println("No matches found")
} else {
println("Matches found")
for item in response.mapItems as! [MKMapItem] {
println("Name = \(item.name)")
println("Phone = \(item.phoneNumber)")
matchingItems.append(item as MKMapItem)
println("Matching items = \(matchingItems.count)")
var placemark = item.placemark;
var subThoroughfare:String = "";
var thoroughfare:String = "";
var locality:String = "";
var postalCode:String = "";
var administrativeArea:String = "";
var country:String = "";
var title = "";
var subtitle = "";
if (placemark.subThoroughfare != nil) {
subThoroughfare = placemark.subThoroughfare;
}
if(placemark.thoroughfare != nil) {
thoroughfare = placemark.thoroughfare;
}
if(placemark.locality != nil) {
locality = placemark.locality;
}
if(placemark.postalCode != nil) {
postalCode = placemark.postalCode;
}
if(placemark.administrativeArea != nil) {
administrativeArea = placemark.administrativeArea;
}
if(placemark.country != nil) {
country = placemark.country;
}
println("viewcontroller placmark data:");
println(locality);
println(postalCode);
println(administrativeArea);
println(country);
title = " \(subThoroughfare) \(thoroughfare) \n \(locality), \(administrativeArea) \n \(postalCode)\(country)";
subtitle = " \(subThoroughfare) \(thoroughfare)";
println(title);
var annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name + " " + subtitle;
self.attractionsMap.addAnnotation(annotation)
}
}
})
}
I then pass the placemark of the matching items through this method...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var attractionsDetailViewController:AttractionsDetailViewController = segue.destinationViewController as! AttractionsDetailViewController
attractionsDetailViewController.attractionLocation = indicatedMapItem;
}
ANy help would be greatly appreciated.
You can make use of NSUserDefaults, "used to determine an application’s default state". Have a look at: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/
Related
I need to check validation of username,contact number and email id.And i am doing in mvvm.
For that
my model :-
class CF_Page1Model: NSObject {
var name:String!
var contactno:String!
var emailid:String!
init?(dictionary :JSONDictionary) {
guard
let name = dictionary["name"] as? String,
let contactno = dictionary["contactno"] as? String,
let emailid = dictionary["emailid"] as? String
else {
return
}
self.name = name
self.contactno = contactno
self.emailid = emailid
}
}
my datasourcemodel :-
class CF_Page1DataSourceModel: NSObject {
var dataListArray:Array<CF_Page1Model>? = []
init(array :Array<[String:Any]>?) {
super.init()
var newArray:Array<[String:Any]> = []
if array == nil{
}
else{
newArray = array!
}
var datalist:Array<CF_Page1Model> = []
for dict in newArray{
let model = CF_Page1Model(dictionary: dict)
datalist.append(model!)
}
self.dataListArray = datalist
}
}
my viewmodel :-
class CF_Page1ViewModel: NSObject {
var datasourceModel:CF_Page1DataSourceModel
var emailid:Bool?
var phonenumber:Bool?
var nameofperson:Bool?
var name:String?
var age:Int?
var contactno:String?
var email:String?
var gender:String?
init(withdatasource newDatasourceModel:CF_Page1DataSourceModel) {
datasourceModel = newDatasourceModel
print(datasourceModel.dataListArray)
}
func isValidEmail(testStr:String)->Bool{
print("validate emilId: \(testStr)")
let emailRegEx = "^(?:(?:(?:(?: )*(?:(?:(?:\\t| )*\\r\\n)?(?:\\t| )+))+(?: )*)|(?: )+)?(?:(?:(?:[-A-Za-z0-9!#$%&’*+/=?^_'{|}~]+(?:\\.[-A-Za-z0-9!#$%&’*+/=?^_'{|}~]+)*)|(?:\"(?:(?:(?:(?: )*(?:(?:[!#-Z^-~]|\\[|\\])|(?:\\\\(?:\\t|[ -~]))))+(?: )*)|(?: )+)\"))(?:#)(?:(?:(?:[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?)(?:\\.[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?)*)|(?:\\[(?:(?:(?:(?:(?:[0-9]|(?:[1-9][0-9])|(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5]))\\.){3}(?:[0-9]|(?:[1-9][0-9])|(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5]))))|(?:(?:(?: )*[!-Z^-~])*(?: )*)|(?:[Vv][0-9A-Fa-f]+\\.[-A-Za-z0-9._~!$&'()*+,;=:]+))\\])))(?:(?:(?:(?: )*(?:(?:(?:\\t| )*\\r\\n)?(?:\\t| )+))+(?: )*)|(?: )+)?$"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
let result = emailTest.evaluate(with: testStr)
print(result)
emailid = result
return emailTest.evaluate(with: testStr)
}
func validate(value: String){
let PHONE_REGEX = "[235689][0-9]{6}([0-9]{3})?"
let phoneTest = NSPredicate(format: "SELF MATCHES %#", PHONE_REGEX)
let result1 = phoneTest.evaluate(with: value)
print(result1)
// phoneTest = result
phonenumber = result1
}
func isValidInput(Input:String) {
let RegEx = "\\A\\w{7,18}\\z"
let Test = NSPredicate(format:"SELF MATCHES %#", RegEx)
let username = Test.evaluate(with: Input)
print(username)
nameofperson = username
print(nameofperson)
}
}
and viewcontroller :-
in that submit button:-
#IBAction func forward(_ sender: AnyObject) {
self.page1ViewModel.name = nametext.text
self.page1ViewModel.contactno = contactnotext.text
self.page1ViewModel.email = emailidtext.text
self.page1ViewModel.isValidInput(Input: self.page1ViewModel.name!)
self.page1ViewModel.validate(value: self.page1ViewModel.contactno!)
self.page1ViewModel.isValidEmail(testStr: self.page1ViewModel.email!)
page1ViewModel.loadFromWebserviceData()
}
in viewcontroller
button action :-
#IBAction func forward(_ sender: AnyObject) {
self.page1ViewModel.name = nametext.text
self.page1ViewModel.contactno = contactnotext.text
self.page1ViewModel.email = emailidtext.text
self.page1ViewModel.isValidInput(Input: self.page1ViewModel.name!)
self.page1ViewModel.validate(value: self.page1ViewModel.contactno!)
self.page1ViewModel.isValidEmail(testStr: self.page1ViewModel.email!)
page1ViewModel.loadFromWebserviceData()
}
Here name,contactno,emailid are textfield and i have used post method .But at submit button i need to validate the nametext,contactnotext and emailidtext .How to do in mvvm.And what changes need to do in model.?
the validation logic will go into viewmodel class.
Remove below code from action:
self.page1ViewModel.isValidInput(Input: self.page1ViewModel.name!)
self.page1ViewModel.validate(value: self.page1ViewModel.contactno!)
self.page1ViewModel.isValidEmail(testStr: self.page1ViewModel.email!)
page1ViewModel.loadFromWebserviceData()
write another method fro validating all fields in viewmodel which will return bool:
func validateEntries() -> Bool {
guard let name = self.name else {
return false
}
guard let contactno = self.contactno else {
return false
}
guard let email = self.email else {
return false
}
let nameValid = self.isValidInput(Input: name)
let contactnoValid = self.validate(value: contactno)
let isEmailValid = self.isValidEmail(testStr: email)
return nameValid && contactNoValid && isEmailValid
}
and in view controller: in action just call this function if it reurns true then fire api call else show any validation message accordingly.
Hope it helps...
I followed a how-to-search-for-location-using-apples-mapkit about searching annotations in mapView
and its searching around the world with MKLocalSearch.
However, I don't want to to search with MKLocalSearch but search my own annotations i added myself like these for example:
let LitzmanLocation = CLLocationCoordinate2DMake(32.100668,34.775192)
// Drop a pin
let Litzman = MKPointAnnotation()
Litzman.coordinate = LitzmanLocation
Litzman.title = "Litzman Bar"
Litzman.subtitle = "נמל תל אביב 18,תל אביב"
mapView.addAnnotation(Litzman)
let ShalvataLocation = CLLocationCoordinate2DMake(32.101145,34.775163)
// Drop a pin
let Shalvata = MKPointAnnotation()
Shalvata.coordinate = ShalvataLocation
Shalvata.title = "Shalvata"
Shalvata.subtitle = "האנגר 28,נמל תל אביב"
mapView.addAnnotation(Shalvata)
let MarkidLocation = CLLocationCoordinate2DMake(32.074961,34.781679)
// Drop a pin
let Markid = MKPointAnnotation()
Markid.coordinate = MarkidLocation
Markid.title = "Markid"
Markid.subtitle = "אבן גבירול 30,תל אביב"
mapView.addAnnotation(Markid)
Here is my Code:
MapViewController.Swift:
import UIKit
import MapKit
import CoreLocation
protocol HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark)
}
class MapViewController: UIViewController,MKMapViewDelegate, CLLocationManagerDelegate,UISearchBarDelegate{
#IBOutlet var mapView: MKMapView!
var resultSearchController:UISearchController? = nil
var selectedPin:MKPlacemark? = nil
#IBAction func MapSearchController(sender: AnyObject) {
resultSearchController!.hidesNavigationBarDuringPresentation = false
self.resultSearchController!.searchBar.delegate = self
presentViewController(resultSearchController!, animated: true, completion: nil)
self.resultSearchController!.searchBar.barTintColor = UIColor.blackColor()
self.resultSearchController!.searchBar.placeholder = "חפש ברים"
self.resultSearchController!.dimsBackgroundDuringPresentation = true
self.resultSearchController!.searchBar.sizeToFit()
}
override func viewDidLoad() {
super.viewDidLoad()
let locationSearchTable = storyboard!.instantiateViewControllerWithIdentifier("LocationSearchTable") as! LocationSearchTable
resultSearchController = UISearchController(searchResultsController: locationSearchTable)
resultSearchController?.searchResultsUpdater = locationSearchTable
locationSearchTable.mapView = mapView
locationSearchTable.handleMapSearchDelegate = self
}
}
}
extension MapViewController: HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark){
// cache the pin
selectedPin = placemark
// clear existing pins
mapView.removeAnnotations(mapView.annotations)
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "(city) (state)"
}
mapView.addAnnotation(annotation)
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegionMake(placemark.coordinate, span)
mapView.setRegion(region, animated: true)
}
}
LocalSearchTable.Swift:
import UIKit
import MapKit
class LocationSearchTable : UITableViewController {
var matchingItems:[MKMapItem] = []
var mapView: MKMapView? = nil
var handleMapSearchDelegate:HandleMapSearch? = nil
func parseAddress(selectedItem:MKPlacemark) -> String {
// put a space between "4" and "Melrose Place"
let firstSpace = (selectedItem.subThoroughfare != nil && selectedItem.thoroughfare != nil) ? " " : ""
// put a comma between street and city/state
let comma = (selectedItem.subThoroughfare != nil || selectedItem.thoroughfare != nil) && (selectedItem.subAdministrativeArea != nil || selectedItem.administrativeArea != nil) ? ", " : ""
// put a space between "Washington" and "DC"
let secondSpace = (selectedItem.subAdministrativeArea != nil && selectedItem.administrativeArea != nil) ? " " : ""
let addressLine = String(
format:"%#%#%#%#%#%#%#",
// street number
selectedItem.subThoroughfare ?? "",
firstSpace,
// street name
selectedItem.thoroughfare ?? "",
comma,
// city
selectedItem.locality ?? "",
secondSpace,
// state
selectedItem.administrativeArea ?? ""
)
return addressLine
}
}
extension LocationSearchTable : UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchBarText
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler { response, _ in
guard let response = response else {
return
}
self.matchingItems = response.mapItems
self.tableView.reloadData()
}
}
}
extension LocationSearchTable {
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matchingItems.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MapSearchCell")!
let selectedItem = matchingItems[indexPath.row].placemark
cell.textLabel?.text = selectedItem.name
cell.detailTextLabel?.text = parseAddress(selectedItem)
return cell
}
}
extension LocationSearchTable {
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedItem = matchingItems[indexPath.row].placemark
handleMapSearchDelegate?.dropPinZoomIn(selectedItem)
dismissViewControllerAnimated(true, completion: nil)
}
}
you just refer to this link
http://www.coderzheaven.com/2016/02/14/mapkit-demo-swift-annotation-custom-annotation-custom-annotation-with-button-search-showing-directions-apple-maps-ios/
you will understand the following topics
Show Annotation in Maps.
Show Custom Annotation in Maps.
Show Custom Annotation with Custom Button in Maps.
TL;DR
Example project: https://github.com/JakubMazur/SO40539590
Ok, so for the beginning I will suggest you separate data from your controller code. I choose json format as most universal one. So:
[
{
"title":"Litzman Bar",
"subtitle":"נמל תל אביב 18,תל אביב",
"coordinates":{
"lat":32.100668,
"lon":34.775192
}
},
{
"title":"Shalvata",
"subtitle":"האנגר 28,נמל תל אביב",
"coordinates":{
"lat":32.101145,
"lon":34.775163
}
},
{
"title":"Markid",
"subtitle":"אבן גבירול 30,תל אביב",
"coordinates":{
"lat":32.074961,
"lon":34.781679
}
}
]
This is basically your database.
Now let's parse it into an Array to use inside your ViewConttroller. Again I will suggest you split it into Model objects like Location and Coordinate. Let's have a look in one class of it as example:
class Location: NSObject {
var title : String = String()
var subtitle : String = String()
var coordinates : Coordinate = Coordinate()
public class func locationFromDictionary(_ dictionary : Dictionary<String, AnyObject>) -> Location {
let location : Location = Location()
location.title = dictionary["title"] as! String
location.subtitle = dictionary["subtitle"] as! String
location.coordinates = Coordinate.coordinateFromDictionary(dictionary["coordinates"] as! Dictionary<String, AnyObject>)
return location;
}
}
I will not paste the code for parsing json file to this objects, because that's what is not this question is about. You will find in into repository.
And now let's focus on question.
I will recommend you not to search annotations, but search your data model and redraw annotations when needed
In order to do that (i will use UISearchBar for it):
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.displayArray = self.locationsDatabase.filter() {
return $0.title.contains("i")
}
Then when you ovverride a setter like this:
var displayArray : Array<Location> = [] {
didSet {
self.mapView .removeAnnotations(self.mapView.annotations)
for location in displayArray {
let coords : Coordinate = location.coordinates
let point = MKPointAnnotation()
point.coordinate = CLLocationCoordinate2DMake(CLLocationDegrees(coords.latitude),CLLocationDegrees(coords.longitude))
point.title = location.title
point.subtitle = location.subtitle
mapView.addAnnotation(point)
}
}
}
You can redraw your annotations. The edge cases like empty search field, case sensivity, dismissing keyboard I leave to you. But hope you will get the general idea. You may think that it's overengineered but having objects as a separate classes and universal format as an data input may benefit in the future.
Full project: https://github.com/JakubMazur/SO40539590
I've been having trouble figuring out how to pass a custom variable in a map pin to another view controller in Swift. I know that passing the coordinates, title, and subtitle are available when you addAnnotation. I would like to try and pass a custom variable but hidden. Is there such a thing? Below I am getting the users location, mapping it, dropping a pin of a couple locations nearby with annotations which goes to another view controller and passes just the title and subtitle. Any insight is greatly appreciated.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var mappedCity = String()
var mappedState = String()
var manager = CLLocationManager()
var annotation:MKAnnotation!
var error:NSError!
var pointAnnotation:MKPointAnnotation!
var pinAnnotationView:MKPinAnnotationView!
var selectedAnnotation: MKPointAnnotation!
private var mapChangedFromUserInteraction = false
#IBOutlet var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
self.navigationItem.titleView = searchController.searchBar
self.definesPresentationContext = true
if CLLocationManager.locationServicesEnabled(){
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
}
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 = MKCoordinateSpanMake(latDelta, lonDelta)
let location:CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(location, span)
self.mapView.setRegion(region, animated: true)
self.mapView.showsUserLocation = true
CLGeocoder().reverseGeocodeLocation(userLocation) { (placemarks, error) in
if (error != nil){
print(error)
}else {
if let p = placemarks?[0]{
let locality = p.locality ?? ""
let administrativeArea = p.administrativeArea ?? ""
self.mappedCity = String(locality)
self.mappedState = String(administrativeArea)
self.parseJSON("\(locality)", state: "\(administrativeArea)")
}
}
}
self.manager.stopUpdatingLocation()
}
func parseJSON(city: String, state: String){
let passedCity = city
let passedState = state
let escapedCity = passedCity.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
let escapedState = passedState.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
let url = NSURL(string:"http://www.API.com/api.php?city=\(escapedCity)&stateAbv=\(escapedState)")!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { (items, response, error) -> Void in
if error != nil {
print(error)
}else {
if let items = items {
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(items, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
if jsonResult.count > 0 {
if let datas = jsonResult["data"] as? NSArray{
for data in datas{
if let title = data["title"] as? String {
if let street = data["street"] as? String {
if let city = data["city"] as? String {
if let stateAbv = data["stateAbv"] as? String {
if let zip = data["zip"] as? String {
self.geoAddress("\(title)", street: "\(street)", city: "\(city)", state: "\(stateAbv)", zip: "\(zip)")
}
}
}
}
}
}
}
}
} catch{}
}
}
}
task.resume()
}
func geoAddress(title: String, street: String, city: String, state: String, zip: String){
let storeName = "\(title)"
let location = "\(street) \(city) \(state) \(zip)"
let geocoder = CLGeocoder();
geocoder.geocodeAddressString(location, completionHandler: {(placemarks: [CLPlacemark]?, error: NSError?) -> Void in
if (error != nil) {
print("Error \(error!)")
} else if let placemark = placemarks?[0] {
let coordinates:CLLocationCoordinate2D = placemark.location!.coordinate
let pointAnnotation:MKPointAnnotation = MKPointAnnotation()
pointAnnotation.coordinate = coordinates
pointAnnotation.title = storeName
pointAnnotation.subtitle = location
self.mapView.addAnnotation(pointAnnotation)
}
})
}
private func mapViewRegionDidChangeFromUserInteraction() -> Bool {
let view: UIView = self.mapView.subviews[0] as UIView
// Look through gesture recognizers to determine whether this region change is from user interaction
if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers {
if( recognizer.state == UIGestureRecognizerState.Began || recognizer.state == UIGestureRecognizerState.Ended ) {
return true
}
}
}
return false
}
func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
mapChangedFromUserInteraction = mapViewRegionDidChangeFromUserInteraction()
if (mapChangedFromUserInteraction) {
// user changed map region
}
}
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if (mapChangedFromUserInteraction) {
// user changed map region
let center = mapView.centerCoordinate
let mapLatitude = center.latitude
let mapLongitude = center.longitude
let locationmove = CLLocation(latitude: mapLatitude, longitude: mapLongitude)
CLGeocoder().reverseGeocodeLocation(locationmove) { (placemarks, error) in
if (error != nil){
print(error)
}else {
if let p = placemarks?[0]{
let locality = p.locality ?? ""
let administrativeArea = p.administrativeArea ?? ""
self.mappedCity = String(locality)
self.mappedState = String(administrativeArea)
self.parseJSON("\(locality)", state: "\(administrativeArea)")
}
}
}
}
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView?.animatesDrop = false
pinView?.canShowCallout = true
pinView?.draggable = true
pinView?.pinTintColor = UIColor.greenColor()
let rightButton: AnyObject! = UIButton(type: UIButtonType.DetailDisclosure)
pinView?.rightCalloutAccessoryView = rightButton as? UIView
}
else {
pinView?.annotation = annotation
}
return pinView
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.rightCalloutAccessoryView {
selectedAnnotation = view.annotation as? MKPointAnnotation
performSegueWithIdentifier("Details", sender: self)
}
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
if newState == MKAnnotationViewDragState.Ending {
let droppedAt = view.annotation?.coordinate
print(droppedAt)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "Details"){
let myDetails = segue.destinationViewController as! DetailViewController
myDetails.mytitle = selectedAnnotation.title
myDetails.mysubtitle = selectedAnnotation.subtitle
}
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
}
}
Subclass the "MKPointAnnotation" class and add your custom property in it.
class MyAnnotation : MKPointAnnotation {
var customProperty : String?
}
And you can use MyAnnotation instead of MKPointAnnotation. Like following
let pointAnnotation:MyAnnotation = MyAnnotation()
pointAnnotation.coordinate = coordinates
pointAnnotation.title = storeName
pointAnnotation.subtitle = location
pointAnnotation.customProperty = "your value"
self.mapView.addAnnotation(pointAnnotation)
My app currently has three tabs, a tab for pinning a location, and a detailView of the pinned location on the second tab. I am trying to save the location of the pin into NSUserdefaults. I would then like that location to stay pinned upon reloading the app, and therefore the detail view would still display the detail view of the pinned location. Here is what I have so far,
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
// Find location of user
var userLocation:CLLocation = locations[0] as! CLLocation
var latitude = userLocation.coordinate.latitude
var longitude = userLocation.coordinate.longitude
var latDelta:CLLocationDegrees = 0.01
var longDelta: CLLocationDegrees = 0.01
var span: MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta)
var location:MKUserLocation = currentLocation;
var region: MKCoordinateRegion = MKCoordinateRegionMake(location.coordinate, span)
var coordinate:CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude);
carInitialLocation = userLocation;
let locationData = NSKeyedArchiver.archivedDataWithRootObject(carInitialLocation);
NSUserDefaults.standardUserDefaults().setObject(locationData, forKey: "locationData");
carInitialCoordinate = coordinate;
self.map.setRegion(region, animated: true);
}
override func viewDidLoad() {
super.viewDidLoad()
if let loadedData = NSUserDefaults.standardUserDefaults().dataForKey("locationData") {
if let loadedLocation = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData) as? CLLocation {
println(loadedLocation.coordinate.latitude);
println(loadedLocation.coordinate.longitude);
var annotation = MKPointAnnotation()
annotation.coordinate = loadedLocation.coordinate
annotation.title = title
self.map.addAnnotation(annotation)
}
}
map.addAnnotations(artworks)
map.delegate = self;
manager.delegate = self;
manager.desiredAccuracy = kCLLocationAccuracyBest;
manager.requestWhenInUseAuthorization();
manager.startUpdatingLocation();
self.map.showsUserLocation = true;
currentLocation = map.userLocation;
}
I then want the pinLocation button to be deactivated once the user has pinned a location once. I try to do this as so:
#IBAction func pinLocationButton(sender: AnyObject) {
// add location to the array, so it can be retrieved and put it into temporary storage
//places.append(["name":title,"lat":"\(newCoordinate.latitude)","lon":"\(newCoordinate.longitude)"])
if let loadedData = NSUserDefaults.standardUserDefaults().dataForKey("locationData") {
if let loadedLocation = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData) as? CLLocation {
println(loadedLocation.coordinate.latitude);
println(loadedLocation.coordinate.longitude);
pinLocationButton.enabled = false;
}
}
var location = carInitialLocation
var coordinate = carInitialCoordinate
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placemarks, error) -> Void in
var title = ""
if (error == nil) {
if let p = CLPlacemark(placemark: placemarks?[0] as! CLPlacemark) {
var subThoroughfare:String = ""
var thoroughfare:String = ""
if p.subThoroughfare != nil {
subThoroughfare = p.subThoroughfare
}
if p.thoroughfare != nil {
thoroughfare = p.thoroughfare
}
completeAddress = self.displayLocationInfo(p);
title = "\(subThoroughfare) \(thoroughfare)"
}
}
// annotation, i.e pins
var annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = title
self.map.addAnnotation(annotation)
// NSUserDefaults.standardUserDefaults().setObject(places, forKey: "places")
})
}
Then, in my detailVC I attempt to reverse geocode the pinned location, but it is defaulting to the current location.. which I don't understand why
Here's the code:
super.viewDidLoad()
addressLabel.font = UIFont(name: addressLabel.font.fontName, size: 18)
smallMapView.delegate = self;
locationManager.delegate = self;
smallMapView.mapType = MKMapType.Hybrid;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.requestWhenInUseAuthorization();
locationManager.startUpdatingLocation();
smallMapView.zoomEnabled = true;
smallMapView.rotateEnabled = true;
if let loadedData = NSUserDefaults.standardUserDefaults().dataForKey("locationData") {
if let loadedLocation = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData) as? CLLocation {
println(loadedLocation.coordinate.latitude);
println(loadedLocation.coordinate.longitude);
CLGeocoder().reverseGeocodeLocation(loadedLocation, completionHandler: { (placemarks, error) -> Void in
var title = "";
var subtitle = "";
var locality = "";
if(error == nil) {
if let placemark = CLPlacemark(placemark: placemarks?[0] as! CLPlacemark) {
var subThoroughfare:String = "";
var thoroughfare:String = "";
var locality:String = "";
var postalCode:String = "";
var administrativeArea:String = "";
var country:String = "";
if (placemark.subThoroughfare != nil) {
subThoroughfare = placemark.subThoroughfare;
}
if(placemark.thoroughfare != nil) {
thoroughfare = placemark.thoroughfare;
}
if(placemark.locality != nil) {
locality = placemark.locality;
}
if(placemark.postalCode != nil) {
postalCode = placemark.postalCode;
}
if(placemark.administrativeArea != nil) {
administrativeArea = placemark.administrativeArea;
}
if(placemark.country != nil) {
country = placemark.country;
}
println("viewcontroller placmark data:");
println(locality);
println(postalCode);
println(administrativeArea);
println(country);
title = " \(subThoroughfare) \(thoroughfare) \n \(locality), \(administrativeArea) \n \(postalCode) \(country)";
subtitle = " \(subThoroughfare) \(thoroughfare)";
println(title);
self.addressLabel.text = title;
}
}
var latitude = loadedLocation.coordinate.latitude;
var longitude = loadedLocation.coordinate.longitude;
var latDelta:CLLocationDegrees = 0.001;
var longDelta:CLLocationDegrees = 0.001;
var span: MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta);
var overallLoc = CLLocationCoordinate2DMake(latitude, longitude);
var region:MKCoordinateRegion = MKCoordinateRegionMake(overallLoc, span);
var annotation = MKPointAnnotation();
annotation.coordinate = loadedLocation.coordinate;
annotation.title = subtitle;
self.smallMapView.addAnnotation(annotation);
self.smallMapView.setRegion(region, animated: true)
})
}
}
}
in the pinLocationButton function, you use the manager.location which is wrong, you should use
CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
var title = ""
if (error == nil) {
// ....
I have the following code which copies data from the iPhone's contacts into an NSMutableDictionary. I'm using optional binding as discussed in this answer.
The firstName and lastName fields copy properly (as well as phone and email fields not shown here), but I have never been able to access the kABPersonNoteProperty, nor have I seen the println statement in the log. I've tried to copy the note as both String and NSString without success. Any ideas what could be causing this problem?
var contactInfoDict:NSMutableDictionary!
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!) {
self.contactInfoDict = ["firstName":"", "lastName":"", "notes":""]
if let firstName:String = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String {
contactInfoDict.setObject(firstName, forKey: "firstName")
println(firstName)
}
if let lastName:String = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String {
contactInfoDict.setObject(lastName, forKey: "lastName")
println(lastName)
}
if let notes:String = ABRecordCopyValue(person, kABPersonNoteProperty)?.takeRetainedValue() as? String {
contactInfoDict.setObject(notes, forKey: "notes")
println("Note: \(notes)")
}
}
Edit Here is the full class:
import UIKit
import AddressBookUI
class ContactsVC: UIViewController, ABPeoplePickerNavigationControllerDelegate {
#IBOutlet weak var done_Btn: UIBarButtonItem!
#IBAction func dismissContacts(sender: UIBarButtonItem) {
self.dismissViewControllerAnimated(true, completion: nil)
println("ContactsVC dismissed")
}
#IBOutlet weak var viewContainer: UIView!
var object:PFObject = PFObject(className: "Contact")
let personPicker: ABPeoplePickerNavigationController
var contactInfoDict:NSMutableDictionary!
required init(coder aDecoder: NSCoder) {
personPicker = ABPeoplePickerNavigationController()
super.init(coder: aDecoder)
personPicker.peoplePickerDelegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
done_Btn.setTitleTextAttributes([NSFontAttributeName: UIFont(name: "Avenir Next Medium", size: 16)!], forState: UIControlState.Normal)
}
#IBAction func addContact() {
let actionSheetController: UIAlertController = UIAlertController(title: nil, message: "Would you like to select a contact from your iPhone or create a new contact directly in the app?", preferredStyle: .ActionSheet)
let selectContactAction: UIAlertAction = UIAlertAction(title: "Select from iPhone", style: .Default) { action -> Void in
self.performPickPerson(UIAlertAction)
}
actionSheetController.addAction(selectContactAction)
let createContactAction: UIAlertAction = UIAlertAction(title: "Create App Contact", style: .Default) { action -> Void in
self.performSegueWithIdentifier("AddContact", sender: self)
}
actionSheetController.addAction(createContactAction)
let cancelAction:UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) {
action -> Void in
}
actionSheetController.addAction(cancelAction)
self.presentViewController(actionSheetController, animated: true, completion: nil)
}
func performPickPerson(sender : AnyObject) {
self.presentViewController(personPicker, animated: true, completion: nil)
}
func peoplePickerNavigationControllerDidCancel(peoplePicker: ABPeoplePickerNavigationController!) {
personPicker.dismissViewControllerAnimated(true, completion: nil)
}
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!) {
self.contactInfoDict = ["firstName":"", "lastName":"", "company":"", "mobilePhone":"", "homePhone":"", "workPhone":"", "personalEmail":"", "workEmail":"", "street":"", "city":"", "state":"", "zipCode":"", "notes":""]
if let firstName:String = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String {
contactInfoDict.setObject(firstName, forKey: "firstName")
println(firstName)
}
if let lastName:String = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String {
contactInfoDict.setObject(lastName, forKey: "lastName")
println(lastName)
}
if let company:String = ABRecordCopyValue(person, kABPersonOrganizationProperty)?.takeRetainedValue() as? String {
contactInfoDict.setObject(company, forKey: "company")
println(company)
}
if let phonesRef:ABMultiValueRef = ABRecordCopyValue(person, kABPersonPhoneProperty)?.takeRetainedValue() as ABMultiValueRef? {
for (var i = 0; i < ABMultiValueGetCount(phonesRef); i++ ) {
var currentPhoneLabel:CFStringRef = ABMultiValueCopyLabelAtIndex(phonesRef, i).takeRetainedValue()
var currentPhoneValue:CFStringRef = ABMultiValueCopyValueAtIndex(phonesRef, i)?.takeRetainedValue() as! CFStringRef
if let mobileResult = CFStringCompare(currentPhoneLabel, kABPersonPhoneMobileLabel, CFStringCompareFlags.CompareCaseInsensitive) as CFComparisonResult? {
if mobileResult == CFComparisonResult.CompareEqualTo {
contactInfoDict.setObject(currentPhoneValue, forKey: "mobilePhone")
}
}
if let homeResult = CFStringCompare(currentPhoneLabel, kABHomeLabel, CFStringCompareFlags.CompareCaseInsensitive) as CFComparisonResult? {
if homeResult == CFComparisonResult.CompareEqualTo {
contactInfoDict.setObject(currentPhoneValue, forKey: "homePhone")
}
}
if let workResult = CFStringCompare(currentPhoneLabel, kABWorkLabel, CFStringCompareFlags.CompareCaseInsensitive) as CFComparisonResult? {
if workResult == CFComparisonResult.CompareEqualTo {
contactInfoDict.setObject(currentPhoneValue, forKey: "workPhone")
}
}
}
}
if let emailsRef:ABMultiValueRef = ABRecordCopyValue(person, kABPersonEmailProperty)?.takeRetainedValue() as ABMultiValueRef? {
for (var i = 0; i < ABMultiValueGetCount(emailsRef); i++ ) {
var currentEmailLabel:CFStringRef = ABMultiValueCopyLabelAtIndex(emailsRef, i).takeRetainedValue()
var currentEmailValue:CFStringRef = ABMultiValueCopyValueAtIndex(emailsRef, i)?.takeRetainedValue() as! CFStringRef
if let email = kABHomeLabel as CFStringRef? {
if let homeResult = CFStringCompare(currentEmailLabel, kABHomeLabel, CFStringCompareFlags.CompareCaseInsensitive) as CFComparisonResult? {
if homeResult == CFComparisonResult.CompareEqualTo {
contactInfoDict.setObject(currentEmailValue as String, forKey: "personalEmail")
}
}
}
if let workResult = CFStringCompare(currentEmailLabel, kABWorkLabel, CFStringCompareFlags.CompareCaseInsensitive) as CFComparisonResult? {
if workResult == CFComparisonResult.CompareEqualTo {
contactInfoDict.setObject(currentEmailValue as String, forKey: "workEmail")
}
}
}
}
if let addressRef:ABMultiValueRef = ABRecordCopyValue(person, kABPersonAddressProperty)?.takeRetainedValue() as ABMultiValueRef? {
if ABMultiValueGetCount(addressRef) > 0 {
var addressDict:NSDictionary = ABMultiValueCopyValueAtIndex(addressRef, 0)?.takeRetainedValue() as! NSDictionary
if let street = addressDict.objectForKey(kABPersonAddressStreetKey) as? String {
contactInfoDict.setObject(street as NSString, forKey: "street")
}
if let city = addressDict.objectForKey(kABPersonAddressCityKey) as? String {
contactInfoDict.setObject(city as NSString, forKey: "city")
}
if let state = addressDict.objectForKey(kABPersonAddressStateKey) as? String {
contactInfoDict.setObject(state as NSString, forKey: "state")
}
if let zipCode = addressDict.objectForKey(kABPersonAddressZIPKey) as? String {
contactInfoDict.setObject(zipCode as NSString, forKey: "zipCode")
}
}
}
// Notes is not currently accessible
if let note:String = ABRecordCopyValue(person, kABPersonNoteProperty)?.takeRetainedValue() as? String {
println("12")
contactInfoDict.setObject(note, forKey: "notes")
println("Note: \(note)")
}
saveToContact()
}
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, shouldContinueAfterSelectingPerson person: ABRecord!, property: ABPropertyID, identifier: ABMultiValueIdentifier) -> Bool {
return false
}
func saveToContact(){
self.object["username"] = PFUser.currentUser()!
if let firstName = contactInfoDict["firstName"] as? String{
self.object["contactFirstName"] = firstName
}
if let lastName = contactInfoDict["lastName"] as? String{
self.object["contactLastName"] = lastName
}
if let company = contactInfoDict["company"] as? String{
self.object["contactCompany"] = company
}
if let mobilePhone = contactInfoDict["mobilePhone"] as? String{
self.object["contactMobilePhone"] = mobilePhone
}
if let homePhone = contactInfoDict["homePhone"] as? String{
self.object["contactHomePhone"] = homePhone
}
if let workPhone = contactInfoDict["workPhone"] as? String{
self.object["contactWorkPhone"] = workPhone
}
if let personalEmail = contactInfoDict["personalEmail"] as? String{
self.object["contactPersonalEmail"] = personalEmail
}
if let workEmail = contactInfoDict["workEmail"] as? String{
self.object["contactWorkEmail"] = workEmail
}
if let street = contactInfoDict["street"] as? String{
self.object["contactStreet"] = street
}
if let city = contactInfoDict["city"] as? String{
self.object["contactCity"] = city
}
if let state = contactInfoDict["state"] as? String{
self.object["contactState"] = state
}
if let zipCode = contactInfoDict["zipCode"] as? String{
self.object["contactZipCode"] = zipCode
}
if let notes = contactInfoDict["notes"] as? String{
self.object["contactNotes"] = notes
}
self.object["contactType"] = "Lead"
self.object["contactIsActive"] = true
var addrObject = self.object
self.object.pinInBackgroundWithName("Contacts")
self.object.saveEventually{ (success, error) -> Void in
if (error == nil) {
println("saved in background")
} else {
println(error!.userInfo)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I just tested it and it works fine, just as you have it. This is effectively my whole code (except for getting authorization, of course):
#IBAction func doPeoplePicker (sender:AnyObject!) {
let picker = ABPeoplePickerNavigationController()
picker.peoplePickerDelegate = self
self.presentViewController(picker, animated:true, completion:nil)
}
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!,
didSelectPerson person: ABRecord!) {
if let note = ABRecordCopyValue(person, kABPersonNoteProperty)?.takeRetainedValue() as? String {
println(note)
} else {
println("no note")
}
}
When my person has a note, I see it; when my person has no note, I see "no note".
EDIT You and I played around with this for a while, sending each other actual projects, and I observed the following difference between your implementation and mine: mine, for which fetching the note works, obtains authorization to access the address book (you'll notice that I did mention that, in the first paragraph of my original answer); yours, for which fetching the note doesn't work, doesn't obtain authorization. Hence, I suggest that this is the missing piece of the puzzle.
Here's my theory. Prior to iOS 8, you needed authorization in order to use ABPeoplePickerNavigationController. Thus, my code, which goes way back, still obtains it. The way this supposedly works in iOS 8 is that, in the absence of authorization, the people picker fetches a copy of the address book data. Well, I think that this copy is faulty (and that you should file a bug report with Apple about this). But because I have authorization, I'm accessing the actual address book data, and so my code can see the note.