SAP

Manage my Account SAP Devs YouTube ↗ Learnings ↗ Community ↗ Provide Feedback ↗
Logout
⤢ Open full site

Implement the Customer Detail

Use the SAP BTP SDK for iOS to implement a Profile Header and Charts into the Customer Detail View.

Overview

You will learn

  • ✔How to implement a Profile Header
  • ✔How to implement a Chart View
Kevin Muessig K Kevin Muessig August 21, 2026
Created on January 9, 2023
Contributors

More from Kevin Muessig

See all 13 tutorials by Kevin Muessig →

Prerequisites

  • Development environment: Apple Mac running macOS Catalina or higher with Xcode 12 or higher
  • SAP BTP SDK for iOS: Version 6.0

Steps

Details

You will learn

  • How to implement a Profile Header
  • How to implement a Chart View

Step 1 Fetch right customer from data service
—

In the last tutorial, you implemented the Overview Table View Controller and the segue to the Customer Detail Table View Controller. In this tutorial, you will utilize the SAP BTP SDK for iOS to implement a FUIProfileHeader and the different Chart Cells.

  1. Open the CustomerDetailTableViewController.swift class and add the following import statements right below the UIKit import:

    Swift
    
    import SAPFiori
    import SAPOData
    import SAPOfflineOData
    import SAPCommon
    import SAPFoundation
    import SAPFioriFlows
    import ESPMContainerFmwk
    import SharedFmwk
  2. Add the following properties right above the var customerId: String! line of code:

    Swift
    
    private let logger = Logger.shared(named: "CustomerDetailTableViewController")
    
    var loadingIndicator: FUILoadingIndicatorView?
    
    private let profileHeader = FUIProfileHeader()

    The above code should look familiar to you from the Overview View Controller.

  3. Make the CustomerDetailTableViewController.swift class implement the SAPFioriLoadingIndicator protocol.

    Swift
    
    class CustomerDetailTableViewController: UITableViewController, SAPFioriLoadingIndicator { ... }
  4. Add the following lines of code as class properties to the CustomerDetailTableViewController.swift class to retrieve the data service:

    Swift
    // The available destinations from Mobile Services are hold in the FileConfigurationProvider. Retrieve it to find the correct data service
    let destinations = FileConfigurationProvider("AppParameters").provideConfiguration().configuration["Destinations"] as! NSDictionary
    
    // Retrieve the data service using the destinations dictionary and return it.
    var dataService: ESPMContainer<OfflineODataProvider>? {
        guard let odataController = OnboardingSessionManager.shared.onboardingSession?.odataControllers[ODataContainerType.eSPMContainer.description] as? ESPMContainerOfflineODataController, let dataService = odataController.dataService else {
            AlertHelper.displayAlert(with: "OData service is not reachable, please onboard again.", error: nil, viewController: self)
            return nil
        }
        return dataService
    }

    All that code is pretty much the same as in the Overview View Controller. Instead of fetching all customers you want only the data of the customer matching the provided ID.

  5. Add the following lines of code right below the viewDidLoad(:) method and read the inline comments for more details about the implemented code:

    Swift
    
    // MARK: - Data loading methods
    
    private func updateTable() {
        // Show the loading indicator
        self.showFioriLoadingIndicator()
    
        // Wait for the completion handler to get executed and hide the loading indicator
        self.loadData {
            self.hideFioriLoadingIndicator()
    
            // You will implement that method in the next steps, for now please just call it here.
            self.setupProfileHeader()
    
            // Reload the Table View to show the newly fetched data
            self.tableView.reloadData()
        }
    }
    
    // Load the Customer for the set Customer ID
    private func loadData(completionHandler: @escaping () -> Void) {
    
        // Expand the OData call to also retrieve the Customers Sales Orders as you're going to display those in the Chart.
        let query = DataQuery().expand(Customer.salesOrders)
    
        // Fetch the customer with a certain ID
        dataService?.fetchCustomerWithKey(customerID: customerId, query: query) { [weak self] result, error in
    
            // If there is an error let the user know and log it to the console.
            if let error = error {
                AlertHelper.displayAlert(with: "Couldn't load sales orders for customer.", error: error, viewController: self!)
                self?.logger.error("Couldn't load sales orders for customer.", error: error)
                return
            }
    
            // Set the result to a customer property
            self?.customer = result!
    
            // Set the retrieved Sales Orders to it's property
            self?.salesOrderHeaders = result!.salesOrders
    
            // You will need this property later for the Charts.
            self?.isDataLoaded = true
    
            // Execute the Completion Handler
            completionHandler()
        }
    }

    Don’t worry, you will fix the compile time errors in a second.

  6. Call the updateTable() in the viewDidLoad(:) method:

    Swift
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        updateTable()
    }

    To fix the rest of the compile-time errors, you have to implement the additional properties for storing the fetched customer, the flag isDataLoaded, as well as a computed property for the Sales Order Headers.

  7. As you want to display the total net amount of the Customers Sales Orders, you will implement that as a computed property to make the needed calculations.

    Add the following lines of code right below the var customerId: String property, read the inline comments for more explanation:

    Swift
    
    // The Sales Order Headers property is an Array.
    private var salesOrderHeaders = [SalesOrderHeader]() {
        // When that property is set through the loadData() make the needed calculations
        didSet {
    
            // With help of the map call on the array you can access the net amount property of the Sales Order Header of each element in the array, make the calculation and safe it in the Series Data property needed for the Chart.
            seriesData = [salesOrderHeaders.map {
                guard let net = $0.netAmount?.doubleValue() else {
                    return 0.0
                }
                return net
                }]
        }
    }
    
    private var customer = Customer()
    private var isDataLoaded = false
  8. The salesOrderHeaders property will have a compile-time error for now because it tries to set the calculated values to the seriesData property which doesn’t exist yet. To fix that implement the following properties right below the isDataLoaded property:

    Swift
    
    private var seriesData: [[Double]]?
    
    private var chartData = (
        series: ["2018"],
        categories: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
    )

    You’re done for now, you will go into detail about how the Chart data is structured at a later point.

Step 2 Set up Table View and register needed cells
+
Step 3 Implement Table View's data source
+
Step 4 Implement the FUIChartViewDataSource and Delegate
+
Step 5 Implement a Profile Header
+
Step 6 Run app on simulator
+

Resources

Discussion

Share feedback on this tutorial or join the conversation in SAP Community.

Submit detailed feedback Discuss in Community
Steps
Step 1 of 6
1. Fetch right customer from data service 2. Set up Table View and register needed cells 3. Implement Table View's data source 4. Implement the FUIChartViewDataSource and Delegate 5. Implement a Profile Header 6. Run app on simulator
Next