mapKit Apple: Bug with places' names and no focus map on user - ios

I try to go from VC1 on VC2 with map by button1 through segue, and It has to focus pin on my location in the center of map and it has bug. it doesn't focus on user, but if I press button 2, go back and press button 1, it focuses on user. And I can't understand where is I need to fix it.
And the last bug: if I set huge value of regionInMeters, for e.g. 10000m, and I zoom, it shows names of places, streets with bug(image) in both cases, when I try to use this method with user. If I set low - 500, it shows correct close in these cases, but just location around user, if I switch there in another city and zoom in, it still will have this bug. And the same story with focus on place. On huge height - country names, cities, rivers are correct everywhere .
let annotationIdentifire = "annotationIdentifire"
let locationManager = CLLocationManager()
let regionInMeters = 500.00
var incomeSegueIdentifire = ""
override func viewDidLoad() {
super.viewDidLoad()
addressLabel.text = ""
mapView.delegate = self
setupMapView()
checkLocationServices()
}
private func checkLocationServices(){
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocationAuthorization()
} else{...}
private func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
if incomeSegueIdentifire == "getAddress" { showUserLocation() }
break
//other cases
}
Also I have another button2 with segue on VC1 which opens VC2 with map and it focuses on place, with address, which I set, and in this case I have button3 on map, which focuses on me correct:
#IBAction func centerViewInUserLocation() {
showUserLocation()
}
There is method which I use to focus on user.
private func showUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion(center: location,
latitudinalMeters: regionInMeters,
longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
}
Value of incomeSegueIdentifire I set in another class
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let identifire = segue.identifier, let mapVC = segue.destination as? MapViewController else { return }
mapVC.incomeSegueIdentifire = identifire
}

I din't find how to fix bug with places' names, but I fixed the main bug(when open map by segue it has to focus on user, but it's not).
I get mistake:Could not retrieve region info. And tried to find the answer for this case, but didn't find. Found couple cases with answers about this mistake and there said, that code is OK, but still mistake, and the main answer/advice - try to run it on your real device, may be it's correct, I don't have iphone/ipad. But for simulator I fixed it by calling this method asynchronously with delay, and it works for me)
private func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
if incomeSegueIdentifire == "getAddress" { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.showUserLocation()
}
}
//other cases
}
}
But still, if you have any ideas with these issues, I'll be glad to know))

Related

How to add an alert to switch toggle for multiple answers?

I created a Quiz app. There is one question with five answers, where the user can choose one of them. However at the moment it's possible to choose more than one answer. How can I add an alert (UIAlertController) and a restriction so if the user has already chosen one answer and tries to choose a second one ?
var questionIndex = 0
var answersChosen: [Answer] = []
func updateUI() {
let currentQuestion = questions[questionIndex]
let currentAnswers = currentQuestion.answers
let totalProgress = Float(questionIndex) /
Float(questions.count)
numberOfQuestion.text = "\(questionIndex+1)/5"
// navigationItem.title = "Вопрос - \(questionIndex+1)"
mainQuestion.text = currentQuestion.text
progressBar.setProgress(totalProgress, animated:
true)
updateMultipleStack(using: currentAnswers)
}
#IBAction func multipleAnswerButtonPressed(_ sender: Any) {
let currentAnswers = questions[questionIndex].answers
if firstSwitch.isOn {
answersChosen.append(currentAnswers[0])
}
if secondSwitch.isOn {
answersChosen.append(currentAnswers[1])
}
if thirdSwitch.isOn {
answersChosen.append(currentAnswers[2])
}
if fourthSwitch.isOn {
answersChosen.append(currentAnswers[3])
}
if fifthSwitch.isOn {
answersChosen.append(currentAnswers[4])
}
nextQuestion()
}
func updateMultipleStack(using answers: [Answer]) {
firstSwitch.isOn = false
secondSwitch.isOn = false
thirdSwitch.isOn = false
fourthSwitch.isOn = false
fifthSwitch.isOn = false
firstQuestionLbl.text = answers[0].text
secondQuestionLbl.text = answers[1].text
thirdQuestionLbl.text = answers[2].text
fourthQuestionLbl.text = answers[3].text
fifthQuestionLbl.text = answers[4].text
}
override func prepare(for segue: UIStoryboardSegue, sender:
Any?) {
if segue.identifier == "ResultSegue" {
let resultsViewController = segue.destination as! ResultViewController
resultsViewController.responses = answersChosen
}
}
}
The approach I'd take would be to separate the user's selection of an answer (UISwitch collection) from processing the selected answer(Submit Answer button). This would allow the user to change their answer. The previous selection is automatically cleared. The answer is final when pressing a "Submit Answer" button (not shown).
In this code example, the switches are set as an IBOutlet collection. The tag on each switch is set from 0 to 4. All the switches are connected to an IBAction called switchPressed. A variable called selectedAnswer is used to identify the user's final picked answer when the "Submit Answer" is pressed.
#IBOutlet var switches: [UISwitch]!
private let noAnswer = -1
private var selectedAnswer = -1
#IBAction func switchPressed(_ sender: UISwitch) {
// Check if user turned off switch
guard sender.isOn == true else {
selectedAnswer = noAnswer
return
}
// Get user's answer and set all switches
selectedAnswer = sender.tag
switches.forEach { $0.isOn = false }
sender.isOn = true
}
#IBAction func submitAnswer(_ sender: UIButton) {
guard selectedAnswer > noAnswer else { return }
// use selectedAnswer to process answer and go to the next question
answersChosen.append(currentAnswers[selectedAnswer])
}
You could store all the switches in an array named Switches for example and then run a for loop to check if more than one is selected when checking answer.
var count = 0
for switchToggle in switches {
if switchToggle.isOn {
count += 1
if count > 1 {
//Print Warning "only select one"
}
} else {
//append answers
}
}
you could also use this to have multiple answers for certain quiz question
For the warning, you have different options you could use. You could use UIAlertController to display a warning if more than one button is selected but this could become annoying for the user after a while.
Another option could be to use a UILabel which warns the user to only pick one answer. You could animate the label to fade away after a few seconds.

