The Blog describes an end to end development of IOS App using Gateway Services. Many developers would like to try out new features provided by SAP or would like to install their own version of ABAP Trail version - You can do so following the step by step by step procedure described in
https://sap.github.io/cloud-s4ext/week-1/unit-6/
This blogs describes in great detail on the steps required to install your personal SAP ABAP 7.5 System.
When this is done you have fully functional Netweaver ABAP 7.5 Version. - For Developing the Odata Service we will set up the eclipse based tools provided by SAP which is described in
https://sap.github.io/cloud-s4ext/week-1/unit-5/ (step 5)
If you are new to Eclipse based system and/or gateway usage try out the first part of
https://sap.github.io/cloud-s4ext/week-1/unit-5/#step-5-install-and-configure-sap-tools-for-eclipse (step 5) and then first part of the
https://sap.github.io/cloud-s4ext/week-2/unit-2/
When this done you are ready to develop the Odata Service required for the Mobile Application Development for IOS using Swift. It is assumed the you have configured the Eclipse to connect to your SAP System.
- Start the Eclipse Development system - Select the Package that you have imported - From the menu choose File - New - Othe - Data Def- Give the name of the odata service and description and Finish,
- copy the code shown below - The line @OData.publish: true will ensure that the odata service is created on the backend - after activation - Make sure that you active and test this on the eclipse by choosing the run button - you should see the products listing@AbapCatalog.sqlViewName: 'ZN_VW_PRODUCTS'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'products without annotations'
@OData.publish: true@ObjectModel: {
createEnabled: true,
updateEnabled: true,
deleteEnabled: true
}
define view Zn_Sample_Demo_Products as select from sepm_iproduct as Products
association [1..*] to Z_Sample_Demo_Soli as _SOItems on $projection.productuuid = _SOItems.ProductID
association [1..1] to sepm_iproductt as _ProductT on $projection.productuuid = _ProductT.productuuid
and _ProductT.language = 'E'
association [1..1] to sepm_ibupa as _Supplier on $projection.supplieruuid = _Supplier.businesspartneruuid
{
key Products.productuuid,
Products.product as ProductID,
Products.producttype as TypeCode,
Products.productcategory as Category,
_ProductT.productname as Name,
'EN' as NameLanguage,
_ProductT.productdescription as Description,
'EN' as DescriptionLanguage,
_Supplier.businesspartner as SupplierID,
_Supplier.companyname as SupplierName,
Products.productvalueaddedtax as TaxTarifCode,
Products.productbaseunit as MeasureUnit,
Products.weight as WeightMeasure,
Products.weightunit as WeightUnit,
Products.currency as CurrencyCode,
Products.price as Price,
Products.width as Width,
Products.depth as Depth,
Products.height as Height,
Products.dimensionunit as DimUnit,
Products.creationdatetime as CreatedAt,
Products.lastchangeddatetime as ChangedAt,
concat(concat('/sap/public/bc/nwdemo_model/IMAGES/',Products.product),'.jpg') as PictureUrl,
Products.supplieruuid,
_SOItems
}
3) Go to the backend login and go to Tcode /IWFND/MAINT_SERVICE - Choose Add Service and select Zn_Sample_Demo_Products. Then Select this service and click on the SAP Gateway Client and Select Execute button - to see the XML metadata - Change the url
/sap/opu/odata/sap/ZN_SAMPLE_DEMO_PRODUCTS_CDS/Zn_Sample_Demo_Products/?format=json and execute it you will the json representation of the data - this is what we will be using in the IOS SWIFT 3 which we will develop
4) IOS Mobile Application Development using SWIFT 3 - Open Xcode - Create New Xcode Project - Choose Master/Detail Application Template - Next button - For the ProductName - Next and then create to create the Project.- Close the Xcode Project.
5) Open an terminal session at the folder where the project root was created and if you have installed cocopods run the command
sudo gem install cocoapods
6) At the command promopt run the command
pod init - This will create an .Podfile which should be changed using vi Podfile and make sure the following lines are present -
platform :ios, '10.0'
target 'SapProducts' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
pod 'Alamofire', '~> 4.4'
# Pods for SapProducts
end
This will ensure Alamofie 4.4 library which we will use to parse is loaded into the project.- Then at the command line type the command
pod install - This will create a .xcworkspace file which will used from here on to develop the ios mobile application. In the Finder double click on the ..xcworkspace to open in Xcode.
7) Initially I looked at very informative blog
https://blogs.sap.com/2016/11/10/build-an-ios-app-with-swift-3.0-hcpms-and-odata-rest/ - this worked with Odata from cloud(northwind odata from the cloud) but not from SAP Odata Service directly from SAP - as we face clients who are not on the cloud I describe here the the process for this parsing.
8) go to the SAP tcode /IWFND/MAIN_SERVICE - Select the Service and choose SAP GateWay Client and in the Result URI enter the following url /sap/opu/odata/sap/ZN_SAMPLE_DEMO_PRODUCTS_CDS/Zn_Sample_Demo_Products/?format=json and click execute - select and copy the response into clipboard and go the website
http://www.json4swift.com/ and paste the json contents - Click generate - This will generate the swift classes required for parsing the data.- The zip files will be downloaded - unzip it.
(see the images below for the list of files generated )
9) Go to Xcode and add these Swift files into the project ( File - Add files to SapProducts) - select all the files and copy into the project - Build the project (Product - Build) - You need to resolve any complie errors in these added classes which are generated. Change all ints and double to strings other wise these values are not parsed.
10) In order to call REST Service you need to add NSApplicationSecruity to info.Plist - Select this file and open this with Source View ( control click and select source view) and add the following before the final dict <key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
If this is not added you will not able to invoke the SAP Server. Additional options can be configured for security if required.
11) Select the MasterViewController and Cell - Give it enough room to add and imageview and lables for ProductID, Name, priice, currency and description as shown in the image
12) Add a new Swift Class - File - Add New (cocoaTouch Class) Give it a name ProductTabelCell - Associate this class with Prototype cell in the identity inspector.
13) Select Assistant Editor and drag and drop outlets and give it name - make sure the association with the swift file is correct( if required choose Manual and select the the swift file that was created)
14) in the story board you need to drop a segue from the cell to the detailed view controller.
15) Add Lables that you need to display in the Product deatails page - I have added labels for Product ID, Name, Description, Dimensions and Supplier Name.
16) The code for the MasterviewController is as follows
import UIKit
import Alamofire
import Foundation
class MasterViewController: UITableViewController {
var detailViewController: DetailViewController? = nil
var objects = [Results]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// self.navigationItem.leftBarButtonItem = self.editButtonItem
//
// let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
// self.navigationItem.rightBarButtonItem = addButton
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
override func viewWillAppear(_ animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
super.viewWillAppear(animated)
var url = "
http://vhcalnplci.dummy.nodomain:8000/sap/opu/odata/SAP/ZN_SAMPLE_DEMO_PRODUCTS_CDS/Zn_Sample_Demo_P..."
let user = "DEVELOPER"
let password = "Appl1ance"
let credentialData = "\(user):\(password)".data(using: String.Encoding.utf8)!
let base64Credentials = credentialData.base64EncodedString(options: [])
let headers = ["Authorization": "Basic \(base64Credentials)"]
Alamofire.request(url,
method: .get,
parameters: nil,
encoding: URLEncoding.default,
headers:headers)
.validate()
.responseJSON { response in
if response.result.value != nil{
//print(response)
do {
if let data = response.data {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary
let json4Swift_Base = Json4Swift_Base(dictionary: json!)
if let results = json4Swift_Base?.d?.results {
self.objects = results
self.tableView.reloadData()
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}else{
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// func insertNewObject(_ sender: Any) {
// objects.insert(NSDate(), at: 0)
// let indexPath = IndexPath(row: 0, section: 0)
// self.tableView.insertRows(at: [indexPath], with: .automatic)
// }
// MARK: - Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let object = objects[indexPath.row] as! Results
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.title = object.productID
controller.SelObj.removeAll()
controller.SelObj.append(object)
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ProductTableCell
cell.productPriceLable.text = "$ " + objects[indexPath.row].price!
cell.productNameLabel.text = objects[indexPath.row].name
cell.currencyLable.text = objects[indexPath.row].currencyCode
cell.detLabel.text = objects[indexPath.row].description
let url = URL(string: "
http://vhcalnplci.dummy.nodomain:8000" + objects[indexPath.row].pictureUrl! as! String)
//print(url)
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
if data != nil {
cell.productImage.image = UIImage(data:data!)
}else{
//cell.imgView.image = UIImage(named: "default.png")
}
}
}
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
objects.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
}
17) The code for the detailedview controller is as follows
Import UIKit
class DetailViewController: UIViewController {
var SelObj = [Results]()
@IBOutlet weak var supplierLabel: UILabel!
@IBOutlet weak var dimensionLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!
@IBOutlet weak var fullnameLabel: UILabel!
@IBOutlet weak var NameLabel: UILabel!
func configureView() {
// Update the user interface for the detail item.
// if let detail = self.detailItem {
if SelObj.count > 0 {
NameLabel.text = SelObj[0].productID
fullnameLabel.text = SelObj[0].name
descriptionLabel.text = SelObj[0].description
supplierLabel.text = SelObj[0].supplierName
dimensionLabel.text = SelObj[0].height! + " X " + SelObj[0].width! + " X " + SelObj[0].depth!
}
// }
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.configureView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var detailItem: NSDate? {
didSet {
// Update the view.
self.configureView()
}
}
}