Skip to Content
Author's profile photo Nabheet Madan

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

 

 

Assigned Tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Andreas Schlosser
      Andreas Schlosser

      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

       

      Author's profile photo Nabheet Madan
      Nabheet Madan
      Blog Post Author

      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.

      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.

       

      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