Allocating the results of Reverse Geocoding to a global variable - swift

I am using Swift 4 and Xcode 9.3.1. I'm including screenshots of the code.
I am new to mobile development/ programming in general and have been thinking about how to phrase this. So this is the best I can explain it:
I am building an app that gets the user's location in order to send assistance through to them. The app gets the user's coordinates, and displays a TextView with their address information. So pretty straight forward mapKit/coreLocation functionality. So far, so good: Getting the coordinates with startUpdatingLocation() works fine, and I've used Reverse Geocoder to get the street name & locality. But they-- meaning the decoded street and locality strings-- only print out if I call them within the closure, not outside it. I've understood (correctly or incorrectly?) that variables that need to be available for multiple functions within a class should to be declared globally at the top. However I can't figure out how to extract the information from this closure in order to use it elsewhere.
I've been googling and reading through questions in stackOverflow and I feel like something really simple is missing but can't figure out what. Things I've tried unsuccessfully so far:
1_ Defining global variables as empty strings at the beginning of the class
and using the variable names inside the closure where the geocoding reverse method happens, in an attempt to store the resulting strings, but when I try to print the variables outside the closure, the global variable is still and empty string ("").
[global variables declared at the top][1]
2_Defining an empty, global array of strings and appending the information from inside the closure to the global array. Still got an empty array outside the closure. (so same as 1)
3_Create a function --func decodedString()-- to return the data as a String, so I can use it by declaring
*let streetLocation : String = decodedString()*
However when I declare that function like this :
var street = ""
var locality = ""
// Get the street address from the coordinates
func deocodedString() -> String {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let placemark = placemarks?.first {
self.street = placemark.name!
self.locality = placemark.locality!
let string = "\(self.street), \(self.locality)"
return string
}
}
}
I get an error of: Unexpected non-void return value in void function
unexpected no void return value in void function
Lastly, if I pass the information straight into a TextView within the closure by using the code below, my textView updates successfully-- but I can't format the strings, which I need to do in order to make them look like the design instructions I'm following (aka some bold text, some regular text, and some different sizes of text):
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
if let placemark = placemarks?.first {
self.street = placemark.name!
self.locality = placemark.locality!
let string = "\(self.street), \(self.locality)"
self.addressTextView.text = string
}
}
So that's why I can't just pass it through with the textView.text = string approach.
I'd appreciate some help...I have been looking though StackOverFlow, youtube and other tutorial places but I can't figure out what I'm missing, or why my function declaration generates an error. I have already destroyed and reversed my code several times over last 24 hs without getting an independent string that I can apply formatting to before passing it into the textView and I'm at a loss as to how else to approach it.

When you call this function the reverseGeocodeLocation runs in the background thread. So if you want to return the address in this method you should use escaping closure.
func getaddress(_ position:CLLocationCoordinate2D,completion:#escaping (String)->()) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let placemark = placemarks?.first {
let street = placemark.name!
let locality = placemark.locality!
let string = "\(street), \(locality)"
completion(string)
}
}
}
self.getaddress(position.target) { address in
print(address)
self.addressTextView.text = address
}

I had a problem with google geocoder to update the label on the map screen.
So I did this, first, create
swift file name: GoogleAPI just call it as you like.
class GoogleAPI {
static let sharedInstance = GoogleAPI()
private init() { }
var addressValue = ""
public func geocoding(lat: Double, long: Double) {
Alamofire.request("https://maps.googleapis.com/maps/api/geocode/json?latlng=\(lat),\(long)&key=YOUR_GOOGLE_KEY").responseJSON { (response) in
if response.result.isSuccess {
let dataJSON : JSON = JSON(response.result.value!)
self.geocoding(json: dataJSON)
} else {
print("Error \(response.result.error!)")
}
}
}
fileprivate func geocoding(json: JSON) {
let json = json["results"]
let address = json[1]["formatted_address"].stringValue
addressValue = address
print("pin address \(addressValue)")
}
}
This is an API call to Google to fetch all from a response and parse the only street.
After that go to your View Controller with a map where is the pin, map etc..
Set up a pin, marker to be draggable. [marker1.isDraggable = true]
Then add this function
mapView(_ mapView: GMSMapView, didEndDragging marker: GMSMarker)
and add call from above like this :
func mapView(_ mapView: GMSMapView, didEndDragging marker: GMSMarker) {
GoogleAPI.sharedInstance.geocoding(lat: marker.position.latitude, long: marker.position.longitude)
DispatchQueue.main.async {
self.txtSearch.text = GoogleAPI.sharedInstance.addressValue
}
}
txtSearch is my search text field.
yea I know, that can be done better, but no time. this is working.
Swift 4.2

Related

Swift 4 Programming for filtering large array with specific condition

