Skip to Content
Author's profile photo Lucas Wonderley

Build Extension Controls in Mobile Development Kit 2.0 SP1

In this post, I’ll show you how easy it is to add custom functionality to a Mobile Development Kit app with an extension control. By the time you’ve gone through these steps, you’ll have integrated a KPI view from the SAP Cloud Platform SDK into your MDK app. First, you’ll see how to add the extension control in the editor. Next, you’ll define a basic Swift control and see it displayed in your app. Finally, you’ll see how to customize the control’s appearance and behavior!

A Mobile Development Kit app consists of controls, or UI elements that show application data and allow user input. Developers can use the visual editor to drag controls such as text fields, buttons, and list pickers into the app. But sometimes you may need to use a control – say, a KPI view – which is not included in MDK out of the box. With extension controls, you can incorporate this functionality while leveraging actions, rules, and user data from your app.

If you want to follow along, you should first complete the video tutorials in this YouTube playlist, including the optional “Setup Client” video. This way, you’ll have a working development environment and a starting point to build on. If you’re new to MDK, you can find more links and information here.

 

Add the extension control in the editor

In the tutorials, you created an application that shows a list of products and allows the user to select a product to view its details. We’ll add an extension control to the detail page. The first step is to register a new extension in the editor. You can do this by right-clicking the top-level Workspace folder and selecting New | MDK Extension Control.

In the wizard, enter “CustomKPIControl” as both the control name and class.

Click Next, then Next again because you don’t need to define a schema just yet. Click Finish to create the extension control definition. You’ll notice that a new project called “MDKExtensionControls” is created with your newly registered extension definition. It’s a separate project so that you can easily reuse the controls in multiple apps.

Now select the ProductDetail page so that the extension can be added to it. Under the Registered Extension Control section in the Controls pane, drag the CustomKPIControl into the sectioned table. In the Properties view, set the Height to 300.

When you’re satisfied, save and publish your app!

Switch over to the MDK client. It should pick up your latest change. Is the detail page any different? You should see that your new control is shown, except it only displays a message: “Extension with class name CustomKPIControl was not found”. The client checked for a Swift class by that name but couldn’t find it. That’s no surprise because you haven’t defined the class yet!

 

Define a basic Swift control and see it displayed in your app

To create a custom extension, you’ll need to open your client project in Xcode. If you have a tns command running in Terminal, cancel it with Ctrl+C. In Xcode, select File | Open… , then find your client project and open <ProjectName>/platforms/ios/<ProjectName>.xcworkspace. You can use this workspace to run the client, just like with the “tns run” command.

The extension is defined as a Swift class, so create one from scratch by right-clicking the client project and selecting New File… . Select the “Swift File” option, then enter “CustomKPIControl.swift” as the file name. If Xcode asks you about configuring an Objective-C bridging header, choose “Don’t Create.”

Now that you have a blank Swift file whose name matches what you specified in the editor, copy the following code into the file. This is a nearly blank extension implementation which only displays a label. You can see that in the viewDidLoad method, the label is created and added to the extension as a subview.

import Foundation
import SAPMDC
import SAPFiori

@objc(CustomKPIControl)
public class CustomKPIControl: UIViewController, MDKExtension {
    public var delegate: SwiftExtensionDelegate?
    
    public func controlValueChanged(with value: NSDictionary) {
    }
    
    public func update(with params: NSDictionary) {
    }

    private let label: UILabel = UILabel()
    
    public override func viewDidLoad() {
        super.viewDidLoad()
        // enable auto-resizing
        self.label.autoresizingMask = [UIViewAutoresizing.flexibleHeight, UIViewAutoresizing.flexibleWidth]
        // Note: In Xcode 10, the above line must be changed to the following:
        // self.label.autoresizingMask = [UIView.AutoresizingMask.flexibleHeight, UIView.AutoresizingMask.flexibleWidth] 
        // Center the text
        self.label.textAlignment = NSTextAlignment.center
        // Update the label frame size
        self.label.frame = self.view.frame
        self.label.text = "This is a sample extension."
        self.view.addSubview(label)
    }
}

Test out your change by clicking Product | Run. If the app builds and runs successfully, you’ll see that the extension control appears a little different this time: The label text that’s created in the viewDidLoad method is shown.

