I have this piece of code and I want to save the locations which I get from the MKLocalSearch in an array, so I can use them later. Do you have any ideas how I can do that?
func searchForBarsAndRestaurants(searchFor: String){
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = typeOfPlace //or whatever you're searching for
request.region = MKCoordinateRegionMakeWithDistance(location.coordinate, 300, 300)
let search = MKLocalSearch(request: request)
search.start { response, error in
guard let response = response
else {
print("There was an error searching for: \(String(describing: request.naturalLanguageQuery)) error: \(String(describing: error))")
return
}
print(response.mapItems.count)
print("There are \(response.mapItems.count)" , searchFor)
for item in response.mapItems {
// You may be able to match the address to what the geoCode gives you
// or present the user with a list of options
print("\(String(describing: item.name))")
var totalDistances: Array<Double> = Array()
let distance = self.location.distance(from: item.placemark.location!)
totalDistances += [distance]
print("distance is " ,distance)
print(totalDistances.count)
}
}
}
Sure, you've just to use a singleton.
What you want is use a global variable. Here is an example of how to do that, w
let sharedNetworkManager = NetworkManager(baseURL: API.baseURL)
class NetworkManager {
// MARK: - Properties
let baseURL: URL
// Initialization
init(baseURL: URL) {
self.baseURL = baseURL
}
}
You need to define totalDistances as Class Property.
var totalDistances: Array<Double> = Array()
func searchForBarsAndRestaurants(searchFor: String){
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = typeOfPlace //or whatever you're searching for
request.region = MKCoordinateRegionMakeWithDistance(location.coordinate, 300, 300)
let search = MKLocalSearch(request: request)
search.start { response, error in
guard let response = response
else {
print("There was an error searching for: \(String(describing: request.naturalLanguageQuery)) error: \(String(describing: error))")
return
}
print(response.mapItems.count)
print("There are \(response.mapItems.count)" , searchFor)
for item in response.mapItems {
// You may be able to match the address to what the geoCode gives you
// or present the user with a list of options
print("\(String(describing: item.name))")
let distance = self.location.distance(from: item.placemark.location!)
self.totalDistances += [distance]
print("distance is " ,distance)
print(self.totalDistances.count)
}
}
}
Related
I'm learning swift and trying to use SwiftyJson to parse a json file and add annotations in the map view but couldn't get the pins showed on the simulator. I have a warning in the debugging area says that Could not inset legal attribution from corner 4. My code is as below and I've checked some of the answers about this problem but still couldn't fix it. Any help is greatly appreciated.
class StationsViewController: UIViewController {
var stations = [Station]()
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self //as MKMapViewDelegate
//mapView.showsUserLocation = YES
fetchJsonData()
mapView.addAnnotations(stations)
}
func fetchJsonData() {
// Fetching client list.
let api_json_url = URL(string:"https://feeds.divvybikes.com/stations/stations.json")
// Create a URL request with the API address
let urlRequest = URLRequest(url: api_json_url!)
// Submit a request to get the JSON data
//let session = URLSession.shared
let task = URLSession.shared.dataTask(with: urlRequest) {data,response,error in
// if there is an error, print the error and do not continue
if error != nil {
print("Failed to parse")
return
}
// if there is no error, fetch the json formatted content
else{
let json = JSON(data:data!)
if let stationJSONs = json["stationBeanList"].array {
for stationJSON in stationJSONs {
if let station = Station.from(json: stationJSON) {
self.stations.append(station)
}
}
}
}// end if
} // end getDataSession
task.resume()
} // end readJsonData function
}
for stationJSON in stationJSONs {
if let station = Station.from(json: stationJSON) {
self.stations.append(station)
let latitude = station["latitude"]
let longitude = station["longitude"]
let annotation = MKPointAnnotation()
let centerCoordinate = CLLocationCoordinate2D(latitude: latitude, longitude)
annotation.coordinate = centerCoordinate
annotation.title = "Pass Title here"
mapView.addAnnotation(annotation)
}
}
check this.
You need to call addAnnotation in fetchJsonData(), because fetchJsonData() is executed asynchronously.
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self //as MKMapViewDelegate
//mapView.showsUserLocation = YES
fetchJsonData()
}
func fetchJsonData() {
// Fetching client list.
let api_json_url = URL(string:"https://feeds.divvybikes.com/stations/stations.json")
// Create a URL request with the API address
let urlRequest = URLRequest(url: api_json_url!)
// Submit a request to get the JSON data
//let session = URLSession.shared
let task = URLSession.shared.dataTask(with: urlRequest) {data,response,error in
// if there is an error, print the error and do not continue
if error != nil {
print("Failed to parse")
return
}
// if there is no error, fetch the json formatted content
else{
let json = JSON(data:data!)
if let stationJSONs = json["stationBeanList"].array {
for stationJSON in stationJSONs {
if let station = Station.from(json: stationJSON) {
self.stations.append(station)
mapView.addAnnotation(station)
}
}
}
}// end if
} // end getDataSession
task.resume()
} // end readJsonData function
I'm using Google Places in one of my apps where given coordinates, I need to find nearby places. Places' iOS API can either use current place or you can use place picker. I don't want either of these but to pass coordinates and get nearby places as I'm getting these coordinates from an image.
When Cinema pass that place you should get type like restaurant,bars etc
Swift
func fetchPlacesNearCoordinate(coordinate: CLLocationCoordinate2D, radius: Double, name : String){
let apiServerKey = "API-Key"
let urlString :URLRequest = URLRequest.init(url: URL.init(string: "https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=\(apiServerKey)&location=\(currentlatitude),\(currentlongitude)&radius=\(radius)&types=\(Cinema)&rankby=prominence&sensor=true&name=\(Cinemas)")!)
print(urlString)
UIApplication.shared.isNetworkActivityIndicatorVisible = true
let placesTask = URLSession.shared.dataTask(with: urlString) {data, response, err in guard let data = data, err == nil else{
print("error ==>\(err)")
return
}
let responsestring = String(data: data, encoding: .utf8)
do {
let jsondata1 = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSDictionary
print("Final data Convert to JSON =>\(jsondata1)")
let results = jsondata1["results"] as! NSArray
print("Results ==> \(results)")
}
catch{
print("error ==>\(err)")
}
}
placesTask.resume()
}
This will return you nearby places by passing coordinates . Just call this function and pass your coordinates
func getPlacesUsingCoordinates(coordinate: CLLocationCoordinate2D, radius: Double, name : String){
//Pass name of place you want to search nearby in name
var urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=\(apiServerKey)&location=\(coordinate.latitude),\(coordinate.longitude)&radius=\(radius)&rankby=prominence&sensor=true"
urlString += "&name=\(name)"
urlString = urlString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
placesTask = session.dataTaskWithURL(NSURL(string: urlString)!) {data, response, error in
if let json = NSJSONSerialization.JSONObjectWithData(data, options:nil, error:nil) as? NSDictionary {
if let results = json["results"] as? NSArray {
if let name = results["name"] as? String {
print(name) //Place Name
}
if let location = results["geometry"]
print(location) //Geometry
}
}
}
self.placesTask.resume()
}
}
If you want further place details use :
CLGeocoder().reverseGeocodeLocation(yourLocation) { (placeDetails, error) in
// This provide every info related to location. Just pass yourLocation here
}
Hope this is what you are looking out for.
I have very little knowledge on data passes and need help on how to pass a LocalSearchResponse that has already populated a mapview to then use a UIButton to segue to a tableview to have those pin annotations to be listed.
matchingItems.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = shopType
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start(completionHandler: {(response, error) in
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No matches found")
} else {
print("Matches found")
for item in response!.mapItems {
print("Name = \(String(describing: item.name))")
print("Phone = \(String(describing: item.phoneNumber))")
self.matchingItems.append(item as MKMapItem)
print("Matching items = \(self.matchingItems.count)")
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
self.mapView.addAnnotation(annotation)
self.listedButton.isEnabled = true
}
}
})
I want to list the places searched with the Name, and Phone in a tableview.
I'm running into a problem when I try to make a request to YQL for stock data, when the symbol (newCompanyStockSymbol) to look up is user-entered. I fetch the stocks in this function:
func handleSave() {
// Fetch stock price from symbol provided by user for new company
guard let newCompanyStockSymbol = stockTextField.text else {
print("error getting text from field")
return
}
var newCompanyStockPrice = ""
let url = URL(string: "https://query.yahooapis.com/v1/public/yql?q=select%20symbol%2C%20Ask%2C%20YearHigh%2C%20YearLow%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22\(newCompanyStockSymbol)%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
} else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
let json = JSON(data: data!)
if let quotes = json["query"]["results"]["quote"].array {
for quote in quotes {
let ask = quote["Ask"].stringValue
newCompanyStockPrice = ask
}
}
print("new company json: \(json)")
}
guard let newCompanyName = self.nameTextField.text else {
print("error getting text from field")
return
}
guard let newCompanyLogo = self.logoTextField.text else {
print("error getting text from field")
return
}
print("2: The new commpany stock price is: \(newCompanyStockPrice)")
// Call save function in view controller to save new company to core data
self.viewController?.save(name: newCompanyName, logo: newCompanyLogo, stockPrice: newCompanyStockPrice)
self.viewController?.tableView.reloadData()
}
task.resume()
// Present reloaded view controller with new company added
let cc = UINavigationController()
let companyController = CompanyController()
viewController = companyController
cc.viewControllers = [companyController]
present(cc, animated: true, completion: nil)
}
And I use string interpolation to insert \(newCompanyStockSymbol) into the request URL at the appropriate place. However I get a crash and error on that line because it's returning nil, I expect because it's using the URL with \(newCompanyStockSymbol) in there verbatim, instead of actually inserting the value.
Is there another way to do this?
EDIT
And the save function in view controller that's called from handleSave() above if it's helpful:
func save(name: String, logo: String, stockPrice: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
let entity =
NSEntityDescription.entity(forEntityName: "Company",
in: managedContext)!
let company = NSManagedObject(entity: entity,
insertInto: managedContext)
company.setValue(stockPrice, forKey: "stockPrice")
company.setValue(name, forKey: "name")
company.setValue(logo, forKey: "logo")
do {
try managedContext.save()
companies.append(company)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
tableView.reloadData()
}
Supposing you entered AAPL in your stockTextField, using simply:
let newCompanyStockSymbol = stockTextField.text
results in newCompanyStockSymbol being:
Optional("AAPL")
which is not what you want in your URL string. The critical section ends up like this:
(%22Optional("AAPL")%22)
Instead, use guard to get the value from the text field:
guard let newCompanyStockSymbol = stockTextField.text else {
// handle the error how you see fit
print("error getting text from field")
return
}
Now your URL should be parsed correctly.
--- Additional info ---
I'm not entirely sure of the rules on 'continued conversation' around here, but hopefully editing this will be acceptable... anyway...
Make sure you are following this flow:
func handleSave() {
let newCompanyName = nameTextField.text
let newCompanyStockSymbol = stockTextField.text
let newCompanyLogo = logoTextField.text
var newCompanyStockPrice = ""
// Fetch stock price from symbol provided by user for new company
let url = URL(string: "https://query.yahooapis.com/v1/public/yql?q=select%20symbol%2C%20Ask%2C%20YearHigh%2C%20YearLow%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22\(newCompanyStockSymbol)%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
} else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
let json = JSON(data: data!)
if let quotes = json["query"]["results"]["quote"].array {
for quote in quotes {
let ask = quote["Ask"].stringValue
newCompanyStockPrice = ask
// task completed, we've parsed the return data,
// so NOW we can finish the save process and
// update the UI
viewController?.save(name: newCompanyName!, logo: newCompanyLogo!, stockPrice: newCompanyStockPrice)
}
}
}
}
task.resume()
}
I'm not testing this, so it might need a tweak, and your .save() function may need to be forced onto the main thread (since it's doing UI updates). But maybe that's a little more clear.
I'm building a small little app for myself– right now, it has a function called performSearch that does a search within your local area of all nearby coffee places, gyms, and restaurants then drops pins at each of those locations. However, I'm confused as to how I can get the annotation to display the name of the location as shown on the actual map view. Anyone have any experience?
Basically, instead of displaying the address only, I want the annotation to say "Starbucks
Address…"
Sample code:
This does a search with any given Search field and drops pins on a map view of all locations in the given area with that search field.
var matchingItems: [MKMapItem] = [MKMapItem]()
#IBOutlet weak var map: MKMapView!
func performSearch(searchField: String) {
matchingItems.removeAll()
//search request
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchField
request.region = self.map.region
// process the request
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler { response, error in
guard let response = response else {
print("There was an error searching for: \(request.naturalLanguageQuery) error: \(error)")
return
}
for item in response.mapItems {
// customize your annotations here, if you want
var annotation = item.placemark
self.reverseGeocoding(annotation.coordinate.latitude, longitude: allData.coordinate.longitude)
self.map.addAnnotation(annotation)
self.matchingItems.append(item)
}
}
}
func reverseGeocoding(latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
let location = CLLocation(latitude: latitude, longitude: longitude)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if error != nil {
print(error)
return
}
else if placemarks?.count > 0 {
let pm = placemarks![0]
let address = ABCreateStringWithAddressDictionary(pm.addressDictionary!, false)
print("\n\(address)")
if pm.areasOfInterest?.count > 0 {
let areaOfInterest = pm.areasOfInterest?[0]
print(areaOfInterest!)
} else {
print("No area of interest found.")
}
}
})
}
First, reverseGeocodeLocation runs asynchronously, so you'd have to use completion handler pattern.
But, second, the reverse geocoding is unnecessary, as the item in the response.mapItems has a name property. So get use that as the title of your annotation.
For example:
func performSearch(searchField: String) {
matchingItems.removeAll()
//search request
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchField
request.region = map.region
// process the request
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler { response, error in
guard let response = response else {
print("There was an error searching for: \(request.naturalLanguageQuery) error: \(error)")
return
}
for item in response.mapItems {
let annotation = MKPointAnnotation()
annotation.title = item.name
annotation.subtitle = item.placemark.title
annotation.coordinate = item.placemark.coordinate
self.map.addAnnotation(annotation)
self.matchingItems.append(item)
}
}
}