Skip to Content
Technical Articles
Author's profile photo Daniel Van Leeuwen

Getting Started with the SAP Cloud Platform SDK for iOS – Part 6 – SAP Fiori for iOS

Previous (Offline OData)   Home

SAP Fiori for iOS includes a set of additional controls that inherit from controls in Apple UIKit. The SAP Fiori Mentor App for iPads can be used to explore and learn more about these controls.

The following are some additional sources of documentation on SAP Fiori for iOS.
Fiori for iOS Design Guidelines
SAPFiori API Docs
Branding and Theming
List Report Floorplan Tutorial
Timeline Tutorial

The following steps will provide some simple examples of using the SAP Fiori for iOS controls.

Adding a Toast and Showing a Loading Indicator
Adding a FUIFilterFeedbackControl
Using and Enhancing a Map

Adding a Toast and Loading Indicator

The following steps will display the results of the OData query using a toast and will show the loading activity indicator while the offline store is being opened which can take a few seconds the first time it is opened.

  1. The SAP Fiori Mentor App can be downloaded from the App Store onto an iPad.
  2. Under the UI Components section of the app, select the Toast Message component.
  3. The control’s settings can be modified as shown below.
  4. Sample code is shown by pressing the following button.

  5. Add the following import to the top of ViewController.swift.
    import SAPFiori

    Add the following code to make use of a FUIToastMessage.
    In the getProductsOffline method, comment out the call to the print method which writes out the result with the following code.

    let msg = "Offline: got \(products.count) products and the first product name is \(products[0].name!)"
    print(msg) msg, icon: UIImage(), inWindow: self.view.window, withDuration: 3.0, maxNumberOfLines: 3)
  6. Run the app and press the button to call getProductsOffline. Notice the result is now shown in a toast.
  7. The following changes will make use of a FUILoadingIndicatorView to indicate that the app is downloading the offline store.
    Add the following line of code to the top of the method getProductsOffline.

    let loadingView = self.view, animated: true)

    Inside of the method, add the following code.

    defer {
        DispatchQueue.main.async {
  8. Delete the app from the simulator, re-run, press the button which triggers the download of an offline database from the server and notice there is now a loading indicator.

Adding a FUIFilterFeedbackControl

The following example demonstrates how to add a table with a filter control.

  1. Under the UI Components section of the SAP Fiori Mentor app, select the Filter Feedback component.
  2. Notice that the filter is interactive. In the screen shot below, a sort order by Price has been specified as well as a filter to show only Computer Systems.
  3. The source code to try out the control is available by pressing the following button.

  4. Create a new project named FilterSample.
  5. Add the embedded binaries for the SAP Cloud Platform SDK for iOS.
  6. Add a new Table View Controller and drag the storyboard entry point to point at the newly added screen.
  7. Create a new file of type Cocoa Touch Class named FilterFeedbackTableViewController that extends UITableViewController and replace its contents with those taken from the SAP Fiori Mentor App.

    The source code is included below in case you do not have an iPad which is required for the SAP Fiori Mentor app.

    import SAPFiori
    import UIKit
    struct Product {
        var image: UIImage
        var name: String
        var id: String
        var mainCategoryName: String
        var description: String
        var price: Int
    class FilterFeedbackExample: UITableViewController {
        let currency = "USD"
        var products = [Product]()
        var productsToDisplay = [Product]()
        let estimatedRowHeight = CGFloat(80)
        var filterFeedbackControl: FUIFilterFeedbackControl!
        var categoryGroup = FUIFilterGroup()
        let categoryItemComputerSystems = FUIFilterItem("Computer Systems", isFavorite: true, isActive: false)
        let categoryItemSmartphonesTablets = FUIFilterItem("Smartphones & Tablets", isFavorite: true, isActive: false)
        let categoryItemPrintersScanners = FUIFilterItem("Printers & Scanners", isFavorite: true, isActive: false)
        override func viewDidLoad() {
            tableView.register(FUIObjectTableViewCell.self, forCellReuseIdentifier: FUIObjectTableViewCell.reuseIdentifier)
            tableView.estimatedRowHeight = estimatedRowHeight
            tableView.rowHeight = UITableView.automaticDimension
            tableView.separatorStyle = .singleLine
            categoryItemComputerSystems.key = "CS"
            categoryItemSmartphonesTablets.key = "ST"
            categoryItemPrintersScanners.key = "PS"
            productsToDisplay = getProducts()
            products = getProducts()
        func getProducts() -> [Product] {
            return [
                Product(image: UIImage(), name: "Photo Scan", id: "HT-1080", mainCategoryName: "Printers & Scanners", description: "Flatbed scanner - 9.600 × 9.600 dpi - 216 x 297 mm - Hi-Speed USB - Bluetooth", price: 129),
                Product(image: UIImage(), name: "Notebook Professional 17", id: "HT-1011",  mainCategoryName: "Computer Systems", description: "Notebook Professional 17 with 2,80 GHz quad core, 17\" Multitouch LCD, 8 GB DDR3 RAM, 500 GB SSD - DVD-Writer (DVD-R/+R/-RW/-RAM),Windows 8 Pro", price: 2299),
                Product(image: UIImage(), name: "Notebook Basic 15", id: "HT-1000", mainCategoryName: "Computer Systems", description: "Notebook Basic 15 with 2,80 GHz quad core, 15\" LCD, 4 GB DDR3 RAM, 500 GB Hard Disc, Windows 8 Pro", price: 956),
                Product(image: UIImage(), name: "Cepat Tablet 8", id: "HT-1258",  mainCategoryName: "Smartphones & Tablets", description: "8-inch Multitouch HD Screen (2000 x 1500) 32GB Internal Memory, Wireless N Wi-Fi, Bluetooth, GPS Enabled, 1.5 GHz Quad-Core Processor", price: 529),
                Product(image: UIImage(), name: "Laser Professional Eco", id: "HT-1040", mainCategoryName: "Printers & Scanners", description: "Print 2400 dpi image quality color documents at speeds of up to 32 ppm (color) or 36 ppm (monochrome), letter/A4. Powerful 500 MHz processor, 512MB of memory", price: 830)
        func setupCategoryGroups() {
            categoryGroup.items = [
            categoryGroup.isMutuallyExclusive = true
            categoryGroup.allowsEmptySelection = true
        func setupFilterFeedback() {
            let frameFilterFeedback = CGRect(x: 0, y: 0, width: tableView.frame.width, height: 44)
            let filterFeedbackControl = FUIFilterFeedbackControl(frame: frameFilterFeedback)
            filterFeedbackControl.filterGroups = [categoryGroup]
            filterFeedbackControl.filterResultsUpdater = self
            tableView.tableHeaderView = filterFeedbackControl
        // MARK: - Table view data source and delegate
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return productsToDisplay.count
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let objectCell = tableView.dequeueReusableCell(
                withIdentifier: FUIObjectTableViewCell.reuseIdentifier,
                for: indexPath as IndexPath) as! FUIObjectTableViewCell
            guard indexPath.row < productsToDisplay.count else {
                return objectCell
            let product = productsToDisplay[indexPath.row]
            objectCell.splitPercent = CGFloat(0.3)
            objectCell.detailImage = product.image
            objectCell.detailImage?.accessibilityIdentifier =
            objectCell.headlineText =
            objectCell.subheadlineText =
            objectCell.footnoteText = product.mainCategoryName
            objectCell.descriptionText = product.description
            objectCell.statusText = "\(product.price) \(currency)"
            return objectCell
    extension FilterFeedbackExample: FUIFilterResultsUpdating {
        func updateFilterResults(for filterFeedbackControl: FUIFilterFeedbackControl) {
            // reset the products to their initial state
            // this means no filters are applied
            // and the products are sorted by name
            let activeFilterItems = filterFeedbackControl.filterItems.filter({ $0.isActive })
            if !activeFilterItems.isEmpty {
                // search if one of the filters is contained in the activeFilters
                // and apply it, then break out of the loop
                // because only one category filter can be active
                for filterItem in categoryGroup.items {
                    if activeFilterItems.contains(filterItem) {
                        let filteredProducts = products.filter { product in
                            product.mainCategoryName == filterItem.title
                        productsToDisplay.append(contentsOf: filteredProducts)
            } else {
                productsToDisplay = products
  8. Select the Table View Controller and specify that its Custom Class is FilterFeedbackExample.
  9. Run the app to have a running example of a FilterFeedback control.

Using and Enhancing a Map

The following instructions demonstrate how to add a map to an application that opens in Satellite mode, centers on the user’s current location and then adds points of interest and a toolbar using FUIMarkerAnnotationView and FUIMapDetailPanel. The following article may also be of use when learning about MapKit.
MapKit Tutorial: Getting Started

  1. In either the FilterSample app or the GettingStarted app, add a new View Controller and drag the storyboard entry point to point at the newly added screen.
  2. Drag a Map Kit View onto the newly added view controller.
  3. Drag each edge of the Map Kit View to the edges of the parent view controller. Then click on the Add New Constraints button. Set each value to 0 and enable each one by clicking on each red I indicator. Finally click on Add 4 Constraints.
  4. Specify the options of the map such as those shown below.  Note, you may see a crash if the traffic option is checked in combination with Satellite.
  5. Create a new file of type Cocoa Touch Class named MapViewController that extends UIViewController.
  6. Set the View Controller’s custom class to be MapViewController.
  7. Replace the contents of MapViewController.swift with the following code which will zoom the map in to the current user’s location the first time that the user’s location becomes available. For further information see CLLocationManagerDelegate.
    import UIKit
    import MapKit
    class MapViewController: UIViewController, CLLocationManagerDelegate {    
        let locationManager = CLLocationManager()
        var firstTime:Bool = true
        override func viewDidLoad() {
            locationManager.delegate = self
            locationManager.desiredAccuracy = kCLLocationAccuracyBest
        func locationManager(_ manager: CLLocationManager,
                             didUpdateLocations locations: [CLLocation]) {
            let location:CLLocation = locations.last!
            let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
            let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
            if firstTime {
                mapView.setRegion(region, animated: true)
                firstTime = false
  8. Create an outlet named mapView by control dragging from the Map View to the MapViewController file. The outlet will be used to specify the map area to show.

  9. Edit the Info.plist file and add a key value for Privacy – Location When In Use Usage Description.
  10. Run it and notice that a map is shown.

    Note, if you are using the simulator, the location can be set via the Debug > Location menu.
  11. Now let’s enhance the map by adding some locations on the map with the help of some controls from Fiori for iOS (icons for the annotations and a FUIDetailsPanelContainer showing a selectable list of annotations).
  12. Duplicate MapViewController.swift (select the file and from the File menu choose Duplicate…) and rename the backup to MapViewController.old.
  13. Replace the contents of MapViewController.swift with the following code taken from the SAP Fiori Mentor app for the FUIMarkerAnnotationView and FUIMapDetailPanel.
    import SAPFiori
    import MapKit
    class MapViewController: UIViewController {
        @IBOutlet weak var mapView: MKMapView!
        let latitudinalMeters = 300_000.0
        let longitudinalMeters = 300_000.0
        var dansCampingLocations: [Location] {
            get {
                let location1 = Location(name: "Long Point Provincial park",
                                         address: "Norfolk County, ON N0E 1M0",
                                         latitude: 42.5788954,
                                         longitude: -80.38762,
                                         country: "Canada",
                                         region: "North America")
                let location2 = Location(name: "Killarney Provincial Park",
                                         address: "960 ON-637, Killarney, ON P0M 2A0",
                                         latitude: 46.0133468,
                                         longitude: -81.4091228,
                                         country: "Canada",
                                         region: "North America")
                let location3 = Location(name: "Bruce Peninsula National Park",
                                         address: "469 Cyprus Lake Rd, Tobermory, ON N0H 2R0",
                                         latitude: 45.2113526,
                                         longitude: -81.6714036,
                                         country: "Canada",
                                         region: "North America")
                let location4 = Location(name: "Beausoleil Island",
                                         address: "2611 Honey Harbour Rd, Honey Harbour, ON P0E 1E0",
                                         latitude: 44.8701488,
                                         longitude: -79.875788,
                                         country: "Canada",
                                         region: "North America")
                return [location1, location2, location3, location4]
        var locationsInTableView = [Location]()
        var container: FUIMapDetailPanel?
        override func viewDidLoad() {
            locationsInTableView = dansCampingLocations
        override func viewWillAppear(_ animated: Bool) {
        override func viewDidAppear(_ animated: Bool) {
            //set initial location in SAP Waterloo:
            let initialLocation = CLLocationCoordinate2D(latitude: 44.4804246, longitude: -80.5528764)
            centerMap(on: initialLocation)
            //Optional code to change the map annotations (icon and color)
            class FioriMarker: FUIMarkerAnnotationView {
                override var annotation: MKAnnotation? {
                    willSet {
                        markerTintColor = .preferredFioriColor(forStyle: .map2)
                        glyphImage =
                        displayPriority = .defaultHigh
            mapView.register(FioriMarker.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
            for location in locationsInTableView {
            DispatchQueue.main.async {
        private func setupDetailPanel() {
            container = FUIMapDetailPanel(parentViewController: self, mapView: mapView)
            if let container = container {
                container.isSearchEnabled = true
                container.searchResults.tableView.dataSource = self
                container.searchResults.tableView.delegate = self
                container.searchResults.tableView.register(FUIMapDetailTagObjectTableViewCell.self, forCellReuseIdentifier: FUIMapDetailTagObjectTableViewCell.reuseIdentifier)
                container.searchResults.tableView.separatorStyle = .singleLine
                container.searchResults.searchBar.delegate = self
        private func centerMap(on location: CLLocationCoordinate2D) {
            let coordinateRegion = MKCoordinateRegion(center: location,
                                                      latitudinalMeters: latitudinalMeters,
                                                      longitudinalMeters: longitudinalMeters)
            mapView.setRegion(coordinateRegion, animated: true)
    extension MapViewController: UISearchBarDelegate {
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            if searchText.isEmpty {
                locationsInTableView = dansCampingLocations
            } else {
                locationsInTableView = dansCampingLocations.filter { location in
                        location.address.localizedCaseInsensitiveContains(searchText) ||
                        location.region.localizedCaseInsensitiveContains(searchText) ||
    extension MapViewController: UITableViewDataSource, UITableViewDelegate {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return locationsInTableView.count
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: FUIMapDetailTagObjectTableViewCell.reuseIdentifier,
                                                     for: indexPath as IndexPath)
            guard let objectCell = cell as? FUIMapDetailTagObjectTableViewCell else {
                return cell
            let location = locationsInTableView[indexPath.row]
            objectCell.splitPercent = CGFloat(0.97)
            objectCell.headlineText =
            objectCell.subheadlineText = location.address
            objectCell.subheadlineLabel.numberOfLines = 2
            return objectCell
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            let selectedAnnotation = locationsInTableView[indexPath.row].pointAnnotation()
            centerMap(on: selectedAnnotation.coordinate)
    class Location {
        let name: String
        let address: String
        let latitude: Double
        let longitude: Double
        let country: String
        let region: String
        init(name: String, address: String, latitude: Double, longitude: Double, country: String, region: String) {
   = name
            self.address = address
            self.latitude = latitude
            self.longitude = longitude
   = country
            self.region = region
        private var coordinate: CLLocationCoordinate2D {
            get {
                return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
        public func pointAnnotation() -> MKPointAnnotation {
            let point = MKPointAnnotation()
            point.coordinate = coordinate
            point.title = name
            point.subtitle = region
            return point
  14. Run it and notice that a map appears containing a few of my favorite camping locations.

Previous (Offline OData)   Home

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.