Next, let’s add the KPI view into the extension. You’ll do this by creating an FUIKPIView control instead of an FUILabel. Replace the contents of CustomKPIControl.swift with the code below to make this change. Notice that whatever kind of control you want to add, you just have to create it and add it as a subview. The code below configures the KPI view with some canned data.

import Foundation
import SAPMDC
import SAPFiori

@objc(CustomKPIControl)
open class CustomKPIControl: UIViewController, MDKExtension {
    public var delegate: SwiftExtensionDelegate?

    var kpiItems: [FUIKPIViewItem]?
    
    public func update(with params: NSDictionary) {
    }
    
    public func controlValueChanged(with binding: NSDictionary) {
    }
    
    override open func viewDidLoad() {
        super.viewDidLoad()
        // Create the stack view to hold the KPIs
        let stackView = UIStackView()
        self.view.addSubview(stackView)
        stackView.axis = .horizontal
        stackView.distribution = .fillEqually
        stackView.alignment = .fill
        stackView.spacing = 25
        stackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
            stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
            ])
        
        // Create a KPI view to add to the stack view
        let kpiView = FUIKPIView()
        let metric = FUIKPIMetricItem(string: "294")
        let currency = FUIKPIUnitItem(string: "USD")
        kpiView.items = [metric, currency]
        kpiView.captionlabel.text = "Price"
        stackView.addArrangedSubview(kpiView)
    }
}

Replace the Swift file with these contents, then run the application again with Product | Run. The KPI view should now appear in the extension control:

 

Customize the control’s appearance and behavior

It’s great that you can develop a Swift control that shows up in the application, but what’s even more useful is that the control can consume data from the OData service. In this case, the extension section is bound to the product being displayed. Its data is provided to the controlValueChanged(with binding: NSDictionary) method when the page loads and anytime the binding happens to be updated. The method is empty now; let’s change that to display data in the KPI! Change the controlValueChanged to the following:

    public func controlValueChanged(with binding: NSDictionary) {
        let price: AnyObject = binding.value(forKey: "Price") as! NSNumber
        let currencyCode: String = binding.value(forKey: "CurrencyCode") as! String
        let metric = FUIKPIMetricItem(string: price.stringValue)
        let currency = FUIKPIUnitItem(string: currencyCode)
        self.kpiItems = [metric, currency]
    }

Now that the KPI items are populated from the OData service, you can stop using canned data. Replace the bottom of viewDidLoad with the following:

        // Create a KPI view to add to the stack view
        let kpiView = FUIKPIView()
        kpiView.items = self.kpiItems
        kpiView.captionlabel.text = "Price"
        stackView.addArrangedSubview(kpiView)

Now run the application again, and you’ll see that the Price and CurrencyCode properties are shown in the KPI!

To bring things full circle, let’s go back to WebIDE to see another way that you can integrate between the app and your extension. Right-click the actions folder in your app and choose the option to create a new action. Choose the Message action type, and name your action “ExtensionMessage.” For the message text, enter something like “Message triggered from the extension!” Specify the Title and OK Caption properties as “Extension Message” and “OK”.

Next, you’ll want to make the action’s definition path (“/Demo/Actions/ExtensionMessage.action”) available in Swift so that it doesn’t need to be hard coded. This can be accomplished with the schema you skipped earlier. Navigate to the MDKExtensionControls project and open the CustomKPIControl extension. Update the schema field with the following:

{
    "OnPressAction": ""
}

This is meant to tell the editor that the extension will pass a string called OnPressAction to the Swift control. Now click “Generate Schema” and the content will be changed to look like this:

This updated JSON is used internally by the editor to make it easy to specify the value of OnPressAction. Save this change and view the ProductDetail page again, selecting the extension section. You’ll see that the right pane now shows the OnPressAction property under Extension Properties.

Click the link button in the Value column to specify the ExtensionMessage action. In the Object Browser, you can find it by first selecting the Actions and Rules view in the top left dropdown, then searching for it in the top right. Double click the action so that it’s added to the Expression field, then click OK.

Now that the action is configured in the Extension Properties, publish your app so that the change is picked up by the client.

On the Swift side, let’s store the action reference and trigger the action when the user touches the control. The Extension Properties are passed to Swift as an NSDictionary in the update(with params:) method. Create a new member variable to store the dictionary, and assign it in the update method:

	var extensionProperties: NSDictionary?
	
	public func update(with params: NSDictionary) {
		extensionProperties = params
	}

