SAP Fiori for iOS meet SAP Cloud Translation API
PS: Demo app at the end. I have not used the Fiori iOS assistant out of my own curiosity to understand the things. You can refer to this wonderful blog Translation via Assistant or API Hub integration via Assistant by Jitendar to quickly get a look and feel. For knowing the new features in the SDK refer this blog What’s new in SAP Cloud Platform SDK for iOS 2.0
My own Rants
So in this blog we have created things from scratch to get a better understanding of Swift as well as how does SAP Fiori iOS SDK integrates with it. One of the most basic thing is to spend at least 2-3 weeks in understanding what Swift is, and how it works. Without a basic understanding of the underlying concept, i believe things will get tough in some complex situation.There are so many important concepts like delegate, protocol, dictionaries, mutable arrays etc. which needs to be understood clearly. So in a nut shell like we always do read, understand and share as much as you can.
What we are trying to make?
We will be making a simple translation app to start the momentum in this blog, which will take input and translate it to the chosen language via SAP cloud translation API.
Technical Details
- FUISimplePropertyFomCell – Input field for text
- FUIListPickerFormCell – Dropdown
- FUIButtonFormCell – Button
- SAPURL Session – Technique to communicate with Rest API’s
- SAP API Hub – Translation – Our API
I would specifically like to thank Steve Guo for his blog series and Robin van het Hof for his blog series which I have referred to get my head around the iOS development.
So, coming to the technical details we are using a navigation controller with a table view by default. Inside table view controller we are adding our Fiori based form elements, please refer this blog to know how to add navigation controller steps in detail.
// Add cell to the view.
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FUISimplePropertyFormCell.self, forCellReuseIdentifier: FUISimplePropertyFormCell.reuseIdentifier)
tableView.register(FUIListPickerFormCell.self, forCellReuseIdentifier: FUIListPickerFormCell.reuseIdentifier)
tableView.register(FUISimplePropertyFormCell.self, forCellReuseIdentifier: FUISimplePropertyFormCell.reuseIdentifier)
tableView.register(FUIButtonFormCell.self, forCellReuseIdentifier: FUIButtonFormCell.reuseIdentifier)
}
Add the cell attributes etc.
//Provide Cell Details and values
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = indexPath.row
switch row{
case 0:
// source text
let cell = tableView.dequeueReusableCell(withIdentifier:FUISimplePropertyFormCell.reuseIdentifier, for: indexPath) as! FUISimplePropertyFormCell
cell.keyName = "Input Text "
cell.value = self.translationModel.inputData
cell.onChangeHandler = {
self.str = $0
}
return cell
case 1:
// target language
let cell = tableView.dequeueReusableCell(withIdentifier:FUIListPickerFormCell.reuseIdentifier, for: indexPath) as! FUIListPickerFormCell
cell.keyName = "Target Language"
cell.valueOptions = TranslationModel.langList
cell.allowsMultipleSelection = false
cell.allowsEmptySelection = false
cell.isEditable = true
if(cell.value.count == 0){
cell.value.append(self.translationModel.targetLang)
}
else{
cell.value[0] = self.translationModel.targetLang
}
cell.onChangeHandler = {
self.translationModel.targetLang = $0[0]
}
return cell
case 2:
// Translated Text
let cell = tableView.dequeueReusableCell(withIdentifier:FUISimplePropertyFormCell.reuseIdentifier, for: indexPath) as! FUISimplePropertyFormCell
cell.keyName = "Translation"
cell.value = self.translationModel.outputData
cell.onChangeHandler = {
self.str = $0
}
return cell
case 3:
// Button
let cell = tableView.dequeueReusableCell(withIdentifier:FUIButtonFormCell.reuseIdentifier, for: indexPath) as! FUIButtonFormCell
cell.button.setTitle("Translate", for: [])
cell.button.tintColor = UIColor.black
//Button Actions
cell.button.addTarget(self, action: #selector(TableViewController.getTranslation(_:)), for: .touchUpInside)
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier:FUISimplePropertyFormCell.reuseIdentifier, for: indexPath) as! FUISimplePropertyFormCell
return cell
}
}
Once user clicks the translate button we call the SAP Cloud API for translation on button click. The implementation of this decoding the response was quite a challenge, explained below in challenges.
// Action for getting translation
@objc func getTranslation(_ sender:UIButton!){
var index = IndexPath(row: 0, section: 0)
var cell = self.tableView.cellForRow(at: index)
// Read current input
self.translationModel.inputData = cell?.value(forKey: "value") as! String
//adding request headers
//API Key for API Sandbox
let headers = [
"Content-Type": "application/json",
"Accept": "application/json;charset=UTF-8",
"APIKey": "a4hcjXcHKxMeCZg0Af9y7GA5YUbqv6os"]
let parameters = [
"sourceLanguage": "en",
"targetLanguages": [TranslationModel.langList[self.translationModel.targetLang]],
"units": [["value": self.translationModel.inputData]]]
as [String : Any]
var postData: NSData? = nil
do {
postData = try JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions(rawValue:0)) as NSData
}
catch {
print(error)
}
//API endpoint for API sandbox
let request = NSMutableURLRequest(url: NSURL(string: "https://sandbox.api.sap.com/ml/translation/translate")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
//setting request method
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData! as Data
let urlSession = SAPURLSession()
//sending request
let dataTask = urlSession.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) -> Void in
// Response Handling in Challenges part
})
dataTask.resume()
}
Demo
A basic iOS app running translation is up.
Challenges
- First big challenge is the sample code in SAP API Hub for Swift does not work with my version of Xcode and Swift. I being a beginner in Swift it was tough to actually resolve the errors but was worth it. It will be great if we can have version dependent Swift sample code in the API hub.
- Secondly Sample code for iOS Fiori is available only on an Ipad app☹ and we have lack of examples online otherwise. I can understand we have just started this SDK and improving day by day but availability of SAP Mentor App on Iphone will be a plus.
- Third biggest challenge for me was how to Parse the response from API. This actually led me to understand in detail about concept of dictionaries (nothing by key, value pair), mutable array (an array which can be changed after initial value assignment) and non-mutable one(non-changeable after initialization).Good use of casting was needed to get the translated text. The challenge comes you can see the whole response but don’t know how to traverse it.
do {
guard let responseObject = try JSONSerialization.jsonObject(with: data!, options:JSONSerialization.ReadingOptions.mutableContainers)as? NSDictionary else {
print("error trying to convert data to JSON")
return
}
let root = responseObject["units"] as? NSMutableArray
let contentDictionary : NSDictionary = root![0] as! NSDictionary
let translationArray = contentDictionary["translations"] as? NSMutableArray
let decoded : NSDictionary = translationArray![0] as! NSDictionary
self.translationModel.outputData = decoded["value"] as! String
- Fourth biggest challenge for me was how to update the value which was returned by SAP Translation API. In my experience while working with Angular & Ionic the moment you update the model the changes are reflected. But in Swift world as far as my understanding till now is, I have to trigger it manually using the below mentioned code. Although 3 lines but took me good amount of time.
DispatchQueue.main.async {
self.tableView.reloadData()
}
What is next?
- More blogs to share the Swift Learning experience while using different controls and trying to mix different things:)
- Might be a series on Swift for ABAPer’s is needed does not matter who writes it, important is to share the knowledge
Feel free to provide your feedback open to all ears. I have also uploaded the code on Github it might help people who are starting the journey
Nabheet, thanks for sharing your experience how you got from nothing to something working. I like how you emphasized the need to invest into basic Swift understanding first - it is a common misconception that you could just figure it out as you go, but understanding the basics is inevitable, and the SDK cannot take away that part from you. The struggle handling plain REST responses is understood; there might be other open source that helps with that, but in the SAP ecosystem, OData is the preferred way of talking to a backend system. These cases are excellently supported by the SDK with the proxy class generation - all the parsing and formatting of responses and requests is taken care of by the frameworks for you. I encourage you to try this out also sometime in the future.
Re Mentor on iPhone - we had thoughts about this but it couldn't fit quite well to the small screen. If you don't have an iPad available, the API docs should still give you relevant code snippets and explanations to get you started, I hope. If something was in particular poorly documented, please let me know.
Thanks
Andreas
Thanks Andreas Schlosser for the great feedback and pointing in the direction of proxy class concepts. Let me dig through proxies to understand it better.
The API docs i believe are too technical to understand. Technical in the sense(just my view point) i being an ABAPer making the switch to Swift, the API's i can have more examples added to them. For example FUIText some basic example of using it along with common usage in table could have helped. It might be that since my understanding as of now is of beginner level, i am not able to understand it. This is coming from my past experience while working with Ionic framework , the help in itself support how you can implement controls in the framework.
One more thing is the sample code provided in SAP API Business Hub does not contain sample code for different version of Swift. It will help the developers who want to directly consume API via iOS framework
Thanks
Nabheet