In our previous posts, we described the example Corporate Directory app that we built using the SAP Cloud Platform for iOS SDK; how we started the project in SAP’s Cloud Portal, and how we used the SAP Cloud Platform SDK for iOS Assistant to generate an Xcode project. In this post we will describe how we customized the iOS project to meet our design standards and requirements.

Customizing the First Screen

Since we want the app to start with the list of employees, we can skip the entity selection screen altogether. To skip the Entity selection and go directly to the User list, we changed the Main Interface in the project settings:

screenshot of Project Settings

This ensures that the app will start with initial view controller from the User storyboard, which was generated by the Assistant. We removed the generated Main storyboard from the project since it was no longer needed.

Customizing the List View

In the User storyboard, we added a navigation controller since we removed the Main storyboard, and made it the initial view controller. We added a new view controller to house our static header with the SAP company address, and then embedded the UserMasterViewController below the static header:

screenshot of Storyboard

SAP provides a number of common user interface components in the iOS SDK, referred to as SAP Fiori Components. There is an SAP Fiori iPad app (called Fiori Mentor) freely available in the app store that demonstrates many of these components - we used it to explore and decide what might work for our purposes. As part of our goal for this project, we used as many of these components as possible in the project.

The generated table view already used FUIObjectTableViewCell, which we customized to match our design:


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  guard let sectionKey = self.directoryIndex?[indexPath.section],
        let userSectionList = self.indexedEntities?[sectionKey] else {
          return UITableViewCell()
  }

  let user = userSectionList[indexPath.row]

  let cell = tableView.dequeueReusableCell(withIdentifier: FUIContactCell.reuseIdentifier) as! FUIContactCell
  cell.headlineLabel.font = UIFont(name: "DINOffc", size: 17)
  cell.subheadlineLabel.font = UIFont(name: "DINOffc", size: 13)
  cell.subheadlineLabel.textColor = UIColor(named: "sapDarkGrey") ?? UIColor.black
  cell.detailImage = user.avatarImage ?? UIImage(named: "default_emp_image")
  let firstName = user.firstName ?? "John"
  let lastName = user.lastName ?? "Doe"
  cell.headlineText = "(firstName) (lastName)"
  cell.subheadlineText = user.title ?? "Employee"

  return cell
}

(Note: we could have done some of this customization in the storyboard or in a subclass, but this approach made it clear to explain.)

Before the list would work correctly, we had to address one minor issue with the generated code. Unfortunately this approach receives a runtime error:

let query = DataQuery().selectAll()

But changing that to select specific attributes works just fine:


  let query = DataQuery()
    .select(User.userID, User.username, User.firstName, User.lastName, User.jobTitle, User.title,
            User.email, User.businessPhone, User.dateOfBirth,
            User.department, User.division,
            User.location, User.addressLine1, User.addressLine2, User.addressLine3,
            User.city, User.state, User.zipCode, User.country)

To add the side index, we first needed to determine which letters needed to be in the index. We added some logic to iterate over the list of employees when it is set from the query; that logic creates a list of unique first letters from the each employee’s last name, and keeps a list of which employees are in each section as defined by the last name letter. We implemented the table view methods to have one section per letter, and correctly return the count of employees in each section. Then we implemented the sectionIndexTitles method to tell the table view to display the list of letters in the index on the right side of the table.

For search, we configured the table view to use a UISearchController, with a simple table view for results. Then, we took advantage of SAP’s DataQuery to filter the list of employees by the search terms:


  // get individual terms
  let terms = controller.searchBar.text?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) ?? ""

  // build query
  let searchQuery = DataQuery()
      .filter(User.firstName.contains(terms)
      .or(User.lastName.contains(terms)))

  // filter using DataQuery
  if let userSearchResultsController = controller.searchResultsController as? UserSearchResultsController {
      userSearchResultsController.searchResults = try? userEntities.filterWithQuery(searchQuery)
      userSearchResultsController.tableView.reloadData()
  }