You can configure the KPI view to respond to touches by directly triggering the action in Swift. Any calls from the extension to the app are made with the control’s delegate. This API can be checked by right-clicking the SwiftExtensionDelegate class near the top of the file and choosing Jump to Definition. This will show you the delegate’s protocol:

To trigger the action, define a showMessage method that will call executeActionOrRule with the path to the definition you defined.

	@objc func showMessage() {
		self.delegate?.executeActionOrRule(definitionPath: extensionProperties?.value(forKey: "OnPressAction") as! NSString, callback: { (result: String) in
			print("Called ExtensionMessage.action.")
		})
	}

Next, connect the user’s touches to this method by adding the following to the bottom of viewDidLoad:

        kpiView.addTarget(self, action:#selector(CustomKPIControl.showMessage), for: .touchUpInside)

Now try it out: Run the app again and see what happens when you press the KPI view. The message should be displayed!

As you can see, Mobile Development Kit makes it a breeze to implement a custom control in Swift. It’s just a matter of defining the extension control in the editor, hooking it up to a Swift class in your project and configuring the control to suit your needs. Please note that there are still more ways to integrate between your app and your extension control: You can also resolve the value of any rule, global or target path and even call into Swift from an MDK rule. Extension controls let you customize your app when needed, and focus on high-level business logic the rest of the time. Please leave your questions and comments. I look forward to seeing how you use extension controls in Mobile Development Kit!

Assigned Tags

      9 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Tenzin Wangmo
      Tenzin Wangmo

      How did you push the swift extension to the mobile service? Thank You!

      Author's profile photo Lucas Wonderley
      Lucas Wonderley
      Blog Post Author

      Tenzin, the Swift extension is a customization to the MDK client, so it doesn’t need to be pushed to the mobile service. Instead, you build the MDK client and include the Swift class in your Xcode project.

      Since you mentioned it, though, we do hope to better integrate extensions with Mobile Services going forward. You can already build an MDK client in the cloud; see these two blogs for more info: https://blogs.sap.com/2018/05/17/cloud-build-feature-in-mobile-service-now-supports-mobile-development-kit-clients/, https://blogs.sap.com/2018/09/05/choosing-the-right-mobile-development-kit-client/. If you choose to build the SAP Asset Manager client, several extension controls including map and analytics are built into the client for you.

      There is currently no way to add your custom-built extensions to this build process, but it’s something that can be done and we’d like to make that improvement at some point.

      Author's profile photo NAN THONG
      NAN THONG

      I get an error when I create the swift file that ‘module compiled with swift 4.1 cannot be imported by the swift 4.2 compiler' for the statement import SAPMDC. Do you know how to resolve this? My xcode version is 10 currently.

      Author's profile photo Lucas Wonderley
      Lucas Wonderley
      Blog Post Author

      Hey Tenzin, it sounds like you're using a version of the Mobile Development Kit SDK which doesn't support Xcode 10. Which version are you using? We recently released 2.1.3, which supports Xcode10/iOS12, to Service Marketplace. It is in process of being posted to the SAP Store.

      If you're not able to download the latest SDK, another option is to download and install Xcode 9.4.1. Follow this link: https://developer.apple.com/download/ then click "See more downloads" at the bottom. Search for Xcode 9.4.1 and you can download and install it from there.

      Author's profile photo girish kumar
      girish kumar

      Hi,

      How can I build a extension control in android

       

      Thanks

      Author's profile photo Arvind Patel
      Arvind Patel

      Hi Lucas,

      could you please share some ideas for creating extension control for Andriod?

       

      Thanks

      Author's profile photo sani kali
      sani kali

      Hi Lucas,

       

      Share some details for android also It will be very helpful.

       

      Thank you.

      Author's profile photo Juan David Forero
      Juan David Forero

      Hola, estoy creando un control personalizado para mi aplicación desarrollada en el MDK y cuando ejecuto el proyecto en XCode , recibo este error:

      Could not find module 'SAPMDC' for target 'x86_64-apple-ios-simulator'; found: arm64

      Estoy ejecutando desde XCode 11, podria alguien ayudarme?

      Author's profile photo Jitendra Kansal
      Jitendra Kansal

      You can find up-to-date info on MDK extensions in new documentation.