Why does my app not display the alert to ask to access user's location?

I am developing a navigation app in iOS which uses Location Services but I am having a problem with it. I have a UITableViewController object which creates an instance of LocationManager, configures it for desired accuracy, distance filter, etc. I have delegates for didUpdateLocations and didChangeAuthorization but for some reason it crashes under the following conditions. Location Services are turned off, I launch the app and I get an alert to turn on Location Services which I do and then return to the app expecting it to display another alert about asking to access the user's location but it never appears. The app crashes when I selecting a row in my table because the user's current location hasn't been initialised in my UITableViewController. didUpdateLocations is not being called.
// CountiesTableVC.swift
// TableViewsApp
//
// Created by Stephen Learmonth on 10/12/2018.
// Copyright © 2018 Stephen Learmonth. All rights reserved.
//
import UIKit
import CoreLocation
import MapKit
class CountiesTableVC: UITableViewController {
let england : England = England()
var countiesArray : [String] = ["Northamptonshire",
"Bedfordshire",
"Hertfordshire",
"Staffordshire",
"Essex",
"North Yorkshire",
"Herefordshire",
"Cornwall",
"Dorset",
"Derbyshire",
"Leicestershire",
"Lancashire",
"Cheshire",
"Merseyside",
"Suffolk",
"County Durham",
"Cumbria",
"Gloucestershire",
"Wiltshire",
"Nottinghamshire",
"Devon",
"Somerset",
"Lincolnshire"
]
var sortedCounties : [ String ] = []
var selectedCounty : String? = nil
var currentCoordinate: CLLocationCoordinate2D! = nil
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.distanceFilter = 10.0
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.requestWhenInUseAuthorization()
sortedCounties = countiesArray.sorted{ $0 < $1 }
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return sortedCounties.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CountyTableViewCell", for: indexPath) as! CountyTableViewCell
let county = sortedCounties[indexPath.row]
let countySites = england.counties[county]
var siteIsSelectable = false
if (countySites?.isEmpty)! == true {
cell.backgroundColor = .gray
cell.isUserInteractionEnabled = false
} else {
siteIsSelectable = true
cell.backgroundColor = .blue
cell.isUserInteractionEnabled = true
}
cell.setLabel(cellLabel: sortedCounties[indexPath.row], selectable: siteIsSelectable)
return cell
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toSitesTableVC" {
let sitesTableVC = segue.destination as? SitesTableVC
if let indexPath = self.tableView.indexPathForSelectedRow {
selectedCounty = sortedCounties[indexPath.row]
sitesTableVC?.selectedCounty = selectedCounty
sitesTableVC?.currentCoordinate = currentCoordinate
}
} else {
return
}
}
}
extension CountiesTableVC: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// get current location if avaialble
guard let currentLocation = locations.last else { return }
// get coordinates of current user location
currentCoordinate = currentLocation.coordinate
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
// authorized location status when app is in use; update current location
locationManager.startUpdatingLocation()
// implement additional logic if needed...
}
}
}
Any help would be greatly appreciated.
Thanks.
Check this answer https://stackoverflow.com/a/38727017/2376336
Pretty much it's telling you need to add these keys into into your info.plist:
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
In the past you could just add them and leave them blank, that's not the case anymore. Make sure to enter proper and concise description for why you need user's location.
if your app requires user's location for "in app use" you need to add
Privacy - Location When In Use Usage Description in info.plist
in case your app requires user's location for "always use" you need to add
Privacy - Location When In Use Usage Description
Privacy - Location Always and When In Use Usage Description
in info.plist
In locationManager(manager:didChangeAuthorization) I needed to change the if conditional expression to status != .authorizedWhenInUse and make a call to
locationManager.requestWhenInUseAuthorization() then I get prompted with the second alert to ask to access the user's location. The authorisation level hadn't been changed from .notDetemined to .authorizedWhenInuse so locationManager(manger:didUpdateLocations) didn't have a non-nil value for the user's current location. This nil value was passed to another view controller which was expecting a non-nil value and didn't have one so it crashed.

