Build an iOS app with Swift 3.0, SAP Cloud Platform Mobile Services and OData REST
[Updated February 28, 2017: Rebranded “SAP HANA Cloud Platform” to “SAP Cloud Platform”]
Purpose of this blog
There are various blogs and resources describing how to build iOS applications with the Swift language, how to implement SAP Cloud Platform mobile services, and using OData REST services. However, after some research, I found no blog describing the full development process end-to-end using all three topics. This blog tries to fill that gap.
Does this blog already cover the announced SAP Cloud Platform SDK for iOS?
No.
Both SAP Fiori for iOS design components as well as the SAP Cloud Platform SDK for iOS are still in development and not available yet.
This blog covers development using ‘current’ available technologies, and therefore has one significant restriction:
- True OData support is not covered. I expect this to be available in the upcoming SAP Cloud Platform SDK for iOS.
Requirements
In order to create the application yourself using this blog, you need at least the following:
- Mac computer running macOS 10.11.4 (El Capitan) or higher
- Xcode 8 development environment (Swift 3.0 is included in Xcode 8)
- Trial account on SAP Cloud Platform
Part 1 – Setting up SAP Cloud Platform, mobile services
- Login to you SAP Cloud Platform account cockpit: https://account.hanatrial.ondemand.com/cockpit/
- Navigate to Services > Mobile Services and click the Development & Operations tile:
- If it’s not yet enabled, enable it.
- Click the link Go to Service.
- The SAP Cloud Platform, mobile services administration cockpit will now open
- Navigate to Applications and click the Create Application button
- Specify a unique Application ID (you will need these later) and Name.
Tick the Ignore Case for User Name checkbox
Set Security Configuration to None
- Click the Save button to finalise.
- In the now created Application, navigate to Back End
- For the purpose of this blog, we connect to the freely accessible and well-known Northwind OData service.
- The Application is now configured on SAP Cloud Platform Mobile Services. You can check the connection to the back-end URL by navigating to the Application Overview page, and click the Ping button:
Part 2 – Create the iOS application skeleton
- Start Xcode on your Mac, and choose Create a new Xcode Project
- Select Single View Application and click Next
- Specify a Product Name (this is the name of your application) and Organization Identifier (i.e. package name).
Make sure Language is set to Swift, and Devices is set to Universal (this ensures your app will run on both iPhone and iPad, on all screen sizes)
- Specify a location to store your app.
Out of habit, I always tick the Create Git Repository for version control; make it your habit too 😉
- Your single view iOS app is now created, and ready for development
Part 3 – Create the Table View layout
Most, if not all, application layout design is done via the app’s Storyboard. A Storyboard is the visual representation of your app’s user interface, screen, content, and navigation flow.
- In the Project Navigator (the ‘folder’ icon in the left pane), select Main.storyboard
- Select the View Controller in the Storyboard, and delete it.
- From the Object Library in the bottom of the right pane, drag a Table View Controller to the Storyboard. Your Storyboard should now resemble the following:
- We now have a view, but not the actual implementing controller behind it. From the menu, select File > New > File… and select Cocoa Touch Class.
- Set Class to Categories, and Subclass of to UITableViewController. You will notice Class is now renamed to CategoriesTableViewController. Also, make sure Language is set to Swift
- Click Next, make sure it is saved inside your project, and click Create.
- In the Storyboard, select the Table View Controller, and from the Identity Inspector at the top of the right pane, set the Custom Class to the just created CategoriesTableViewController class.
- With the Table View Controller selected, switch from the Identity Inspector to the Attributes Inspector and tick the Is Initial View Controller checkbox. This will make the view the starting point for the application’s Storyboard.
- For convenience, we also create a custom controller class for the table’s cells. From the menu, select File > New > File… and select Cocoa Touch Class.
- Set Class to Categories, and Subclass of to UITableViewCell. You will notice Class is now renamed to CategoriesTableViewCell. Also, make sure Language is set to Swift
- Click Next, make sure it is saved inside your project, and click Create.
- In the Storyboard, select the Table View Cell, and from the Identity Inspector at the top of the right pane, set the Custom Class to the just created CategoriesTableViewCell class.
Part 4 – Create the Table View Cell layout
Now, we will design the table’s cell layout. For the purpose of this blog, we are going to query the Northwind’s Category entityset, and we want to display both the Category Name and Description in the cell. To accommodate these details, we need to adjust the table cell height, and add the controls to the table cell which will display these properties.
- Increase the Row Height of both the Table View:
as well as the Table View Cell:
to 80 pixels. - Set the Identifier of the Table View Cell to CategoryCell:
This identifier is needed later in our code. - From the Object Library, drag two Label controls to the Table View Cell, one on top of the other.
Make them span the whole width of the table cell margin guides, and increase the top label font size to 24.0.
Your Table View Cell should now resemble the following:
- Click the Assistant Editor button in the top-right corner of Xcode’s window. The Assistant Editor for the Storyboard now opens on the right. Switch the Assistant Editor from Automatic to Manual, and point to the CategoriesTableViewCell.swift class
- In the Storyboard, select the top label in the table cell, and Ctrl-drag it just below the line class CategoriesTableViewCell: UITableViewCell {
- In the dialog, set Name to nameLabel and click Connect.
A line of code should now be added:
@IBOutlet weak var nameLabel: UILabel! - Do the same for the lower label; set Name to descriptionLabel.
- Your CategoriesTableViewCell.swift class should now look like this:
// // CategoriesTableViewCell.swift // HCPmsDemo // // Created by Robin van het Hof on 09/11/2016. // Copyright © 2016 Robin van het Hof. All rights reserved. // import UIKit class CategoriesTableViewCell: UITableViewCell { // MARK: - Properties @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var descriptionLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } }
Part 5 – Connect to SAP Cloud Platform Mobile Services datasource
If you made it this far, and click the Run button, your app should run successfully, but doesn’t show anything:
Of course, this makes perfect sense, since we didn’t create any connectivity to our datasource on SAP Cloud Platform Mobile Services.
In this part of the blog, we create the connection using an open-source project called Alamofire. (We could do it using the standard networking libraries in Swift, but that would require a bit more coding. Besides, as developers, we don’t want to re-invent the wheel)
To include Alamofire to our project, we use Cocoapods, a dependency manager for Swift / Cocoa projects (think of Cocoapods as the Cocoa equivalent to Java’s Maven and Javascript’s npm, Browserify or Bower)
- If you haven’t already installed Cocoapods, simply run the following command from Terminal:
sudo gem install cocoapods
- Close Xcode using Cmd-Q. It is important to have it fully closed before continuing!
- In Terminal, change directory to your project root directory
- Run the command:
pod init
- This command will create a file Podfile. Open this file with a text editor (Sublime, Atom, any text editor without formatting)
- Uncomment the line:
platform :ios, '9.0'
- Under
# Pods for HCPTableApp
add the line
pod 'Alamofire', '4.0.1'
Your Podfile should now resemble the following:
# Uncomment the next line to define a global platform for your project platform :ios, '9.0' target 'HCPmsDemo' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # Pods for HCPmsDemo pod 'Alamofire', '4.0.1' end
- In Terminal, run the command
pod install
- It will now add a dependency to your project for Alamofire. You will also note it will add (among other files) a <Project Name>.xcworkspace file. From now on, always use the workspace file instead of the project file to edit your project!
- In Finder, double-click the file <Project Name>.xcworkspace to open your Xcode project workspace.
- Edit the file CategoriesTableViewController.swift and just below the line import UIKit, add a line import Alamofire
- It will now show a build error:
Simply press Cmd-B to resolve the dependencies, and the error should be removed. - Since we will consume the Category entityset of the OData service exposed through SAP Cloud Platform Mobile Services, it is convenient to create a simple class to hold this data.
From the menu, choose File > New > File… and select Swift File - Save it as Category, and click Create
- Open the just created Category.swift file, and replace the line
import Foundation
with
import UIKit
- Add two properties name and description, and implement the required init function. Your code should be like this:
import UIKit class Category { // MARK: - Properties var name: String var description: String // MARK: - Initialization init(name: String, description: String) { self.name = name self.description = description } }
- Switch back to the file CategoriesTableViewController.swift, and just below the line class CategoriesTableViewController: UITableViewController { add the line:
var categories = [Category]()
This will hold the returned data from the OData service into an array of Category objects (the class we created earlier)
- Change the following function to return the size of the just defined categories array:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return categories.count }
- Change the following function to return 1 section instead of 0:
override func numberOfSections(in tableView: UITableView) -> Int { return 1 }
- Change the following function to return the CategoriesTableViewCell, and set the cell’s control text properties to the equivalent Category properties:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell", for: indexPath) as! CategoriesTableViewCell let category = categories[indexPath.row] cell.nameLabel.text = category.name cell.descriptionLabel.text = category.description return cell }
- Now, create an empty function loadCategories, and call that function in the overridden function viewDidLoad(). Your code should now look like this:
import UIKit import Alamofire class CategoriesTableViewController: UITableViewController { var categories = [Category]() override func viewDidLoad() { super.viewDidLoad() loadCategories() } func loadCategories() { //TODO: Implement! } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return categories.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell", for: indexPath) as! CategoriesTableViewCell let category = categories[indexPath.row] cell.nameLabel.text = category.name cell.descriptionLabel.text = category.description return cell } }
- The last step is to implement the just created function loadCategories.
I won’t go into too much details here (it is described in detail here), but what needs to happen is the following:- The unique Device ID is needed to register on Cloud Platform Mobile Services
- As extra properties, we also capture the Device Type and Name
- HTTP Headers are defined:
- Authorization header with basic authentication
- Content-Type is set to JSON
- X-SMP-APPCID is required, and should contain the Device ID
- A Parameters array is defined for the Device Type and Name
- A POST request will be performed to set up the connection and register the device to Cloud Platform Mobile Services, using the aforementioned HTTP Headers. The Parameters array is sent as payload in the request.
- If connected successfully, we can now perform the actual GET request to retrieve the Categories entityset.
- The categories array will be filled with the retrieved Category objects, and the table is reloaded to display the retrieved data.
- The complete loadCategories function implementation is:
func loadCategories() { let deviceId = UIDevice.current.identifierForVendor!.uuidString let deviceType = UIDevice.current.model let deviceName = UIDevice.current.name let urlConnection = "https://hcpms-s0007138856trial.hanatrial.ondemand.com/odata/applications/latest/nl.qualiture.swift.demo/Connections" let urlService = "https://hcpms-s0007138856trial.hanatrial.ondemand.com/nl.qualiture.swift.demo" let httpHeaders : HTTPHeaders = [ "Authorization" : "Basic czA<...replace with your own string...>LUy4=", "Content-Type" : "application/json", "X-SMP-APPCID" : deviceId ] let parameters: Parameters = [ "DeviceType": deviceType, "UserName" : deviceName ] Alamofire.request(urlConnection, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: httpHeaders).responseString { response in let statusCode = response.response?.statusCode Alamofire.request(urlService + "/Categories", method: .get, headers: httpHeaders).responseJSON { (response) -> Void in let jsonValue = response.result.value as! NSDictionary if let categoryArray = jsonValue["value"] as? [NSDictionary] { for categoryObj in categoryArray { let name: String = categoryObj.value(forKey: "CategoryName") as! String let description: String = categoryObj.value(forKey: "Description") as! String let category: Category = Category(name: name, description: description) self.categories.append(category) self.tableView.reloadData() } } } } }
Part 6 – Run the application
- If you now run the application, everything should now go as planned, and you will be presented the following:
- Whoooohoo, data is retrieved!! However, as you see, the table doesn’t have a nice header, and the top table cell is slightly under the phone’s top bar. To solve this, select the CategoriesTableViewController and from the top menu, select Editor > Embed in > Navigation Controller. Your storyboard should now look like the following:
- Select the Navigation Item, and change the Title to Categories.
- Run the application again, it it should now look a lot nicer:
- If you now go back to SAP Cloud Platform Mobile Services administration cockpit, and navigate to Registrations and Users, you will now see the device properly registered to the defined Application ID on SAP Cloud Platform Mobile Services:
Final words
I hope this blog would enable you to jumpstart iOS Swift development using SAP Cloud Platform. There is a lot more to discover — for instance, next to the Single View Application we used, there is also a Master Detail Application template — and hopefully you will be up to speed once the SAP Fiori for iOS or SAP Cloud Platform SDK for iOS are available in 2017.
Until then… happy coding!
Robin!!! thank you for sharing this blog. It is very helpful and informative pal. what is theapproximate time of configuration vs actual developing? as a new developer in the ios world... I am seeing myself struggling more on the finding where stuff is .. so I was just curious on your experience.
Thank you again for your share!
Hi Sergio!
Actually, configuring HANA Cloud Platform, mobile services is pretty straightforward; didn't cost me more than 5-10 minutes.
If you already have some Xcode knowledge, then you will be up to speed in no-time. If Xcode and/or Swift is new, I can recommend follow Apple's Swift Essentials and subsequent 'walkthrough' tutorial where you get to know everything about creating an iOS app using Swift.
Another very good one is Stanford's free Developing iOS 9 Apps with Swift classes via iTunes University.
But really, Swift is a truly elegant language, and if you're familiar with Java and/or C, you will get up to speed with Swift in no time as well 🙂
However, even if you're unexperienced, when following this blog you could be ready end-to end within 30-40 minutes
thanks Robin, yes i am new w Swift and I have seen that apple's tutorial.. i am close to getting my first swift app going 🙂 thank you again, will report my findings soon! 🙂
Hi Robin,
excellent blog, thanks for sharing this piece. I had done some XCode iOS development before, even with cocoaPods, so managed to get everything created rather quickly. However I do not get any data into the app. Changed the URLs provided and the base64 encoded username:password (assuming this is username and password for the HCP trial). Any tips how to analyze the problem into more detail?
Thanks,
Alex
Hi Alexander,
One thing I always try is to use Postman / ARC to test the POST request (here's the request I fired using Chrome app ARC)

If the connection above is OK, and the subsequent GET request returns results, I would then try and set a breakpoint in your code and debug the response of the
Alamofire.request
call.Hope this helps a bit!
Hi Robin,
thanks for taking the time to answer back. ARC was a good suggestion. My requests were working there so I already know that on the HCP side everything seems to be fine.
Debugging the code in XCode I can see that the array gets published with the data from the HCP backend. What is not working though is the display of the data in the Table View. It seems it is not updated after the data has been received.
Regards,
Alex
Hi Alexander,
Did you perform a reload of the table? In my step 22, the last line in the
Alamofire.request
closure reloads the table:self.tableView.reloadData()
Also, make sure you implemented the two overridden
tableView
functions in step 22.Hope this helps!
Hi Robin,
thanks again. I had checked the code from step 22 a number of times and it was fine. The issue was actually the code foroverride func numberOfSections where my code was still returning 0 instead of 1. Fixed and working as expected now.
Thanks a lot for sharing the tutorial on this, Already looking forward to an updated version of this thing based on the iOS HCP SDK for connecting to HCP once it becomes available next year.
Regards,
Alex
Hi Alexander,
Glad it worked eventually! I will update the blog so it is clear the
numberOfSections
function should be changed too implicitly (now it's just buried in the code in step 20)And yes, I am confident the upcoming SDK will make things even more easier... fingers crossed! 😉
Hi Robin,
Thanks for this wonderful & comprehensive blog. I'm looking forward to work on IOS - HANA integration.
I recently tried Android - HANA integration.
https://blogs.sap.com/2017/01/26/android-hana-database-integration/
Will surely try to implement this.
Thanks
Sai Giridhar
How to get the authorization String which I have to replace?
Hi Robin,
Thanks for this great tutorial.
I couldn’t find auth string.. Can you help? I'am getting this error: "fatal error: unexpectedly found nil while unwrapping an Optional value"
Hey Robin,
Nice to share you such a wonderful information, But ,may be its very old , I need some more information from you as I m a iOS developer and new in SAP.
Thank you robin,
Himanshu Patel
9624648633
Hi Himanshu,
You are right, this is indeed a really old blog, written 5 months before the first version of the SAP Cloud Platform SDK for iOS went life...
Since an app created using the SAP Cloud Platform SDK for iOS Assistant has the mandatory requirement to be connected to SAP Cloud Platform Mobile Services, it will inevitable use the login mechanism configured for your platform (either SAP Cloud Platform default IdM, or your own IdM provider)
However, you may be able to develop your own login screen using the SAPFioriFlows framework. See https://help.sap.com/doc/978e4f6c968c4cc5a30f9d324aa4b1d7/Latest/en-US/Documents/Frameworks/SAPFioriFlows/index.html for the API docs
Hope this helps!