kindly note that my question is specific to Swift 4 syntax. I have a bulky/large array of Strings and I want to filter it for getting all values in it which are started with some specific characters/substring. That means I need each String in my array which matches to started with some substring. I found different links which gives me code for Objective-C and I am unable to implement it in Swift 4 because of that methods are not available in Swift 4. I solved my question manually by iterating my array in for loop but it gives very slow result So I don't want to use any loop here, So any help will useful. Thanks in advance. See my code below:
func search() -> Void {
var dummyStringsArray:[String] = ["Hotel Restaurants","Restaurants","Certified Green Restaurant(R)","Japnies Food Restauarants","Grill Restaurants","Restaurant Equipment","Wholsale Restaurant Fixtures","American Food","Wholsale Restaurant Supplies","Veg Restaurants","Barbecue Restaurants","Non-Veg Restaurants"]
var displayDataArray:[String] = []
let searchString:NSString = (textField.text!).lowercased() as NSString
for string in self.dummyStringsArray {
let mainString:NSString = string.lowercased() as NSString
if mainString.length >= searchString.length {
let compareString = String(mainString.substring(to: searchString.length))
if searchString as String == compareString {
displayDataArray.append(string)
}
}
}
}
So if I entered text in textField as 're' then it should return displayDataArray containing values like "Restaurants", "Restaurant Equipment".
I think you can't do this without any loop because you need to go through all the elements
But I can offer you more elegant solution with filter function:
func search() -> Void {
let dummyStringsArray:[String] = ["Hotel Restaurants","Restaurants","Certified Green Restaurant(R)","Japnies Food Restauarants","Grill Restaurants","Restaurant Equipment","Wholsale Restaurant Fixtures","American Food","Wholsale Restaurant Supplies","Veg Restaurants","Barbecue Restaurants","Non-Veg Restaurants"]
let searchString: String = (textField.text!).lowercased()
let displayDataArray: [String] = dummyStringsArray.filter({ String($0.prefix(searchString.count)).lowercased() == searchString })
}
You will probably get the best performance using range(of:options:) while passing in the options of .caseInsensitive and .anchored.
func search() -> Void {
let dummyStringsArray = ["Hotel Restaurants","Restaurants","Certified Green Restaurant(R)","Japnies Food Restauarants","Grill Restaurants","Restaurant Equipment","Wholsale Restaurant Fixtures","American Food","Wholsale Restaurant Supplies","Veg Restaurants","Barbecue Restaurants","Non-Veg Restaurants"]
let searchString = textField.text!
let displayDataArray = dummyStringsArray.filter { $0.range(of: searchString, options: [ .caseInsensitive, .anchored ]) != nil }
}

Filtering an Array inside a Dictionary - Swift

I am trying to search through a indexed dictionary to return a specific client based on the client's last name. Below are the data structures I am using. Each client object has a name property which is a String.
var clients = Client.loadAllClients() //Returns client array
var contacts = [String: [Client]]() //Indexed clients in a dictionary
var letters: [String] = []
var filteredClient = [Client]()
var shouldShowSearchResults = false
var searchController : UISearchController!
When I do my indexing, the contacts dictionary returns:
{A: [Client("Andrew")]}
Letters array returns:
[A]
I am using the UISearchController to display the filtered array of clients.
func updateSearchResults(for searchController: UISearchController) {
// how to filter the dictionary
self.tableView.reloadData()
}
However, I have no idea how to filter the dictionary to return the correct list of clients. I have tried to use
contacts.filter(isIncluded: ((key: String, value: [Client])) throws -> Bool((key: String, value: [Client])) throws -> Bool)
But I was very confused about the implementation. I am using Xcode 8.0 and Swift 3.0.
If anyone could point me in the right direction, that would be much appreciated. Please let me know if I need to clarify anything. Thank you in advance. The full code can be found at my Github
The main problem is that you are using a dictionary as data source array.
My suggestion is to use a custom struct as model
struct Contact {
let letter : String
var clients : [Client]
init(letter: String, clients : [Client] = [Client]()) {
self.letter = letter
self.clients = clients
}
mutating func add(client : Client) {
clients.append(client)
}
}
Then create your data source array
var contacts = [Contact]()
and the letter array as computed property
var letters : [String] = {
return contacts.map{ $0.letter }
}
It's easy to sort the array by letter
contacts.sort{ $0.letter < $1.letter }
Now you can search / filter this way (text is the text to be searched for)
filteredClient.removeAll()
for contact in contacts {
let filteredContent = contact.clients.filter {$0.name.range(of: text, options: [.anchored, .caseInsensitive, .diacriticInsensitive]) != nil }
if !filteredContent.isEmpty {
filteredClient.append(filteredContent)
}
}
You can even keep the sections (letters) if you declare filteredClient also as [Contact] and create temporary Contact instances with the filtered items.
Of course you need to change all table view data source / delegate methods to conform to the Contact array, but it's worth it. An array as data source is more efficient than a dictionary.