how to match (or compare) taps to annotations?

EnvironmentXcode 8Swift 3
Problem Statement
I want to be able to determine if a user taps on a MKPointAnnotation and then extract information (like title and subtitle) from that annotation for use within my app.
I imagine this is not terribly difficult, but I'm a bit lost in terms of what I need to do / what various classes / objects / methods / etc. I need to use to do this.So I'm looking for pointers / guidance - code is welcome, but at this point the pointers / guidance would be a significant step forward for me.
Code SnippetsAbridged version of the code thus far (trying to limit it to just the relevant pieces)
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
//... various #IBOutlet's for text fields, buttons, etc. ...
#IBOutlet weak var map: MKMapView!
var coords: CLLocationCoordinate2D?
var locationManager: CLLocationManager = CLLocationManager()
var myLocation: CLLocation!
var annotation: MKPointAnnotation!
var annotationList: [MKPointAnnotation] = []
var matchingItems: [MKMapItem] = [MKMapItem]()
override func viewDidLoad() {
super.viewDidLoad()
//... text field delegates, and other initilizations ...
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
}
myLocation = nil
//... other initializations...
}
// Search for things that match what my app is looking for ("<search string>")
func performSearch() {
annotationList.removeAll() // clear list
matchingItems.removeAll() // clear list
var closest = MKMapItem()
var distance = 10000.0
let request = MKLocalSearchRequest()
let span = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001)
request.naturalLanguageQuery = "<search string>"
request.region = MKCoordinateRegionMake(myLocation.coordinate, span)
let search = MKLocalSearch(request: request)
if search.isSearching {
search.cancel()
}
search.start(completionHandler: {
(_ response, _ error) in
if error != nil {
self.showAlert(msg: "Error occurred in search\nERROR: \(error?.localizedDescription)")
}
else if response!.mapItems.count == 0 {
self.showAlert(msg: "No matches found")
}
else {
for item in response!.mapItems {
// Track the closest placemark to our current [specified] location
let (distanceBetween, prettyDistance) = self.getDistance(loc1: self.myLocation, loc2: item.placemark.location!)
let addrObj = self.getAddress(placemark: item.placemark)
//... some code omitted ...
// Add markers for all the matches found
self.matchingItems.append(item as MKMapItem)
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
annotation.subtitle = "\(addrObj.address!) (\(prettyDistance))"
self.map.addAnnotation(annotation)
self.annotationList.append(annotation)
}
//... some code omitted ...
}
})
}
//... code for getDistance(), getAddress() omitted for brevity - they work as designed ...
//... other code omitted as not being relevant to the topic at hand
}
I imagine that I will need to override touchesEnded and possibly touchesBegan and maybe touchesMoved in order to detect the tap.
What I cannot figure out is how to compare a touch's location (represented as X/Y coordinates on the screen) to an MKPointAnnotation's or MKMapItem's location (which is represented as latitude/longitude coordinates on a map)
So - that's kind of where I'm currently stuck. I searched various terms on the web but wasn't able to find anything that [simplisticly] answerwed my question - and in Swift code format (there were a number of postings that looked like they might help, but the code presented wasn't in Swift and I don't do the translation that easily).
UPDATE (19:48 ET)
I found this article: How do I implement the UITapGestureRecognizer into my application and tried to follow it, but ...
I modified the code a bit (added UIGestureRecognizerDelegate):
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//...other code...
let tapHandler = UITapGestureRecognizer(target: self, action: Selector(("handleTap:"))) //<<<== See notes below
tapHandler.numberOfTapsRequired = 1
tapHandler.numberOfTouchesRequired = 1
tapHandler.delegate = self
print("A")//#=#
map.addGestureRecognizer(tapHandler)
map.isUserInteractionEnabled = true
print("B")//#=#
}
func handleTap(tap: UITapGestureRecognizer) {
print("ARRIVED")//#=#
let here = tap.location(in: map)
print("I AM HERE: \(here)")//#=#
}
//...
}
With regard to the declaration / definition of tapHandler, I tried the following:
let tapHandler = UITapGestureRecognizer(target: self, action: "handleTap:")
let tapHandler = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
let tapHandler = UITapGestureRecognizer(target: self, action: Selector(("handleTap:"))) // supresses warning
The first two caused a warning to show up in Xcode, the last simply supresses the warning:
[W] No method declared with Objective-C selector 'handleTap:'
When I run my app and tap on a pin - I get the following in my log:
A
B
libc++abi.dylib: terminating with uncaught exception of type NSException
Which would seem (to me) to indicate that the general setup in viewDidLoad is okay, but as soon as it tries to handle the tap, it dies without ever getting to my handleTap function - and thus the warning (shown above) would seem to be far more serious.
So, I'm not sure if I can count this as making progress, but I'm trying...
Thanks to this MKAnnotationView and tap detection I was able to find a solution. My code changes from those originally posted:
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//...other code...
let tapHandler = UITapGestureRecognizer() //<<<== No parameters
tapHandler.numberOfTapsRequired = 1
tapHandler.numberOfTouchesRequired = 1
tapHandler.delegate = self
map.addGestureRecognizer(tapHandler)
map.isUserInteractionEnabled = true
}
// Not sure who calls this and requires the Bool response, but it seems to work...
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return self.handleTap(touch: touch).count > 0
}
// Major Changes
private func handleTap(touch: UITouch) -> [MKAnnotationView] {
var tappedAnnotations: [MKAnnotationView] = []
for annotation in self.map.annotations {
if let annotationView: MKAnnotationView = self.map.view(for: annotation) {
let annotationPoint = touch.location(in: annotationView)
if annotationView.bounds.contains(annotationPoint) {
self.name.text = annotationView.annotation?.title!
let addr = AddrInfo(composite: ((annotationView.annotation?.subtitle)!)!)
self.address.text = addr.street!
self.city.text = addr.city!
self.state.text = addr.state!
self.zipcode.text = addr.zip!
tappedAnnotations.append(annotationView)
break
}
}
}
return tappedAnnotations
}
//...
}
The AddrInfo piece is my own little subclass that, among other things, takes a string like "1000 Main St., Pittsburgh, PA 15212, United States" and breaks it into the individual pieces so that they can be accessed, well, individually (as indicated in the code above).
There might be an easier, or better, way to achieve what I was looking for - but the above does achieve it, and so I consider it to be the answer for my issue.

