Swift / XMLParser / How to parse a specific element / CheatyXML - ios

I want to parse and download the current EUR - USD exchange rate. I have descided to get the value from the European Central Bank Feed.
I'm using the CheatyXML XMLParser extension.
How can I get the USD value?
With the following code, I get the value: "European Central Bank". My String is an optional on porpuse. Because my app crashed like 1 trillion times during finding the correct code to get the currency rate...
let feedUrl = NSURL(string: "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml")
let parser: XMLParser! = XMLParser(contentsOfURL: feedUrl!)
let exchangeString: String? = parser["gesmes:Sender"]["gesmes:name"].string // Returns an optional String
print(exchangeString)
How do I get the value of <Cube currency="USD" ?
let blogName: String? = parser["Cube"]["Cube"].string // Returns an optional String
Is not working.
Help is very appreciated.

You need to go one level deeper (there's three "Cube" fields), then get the attributes and finally subscript with the right key, for example:
parser["Cube"]["Cube"]["Cube"].attributes["currency"] as? String // "USD"
parser["Cube"]["Cube"]["Cube"].attributes["rate"] as? String // "1.1287"

Related

How to get substring from user input?

i wrote code to get character when user enter in text field and do math with them
this :
#IBOutlet weak internal var textMeli: UITextField!
var myChar = textMeli.text
var numb = [myChar[0]*3 , myChar[1]*7]
but one is wrong
textMeli.text is a String.
myChar is a String.
You can't access a Character from a String using bracket notation.
Take a look at the documentation for the String structure.
You'll see that you can access the string's characters through the characters property. This will return a collection of Characters. Initalize a new array with the collection and you can then use bracket notation.
let string = "Foo"
let character = Array(string.characters)[0]
character will be of type Character.
You'll then need to convert the Character to some sort of number type (Float, Int, Double, etc.) to use multiplication.
Type is important in programming. Make sure you are keeping track so you know what function and properties you can use.
Off the soap box. It looks like your trying to take a string and convert it into a number. I would skip the steps of using characters. Have two text fields, one to accept the first number (as a String) and the other to accept the second number (as a String). Use a number formatter to convert your string to a number. A number formatter will return you an NSNumber. Checking out the documentation and you'll see that you can "convert" the NSNumber to any number type you want. Then you can use multiplication.
Something like this:
let firstNumberTextField: UITextField!
let secondNumberTextField: UITextField!
let numberFormatter = NumberFormatter()
let firstNumber = numberFormatter.number(from: firstNumberTextField.text!)
let secondNumber = numberFormatter.number(from: secondNumberTextField.text!)
let firstInt = firstNumber.integerValue //or whatever type of number you need
let secondInt = secondNumber.integerValue
let product = firstInt * secondInt
Dealing with Swift strings is kind of tricky because of the way they deal with Unicode and "grapheme clusters". You can't index into String objects using array syntax like that.
Swift also doesn't treat characters as interchangeable with 8 bit ints like C does, so you can't do math on characters like you're trying to do. You have to take a String and cast it to an Int type.
You could create an extension to the String class that WOULD let you use integer subscripts of strings:
extension String {
subscript (index: Int) -> String {
let first = self.startIndex
let startIndex = self.index(first, offsetBy: index)
let nextIndex = self.index(first, offsetBy: index + 1)
return self[startIndex ..< nextIndex]
}
}
And then:
let inputString = textMeli.text
let firstVal = Int(inputString[0])
let secondVal = Int(inputString[2])
and
let result = firstVal * 3 + secondVal * 7
Note that the subscript extension above is inefficient and would be a bad way to do any sort of "heavy lifting" string parsing. Each use of square bracket indexing has as bad as O(n) performance, meaning that traversing an entire string would give nearly O(n^2) performance, which is very bad.
The code above also lacks range checking or error handling. It will crash if you pass it a subscript out of range.
Note that its very strange to take multiple characters as input, then do math on the individual characters as if they are separate values. This seems like really bad user interface.
Why don't you step back from the details and tell us what you are trying to do at a higher level?

Converting a JSON into a Model Value in Swift

In my quest to learn more about Swift, I'm looking at ways to improve my app and noticed a few places where I'm making assumptions where perhaps I shouldn't be.
When creating a new object, lets say a 'student', they need things like a name (String), age (Int) and score (Float). I read these from a JSON file, and put them into an object like this:
// note, details is a [String:Any] type
let name = details["name"] as! String
let age = details["age"] as! Int
let score = Float(details["score"])
self.student = Student(name: name, tutor_group: tutor_group, score: score)
So my questions are as follows;
1. How should I modify my code to check that if a value is not a number, where it should be, the variable becomes just nil, or even better 0?
2. What if the key in the dictionary doesn't exist?
3. Are the different ways to do this, and if so, which is best practice?
Note that I want to keep this code as short as possible - if/else statements for each line are not what I'm looking for.
Thank you so much in advance!
The Solution suggested by the Swift team
Recently Apple described the suggested way to face this problem.
You can define the Student struct (or class) this way
struct Student {
let name: String
let age: Int
let score: Float
init?(json: [String:Any]) {
guard
let name = json["name"] as? String,
let age = json["age"] as? Int,
let score = json["score"] as? Float
else { return nil }
self.name = name
self.age = age
self.score = score
}
}
Benefits of this approach
You encapsulate the logic to convert a JSON into a Student inside the Student struct itself
If the JSON doesn't contain a valid data (e.g. there is no age field with a valid Int) then the initializer fails and returns nil. This means there is no way to create a Student with missing fields (and this is a good thing) and this scenario will not cause a crash.
More
The post I linked above also describes a more advanced approach where a missing field/value throws an exception. However if you don't need to log (or notify to the caller) why the initialization failed you don't need this.
So my questions are as follows; 1. How should I modify my code to check that if a value is not a number, where it should be, the variable becomes just nil, or even better 0? 2. What if the key in the dictionary doesn't exist? 3. Are the different ways to do this, and if so, which is best practice?
let age = (details["age"] as? Int) ?? 0
In all cases, age will have the type Int
If the key doesn't exist, details["age"] will return nil, as? Int will return an Int? with value nil and the nil coalescing operator ?? will set the value to 0.
If the type isn't an Int, the conditional cast as? Int will return nil and the value will be set to 0.
In the expected case, age will have the Int value that was stored in details["age"].
For the other fields:
let name = (details["name"] as? String) ?? ""
// If score is stored as a String
let score = Float(details["score"] as? String ?? "") ?? 0
OR
// If score is stored as a number
let score = (details["score"] as? Float) ?? 0
You can use guard instead:
guard let name = details["name"] as? String else {
return
}
print("\(name)")
Thanks!

Can I use iOS9 Contacts Framework to get a formatted phone number?

I would like to be able to store a phone number in a standard way, e.g. just the digits (potentially with the '+' for the country code), something like these examples...
"17185555555"
"+17185555555"
"+447788888888"
... but I'd like to be able to DISPLAY it to a user in a properly formatted way, e.g.
"1 (718) 555-5555"
"+1 (718) 555-5555"
"+44 (7788) 888888"
...WITHOUT having to rely on a CNContactViewController to format it.
Obviously doing this just for US/Canada numbers would be easy - it's just one standard format - but I'd like it to be generic so it can work for numbers from any country. I know this question gives an answer for US/Can numbers only.
I know that I could use a CNContactViewController to display the number in the correct format, e.g.
let newContact = CNMutableContact()
newContact.givenName = "John"
newContact.familyName = "Smith"
newContact.phoneNumbers = [CNLabeledValue(label: CNLabelPhoneNumberiPhone, value: CNPhoneNumber(stringValue:"17185555555"))]
let contactView = CNContactViewController(forContact: newContact)
self.presentViewController(contactView, animated: true, completion: nil)
This will show the number on screen properly formatted, i.e.
1 (718) 555-5555
... so I know that something in the framework can do it. (This approach works for other country phone number formats, as long as you prefix the number with the right country code - e.g. "+44...")
From various other questions I know that I can get the raw digits and country code out of a CNContact, e.g. (following above example)
for pNumber: CNLabeledValue in newContact.phoneNumbers {
let value = pNumber.value as! CNPhoneNumber
let cc = value.valueForKey("countryCode") as? String
let digits = value.valueForKey("digits") as? String
print("cc:" + cc + ", " + digits)
}
... but this will just show the unformatted string again - not what I am looking for in this case.
Any help or other recommended approaches really appreciated!
My answer proposes another lib
You can format your numbers with this lib.
And you can use like this:
let phoneNumber: NBPhoneNumber = try phoneUtil.parse("17185555555", defaultRegion: yourRegion)
let formattedString: String = try phoneUtil.format(phoneNumber, numberFormat: .E164)

`CountedSet` initialization issue

I'm comparing the characters contained within two words. In seeking to accomplish this, Set (aka NSSet) seemed like the way to go to accomplish this task. I've discovered it returns false positives on matches, so I am attempting to use CountedSet (aka NSCountedSet) instead.
I'm able to initialize a Set without issue, but I can't get the CountedSet initializer to work. Here's what I've done...
I start with a String:
// Let's say myTextField.text = "test"
let textFieldCharacters = myTextField.text?.characters
// word is a string from the ENABLE list of words
let wordCharacters = word.characters
Then I dump the characters into an Array:
var wordCharactersArray = [Character]()
for character in wordCharacters {
wordCharacterArray.append(character)
}
var textFieldCharactersArray = [Character]()
for character in textFieldCharacters {
wordCharacterArray.append(character)
}
Then I create a Set from the character arrays:
let textFieldSet = Set<Character>(textFieldCharactersArray)
let wordSet = Set<Character>(wordCharactersArray)
Finally, I test to see if the textFieldSet is a superSet of wordSet with the following:
textFieldSet.isSuperset(of: wordSet)
Going back to my example, if myTextField.text is "test", I'm returning values for word whose characters are a superset of the wordSet, but the counts of the individual elements don't match the character counts of myTextField.text
In researching my issue, I've found CountedSet (fka NSCountedSet), which I think would resolve my issue. It has two method signatures:
public convenience init(array: [AnyObject])
public convenience init(set: Set<NSObject>)
I've tried initializing the 2 sets of characters like so:
let textFieldSet = CountedSet(array: textFieldCharacterArray)
let wordSet = CountedSet(array: wordCharacterArray)
I get the following error for the sets
Cannot convert value of type '[Character]' to expected argument type
'[AnyObject]'.
So I tried initializing the set like this:
let textFieldSet = CountedSet(array: textFieldCharacterArray as! [AnyObject])
Which yields the following error:
'AnyObject' is not a subtype of 'Character'
I've also tried to initialize the CountedSet with a Set, per the method signature, but I get errors when I try to do that, too.
Any suggestions how to initialize a CountedSet would be greatly appreciated.
You are correct that if you need to compare not just the presents of elements but also their count, you should use CountedSet, which is a renaming of NSCountedSet for swift 3.0. The problem you are running into is CountedSet can only accept elements that are objects and Characters are not. As Eric D points out in their comment, the easies way to get around this is by mapping your [Character] to [String] which will bridge to [NSString].
You are not running into this problem using Set, because it is a native Swift collection type that initialize with elements of any type. This is why you can initialize a Set with [Character].
To see the difference:
let word = "helo"
let wordCharacters = Array(word.characters)
let wordSet = Set(wordCharacters)
let wordCharStrings = wordCharacters.map{String($0)}
let wordCountedSet = CountedSet(array: wordCharStrings)
let textField = "hello"
let textFieldCharacters = Array(textField.characters)
let textSet = Set(textFieldCharacters)
let textFieldCharStrings = textFieldCharacters.map{String($0)}
let textFieldCountedSet = CountedSet(array: textFieldCharStrings)
textFieldCountedSet.isSubset(of: wordCountedSet as! Set<NSObject>) // returns false, but if word had two or more l's it would return true
textSet.isSubset(of: wordSet) // returns true

Access Users Contacts Phone Numbers CNContacts

I am trying to get to phone numbers of my contacts using CNContact, i want to have the number as a simple string of its didgits such as "04xxxxxxxx" but the closest I can get to is the following. ("contact" is of type CNContact)
contact.phoneNumbers[0].value
\\Which prints: <CNPhoneNumber: 0x13560a550: countryCode=au, digits=04xxxxxxxx>
ive tried all the obvious things and not so obvious things, thanks
If anyone has a more legitimate solution please do post it, otherwise this rather hacky approach works:
let value = String(contact.phoneNumbers[0].value)
let start = value.rangeOfString("digits=")!.endIndex
let end = value.endIndex.predecessor()
let number = value.substringWithRange(Range<String.Index>(start: start, end: end))
Using this category you can now access the phone number via swift.
don't forget to include this file in the bridging header
#implementation CNPhoneNumber (SwiftSupport)
- (NSString*)toString {
return self.stringValue;
}
#end
Fetch the number value as follows:
let number = value.valueForKey("digits") as! String
From iOS9:
let number = value.stringValue
According to Apple's documentation on CNPhoneNumber:
stringValue
Property
The string value of the phone number. (read-only)
Declaration
SWIFT
var stringValue: String { get }

Resources