Skip to Content

The mobile development kit allows developers to easily customize applications using a visual programming model. The tool abstracts away low-level details that can distract you from developing business logic. This makes it easy to rapidly develop complex applications. But what happens when the built-in functionality don’t quite match your needs? In cases like this, extension controls allow developers to incorporate custom functionality where it’s needed, without adding unnecessary details or complexity to the rest of the app. This post explains the basic steps you’ll need to create your own extension, test it out and integrate it into your app.

A mobile development kit app consists of “controls”, which are UI elements that show application data or allow user input. Developers can use the WebIDE-based editor to drag controls such as text input fields, buttons, and camera attachments into the app. But sometimes you may need to use a control – say, a 3rd party map control – that’s not currently available out-of-the-box. With extension controls, you can incorporate this functionality while leveraging actions, rules, and user data from your app.

This post will guide you through the basic steps of building a custom extension:

  • Set up the sample project
  • Run the app and debug the sample extension
  • Show application data in the sample extension
  • Build a custom map extension
  • Show application data in the map

Set up the sample project

Extension controls are developed with Swift and Xcode. The Swift module is referenced in the application metadata using the WebIDE-based editor. The controls are fully integrated into the application, having access to user data and being able to facilitate user interactions. To make this process easy, we provide a sample project with built-in application metadata, an offline OData store and a sample extension project. So to get started, first download this sample.

Make sure you’ve already downloaded the 2.0 PL01 release of the mobile development kit SDK on your Mac and installed the prerequisites. You’ll also need to download and unzip the ExtensionSample project from the SAP Document Center. This has some scripts, sample metadata and sample TypeScript code that will make it easy to set up your first extension.


Note: If you want to skip straight to the final product, copy the contents of src/iOS/Frameworks/SAPExtensionControlSample/SAPExtensionControlSample/SAPExtensionControlMapViewController.swift into src/iOS/Frameworks/SAPExtensionControlSample/SAPExtensionControlSample/SAPExtensionControlSampleViewController.swift. Then add MapKit using the steps in “Build a custom map extension” below. Finally, proceed with the steps below to run the app.


Once you’ve unzipped the sample project, you’ll need to open the Terminal app. Set the MDK_SDK_LOC environment variable to the path of your client SDK with this command:

export MDK_SDK_LOC=path/to/SEAMClient_SDK

Then run the installToSDK.sh script using the Terminal app:

./installToSDK.sh

This will create the client project with the sample code. When the script completes, you’ll be looking at an Xcode workspace that you can use to develop and debug your extension. The workspace has a sample extension with some very basic functionality that gives you a starting point for development. In particular, take a look at SAPExtensionControlSampleViewController.swift.

The SAPExtensionControlSampleViewController class simply displays an FUILabel inside its view. The message to be displayed in the label comes from the _messages array. It’s assigned to the label’s text property in the updateView method.

    private func setup() {
        let label = FUILabel(frame: self.view.frame)
        // Size the label view as the same as the parent
        self.view.addSubview(label)
        // Center the text
        label.textAlignment = NSTextAlignment.center
        // enable auto-resizing
        label.autoresizingMask = [UIViewAutoresizing.flexibleHeight, UIViewAutoresizing.flexibleWidth]
        _label = label
        updateView()
    }

    func updateView() {
        guard let label = _label else {
            return
        }
        let message = _messages[0]
        // Update the label text
        label.text = message
        // "Pop" the first message, then
        // add it to the back to the end of the array
        _messages.remove(at: 0)
        // Add message to back
        _messages.append(message)
    }

Run the app and debug the sample extension

Let’s try running the app to see the extension in action. In the top left of the window, select one of the iPad simulators, then in the top menu select Product | Run. The iOS Simulator will display the client welcome screen. Note that for the purposes of this sample, a demo database with built-in metadata is provided so that you can test out your extension without connecting to mobile services. So click “Try the Demo” near the bottom, and you’ll see the app’s main page. It contains a simple object table that shows the Customers entity set.

Tap one of the records to navigate to a page which shows the details of the selected record and includes our sample extension. The page has an object header section that shows some of the customer’s properties. Below that the sample extension view controller. Lastly, there’s a button which can be used to update the extension’s message.

Try clicking the button to see the content of the label change dynamically. This is accomplished by calling into the extension through a rule. You’ll also see an action triggered that shows a message at the top of the screen. The action is invoked by the extension control’s “didUpdate” call on line 85.

Now that you see the extension working, try debugging the code by setting a breakpoint in the “update” method, which is triggered when the control is first loaded or when its binding changes. You can set a breakpoint by clicking on the left side of the targeted line. Then in the Simulator, navigate back to the list of customers and select another one. The breakpoint will be triggered, pausing the app and allowing you to inspect the extension control’s state. This is helpful for troubleshooting issues and understanding how everything fits together.

Next, import the sample metadata into WebIDE to get an idea of how the extension fits into the app. To do this, first navigate to WebIDE in your browser. Then select File | Import > From File System. Select SampleExtension/src/metadata/zip/ExtensionSampleMetadata.zip. Click OK, and you’ll see the ExtensionSampleMetadata project in the editor. Now navigate to ExtensionSampleMetadata/Pages/CustomerDetails.page. The layout editor will show the page from the sample app, except there’s a wireframe in place of the extension control. You can’t see your Swift control, but you can click on the wireframe to view its metadata properties. These include the Swift class name (“SAPExtensionControlSampleViewExtension”) and the height of the view. You can also see the ExtensionProperties field, which is used to provide custom app data (such as the label’s messages) to Swift.