Current Location not showing in iOS Simulator

I'm trying to show the user's current location in an MKMapView, mapView. I ask for permission and I believe all my bases are covered but for some reason the current location does not show up on the map.
Below is the code. Do not mind the segment controls, I just use these to change the map type.
import UIKit
import MapKit
let annotationId = "annotationID";
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var segControls: UISegmentedControl!
let locationManager = CLLocationManager();
var zoomToUserLocation = false;
// Switch statements for the seg controls.
#IBAction func mapType(sender: UISegmentedControl) {
switch segControls.selectedSegmentIndex {
case 0:
mapView.mapType = MKMapType.Standard
case 1:
mapView.mapType = MKMapType.Satellite
case 2:
mapView.mapType = MKMapType.Hybrid
default:
mapView.mapType = MKMapType.Standard
}
}
override func viewDidLoad() {
super.viewDidLoad()
// We are setting the delegate equal to self here to be able to use the indentifier that deques the annotations.
locationManager.delegate = self
// Status of user is variable that speaks directly to (in this case) authorization status
let status = CLLocationManager.authorizationStatus()
if status == CLAuthorizationStatus.NotDetermined {
locationManager.requestWhenInUseAuthorization()
} else if status != .Denied {
locationManager.startUpdatingLocation()
}
// let annotation = MKPointAnnotation();
}
// If the status changes to authorize when in use or always authorize
// then start updating the location, if not don't do anything.
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.AuthorizedWhenInUse || status == CLAuthorizationStatus.AuthorizedAlways {
locationManager.startUpdatingLocation()
}
}
// If the location failed when trying to get users location execute this function
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Error: \(error)")
}
}
extension ViewController: MKMapViewDelegate {
func mapView(mapView: MKMapView!, didUpdateUserLocation userLocation: MKUserLocation!) {
let coordinate = userLocation.coordinate; // this sets the var coordinates to the location of the user.
println("User Location = (\(coordinate.latitude), \(coordinate.longitude))");
if zoomToUserLocation == true {
let locationOfDevice: CLLocation = userLocation.location // this determines the location of the device using the users location
let deviceCoordinate: CLLocationCoordinate2D = locationOfDevice.coordinate // determines the coordinates of the device using the location device variabel which has in it the user location.
let span = MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1) // this determines the span in which its determined that 1 degree is 69 miles.
let region = MKCoordinateRegion(center: deviceCoordinate, span: span) // set the center equal to the device coordinates and the span equal to the span variable we have created this will give you the region.
mapView.setRegion(region, animated: true);
}
}
}
You need to set the MKMapView's showsUserLocation property to true. It's a boolean property that will display the current location (that pulsing blue dot you refer to - which, can be tinted and is not always blue).
Here's how Apple sums it up in their documentation
This property does not indicate whether the user’s position is actually visible on the map, only whether the map view should try to display it. Setting this property to true causes the map view to use the Core Location framework to find the current location and try to display it on the map. As long as this property is true, the map view continues to track the user’s location and update it periodically. The default value of this property is false.
Showing the user’s location does not guarantee that the location is visible on the map. The user might have scrolled the map to a different point, causing the current location to be offscreen. To determine whether the user’s current location is currently displayed on the map, use the userLocationVisible property.
So in your viewDidLoad function, you should set this property:
mapView.showsUserLocation = true
The zoomToUserLocation property does not control the current location dot.
Sometimes you need to set a custom location first to get the simulator to update and actually set a location (no idea why). What I do is go to Debug->Location->Apple just to check that the map location stuff actually works. Then later i set a custom location with coordinates which i find from google maps or similar.

iOS 8 Today Widget shows up blank for a few seconds

My today widget takes between 0 and 5 seconds to show up after pulling down the notification center. As soon as I move up my notification center a bit, my widget disappears again.
Once the widget is visible, everything works fine.
What am I missing?
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
if (self.locationManager.respondsToSelector(Selector("requestWhenInUseAuthorization"))){
self.locationManager.requestWhenInUseAuthorization()
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
self.locationManager.startUpdatingLocation()
imageViewCheck.layer.cornerRadius = 5.0
self.getLocation()
}
func getLocation() -> Bool{
var test = self.locationManager.location
if test != currentLocation {
currentLocation = test
return true
} else {
return false
}
}
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
var newLocation = self.getLocation()
if (newLocation) {
completionHandler(NCUpdateResult.NewData)
} else {
completionHandler(NCUpdateResult.NoData)
}
}
Solved:
After hours of not finding anything I decided to delete my whole Code and put it back together piece by piece. Unfortunately the problem was still there without any code left. After checking all Layout constraints without finding any error I deleted the widget and added a new one.
And guess what? It worked...
It seemes like xCode didn't like my experimenting with the layout and had something messed up where I could not fix it.
After adding the new widget and reproducing my old one with the exact same constraints and code everything worked perfectly.

Resources