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!
Instead of loading the actual webpage is there a way to make it just load the text of the article?
Hi James ๐
Yes you can load only text. UIWebView can load static html instead of a url using the loadHTMLString API. Basically you will pass the RSS content element instead of the url to render it in the web view. I modified the project for you to illustrate how to do so and I commented the old code so you learn what was changed etc ๐ download the modified project here: http://sweettutos.com/wp-content/uploads/2015/04/RSSFeedReader_static_html_loading.zip
Wow! Thanks!
Hello tmalek, this tutorial is awesome thank so much for sharing this information, my RSS file that i am using as a base has a CDATA Tag, how can i extract the information within this tags? This is the example.
Todo lo que querรญas saber acerca de: Jimena Sรกnchez
http://www.kalot.com.mx/2015/04/05/todo-lo-que-querias-saber-acerca-de-jimena-sanchez/
http://www.kalot.com.mx/2015/04/05/todo-lo-que-querias-saber-acerca-de-jimena-sanchez/#comments
Mon, 06 Apr 2015 02:29:53 +0000
http://www.kalot.com.mx/?p=20986
<![CDATA[
Podrรก haber decenas de mujeres involucradas en la conducciรณn deportiva desde hace un par de dรฉcadas y eso que en anteriormente era poco comรบn que el gรฉnero femenino se viese involucrado en este tipo de trabajo. Esto por varias razones: principalmente por el poco o nulo interรฉs de las fรฉminas en cuanto a la narraciรณn […] The post Todo lo que querรญas saber acerca de: Jimena Sรกnchez appeared first on Kalot | Conoce todo sobre todo.
]]>
<![CDATA[
Podrรก haber decenas de mujeres involucradas en la conducciรณn deportiva desde hace un par de dรฉcadas y eso que en anteriormente era poco comรบn que el gรฉnero femenino se viese involucrado en este tipo de trabajo. Esto por varias razones: principalmente por el poco
]]>
<![CDATA[
o nulo interรฉs de las fรฉminas en cuanto a la narraciรณn de las actividades diarias deportivas, la estereotipaciรณn de la mujer con respecto al tema e inclusive la estigmatizaciรณn de que la mujer pudiese saber, conocer u opinar acerca de las diferentes disciplinas que conlleva el deporte.
Esto cambiรณ hasta mediados de los 80โs que se comenzรณ a abrir el panorama para las conductoras femeninas de este ramo de la noticia y damos gracias a quienquiera que fuese la precursora en este รกmbito porque gracias a ello conocemos a Jimena Sรกnchez
]]>
<![CDATA[
. Como la misma Jimena nos cuenta en su carta de presentaciรณn para
]]>
How can i manage to read those tags to be presented within the reader i have read something regarding the use of a method of the parser but i do not know how to implent it.
Can you help me?
Hi ๐
By default, the content represented in a CDATA section is ignored by the parser. If you need to parse it anyway, you can implement the delegate function – parser:foundCDATA:
More infos https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSXMLParserDelegate_Protocol/#//apple_ref/occ/intfm/NSXMLParserDelegate/parser:foundCDATA:
Love the tutorial. Just wondering how to add a “Loading” Pop up screen. When I put my RSS feed in the app that loads the text instead of the website it never loads. What am I doing wrongs. Both work great with the rss feed from your site.
how to I allow the user to scroll up/down to load the next set of headlines ? email me at timbojill@gmail.com. Dont come here that often.
I need a little help. I am trying to create a RSS Feed App for my High School Journalism class website. They have three different feeds to pick from. I want to have the app load a view controller with three images. When the user clicks on each image they will be taken to there respective RSS feeds. Just wondering how to acoomplish this in swift.
Hi ๐
You can make two screens, the first handle the user selection for the feed, and the second will load the selected feed. You basically have to move all RSS parsing code to the second screen controller for this and go from there.
I need a little help here. I am only 15. Trying to create this for my high school Journalism class. I I created a Single View Application in xcode 6.3. I setup the home page the way I wanted. There are three buttons. When the user clicks on eith buttons it will take them to different feed. Will I have to copy the Detailed view three times ? lol
You are almost there, you just need to get the selected feed in the detail screen (no need to duplicate your views and code). So from the home page, you have to pass the selected feed name or url to the details screen, and from there (from the details screen) you will fetch the rss and parse it accordingly.
You can pass data between controllers within the segue object. Learn more here :https://developer.apple.com/library/ios/recipes/xcode_help-IB_storyboard/chapters/StoryboardSegue.html
I hope my answer was detailed for you ๐
Still working on my app. I was wondering how do I pull the
first picture/image from each article in the RSS feed so that it shows up in the home page/View Controller. Similar to the NY Times app? Sample feed: http://viconsortium.com/feed/.
Hi ๐
It depends whether your blog articles have images. I will assume each blog entry has a main picture, in this case, you need to fetch the tag name of the image and download the image then set it to the UITableViewCell imageView property. Downloading images can be done in the cellForRowAtIndexPath data source method. More info about the cell properties here: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewCell_Class/#//apple_ref/occ/instp/UITableViewCell/imageView
Here is the feed I am try to include the picture in the detailsview: viconsortium.com/feed/. I didn’t setup the feed. Will the person have to add the image field before I can test it ?
Kind of, yes. You need to know which tag is made for the image to be able to extract its content (the image url) easily in the NSXMLParser delegate protocol methods.
I am an IOS newbie so bear with me. Some articles in the RSS Feed I am using have an IMG tag and some don’t. So to start do I add a variable (var entryPic: String!). The goal is to add a small picture for each article for each cell.
So I’m using Xcode 7 and when I ran the completed project (I just downloaded yours) with your URL, it crashed saying it unexpectedly found nil when unwrapping an Optional value. But when I put in my URL which is the iTunes new applications rss (iTunes.apple.com/us/rss/newapplications/limit=100/xml) it did not crash. However instead of showing me the 100 newest applications on the store, it showed me an empty table view.
Please help me out here! I don’t know what the problem is! I’ve used online rss readers on my computer and used this url before (the iTunes one not yours) and it shows results like expected but it won’t do it in an app!
I’ve tried every different tutorials (which pretty much all do the same thing) like Geeky Lemon’s, App Coda’s and now yours and none of them work! Please help me out here!
Here is an updated project for Xcode 7 (Swift 2), and guess what ? It’s now working with your rss feed url as well ๐ http://sweettutos.com/wp-content/uploads/2015/07/RSSFeedReader-5.zip
I have changed the project code a little bit to adapt it to the rss feed structure of the iTunes url you provided.
The app was crashing because the ATS was blocking the url request. So I added the following tags to the info.plist file to disable the ATS:
NSAppTransportSecurity
NSAllowsArbitraryLoads
More info about ATS here https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/
Enjoy ๐
When i try to build this in Xcode 7 for iOS 9, it tells me that sendAsynchronousRequest has been deprecated and that i need to use dataTaskWithRequest. Got an update with the proper code change?
I need some help my fellow Swettutos. I used this app as the base and found a tutorial to add an image field. the images come out very blurry. Is there a way to fix it ?
Hi Malek_T ๐
I now tried to finish several Swift RSS Tutorials.. yours is the first one which worked, thank you for that! Nevertheless I have one (hopefully small) problem:
If I change the URL of the RSS Feed, I am not getting any content at all. Even no titles. Do you know, how I could solve this problem?
Best,
Nader
Hi Nader,
This is because the feed you use may not have the same tags as the feed I use. Try to run the url of the feed on the browser and see what are the relevant tags you want to use while the NSXMLParser is traversing the feed. Once you identify the tags, just change the names accordingly in your code.
Cheers
Hi Malek
I have downloaded the latest version as mentioned in the comments below and have changed the URL to http://www.chrisharrisracing.com/feed
When I launch it it says Build Succeeded, however on launch there is no content shown, just a screen saying Titles, any ideas
Hi Did you get this working for your RSS feed, as I can’t figure this out
Did you sort this out as I am trying to use this as well
Hi Malek
I have used the above process in parsing down the xml feeds and populating the data on the tableview. But at times my app is crashing. I am recieving this error : โincorrect checksum for freed objectโโโobject was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debugโ
Please help me out