RSS is a well known web feed format, usually used to publish different informations frequently changed, such as blog entries and headlines. It’s one form of web syndication where a site make its data available to other sites to use, through a standard format, commonly XML.
In this tutorial, you will make sweettutos.com content available for use outside the blog in your iOS project.
Basically, you will grab the RSS feed of the blog and then display it on the app within a UITableView to read its content in details. More topics will be covered along the way like xml parsing, self-sizing cell and AutoLayout to ensure your project UI will run smoothly on all devices and orientations 🙂
Open up Xcode, go to File/New/Project menu, then select Single View Application template from the iOS/Application section, name the project “RSSFeedReader” and make sure Swift is the selected language, choose a location where to save the project then click the Create button.
Storyboard vs nib
I am not a big fan of Storyboard since it was announced in iOS5, although it was improved a lot in the recent versions of Xcode, I still prefer to get rid of it for nib files as they provide me with more freedom customising animations and transitions. Besides, I like to manage UI views separately instead of grabbing them into one single file. So in this tutorial, we gotta remove storyboard and work with nibs to get your hands dirty understanding how all is put together 😉
Let’s start by removing the storyboard file. From the project navigator, right click Main.storyboard and select Delete. A new window will show up, select Move to Trash to completely remove the file from your project.
If you try to run the app, it will end up with a beautiful crash:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Could not find a storyboard named 'Main' in bundle NSBundle
This is obvious since your project now doesn’t have a starting screen to show after the app finish launching. To fix this, we need to tell the app to show another screen other than the storyboard file you just removed. Stop the project and select AppDelegate.swift from the project navigator.
Basically, you will instantiate a new root view controller and set it as the root for the app window. To do so, first declare a property for the view controller below the AppDelegate class name:
var viewController: ViewController?
Then place the following code inside application(_:didFinishLaunchingWithOptions:) function (before the return):
self.window = UIWindow(frame:UIScreen.mainScreen().bounds) self.viewController = ViewController(nibName:"ViewController",bundle:nil) var navController = UINavigationController(rootViewController: self.viewController!) navController.navigationBarHidden = true self.window?.rootViewController = navController self.window?.makeKeyAndVisible()
Basically, after drawing the window frames, the code above instantiates the ViewController and embed it in a navigation controller, then sets the navigation controller as the root view of the window.
Note: Embed the view inside the navigation controller is important because we will push and pop controllers later on to show the entry details of the feed.
Let’s create the nib file named ‘ViewController’ which you used above. Select File/New/File from the menu and choose View from the iOS/User Interface section, name it ViewController and click the create button.
You will notice a new file called ViewController.xib is now added in the project navigator, select it to open in the canvas, select the File’s Owner and then set its custom class to ViewController.
Switch to the Connections inspector and drag a line from the view outlet to the view in the canvas to bind it to the controller.
Last remaining action, to completely replace the storyboard file, is to delete the Main storyboard file from the property list file. Expand the Supporting Files folder from the project navigator and select the Info.plist file, then clear the value for the key “Main storyboard file base name”.
Run the app, you should see a white screen after the app finishes launching. Now, you are going to add few UI objects to the first screen, basically, a top bar and a UITableView in which the feed entries will be displayed.
Select ViewController.xib file, and from the object library, drag a UIView. Select the Size Inspector in the right, and set its x and y position to 0, also make sure its width is equal to 600 and its height is equal to 50. Select the Attributes inspector, change the background color to something other than the default white color.
Switch to the assistant editor from the editor selector in the main window toolbar, you will notice Xcode editor is split in two. Select the preview assistant editor and choose ViewController.xib as the file to preview.
Xcode will made a preview of how your screen will look like in different screen sizes, you can add more screen sizes by selecting the “+” icon in the left bottom of the preview assistant editor.
Try to switch the 4.7 inch or 5.5 inch screen to landscape orientation (you can do that by hovering over the screen and selecting the arrow icon at the bottom). You may notice the top bar is not fulling the whole width of the screen on landscape orientation, this is because you hardcoded the width of the bar to be 600px , which is less than the 5.5 inch landscape width, hence the unwanted white space on the right.
This is gonna be fixed with some autolayout constraints 🙂
Switch back to the Standard editor and select the top bar view in the canvas, then select the Pin menu from the Auto Layout menu in the bottom right of the canvas. First, activate the top, leading and trailing constraints and set them to 0. Also, make sure the height constraint is checked and set to 50.
Basically, the constraints you added above allow to make the top/right/left spacing between the view and its nearest neighbor (in this case the superview) fixed to 0, so that the view will be expanded in width automatically to fit its new dimension when the orientation get changed. Also, the fixed height constraint of 50 is important to keep the top bar height the same on different screens.
If you try to visualise the screen again in the preview editor, you will notice the top bar fits correctly in the landscape orientation 🙂
Now drag a UILabel from the object library to the canvas, make sure it’s placed above the top bar view you added previously, the label should be a subview of the top bar when displayed from the document outline.
From the Size inspector, make sure to change the x to 280, the y to 25, width to 41 and height to 21. Also, change the label text to “Titles” from the attributes inspector.
Switch to the preview editor, you may notice the label doesn’t get centered in the top bar in all screen sizes and orientations, so you need to add some constraints in order to establish a relationship between the label and its superview (the top bar).
To do so, first select the label, then select the Align menu, check the “Horizontal Center in Container” constraint and click the Add Constraint button in the bottom of the Align menu.
Second, select the Pin menu, make sure the top spacing is 25px then activate it and select the Add Constraint button in the bottom of the Pin menu.
Now the label is horizontally centered in all screen sizes and orientations.
To finish up with this screen, drag a UITableView from the object library to the view, and from the size inspector, change its position and dimensions to x=0, y=50, width=600 and height=550. Finally, select the Pin menu, make sure the four spacing edges constraints are activated and set to 0 like below and click the Add Constraint button to apply the constraints.
This way, the UITableView will always fit the whole space in the screen below the top bar.
Note: As a best practise, you should apply the Auto Layout constraints incrementally like we do so far, it means to work on one object at a time and ensure it’s displaying correctly on all screens and/or orientations before adding the next object to the canvas.
Before you finish with this screen, let’s connect the UITableView to its datasource and delegate. Select the UITableView in the canvas, then select the Connections Inspector from the Utilities area. Drag a line from the datasource Outlet to the File’s owner in the Document outline area in the left and do the same with the delegate Outlet.
Last think is to connect the UITableView to the code. To do so, switch to the Assistant editor and make sure the ViewController.swift file is displayed on the split editor along with the ViewController.xib.
Ctrl drag from the UITableView in the canvas to the code (below the class name).
Name the outlet “titlesTableView” and click the Connect button.
You are done with the Titles screen, now time to build the Details screen where the entry details will be loaded. Basically, you will reproduce almost the same steps you have done previously, except that the Details screen will contain a UIWebView to load the details of the entry being selected in the Titles screen and a UIActivityIndicatorView to show while the web view is loading the entry url.
Since it’s not important to go through the same steps again for the sake of this tutorial, I prepared the project with the Details screen ready to go, download it here before you continue reading 🙂
Open up the downloaded project, the details screen is called DetailsViewController, it has four main objects hooked up to the code: The back button, the top bar title label, the activity indicator and the web view.
Now it’s time for some code to make all this work together 🙂
As I mention in the beginning of this tutorial, you will be parsing the RSS as an XML feed. Hopefully, iOS provide a native XML parser called NSXMLParser which we will be using here.
The XML feed of the blog is composed of common elements like title, description, url, pubDate and many other. Here, we need to display the title and description of each feed entry and store them along with the url to send to the next view in order to be loaded in a UIWebView.
NSXMLParser delegate will notify the view controller of several events while it’s parsing the document, example of events is when the parser starts traversing the document, or when founding characters or reaching the end of the document. You will make the view controller conform to the NSXMLParser delegate in order to be able to react to such events by implementing some NSXMLParserDelegate protocol methods.
Select ViewController.swift from the project navigator to open it in the editor, edit the class declaration with the following:
class ViewController: UIViewController, NSXMLParserDelegate {
Now your view controller conform to NSXMLParserDelegate. Later on, you will implement some protocol methods to react to different xml parsing events.
Some variables to store parsed data are required, place the following code right below the class name:
var xmlParser: NSXMLParser! var entryTitle: String! var entryDescription: String! var entryLink: String! var currentParsedElement:String! = String() var entryDictionary: [String:String]! = Dictionary() var entriesArray:[Dictionary]! = Array()
The first variable is the xml parser property itself which will handle all of the parsing work, the rest are properties to store parsed title, description and url and a dictionary to store all of them in a key/value collection. entriesArray will store all entries dictionaries.
Move to the viewDidLoad function and place the following code right below super.viewDidLoad() :
//1 self.titlesTableView.estimatedRowHeight = 40.0 //2 var urlString = NSURL(string: "http://sweettutos.com/wp-rss.php") var rssUrlRequest:NSURLRequest = NSURLRequest(URL:urlString!) let queue:NSOperationQueue = NSOperationQueue() NSURLConnection.sendAsynchronousRequest(rssUrlRequest, queue: queue) { (response, data, error) -> Void in //3 self.xmlParser = NSXMLParser(data: data) self.xmlParser.delegate = self self.xmlParser.parse() }
Let’s explain what you did above:
//1 You tell the table view object to put an estimated row height of 40, instead of a fixed height. This is important especially if your cell content may vary, which is our case for sure.
//2 You defined a url string with sweettutos.com RSS url and assigned it to a request object, then you defined an NSOperationQueue object which will serve for NSURLConnection to fetch the url asynchronously 🙂
//3 This is the callback of the NSURLConnection.sendAsynchronousRequest API, After the data is fetched correctly, you need to assign it in the xml parser object instantiation and call the parse() API to start the XML parsing adventure. You can test the error value for potential networking failure handling if you want to.
Next, you need to implement some NSXMLParserDelegate protocol methods to handle parsing events, this is important in order to monitor the parsing work as it goes.
Place the following implementations somewhere in the ViewController.swift file before the closing bracket.
//1 func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName: String!, attributes attributeDict: [NSObject : AnyObject]!){ if elementName == "title"{ entryTitle = String() currentParsedElement = "title" } if elementName == "description"{ entryDescription = String() currentParsedElement = "description" } if elementName == "link"{ entryLink = String() currentParsedElement = "link" } } //2 func parser(parser: NSXMLParser!, foundCharacters string: String!){ if currentParsedElement == "title"{ entryTitle = entryTitle + string } if currentParsedElement == "description"{ entryDescription = entryDescription + string } if currentParsedElement == "link"{ entryLink = entryLink + string } } //3 func parser(parser: NSXMLParser!, didEndElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!){ if elementName == "title"{ entryDictionary["title"] = entryTitle } if elementName == "link"{ entryDictionary["link"] = entryLink } if elementName == "description"{ entryDictionary["description"] = entryDescription entriesArray.append(entryDictionary) } } //4 func parserDidEndDocument(parser: NSXMLParser!){ dispatch_async(dispatch_get_main_queue(), { () -> Void in self.titlesTableView.reloadData() }) }
Let’s explain what the above functions do in details:
1// This function will be called each time the parser starts a new element in the XML feed. You use this function to catch which element is parsed at the call time, and store the value in currentParsedElement property, which in turn serve as a checker in the foundCharacters function.
2// Based on the checker property -currentParsedElement- you will append the characters to its relevant property. This function is called the most, since it’s fired on each parsed character. It’s like telling the parser: Hey parser, if you are currently traversing the title element, then append the current character to entryTitle property. Same for the description and url.
3// The didEndElement function will notify the parser object each time it reach the end of an element in the XML feed. At that time, you stored the content of the appended property from the previous function in the dictionary. Since the description is the third element in the RSS feed for each entry in the channel, you added the dictionary of each element to the array when the parser finish parsing the description element.
4// Once the document parsing is completed, time to display the data in the table view, you call the reloadData API from the main thread since the parsing was started from within a background thread in the sendAsynchronousRequest API used previously.
That’s all for parsing work, all XML will be parsed and saved in the array of dictionary as you did above.
Now time to take care of the table view cells, place the following datasource functions following the code you implemented above:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{ var cell:UITableViewCell! = tableView.dequeueReusableCellWithIdentifier("cellIdentifier")as? UITableViewCell if (nil == cell){ cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "cellIdentifier") } cell!.textLabel?.text = entriesArray[indexPath.row]["title"] cell!.textLabel?.numberOfLines = 0 cell!.detailTextLabel?.text = entriesArray[indexPath.row]["description"] cell!.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator return cell } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return entriesArray.count }
The datasource functions implementations are straightforward, one thing I want to point out is the numberOfLines API which you called for the cell’s textLabel property on cellForRowAtIndexPath function above, setting this to 0 is very important to make the cell’s textLabel do some self-sizing of the content on different lines.
Let’s finish up with this class by implementing one important UITableViewDelegate method, which will be used to trigger the cell click and hence fire the navigation to the next details screen.
Paste the following method implementation to do so:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){ let detailsVC = DetailsViewController(nibName: "DetailsViewController", bundle: nil) detailsVC.entryUrl = entriesArray[indexPath.row]["link"] detailsVC.entryTitle = entriesArray[indexPath.row]["title"] self.navigationController?.pushViewController(detailsVC, animated: true) }
Here you just make an instance of DetailsViewController class and set two of its properties values, then you pushed the instantiated view controller to the navigation stack.
You will get two error messages for entryUrl and entryTitle properties. Never mind, you will add them right now 🙂
Select DetailsViewController.swift to open it in the editor, first, make the class conform to UIWebViewDelegate:
class DetailsViewController: UIViewController, UIWebViewDelegate{
Then, declare the entryUrl and entryTitle, below the class name, to shoot down the ugly errors you got previously:
var entryUrl:String! var entryTitle:String!
Later, implement the following code in the viewDidLoad function, under the super.ViewDidLoad call:
entryTitleLabel.text = entryTitle var url:NSURL = NSURL(string: entryUrl)! self.webView.loadRequest(NSURLRequest(URL:url)) self.webView.delegate = self activityIndicatorView.startAnimating()
Basically, you just assigned the entryTitle value to the top bar label to show it when the details view controller is loaded, then you load the url in the web view with an activity indicator loading in the screen.
Now, quickly add the back button action code to go back to the previous view controller. Place the following code inside the “backAction” action function:
self.navigationController?.popToRootViewControllerAnimated(true)
Last thing before you run the project, you need to implement one UIWebViewDelegate protocol method in order to be notified when the url is finished loading, at that time, you should dismiss the activity indicator from the screen.
Place the following code before the class closing bracket
func webViewDidFinishLoad(webView: UIWebView){ activityIndicatorView.stopAnimating() activityIndicatorView.hidden = true }
That’s it, run the project and enjoy your RSS feed parser 🙂
Here is the completed project. Feel free to join the discussion below and leave a comment. If you feel this tutorial has helped you, please consider sharing it!
Discover more from SweetTutos
Subscribe to get the latest posts sent to your email.