I will be explaining how to create an XML parser, parse a simple XML document from URL and convert it into objects in Swift 3. Since I have been learning iOS lately and have not found a clear answer with a good explanation, I will do it myself here.
Let’s start by describing our XML structure. Imagine that we have a web service that will return a list of houses and their locations as a result. The XML could in this case look like this:
There are three steps that we need to do in order to finish our task – get contents of URL, parse the structure using our XML parser, create a custom House class and save custom objects.
1. Get contents of URL
There is a lot of ways to parse XML content from specific URL. I’m going to describe a very, very simple way that does not require a lot of coding. There is a class called XMLParser that will let you parse the URL without having to create an URLSession, or anything similar. All you have to do is specify an URL and create a new object of this class.
var parser = XMLParser() override func viewDidLoad() { super.viewDidLoad() let urlString = URL(string: "http://your.address.here/file.php?parameter=value") self.parser = XMLParser(contentsOf: urlString!)! self.parser.delegate = self let success:Bool = self.parser.parse() if success { print("success") } else { print("parse failure!") } }
As you might notice, we already used the delegate parameter, which will connect the parser with the class. In the second step, we will use the methods from XMLParserDelegate class, but before we do, you have to implement this class into yours by doing this:
class ViewController: UIViewController, XMLParserDelegate { ... }
2. Create XML Parser & Parse the XML structure
So far we have parsed the contents of URL address and set up our class in order to use the XMLParserDelegate. Now we have to implement its functions and specify their functionality. We will implement four functions:
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { } func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { } func parser(_ parser: XMLParser, foundCharacters string: String) { } func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) { }
The first function (didStartElement) is called whenever the XML Parser came across a new element in the content. For example, it would be called anytime an element <house> is found. The second function (didEndElement) is called whenever any element is closed, f.e. </house>. The third function (foundCharacters) is called when any characters are found between these two tags. And the last function is called when a parsing error has occured.
We do not care too much about the second and third function, because we do not really need it. The way we are going to do it is that anytime we come across the element <house> we get its children and create an object of our custom House class. The fourth function will serve us only for printing out the error that has occurred, so it will look like this:
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) { print("failure error: ", parseError) }
The first function is the most important. All the magic will happen inside of it. We do not need to parse the Result element, as all we care about is Houses. So we will compare the elementName parameter of the function to “House”. The function also provides us with another parameter – attributeDict. It is a collection (of 2D type [String:String]) of the element’s children. Looping through them and checking their element names is a way to go.
if(elementName=="House") { for string in attributeDict { switch string.key { case "id": ... break case "name": ... break case "location": ... break default: break } } }
That is all for this step. What to do next – create a custom House class.
3. Create a custom House class
In order to be able to save every house and work with it, we will create its custom class, from which we will create a new object for each house.
class House { var id:Int = 0 var name:String = "" var location:String = "" }
You might create your constructor and other functions, but this is all we need for now.
4. Save custom objects
var housearray = [House]() func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { if(elementName=="House") { let house = House() for string in attributeDict { let strvalue = string.value as NSString switch string.key { case "id": house.id = strvalue.integerValue break case "name": house.name = strvalue as String break case "location": house.location = strvalue as String break default: break } } housearray.append(house) } }
And that is all. You should now be able to parse any XML document. All you have to do is to modify the function above based on your requirements.
For more articles from iOS category, proceed here.
Hi Vladimir,
Firstly, Thank you for this working, it’s soo useful. I’m use for my project. Everythings okey but “self.parser.delegate = self” not working. This code line Error is “Cannot assign value of type ‘selectedTableViewController’ to type ‘XMLParserDelegate?'” What is the problem resource ? And I m reading xml file internal directory.
I want to parse one item in a regular ViewController without using TableView or an array
How to do that..Waiting for your kind reply:)
Hello Aisha, one way to do this would be to simply use XML as a text – you parse the result of website/webservice as text, and then look for your element using regex, for example “\[A-Za-z0-9]{0,}\” … Another way would be not using the for loop in step 4 but only getting first entry of the attributeDict and then getting its key and value:
Schoolboy error – my XML was using child nodes and not attributes – switched that over and all’s working fine! Thanks again for the quick replies and the excellent article!
No worries, thanks for the nice words 🙂
Thanks for the quick reply! I’m still confused about the use of the array (housearray in this example) that is created.
I’ve managed to load my own xml successfully (as I can see that the housearray.count changes when I update the xml – so I know it’s picking up my main nodes).
Can I somehow extract say the name value in housearray[0]? Xcode autofills housearray[0].name (once I’ve typed ‘housearray[0].n’ ) but trying to print this value shows nothing?
Many thanks again
W
My advice would be to foreach the whole array and print out anything you can about it (position in array, house name, ID and location, etc). Usually something like this should reveal where your problem is. Could it for example be that your house on index 0 has an empty string as a name?
Simpleton question here but how, once we’ve populated the array class, do we retrieve a value from a particular array?
i.e. how do I print the location of (in your small xml example) house with id=2 – do I treat it like a multidimensional array – housearray[1][3]?
Not really. You still consider it a uni-dimensional array, so you loop through it and get properties of each of the elements in the array. If you look at the last code snippet of part 2 of the tutorial, you will see the example of extraction of the properties of each House (the elementName==”Bus” is not right, it’s a typo and I’m going to fix it soon). We loop through each string in attributeDict – which is a dictionary of all houses – and then for each of them we get the value of string.key; therefore on place where three dots are you would write sth like this:
You can also look at the step 4 of the tutorial, where the example is filled.
I’ve debug the code from your example and find that the
attributeDict [String : String] 0 key/value pairs
Here is the code :
the “if” is executed but the “for” is not, since the attributeDict is 0 value, any idea?
It all depends on the structure of your XML document. The for loop goes through every child of your ‘items’ element/array, although if children of your ‘items’ are not key-value pairs, but for example another arrays, then the ‘attributeDict’ size will be 0.
Can you post a short example of your XML structure?
It’s similar like your example, the xml like this :
I’m not sure about the case-sensitivity of XML when parsing using my parser. Try to replace
if(elementName==”items”)
with upper-case string:
if(elementName==”Items”)
Do the same for item_id, item_name and item_qty. Does it work now?
it doesn’t works, appears the next error “Use of unresolved identifier ‘string’ “, this is happening in the next block of code:
What line exactly? Are you sure you use Swift 3, not some older version?
yes, I am sure, I am using Swift 3, the error appears in the first function (didStartElement), in the next line of code:
let strvalue = string.value as NSString
Thanks for pointing out the problem here Johan. I found the problem – when I wrote the article, I accidentally swapped two lines with each other. The line ‘let strvalue = string.value as NSString’ is supposed to be located inside of the ‘for string in attributeDict’ loop. The reason you got that error is because the variable strvalue was defined with something that was not initialized yet (the variable ‘string’). I updated the post, can you try it now and let me know if it works?
Thank you very much Vladimir, now it’s working perfectly. I started working with swift recently, so I’m learning little by little, At the moment I’m working on a project, to read XML files, and use their information to make decisions within swift.But I had not found information on how to do it until I met your post,So thank you so much for taking the time to do this post, and also the time to answer my question, I appreciate it.
I would like to know, if at any time I have another question, could you help me?
Youre more than welcome. Im not that good in swift myself too. Every time I come across something that gives me a hard time and I manage to figure it out, I write it on my website in order to let other people with the same problem know about my solution. Although, if you have any problem and need another mind to take a look at it, you can leave me a message by clicking on Contact button attached to the left side of screen. Hopefully, I will be able to help 🙂