Get the name of a connected screen Swift

Does anyone know of a way to get the screen name, or model name/number from a display that is connected to the system? I've been looking around for quite some time to see if there is a way to do this. The only method I've seen anyone post only works with a deprecated API (CGDisplayIOServicePort), (and there's not replacement listed for that API), so that isn't really an option.
Basically, I am wanting to give the user a list of connected screens to display the output of the app, and I feel like giving them a list of names of the displays would be much more elegant and nicer than whatever the ID is that is returned from NSScreen or CGGetActiveDisplayList, etc. It has to possible, when you go to the display preferences in OS X it gives you the names of the displays there. Anyone have any ideas?
macOS 10.15 Catalina introduced a new property localizedName for getting the external display name:
NSScreen.screens.forEach {
print($0.localizedName)
}
You can get the names of connected screens directly from IOReg
func screenNames() -> [String] {
var names = [String]()
var object : io_object_t
var serialPortIterator = io_iterator_t()
let matching = IOServiceMatching("IODisplayConnect")
let kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault,
matching,
&serialPortIterator)
if KERN_SUCCESS == kernResult && serialPortIterator != 0 {
repeat {
object = IOIteratorNext(serialPortIterator)
let info = IODisplayCreateInfoDictionary(object, UInt32(kIODisplayOnlyPreferredName)).takeRetainedValue() as NSDictionary as! [String:AnyObject]
if let productName = info["DisplayProductName"] as? [String:String],
let firstKey = Array(productName.keys).first {
names.append(productName[firstKey]!)
}
} while object != 0
}
IOObjectRelease(serialPortIterator)
return names
}
let names = screenNames()

Modifying struct instance using call to name in function parameter

