I'm working on an app that heavily uses addresses in different countries. We have a bunch of ways we input them from getting addresses imported, to dropping pins on map, to reverse geocode our current location.
My current project is to correctly format international address:
In the USA:
18 Street Name
In Norway:
Street Name 18
I've figured out a bunch of ways to instantiate CNMutablePostalAddress with CLPlacemark to get some pretty good results, the problem I'm having is this.
I want just the street name and number returned as a one line string:
So:
street1: placemarker.thoroughfare, (street name)
street2: placemarker.subThoroughfare (street number),
but CNPostalAddress only has one street property, so to use it you need to do something like this:
cNPostalAddress.street = placemarker.subThoroughfare + " " + placemarker.thoroughfare
This will not work for countries like Norway where they are revered.
You can hack it and use the take the first line from the formatted address:
CNPostalAddressFormatter.string(from: placemarker.mailingAddress , style: .mailingAddress)
but that's super hacky and I'm sure it will break with countries that order their mailing address differently like japan.
At the moment I can't even find any resources that tell me which countries reverse subThoroughfare and thoroughfare, because if I had a list like that I could just reverse it manually.
Here is some sample code of what I've managed so far:
static func mulitLineAddress(from placemarker: CLPlacemark, detail: AddressDetail) -> String {
let address = MailingAddress(
street1: placemarker.thoroughfare,
street2: placemarker.subThoroughfare,
city: placemarker.locality,
state: placemarker.administrativeArea,
postalCode: placemarker.postalCode,
countryCode: placemarker.country)
return self.mulitLineAddress(from: address, detail: detail)
}
static func mulitLineAddress(from mailingAddress: MailingAddress, detail: AddressDetail) -> String {
let address = CNMutablePostalAddress()
let street1 = mailingAddress.street1 ?? ""
let street2 = mailingAddress.street2 ?? ""
let streetSpacing = street1.isEmpty && street2.isEmpty ? "" : " "
let streetFull = street1 + streetSpacing + street2
switch detail {
case .street1:
address.street = street1
case .street2:
address.street = street2
case .streetFull:
address.street = streetFull
case .full:
address.country = mailingAddress.countryCode ?? ""
fallthrough
case .withoutCountry:
address.street = streetFull
address.city = mailingAddress.city ?? ""
address.state = mailingAddress.state ?? ""
address.postalCode = mailingAddress.postalCode ?? ""
}
return CNPostalAddressFormatter.string(from: address, style: .mailingAddress)
}
Any ideas? Even resources like list of countries that reverse street1 and street2 would be useful.
I ended up taking a hybrid approach.
For the CLPlacemark to the CNMutablePostalAddress() it's pretty stright forward:
cnPostalAddress.street = placemarker.postalAddress?.street
However this doesn't work any any other input method and can't be modified to a different format from the CNMutablePostalAddress()
When bringing in address info from other sources I needed to do it manually, here is a bit of an example that works with a few countries:
static private func generateLocalizedStreetAddress(from adderss: MailingAddress) -> String {
guard adderss.localizedStreet.isEmpty else { return adderss.localizedStreet ?? "" }
let country = CountryCode.country(for: adderss.countryCode)
let thoroughfare = adderss.thoroughfare ?? ""
let subThoroughfare = adderss.subThoroughfare ?? ""
let delimiter = self.generateDelimiter(from: thoroughfare, and: subThoroughfare, with: country)
switch country {
case .belgium, .czechRepublic, .denmark, .finland, .germany, .latvia, .netherlands, .norway, .poland, .portugal, .sweden:
return thoroughfare + delimiter + subThoroughfare
default:
return subThoroughfare + delimiter + thoroughfare
}
}
static private func generateDelimiter(from thoroughfare: String, and subThoroughfare: String, with country: Country) -> String {
guard !thoroughfare.isEmpty && !subThoroughfare.isEmpty else { return "" }
switch country {
case .spain:
return ", "
default:
return " "
}
}
CNPostalAddressFormatter has an interesting API to get an NSAttributedString where each address component is identified in it. You could use this to pull out just the information you want, like the street which includes the properly localized sub-thoroughfare and thoroughfare no matter where it may exist in the postal address.
let addressFormatter = CNPostalAddressFormatter()
let attributedAddress = addressFormatter.attributedString(from: postalAddress, withDefaultAttributes: [:])
let nsString = attributedAddress.string as NSString
let range = NSRange(location: 0, length: nsString.length)
var street: String?
attributedAddress.enumerateAttributes(in: range, options: []) { result, range, stop in
if let component = result[NSAttributedString.Key(CNPostalAddressPropertyAttribute)] as? String {
if component == CNPostalAddressStreetKey {
street = nsString.substring(with: range)
stop.pointee = true
}
}
}
I've also filed feedback with Apple to add a more powerful and flexible API: FB8648023 Template API desired to format addresses with specified components similar to DateFormatter.setLocalizedDateFormatFromTemplate
Related
I have a string which contains English and Arabic together. I am using an API, that is why I cannot set an indicator in it.
What I want to get is: the Arabic and English split into tow parts. Here is a sample String:
"بِاسْمِكَ رَبِّي وَضَعْتُ جَنْبِي، وَبِكَ أَرْفَعُهُ، فَإِنْ أَمْسَكْتَ نَفْسِي فَارْحَمْهَا، وَإِنْ أَرْسَلْتَهَا فَاحْفَظْهَا، بِمَا تَحْفَظُ بِهِ عِبَادَكَ الصَّالِحِينَ.Bismika rabbee wadaAAtu janbee wabika arfaAAuh, fa-in amsakta nafsee farhamha, wa-in arsaltaha fahfathha bima tahfathu bihi AAibadakas-saliheen. In Your name my Lord, I lie down and in Your name I rise, so if You should take my soul then have mercy upon it, and if You should return my soul then protect it in the manner You do so with Your righteous servants.",
I cannot find how to split it into 2 parts that I get Arabic and English into two different parts.
What I want:
so there can be any language, my problem is to only take out English or Arabic language and show them in respective fields.
How can I achieve it?
You can use a Natural Language Tagger, which would work even if both scripts are intermingled:
import NaturalLanguage
let str = "¿como? بداية start وسط middle начать средний конец نهاية end. 從中間開始. "
let tagger = NLTagger(tagSchemes: [.script])
tagger.string = str
var index = str.startIndex
var dictionary = [String: String]()
var lastScript = "other"
while index < str.endIndex {
let res = tagger.tag(at: index, unit: .word, scheme: .script)
let range = res.1
let script = res.0?.rawValue
switch script {
case .some(let s):
lastScript = s
dictionary[s, default: ""] += dictionary["other", default: ""] + str[range]
dictionary.removeValue(forKey: "other")
default:
dictionary[lastScript, default: ""] += str[range]
}
index = range.upperBound
}
print(dictionary)
and print the result if you'd like:
for entry in dictionary {
print(entry.key, ":", entry.value)
}
yielding :
Hant : 從中間開始.
Cyrl : начать средний конец
Arab : بداية وسط نهاية
Latn : ¿como? start middle end.
This is still not perfect since the language tagger only checks to which script the most number of letters in a word belong to. For example, in the string you're working with, the tagger would consider الصَّالِحِينَ.Bismika as one word. To overcome this, we could use two pointers and traverse the original string and check the script of words individually. Words are defined as contiguous letters:
let str = "بِاسْمِكَ رَبِّي وَضَعْتُ جَنْبِي، وَبِكَ أَرْفَعُهُ، فَإِنْ أَمْسَكْتَ نَفْسِي فَارْحَمْهَا، وَإِنْ أَرْسَلْتَهَا فَاحْفَظْهَا، بِمَا تَحْفَظُ بِهِ عِبَادَكَ الصَّالِحِينَ.Bismika rabbee wadaAAtu janbee wabika arfaAAuh, fa-in amsakta nafsee farhamha, wa-in arsaltaha fahfathha bima tahfathu bihi AAibadakas-saliheen. In Your name my Lord, I lie down and in Your name I rise, so if You should take my soul then have mercy upon it, and if You should return my soul then protect it in the manner You do so with Your righteous servants."
let tagger = NLTagger(tagSchemes: [.script])
var i = str.startIndex
var dictionary = [String: String]()
var lastScript = "glyphs"
while i < str.endIndex {
var j = i
while j < str.endIndex,
CharacterSet.letters.inverted.isSuperset(of: CharacterSet(charactersIn: String(str[j]))) {
j = str.index(after: j)
}
if i != j { dictionary[lastScript, default: ""] += str[i..<j] }
if j < str.endIndex { i = j } else { break }
while j < str.endIndex,
CharacterSet.letters.isSuperset(of: CharacterSet(charactersIn: String(str[j]))) {
j = str.index(after: j)
}
let tempo = String(str[i..<j])
tagger.string = tempo
let res = tagger.tag(at: tempo.startIndex, unit: .word, scheme: .script)
if let s = res.0?.rawValue {
lastScript = s
dictionary[s, default: ""] += dictionary["glyphs", default: ""] + tempo
dictionary.removeValue(forKey: "glyphs")
}
else { dictionary["other", default: ""] += tempo }
i = j
}
You can use the NaturalLanguageTagger as answered by #ielyamani but the only limitation is that it is iOS 12+
If you are trying to do this on earlier iOS versions, you can take a look at NSCharacterSet
You can create your own characterset to check whether a string has english characters and numbers
extension String {
func containsLatinCharacters() -> Bool {
var charSet = NSCharacterSet(charactersInString: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
charSet = charSet.invertedSet
let range = (self as NSString).rangeOfCharacterFromSet(charSet)
if range.location != NSNotFound {
return false
}
return true
}
}
Another option is to use the charactersets already available:
let nonLatinString = string.trimmingCharacters(in: .alphanumerics)//symbols will still get through
let latinString = string.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)//symbols and non-latin characters wont get through
With these you can get the strings you want quite easily. But if these are not good enough, you can look to create your own characterset, use union, intersect etc to filter out the wanted and the unwanted characters.
Step 1:
You have to split whole string into an array by "." as I can see there are "." between sentence.
Step 2:
Pass each sentence to determine its language and append into different string.
Final Code
//add in your viewController
enum Language : String {
case arabic = "ar"
case english = "en"
}
override func viewDidLoad() {
super.viewDidLoad()
//make array of string
let kalmaArray = "بِاسْمِكَ رَبِّي وَضَعْتُ جَنْبِي، وَبِكَ أَرْفَعُهُ، فَإِنْ أَمْسَكْتَ نَفْسِي فَارْحَمْهَا، وَإِنْ أَرْسَلْتَهَا فَاحْفَظْهَا، بِمَا تَحْفَظُ بِهِ عِبَادَكَ الصَّالِحِينَ.Bismika rabbee wadaAAtu janbee wabika arfaAAuh, fa-in amsakta nafsee farhamha, wa-in arsaltaha fahfathha bima tahfathu bihi AAibadakas-saliheen. In Your name my Lord, I lie down and in Your name I rise, so if You should take my soul then have mercy upon it, and if You should return my soul then protect it in the manner You do so with Your righteous servants.".components(separatedBy: ".")
splitInLanguages(kalmaArray: kalmaArray)
}
private func splitInLanguages(kalmaArray: [String]){
var englishText = ""
var arabicText = ""
for kalma in kalmaArray {
if kalma.count > 0 {
if let language = NSLinguisticTagger.dominantLanguage(for: kalma) {
switch language {
case Language.arabic.rawValue:
arabicText.append(kalma)
arabicText.append(".")
break
default: // English
englishText.append(kalma)
englishText.append(".")
break
}
} else {
print("Unknown language")
}
}
}
debugPrint("Arabic: ", arabicText)
debugPrint("English: ", englishText)
}
I hope it will help you to split the string in two language. Let me know if you are still having any issue.
The Apple documentation suggests using CNPostalAddressFormatter to display addresses as a formatted string, which helps immensely with internationalization.
This code:
let postalString = CNPostalAddressFormatter.string(from: postalAddress.value, style: .mailingAddress)
yields this work address for the John Appleseed contact on the simulator:
3494 Kuhl Avenue
Atlanta GA 30303
The address is missing the comma between city and state.
Is there a way for CNPostalAddressFormatter to insert the comma? The documentation doesn't list anything.
If not, doesn't this mean you must manually format addresses for each locality, and are unable to use CNPostalAddressFormatter for localization?
Here is my snippet to add "," after city for US addresses.
#if canImport(Contacts)
import Contacts
public extension CLPlacemark {
/// Get a formatted address.
var formattedAddress: String? {
guard let postalAddress = postalAddress else {
return nil
}
let updatedPostalAddress: CNPostalAddress
if postalAddress.isoCountryCode == "US" {
// add "," after city name
let mutablePostalAddress = postalAddress.mutableCopy() as! CNMutablePostalAddress
mutablePostalAddress.city += ","
updatedPostalAddress = mutablePostalAddress
} else {
updatedPostalAddress = postalAddress
}
return CNPostalAddressFormatter.string(from: updatedPostalAddress, style: .mailingAddress)
}
}
#endif
I am reading data from a firebase DB and storing it in a message object, How can I then access each element in that array? i.e how can I use the City string as I wish to assign that to a label. Same with each other element in the array.
firebaseDB.collection("user").document(key).collection("address").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
self.dataArr.removeAll()
for document in querySnapshot!.documents {
//print("\(document.documentID) => \(document.data())")
let msgdata = document.data() as! [String:Any]
var msgObj = Details()
if let city = msgdata["city"] as? String {
msgObj.city = city
}
if let country = msgdata["country"] as? String {
msgObj.country = country
}
if let county = msgdata["county"] as? String {
msgObj.county = county
}
if let lineOne = msgdata["lineOne"] as? String {
msgObj.lineOne = lineOne
}
if let lineTwo = msgdata["lineTwo"] as? String {
msgObj.lineTwo = lineTwo
}
if let postCode = msgdata["postCode"] as? String {
msgObj.postCode = postCode
}
self.dataArr.append(msgObj)
}
}
}
I will need to access each element as I have another function which will take each element and place it on a label in my ViewController
Something like this is what I wish to have
func DisplayAddress(){
city.text = city
postCode.text = postCode
}
I may be totally reading the question wrong but in trying to read into your question, I think the terminology may be where clarification is needed; Object vs Array
An object has properties - lets examine the Details() object
var msgObj = Details()
which contains address information for one user. So conceptually this is how it would be represented in FireStore
users
this_user
address
city: "some city"
country: "some country"
county: "some county"
line1: "line one"
the 'documents' are the items stored within the address collection
city: "some city"
country: "some country"
county: "some county"
line1: "line one"
and your Details() object has properties that correspond to those documents and stores them as properties within the object; city, county etc
msgObj.city = city
msgObj.country = country
On the other hand, an array contains a series of objects, not properties. e.g. an array would generally not contain city, country etc, but it would contain a series of Details() objects and each of those Detail() objects has it's properties of city, country etc. For example, suppose you want to work with addresses of several different users - you would create a Details() object for each user, which contains their address information and append each one to an array.
self.dataArry[0] = the Details() objects of one user
self.dataArry[1] = the Details() object of another user
self.dataArry[2] = the Details() object of a third user
You could then, for example, display the users within a certain radius of this user, or send them all an email etc.
To answer your question, if you are working with a single users address information there is no need for an array, you can simply store it as a single Details() object variable within the class.
class ViewController: UIViewController {
var myUserAddress = Details()
func to get this users address documents from FireStore {
if let city = msgdata["city"] as? String {
self.myUserAddress.city = city
}
if let country = msgdata["country"] as? String {
self.myUserAddress.country = country
}
//remember that the properties are only valid from here on
//as FireStore is asychronous
self.DisplayCity()
self.DisplayLocation()
}
//and then later on when you want to display those properties
func DisplayCity() {
let city = self.myUserAddress.city
print(city)
}
func DisplayLocation() {
let lon = self.myUserAddress.logitude
let lat = self.myUserAddress.latitude
//show the location on a map via lon & lat
}
I'm attempting to use NSDataDetector to addresses from a string. I've taken a look at NSHipster's article on NSDataDetector as well as Apple's NSDataDetector documentation. I've got the following method to the point where it'll pull addresses out of a string:
func getAddress(from dataString: String) -> [String] {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.address.rawValue)
let matches = detector.matches(in: dataString, options: [], range: NSRange(location: 0, length: dataString.utf16.count))
var addressArray = [String]()
// put matches into array of Strings
for match in matches {
let address = (dataString as NSString).substring(with: match.range)
addressArray.append(address)
}
return addressArray
}
I'd like to pull out elements of addresses, not the entire address. In NSHipster's NSDataDetector post in the Data Detector Match Types section, I see address components such as NSTextCheckingCityKey, NSTextCheckingStateKey, and NSTextCheckingZIPKey. I'm unable to use those keys in the NSDataDetector's initialization.
I dug around on GitHub to see if I could find an example to crib from, but the only stuff I'm able to find is Objective-C code or declarative stuff in the master Swift repo.
I'm 99% sure I can pull out the individual components of an address, but I'm too dumb to figure it out. Thank you for reading. I welcome suggestions.
I haven't used this class before, but it looks like it returns objects of type NSTextCheckingResult. If you get a result of type NSTextCheckingTypeAddress then you can ask the result for it's addressComponents, which will be a dictionary containing the different parts of the address.
EDIT:
Here is some working playground code I just banged out:
import UIKit
var string = "Now is the time for all good programmers to babble incoherently.\n" +
"Now is the time for all good programmers to babble incoherently.\n" +
"Now is the time for all good programmers to babble incoherently.\n" +
"123 Elm Street\n" +
"Daton, OH 45404\n" +
"Now is the time for all good programmers to babble incoherently.\n" +
"2152 E Street NE\n" +
"Washington, DC 20001"
let results = getAddress(from: string)
print("matched \(results.count) addresses")
for result in results {
let city = result[NSTextCheckingCityKey] ?? ""
print("address dict = \(result).")
print(" City = \"\(city)\"")
}
func getAddress(from dataString: String) -> [[String: String]] {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.address.rawValue)
let matches = detector.matches(in: dataString, options: [], range: NSRange(location: 0, length: dataString.utf16.count))
var resultsArray = [[String: String]]()
// put matches into array of Strings
for match in matches {
if match.resultType == .address,
let components = match.addressComponents {
resultsArray.append(components)
} else {
print("no components found")
}
}
return resultsArray
}
This code prints:
matched 2 addresses
address dict = ["Street": "123 Elm Street", "ZIP": "45404", "City": "Daton", "State": "OH"].
City = "Daton"
address dict = ["Street": "2152 E Street NE", "ZIP": "20001", "City": "Washington", "State": "DC"].
City = "Washington"
You can easily extract all addresses, URLs and phone numbers using NSDataDetector.
Swift 4.2
let string = "This is an address PO Box 7775, San Francisco, CA. This is a url
http:/
www.swiftdevcenter.com/. This is second url: https://www.google.com/. This is
mobile number
+18987656789. This is a date 01/26/2019"
let detectorType: NSTextCheckingResult.CheckingType = [.address, .phoneNumber,
.link, .date]
do {
let detector = try NSDataDetector(types: detectorType.rawValue)
let results = detector.matches(in: string, options: [], range:
NSRange(location: 0, length: string.utf16.count))
for result in results {
if let range = Range(result.range, in: string) {
let matchResult = string[range]
print("result: \(matchResult), range: \(result.range)")
}
}
} catch {
print("handle error")
}
For address use only
let detectorType: NSTextCheckingResult.CheckingType = [.address]
Credits: http://www.swiftdevcenter.com/how-to-detect-url-address-phone-number-and-dates-using-nsdatadetector/
var LabelAQI = "-"
var LabelExtraInfo = "-"
var arrAQI = [NSString]()
var AQI1:NSString = "55a"
var AQI2:NSString = "95a"
var AQI3:NSString = "66"
var AQI4:NSString = "25"
var AQI5:NSString = "88b"
var AQI6:NSString = "#"
arrAQI[0...5] = [AQI1, AQI2, AQI3, AQI4, AQI5, AQI6]
Using Swift, I'm getting AQI (Air Quality Index) data from a website in the form of NSString. AQI1 to AQI6 is essentially AQI data for every hour (e.g. 12am to 5am, I'm having up to AQI24 in the actual code though). "#" means data is not yet available. The integer is the AQI, and sometimes there may be associated extra information represented by "a"/"b" with the integers which means "Carbon Monoxide present"/"Sulphur Dioxide present". What I'm trying to do is to:
1) Display the last integer from arrAQI (excluding "#", and not displaying "a"/"b", which is "88") on LabelAQI
2) Display "Carbon Monoxide present"/"Sulphur Dioxide present" on LabelExtraInfo if there is "a"/"b" with the last integer from arrAQI, if not, leave LabelExtraInfo.text = "-"
I'm only a few weeks into programming. Can anybody help with this? Is there a better way to do what I want to do? Thank you so much in advance.
It usually helps to break the problem down into multiple functions. Here, you need to do two things – process the string, extracting a number and possible extra info. Then, loop over the array of strings until you find a suitable entry.
First the string processing. Given a string, you want to return either a pair (the reading and the extra info), or “not found”. Whenever you need to return something or not found, an optional is a good choice. So you want a function that takes a string and returns an optional (Int,String) pair:
func extractData(input: String) -> (Int,String)?
But you could go one step further and define an enum to represent the different bits of extra info:
enum AirQualityInfo {
case CarbonMonoxide,
SulphurDioxide,
NoInfo
}
func extractData(input: String) -> (Int,AirQualityInfo)?
This way, all your nasty string processing is contained within extractData.
Within that function, you want to check for a known trailing character, then strip that off, and if what remains is a number, return the two values. But if what remains isn’t a number, return nil:
func extractData(input: String) -> (Int,AirQualityInfo)? {
let stripped: String
let extraInfo: AirQualityInfo
switch last(input) {
case .Some("a"):
stripped = dropLast(input)
extraInfo = .CarbonMonoxide
case .Some("b"):
stripped = dropLast(input)
extraInfo = .SulphurDioxide
default:
stripped = input
extraInfo = .NoInfo
}
return stripped.toInt().map { ($0,extraInfo) }
}
You could, as others have suggested, use regular expressions for this, but personally I think this is overkill given your data parsing needs are so specific.
Once you have this function, you can loop over the array in reverse order, checking each value using the function, until you find a valid value:
for idx in reverse(indices(arrAQI)) {
if let (index, extraInfo) = extractData(arrAQI[idx]) {
LabelAQI = toString(index)
switch extraInfo {
case .CarbonMonoxide:
LabelExtraInfo = "Carbon Monoxide Present"
case .SulphurDioxide:
LabelExtraInfo = "SulphurDioxide Present"
case .NoInfo:
LabelExtraInfo = "-"
}
// stop going around the loop
break
}
}
You could also factor out that conversion of the string out further as well:
extension AirQualityInfo {
var displayString: String {
switch self {
case .CarbonMonoxide:
return "Carbon Monoxide Present"
case .SulphurDioxide:
return "SulphurDioxide Present"
case .NoInfo:
return "-"
}
}
}
for idx in reverse(indices(arrAQI)) {
if let (index, extraInfo) = extractData(arrAQI[idx]) {
LabelAQI = toString(index)
LabelExtraInfo = extraInfo.displayString
break
}
}
Finally, if you’re feeling super-adventurous, you could write a function that does that finding and mapping operation in one shot:
func findSome<C: CollectionType, T>
(source: C, match: C.Generator.Element -> T?)
-> T? {
for element in source {
if let x = match(element) {
return x
}
}
return nil
}
if let (index, extraInfo) = findSome(reverse(arrAQI),extractData) {
LabelAQI = toString(index)
LabelExtraInfo = extraInfo.displayString
}
By the way, a few other Swift tips: it’s generally better to use String rather than NSString unless you have a specific need for something to be an NSString (which it doesn’t look like here); you don’t have to name the types when declaring them – you can write let str = "hello" rather than let str: String = "hello" which tends to make code look a little cleaner and easier to read; and it’s best to use let rather than var unless you explicitly need to change (“mutate”) the value later in the code… so given all that, here’s how you could declare your original array:
let AQI1 = "55a"
let AQI2 = "95a"
let AQI3 = "66"
let AQI4 = "25"
let AQI5 = "88b"
let AQI6 = "#"
let arrAQI = [AQI1,AQI2,AQI3,AQI4,AQI5,AQI6,]
This could by done by exploding string into array of objects, and then check each of them:
var LabelAQI = "-"
var LabelExtraInfo = "-"
var stringFromServer = "55a 95a 66 25 88b #"
var objectsSeparated = stringFromServer.componentsSeparatedByString(" ")
for object in objectsSeparated
{
if object.hasSuffix("a")
{
LabelAQI = object.substringToIndex(object.endIndex.predecessor())
LabelExtraInfo = "Carbon Monoxide present"
}
else if object.hasSuffix("b")
{
LabelAQI = object.substringToIndex(object.endIndex.predecessor())
LabelExtraInfo = "Sulphur Dioxide present"
}
else if object.hasSuffix("#")
{
LabelAQI = object.substringToIndex(object.endIndex.predecessor())
LabelExtraInfo = "-"
}
else
{
LabelAQI = object
LabelExtraInfo = "-"
}
}
UPDATE
To find the last object, that doesn't have '#' suffix you could use following code:
var LabelAQI = "-"
var LabelExtraInfo = "-"
var stringFromServer = "55a 95a 66 25 88b #"
var objectsSeparated = stringFromServer.componentsSeparatedByString(" ")
var index = objectsSeparated.count
while 0 >= index
{
var object = objectsSeparated[index]
index--
if object.hasSuffix("#")
{
//this object has '#' suffix -> skip it
continue
}
else
{
//found non '#' object
if object.hasSuffix("a")
{
LabelAQI = object.substringToIndex(object.endIndex.predecessor())
LabelExtraInfo = "Carbon Monoxide present"
}
else if object.hasSuffix("b")
{
LabelAQI = object.substringToIndex(object.endIndex.predecessor())
LabelExtraInfo = "Sulphur Dioxide present"
}
else
{
LabelAQI = object
LabelExtraInfo = "-"
}
break
}
}
There are a number of ways you could do this, here's one suggestion using NSScanner:
var LabelAQI = "-"
var LabelExtraInfo = "-"
var arrAQI = [NSString]()
let AQI1:NSString = "55a", AQI2 = "95a", AQI3 = "66", AQI4 = "25", AQI5 = "88b", AQI6 = "#"
arrAQI.extend([AQI1, AQI2, AQI3, AQI4, AQI5, AQI6])
for AQI in reverse(arrAQI) {
if AQI != "#" {
var str:NSString?
let numberSet = NSCharacterSet.decimalDigitCharacterSet()
let letterSet = NSCharacterSet(charactersInString: "ab")
let scanner = NSScanner(string: AQI as String)
if scanner.scanCharactersFromSet(numberSet, intoString:&str) {
LabelAQI = str as! String
if scanner.scanCharactersFromSet(letterSet, intoString: &str) {
switch str as! String {
case "a":
LabelExtraInfo = "Carbon Monoxide present"
case "b":
LabelExtraInfo = "Sulphur Dioxide present"
default:
break
}
}
break
}
}
}
LabelAQI // "88"
LabelExtraInfo // "Sulphur Dioxide present"
I first of all reverse the array and loop through it for the last non-hash symbol entry. I then scan the string first for a number and second for the letters a/b using NSCharacterSet searches. Once found we can break out of the loop and all is done.
Note: this line of code arrAQI[0...5] = [AQI1, AQI2, AQI3, AQI4, AQI5, AQI6] didn't work, so I replaced it with arrAQI.extend([AQI1, AQI2, AQI3, AQI4, AQI5, AQI6]).
Using regular expressions, you can easily get the numbers out of a string. You probably want to do something along these lines:
let regex = NSRegularExpression(pattern: "[\a-z\s]", options: nil, error: nil)
let aq1value = regex?.stringByReplacingMatchesInString(arrAQI[0], options: nil, range: NSMakeRange(0, count(arrAQI[0])), withTemplate: "")
This will take your string and remove all alphabetic characters, leaving you with only numbers. You can then cast the string as an integer as you wish!