Customizing the Detail View

The generated detail view looked like this:

screenshot of the Detail View

Again, all the plumbing was in place for us, and all we had to do was adjust it to our design. To do this, we took advantage of several SAP Fiori components. For the header view, including the employee’s name, position, and avatar, we used a FUIProfileHeader. We customized it to match our design:

  let profileHeader = FUIProfileHeader()
  profileHeader.backgroundColor = UIColor(named: "sapLightGrey")

  guard let entity = self.entity else  return profileHeader 

  profileHeader.imageView.image = entity.avatarImage ?? UIImage(named: "default_emp_image")

  let firstName = entity.firstName ?? "John"
  let lastName = entity.lastName ?? "Doe"

  profileHeader.headlineText = "(firstName) (lastName)"
  profileHeader.headlineLabel.textColor = .black
  profileHeader.headlineLabel.font = UIFont.systemFont(ofSize: 32)

  profileHeader.subheadlineText = entity.jobTitle ?? "Employee"
  profileHeader.subheadlineLabel.font = UIFont.systemFont(ofSize: 14)
  profileHeader.subheadlineLabel.textColor = UIColor(red: 142/255, green: 142/255, blue: 147/255, alpha: 1)

We also wanted to have the buttons for messaging, email, and phone available in the header view. Fortunately that was a simple addition, using the FUIActivityControl, which we added to the profile header’s detailContentView:


  let tintColor = UIColor(named:"sapControlTint")!
  let activityControl = FUIActivityControl()
  activityControl.addActivities([.message, .phone, .email])
  activityControl.maxVisibleItems = 3
  activityControl.activityItems[.phone]?.setTintColor(tintColor, for: .normal)
  activityControl.activityItems[.phone]?.setImage(UIImage(named:"call_active"), for: .normal)
  activityControl.activityItems[.message]?.setTintColor(tintColor, for: .normal)
  activityControl.activityItems[.message]?.setImage(UIImage(named:"text_active"), for: .normal)
  activityControl.activityItems[.email]?.setTintColor(tintColor, for: .normal)
  activityControl.activityItems[.email]?.setImage(UIImage(named:"mail_active"), for: .normal)

  activityControl.spacing = 40

  profileHeader.detailContentView = activityControl

  profileHeader.splitPercent = 0.75

Once the header was in place, we decided to limit the additional information to a few set attributes - contact information first, then employment details. We used the FUIKeyValueFormCell for each of these pieces of information and just did minor styling work to it to make it match our design.

What’s Next

What will we do next? For an app like this, offline capability is an important feature. We will be investigating the SDK’s offline features and integrating them into this app. When we build similar apps for clients, we typically ship a “seed database” with data at a known point in time, and then provide the ability to sync from there. This prevents the app from having to do a large initial sync, especially for non-transactional data.  We will be investigating how to accomplish this approach using the OfflineODataProvider.

We are also interested in exploring how managers can leverage HR data at their fingertips - there are lots of possibilities, including: capturing and reviewing data for performance reviews; reviewing department budgets and cost center performance (even being notified when estimates are exceeded or targets are hit); and being able to more closely manage key performance indicators without having to wait for generated reports or meetings.  

In addition, we will be exploring how we can leverage other Success Factors capabilities from SAP to build apps that extend the enterprise into the mobile space. An obvious direction is to use this same concept for client or customer contact information, and to consider what simple features (including basic order and delivery status) could provide strong customer service benefits.

We would be happy to chat about what we did, and learn what you think would be valuable in a mobile app built with SAP’s Cloud Platform SDK for iOS. Contact us at http://martiancraft.com.

Joe Keeley

Chief Technology Officer

MartianCraft builds world class apps for the biggest brands in the world, and the most passionate entrepreneurs. We'd love to help bring your mobile ideas to reality. Get in touch.