I am attempting to use Parse to call up some variables and put them into a struct that is already initialized. The calling of the variables is happening smoothly and the data is available, but the inputing of the class into the function is not happening.
'unit' is a struct that has the name, hp, attack, etc. variables contained within it.
Is it not possible to pass along an instance of a struct and modify it's values like this? It would save me a lot of copy-pasting code to do it this way.
Thanks for your help!
func fetchStats(name: String, inout nameOfClass: unit) {
var unitStatArray = []
let query = PFQuery(className: "UnitStats")
query.whereKey("name", equalTo: name)
query.findObjectsInBackgroundWithBlock{(objects:[PFObject]?, error: NSError?)->Void in
if (error == nil && objects != nil){ unitStatArray = objects! }
nameOfClass.name = "\(unitStatArray[0].objectForKey("name")!)"
print("class name is \(nameOfClass.name)")
print("cannon name is \(cannon.name)")
nameOfClass.hitPoints = unitStatArray[0].objectForKey("hitPoints") as! Double
nameOfClass.hitPointsMax = unitStatArray[0].objectForKey("hitPointsMax") as! Double
nameOfClass.attack = unitStatArray[0].objectForKey("attack") as! Double
nameOfClass.defense = unitStatArray[0].objectForKey("defense") as! Double
nameOfClass.rangedAttack = unitStatArray[0].objectForKey("rangedAttack") as! Double
nameOfClass.rangedDefense = unitStatArray[0].objectForKey("rangedDefense") as! Double
nameOfClass.cost = unitStatArray[0].objectForKey("cost") as! Int
}
}
fetchStats("3-inch Ordnance Rifle", nameOfClass: &cannon)
This is an attempt to explain what I had in mind when writing my comment above.
Because there's an asynchronous call to findObjectsInBackgroundWithBlock, the inout won't help you here. The idea is to add a callback fetched like this:
func fetchStats(name: String, var nameOfClass: unit, fetched: unit -> ()) {
// your code as above
query.findObjectsInBackgroundWithBlock {
// your code as above plus the following statement:
fetched(nameOfClass)
}
}
This can be called with
fetchStats("3-inch Ordnance Rifle", nameOfClass: cannon) { newNameOfClass in
nameOfClass = newNameOfClass
}
(all of this code has not been tested)
The point is that you understand that your code is asynchronous (I know, I'm repeating myself). After you have called fetchStats you don't know when the callback (here: the assignment nameOfClass = newNameOfClass) will be executed. You cannot assume the assignment has been done after fetchStats has returned.
So whatever you need to do with the changed nameOfClass: the corresponding statements must go into the callback:
fetchStats("3-inch Ordnance Rifle", nameOfClass: cannon) { newNameOfClass in
// do whatever you want with the received newNameOfClass
}
Hope this helps.

How do i return coordinates after forward geocoding?

I am trying to see whether the user is within a certain distance of an address. I have successfully managed to get the users location, and convert the address with forward geocoding. I am left with two sets of coordinates. I am trying to make an if statement saying if they are within "a distance", print something!
Currently when i print the coordinates inside the placemark function i get the desired coordinates. When i call them to create eventLatitude and eventLongitude they become 0.0. I know this is a ascycronous problem, but i am unsure on who to resolve this. Can someone give me an example.
My code is below
before the viewdidload i have these variables
var placemarkLongitude = CLLocationDegrees()
var placemarkLatitude = CLLocationDegrees()
then inside the function i set these variables to the placemark coordinates
if let objects = objects {
for object in objects {
self.geocoder = CLGeocoder()
//get address from object
let COAddress = object.objectForKey("Address")as! String
let COCity = object.objectForKey("City")as! String
let COState = object.objectForKey("State")as! String
let COZipCode = object.objectForKey("ZipCode")as! String
let combinedAddress = "\(COAddress) \(COCity) \(COState) \(COZipCode)" //all parts of address
print(combinedAddress)
//make address a location
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {(placemarks, error) -> Void in
if(error != nil)
{
print("Error", error)
}
else if let placemark = placemarks?[0]
{
let placemark = placemarks![0]
self.placemarkLatitude = (placemark.location?.coordinate.latitude)! //THIS RETURNS A VALUE
self.placemarkLongitude = (placemark.location?.coordinate.longitude)! //THIS RETURNS A VALUE
print("Longitude: ", self.placemarkLongitude, " Latitude: ", self.placemarkLatitude)
}
})
// user location
let userLatitude = self.locationManager.location?.coordinate.latitude //THIS RETURNS A VALUE
let userLongitude = self.locationManager.location?.coordinate.longitude //THIS RETURNS A VALUE
print("User Location is ", userLatitude, ", " ,userLongitude)
let userLocation = CLLocation(latitude: userLatitude!, longitude: userLongitude!)
// event location
let eventLatitude = self.placemarkLatitude // THIS RETURNS 0.0
let eventLongitude = self.placemarkLatitude // THIS RETURNS 0.0
print("Event Location is ", eventLatitude, ", " ,eventLongitude)
let eventLocation = CLLocation(latitude: eventLatitude, longitude: eventLongitude)
//Measuring my distance to my buddy's (in km)
let distance = userLocation.distanceFromLocation(eventLocation) / 1000
//Display the result in km
print("The distance to event is ", distance)
if (distance < 100) {
print("yay")
}
}
}
You are correct about the asynchronous issue. Basically, you cannot do anything after this code:
// [A1]
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {
(placemarks, error) -> Void in
// [B] ... put everything _here_
})
// [A2] ... nothing _here_
The reason is that the stuff inside the curly braces (B) happens later than the stuff outside it (including the stuff afterward, A2). In other words, the code in my schematic above runs in the order A1, A2, B. But you are dependent on what happens inside the curly braces, so you need that dependent code to be inside the curly braces so that it executes in sequence with the results of the geocoding.
Of course this also means that the surrounding function cannot return a result, because it returns before the stuff in curly braces has even happened. The code in my schematic goes A1, A2, return! Only later does B happen. So clearly you cannot return anything that happens in B because it hasn't happened yet.
Just pass the coordinate values obtained from the completionHandler to any other method and do what you like to do.
{
self.placemarkLatitude = (placemark.location?.coordinate.latitude)! //THIS RETURNS A VALUE
self.placemarkLongitude = (placemark.location?.coordinate.longitude)! //THIS RETURNS A VALUE
// After this code pass the values like,
passingTheCoordinates(placemarkLatitude, placemarkLongitude)
}
func passingTheCoordinates(latitude:CLLocationDegrees, _ longitude:CLLocationDegrees){
}
Did not have enough reputation to reply your question but I also have this same problem today. I don't know much about your app design but for my case (which is stuck at the same place like you, same func, same problem, can't save to variable). My solution (maybe kinda temporally, does not good) is to save (placemark.location?.coordinate.latitude)! and (placemark.location?.coordinate.longitude)! to CoreData as Double.
This is how I implemented it. As I said before, since I don't know your app much so depend on your need, you might want something else.
LocationManager.sharedInstance.getReverseGeoCodedLocation(address: searchBar.text!, completionHandler: { (location:CLLocation?, placemark:CLPlacemark?, error:NSError?) in
if error != nil {
print((error?.localizedDescription)!)
return
}
if placemark == nil {
print("Location can't be fetched")
return
}
//Saving geo code to Core Data
newEntry.lat = (placemark?.location?.coordinate.latitude)!
newEntry.long = (placemark?.location?.coordinate.longitude)!
})
Credit to this repo for the LocationManager.swift file