Show application data in the sample extension

Now that you know how the extension is configured in WebIDE, return to Xcode to enhance it to show data from the app. An extension has access to the same data binding mechanism as the other controls, so it should be able to show properties of the selected customer.

Since we will be showing the customer name instead of the sample messages, the first step is to change the _messages array to a _message string:

    /// Message that can be shown
    private var _message: String?

Also, delete the code in “didChangeParams” that stored the sample messages:

        if let messages = _params?.mutableArrayValue(forKey: "Messages") as? [String] {
            // Clear old messages
            self._messages.removeAll()
            self._messages.append(contentsOf: messages)
        }

You can capture properties of the customer by changing the “update” method. The OData binding is passed as an NSDictionary containing all the entity’s properties. According to the OData schema, each customer has a FirstName and a LastName property. So you can access these properties and use string interpolation to put them in the _message string.

    // Display the FirstName and LastName properties in the displayed message
    func update(value: NSDictionary) {
        let firstName = value["FirstName"] ?? ""
        let lastName = value["LastName"] ?? ""
        _message = "Customer name is \(firstName) \(lastName)."
        updateView()
    }

Change the “updateView” method to show the _message string in the label.

    func updateView() {
        guard let label = _label else {
            return
        }
        // Update the label text
        label.text = _message
    }

Now run the application again. Because the extension project is in the workspace, it will be built along with your application project. When you navigate to a record, you should see the customer’s name in the extension’s label. The button presses will now have no effect on the label.

Build a custom map user interface

To showcase something a bit more interesting than plain text, let’s develop a simple map control using Apple’s MapKit framework. The map will show the location of the selected customer. First, you’ll need to import MapKit to make it available to the extension. To do this, click on the SAPExtensionControlSample project in the left pane, then select the SAPExtensionControlSample target. Choose the Build Phases tab. Then in the Link Binary With Libraries section, click the + icon to add a new framework. Use the search bar to choose MapKit.framework and click Add. Now the framework is ready to use.

Returning to SAPExtensionControlSampleViewController.swift, add an import statement at the top of the file to make the MapKit APIs available.

import MapKit

Now you’re ready to exchange the label for the map control. You can do this by replacing the _label declaration with a _map variable:

private var _map: MKMapView?

To make the class compile, first change the setup function to create a map instead of a label:

    private func setup() {
        let map = MKMapView(frame: self.view.frame)
        self.view.addSubview(map)
        map.autoresizingMask = [UIViewAutoresizing.flexibleHeight, UIViewAutoresizing.flexibleWidth]
        _map = map
        updateView()
    }

Then replace all other instances of “_label” with “_map”, except for the contents of “updateView”, which should be removed completely.

Once you’ve made these changes, try them out by running the application again. You should see a map control on the CustomerDetails page.

Show application data in the map

Of course, the map’s not much use unless it can show data. You can get customer properties in the “update” method and make them available to the map just like you did with the label control. Each customer has City and Country properties, and MapKit provides the ability to figure out the coordinates of these locations and add them to the map. As a first step in showing a location, change the name of the _message string to _address.

private var _address: String?

Next, implement this “geocoding” capability by adding the “locationForAddress” method:

    private func locationForAddress(callback: @escaping (CLLocationCoordinate2D?) -> Void) {
        guard let address = _address else {
            return
        }
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString(address) {
            (placemarks, error) in
            guard error == nil else {
                print("Error occurred while geocoding address: \(address)")
                callback(nil)
                return
            }
            callback(placemarks?.first?.location?.coordinate)
        }
    }

This code uses the asynchronous CLGeocoder.geocodeAddressString API to map the _address  string to a CLLocationCoordinate2D object. If successful, the callback is invoked with the result.

Next, to test this functionality, hard-code a sample location such as “Chicago US” as the address. This is consistent with the city-and-country format we’ll use for each customer.

    func update(value: NSDictionary) {
        _address = "Chicago US"
        updateView()
    }

To show the address in the map, re-implement the updateView method:

    func updateView() {
        guard let map = _map else {
            return
        }
        locationForAddress() {
            (location) in
            guard let location = location else {
                print("Location was nil")
                return
            }
            // Add the location to the map
            let coordinates = CLLocationCoordinate2DMake(location.latitude, location.longitude)
            let annotation = MKPointAnnotation()
            annotation.coordinate = coordinates
            annotation.title = self._address
            map.showAnnotations([annotation], animated: true)
        }
    }

Once you’ve made these changes, test them out by running the app. When you choose a customer, you’ll see the hard-coded address shown on the map.

The last step is to replace the contents of “update” with code that uses the customer’s real city and country:

    func update(value: NSDictionary) {
        let city = value["City"] ?? ""
        let country = value["Country"] ?? ""
        _address = "\(city) \(country)"
        updateView()
    }

Run the app again, and this time each customer’s city will be pinpointed on the map.

In this post, you’ve seen how you can build and run the sample extension project, then customize it to your own needs. Feel free to play around with the extension by further tweaking the Swift code or even the sample application metadata. Possible next steps would be to move the extension to the first page and show all the customers, or to allow more user interaction with the map. In a future post, I’ll explain how to further integrate your extension control with your app, including more in-depth data processing and handling of user input. Please make sure to leave questions and feedback in the comments. Have fun building custom controls that fit perfectly into a mobile development kit app!

To report this post you need to login first.

1 Comment

You must be Logged on to comment or reply to a post.

Leave a Reply