You will need a class that manages the connection between your app and SDL Core. Since there should be only one active connection to the SDL Core, you may wish to implement this proxy class using the singleton pattern.
// ProxyManager.h #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface ProxyManager : NSObject + (instancetype)sharedManager; @end NS_ASSUME_NONNULL_END // ProxyManager.m #import "ProxyManager.h" NS_ASSUME_NONNULL_BEGIN @interface ProxyManager () @end @implementation ProxyManager + (instancetype)sharedManager { static ProxyManager* sharedManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedManager = [[ProxyManager alloc] init]; }); return sharedManager; } - (instancetype)init { self = [super init]; if (!self) { return nil; } return self; } @end NS_ASSUME_NONNULL_END
class ProxyManager: NSObject { // Singleton static let sharedManager = ProxyManager() private override init() { super.init() } }
Your app should always start passively watching for a connection with a SDL Core as soon as the app launches. The easy way to do this is by instantiating the ProxyManager
class in the didFinishLaunchingWithOptions()
method in your AppDelegate
class.
The connect method will be implemented later. To see a full example, navigate to the bottom of this page.
@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Initialize and start the proxy [[ProxyManager sharedManager] connect]; } @end
class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Initialize and start the proxy ProxyManager.sharedManager.connect() return true } }
At the top of the ProxyManager
class, import the SDL for iOS library.
#import <SmartDeviceLink/SmartDeviceLink.h>
import SmartDeviceLink
The SDLManager
is the main class of SmartDeviceLink. It will handle setting up the initial connection with the head unit. It will also help you upload images and send RPCs.
#import "ProxyManager.h" NS_ASSUME_NONNULL_BEGIN @interface ProxyManager () @property (nonatomic, strong) SDLManager *sdlManager; @end @implementation ProxyManager + (instancetype)sharedManager { static ProxyManager *sharedManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedManager = [[ProxyManager alloc] init]; }); return sharedManager; } - (instancetype)init { self = [super init]; if (!self) { return nil; } return self } @end NS_ASSUME_NONNULL_END
class ProxyManager: NSObject { // Manager fileprivate var sdlManager: SDLManager! // Singleton static let sharedManager = ProxyManager() private override init() { super.init() } }
In order to instantiate the SDLManager
class, you must first configure an SDLConfiguration
. To start, we will look at the SDLLifecycleConfiguration
. You will at minimum need a SDLLifecycleConfiguration
instance with the application name and application id. During the development stage, a dummy app id is usually sufficient. For more information about obtaining an application id, please consult the SDK Configuration section of this guide. You must also decide which network configuration to use to connect the app to the SDL Core. Optional, but recommended, configuration properties include short app name, app icon, and app type.
There are two different ways to connect your app to a SDL Core: with a TCP (Wi-Fi) network connection or with an iAP (USB / Bluetooth) network connection. Use TCP for debugging and use iAP for production level apps.
SDLLifecycleConfiguration* lifecycleConfiguration = [SDLLifecycleConfiguration defaultConfigurationWithAppName:@"<#App Name#>" fullAppId:@"<#App Id#>"];
let lifecycleConfiguration = SDLLifecycleConfiguration(appName:"<#App Name#>", fullAppId: "<#App Id#>")
SDLLifecycleConfiguration* lifecycleConfiguration = [SDLLifecycleConfiguration debugConfigurationWithAppName:@"<#App Name#>" fullAppId:@"<#App Id#>" ipAddress:@"<#IP Address#>" port:<#Port#>];
let lifecycleConfiguration = SDLLifecycleConfiguration(appName: "<#App Name#>", fullAppId: "<#App Id#>", ipAddress: "<#IP Address#>", port: <#Port#>)
If you are connecting your app to an emulator using a TCP connection, the IP address is your computer or virtual machine’s IP address, and the port number is usually 12345. If you are connecting to Manticore, the Manticore UI will give you your IP / Port to connect to.
This is a shortened version of your app name that is substituted when the full app name will not be visible due to character count constraints. You will want to make this as short as possible.
lifecycleConfiguration.shortAppName = @"<#Shortened App Name#>";
lifecycleConfiguration.shortAppName = "<#Shortened App Name#>"
This is a custom icon for your application. Please refer to Adaptive Interface Capabilities for icon sizes.
UIImage* appImage = [UIImage imageNamed:@"<#AppIconName#>"]; if (appImage) { SDLArtwork* appIcon = [SDLArtwork persistentArtworkWithImage:appImage name:@"<#ArtworkName#>" asImageFormat:SDLArtworkImageFormatPNG /* or SDLArtworkImageFormatJPG */]; lifecycleConfiguration.appIcon = appIcon; }
if let appImage = UIImage(named: "<#AppIconName#>") { let appIcon = SDLArtwork(image: appImage, name: "<#ArtworkName#>", persistent: true, as: .JPG /* or .PNG */) lifecycleConfiguration.appIcon = appIcon }
Persistent files are used when the image ought to remain on the remote system between ignition cycles. This is commonly used for menu artwork, soft button artwork and app icons. Non-persistent artwork is usually used for dynamic images like music album artwork.
The app type is used by car manufacturers to decide how to categorize your app. Each car manufacturer has a different categorization system. For example, if you set your app type as media, your app will also show up in the audio tab as well as the apps tab of Ford’s SYNC® 3 head unit. The app type options are: default, communication, media (i.e. music/podcasts/radio), messaging, navigation, projection, information, and social.
Navigation and projection applications both use video and audio byte streaming. However, navigation apps require special permissions from OEMs, and projection apps are only for internal use by OEMs.
lifecycleConfiguration.appType = SDLAppHMITypeMedia;
lifecycleConfiguration.appType = .media
If one app type doesn't cover your full app use-case, you can add additional AppHMIType
s as well.
lifecycleConfiguration.additionalAppTypes = @[SDLAppHMITypeInformation];
lifecycleConfiguration.additionalAppTypes = [.information];
You can customize the color scheme of your templates. For more information, see the Customizing the Template guide section.
You have the ability to determine a minimum SDL protocol and minimum SDL RPC version that your app supports. You can also check the connected vehicle type and disconnect if the vehicle module is not supported. We recommend not setting these values until your app is ready for production. The OEMs you support will help you configure correct values during the application review process.
If a head unit is blocked by protocol version, your app icon will never appear on the head unit's screen. If you configure your app to block by RPC version, it will appear and then quickly disappear. So while blocking with minimumProtocolVersion
is preferable, minimumRPCVersion
allows you more granular control over which RPCs will be present.
lifecycleConfiguration.minimumProtocolVersion = [SDLVersion versionWithMajor:3 minor:0 patch:0]; lifecycleConfiguration.minimumRPCVersion = [SDLVersion versionWithMajor:4 minor:0 patch:0];
lifecycleConfiguration.minimumProtocolVersion = SDLVersion(major: 3, minor: 0, patch: 0) lifecycleConfiguration.minimumRPCVersion = SDLVersion(major: 4, minor: 0, patch: 0)
If you are blocking by vehicle type and you are connected over RPC v7.1+, your app icon will never appear on the head unit's screen. If you are connected over RPC v7.0 or below, it will appear and then quickly disappear. To implement this type of blocking, you need to implement the SDLManager delegate. You will then implement the optional didReceiveSystemInfo
method and return YES
if you want to continue the connection and NO
if you wish to disconnect. See the section example implementation of a proxy class for an example.
A lock screen is used to prevent the user from interacting with the app on the smartphone while they are driving. When the vehicle starts moving, the lock screen is activated. Similarly, when the vehicle stops moving, the lock screen is removed. You must implement a lock screen in your app for safety reasons. Any application without a lock screen will not get approval for release to the public.
The SDL SDK can take care of the lock screen implementation for you, automatically using your app logo and the connected vehicle logo. If you do not want to use the default lock screen, you can implement your own custom lock screen.
For more information, please refer to the Adding the Lock Screen section; for this guide we will be using SDLLockScreenConfiguration
's basic enabledConfiguration
.
[SDLLockScreenConfiguration enabledConfiguration]
SDLLockScreenConfiguration.enabled()
A logging configuration is used to define where and how often SDL will log. It will also allow you to set your own logging modules and filters. For more information about setting up logging, see the logging guide.
[SDLLogConfiguration defaultConfiguration]
SDLLogConfiguration.default()
The file manager configuration allows you to configure retry behavior for uploading files and images. The default configuration attempts one re-upload, but will fail after that.
[SDLFileManagerConfiguration defaultConfiguration];
SDLFileManagerConfiguration.default()
The SDLConfiguration
class is used to set the lifecycle, lock screen, logging, and optionally (dependent on if you are a Navigation or Projection app) streaming media configurations for the app. Use the lifecycle configuration settings above to instantiate a SDLConfiguration
instance.
SDLConfiguration* configuration = [[SDLConfiguration alloc] initWithLifecycle:lifecycleConfiguration lockScreen:[SDLLockScreenConfiguration enabledConfiguration] logging:[SDLLogConfiguration defaultConfiguration] fileManager:nil encryption:nil];
let configuration = SDLConfiguration(lifecycle: lifecycleConfiguration, lockScreen: .enabled(), logging: .default(), fileManager: nil, encryption: nil)
Now you can use the SDLConfiguration
instance to instantiate the SDLManager
.
self.sdlManager = [[SDLManager alloc] initWithConfiguration:configuration delegate:self];
sdlManager = SDLManager(configuration: configuration, delegate: self)
The manager should be started as soon as possible in your application's lifecycle. We suggest doing this in the didFinishLaunchingWithOptions()
method in your AppDelegate
class. Once the manager has been initialized, it will immediately start watching for a connection with the remote system. The manager will passively search for a connection with a SDL Core during the entire lifespan of the app. If the manager detects a connection with a SDL Core, the startWithReadyHandler
will be called.
Create a new function in the ProxyManager
class called connect
.
- (void)connect { [self.sdlManager startWithReadyHandler:^(BOOL success, NSError * _Nullable error) { if (success) { // Your app has successfully connected with the SDL Core } }]; }
func connect() { // Start watching for a connection with a SDL Core sdlManager.start { (success, error) in if success { // Your app has successfully connected with the SDL Core } } }
In production, your app will be watching for connections using iAP, which will not use any more battery power than normal.
If the connection is successful, you can start sending RPCs to the SDL Core. However, some RPCs can only be sent when the HMI is in the FULL
or LIMITED
state. If the SDL Core's HMI is not ready to accept these RPCs, your requests will be ignored. If you want to make sure that the SDL Core will not ignore your RPCs, use the SDLManagerDelegate
methods in the next section.
The ProxyManager
class should conform to the SDLManagerDelegate
protocol. This means that the ProxyManager
class must implement the following required methods:
managerDidDisconnect
This function is called when the proxy disconnects from the SDL Core. Do any cleanup you need to do in this function.hmiLevel:didChangeToLevel:
This function is called when the HMI level changes for the app. The HMI level can be FULL
, LIMITED
, BACKGROUND
, or NONE
. It is important to note that most RPCs sent while the HMI is in BACKGROUND
or NONE
mode will be ignored by the SDL Core. For more information, please refer to Understanding Permissions.In addition, there are several optional methods:
audioStreamingState:didChangeToState:
Called when the audio streaming state of this application changes on the remote system. For more information, please refer to Understanding Permissions.videoStreamingState:didChangeToState:
Called when the video streaming state of this application changes on the remote system. For more information, please refer to Understanding Permissions.systemContext:didChangeToContext:
Called when the system context (i.e. a menu is open, an alert is visible, a voice recognition session is in progress) of this application changes on the remote system. For more information, please refer to Understanding Permissions.managerShouldUpdateLifecycleToLanguage:hmiLanguage:
Called when the module's HMI language or voice recognition language does not match the language
set in the SDLLifecycleConfiguration
but does match a language included in languagesSupported
. If desired, you can customize the appName
, the shortAppName
, and ttsName
for the head unit's current language. For more information about supporting more than one language in your app please refer to Getting Started/Adapting to the Head Unit Language.didReceiveSystemInfo
Called when the module receives vehicle information, which is before RPC connection on RPC v7.1+ and after RPC connection on RPC v7.0 or below. Returning YES
will continue the connection, and returning NO
will cause your app to disconnect from the module.The following code snippet has an example of setting up both a TCP and iAP connection.
// ProxyManager.h #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface ProxyManager : NSObject + (instancetype)sharedManager; - (void)start; @end NS_ASSUME_NONNULL_END // ProxyManager.m #import <SmartDeviceLink/SmartDeviceLink.h> NS_ASSUME_NONNULL_BEGIN static NSString* const AppName = @"<#App Name#>"; static NSString* const AppId = @"<#App Id#>"; @interface ProxyManager () <SDLManagerDelegate> @property (nonatomic, strong) SDLManager* sdlManager; @end @implementation ProxyManager + (instancetype)sharedManager { static ProxyManager *sharedManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedManager = [[ProxyManager alloc] init]; }); return sharedManager; } - (instancetype)init { self = [super init]; if (!self) { return nil; } // Used for USB Connection SDLLifecycleConfiguration* lifecycleConfiguration = [SDLLifecycleConfiguration defaultConfigurationWithAppName:AppName fullAppId:AppId]; // Used for TCP/IP Connection // SDLLifecycleConfiguration* lifecycleConfiguration = [SDLLifecycleConfiguration debugConfigurationWithAppName:AppName fullAppId:AppId ipAddress:@"<#IP Address#>" port:<#Port#>]; UIImage* appImage = [UIImage imageNamed:@"<#AppIcon Name#>"]; if (appImage) { SDLArtwork* appIcon = [SDLArtwork persistentArtworkWithImage:appImage name:@"<#Name to Upload As#>" asImageFormat:SDLArtworkImageFormatJPG /* or SDLArtworkImageFormatPNG */]; lifecycleConfiguration.appIcon = appIcon; } lifecycleConfiguration.shortAppName = @"<#Shortened App Name#>"; lifecycleConfiguration.appType = [SDLAppHMIType MEDIA]; SDLConfiguration* configuration = [SDLConfiguration configurationWithLifecycle:lifecycleConfiguration lockScreen:[SDLLockScreenConfiguration enabledConfiguration] logging:[SDLLogConfiguration defaultConfiguration] fileManager:[SDLFileManager defaultConfiguration]]; self.sdlManager = [[SDLManager alloc] initWithConfiguration:configuration delegate:self]; return self; } - (void)connect { [self.sdlManager startWithReadyHandler:^(BOOL success, NSError * _Nullable error) { if (success) { // Your app has successfully connected with the SDL Core } }]; } #pragma mark SDLManagerDelegate - (void)managerDidDisconnect { NSLog(@"Manager disconnected!"); } - (void)hmiLevel:(SDLHMILevel *)oldLevel didChangeToLevel:(SDLHMILevel *)newLevel { NSLog(@"Went from HMI level %@ to HMI Level %@", oldLevel, newLevel); } - (BOOL)didReceiveSystemInfo:(SDLSystemInfo *)systemInfo { NSLog(@"Connected to system: %@", systemInfo); return YES; } @end NS_ASSUME_NONNULL_END
import SmartDeviceLink class ProxyManager: NSObject { private let appName = "<#App Name#>" private let appId = "<#App Id#>" // Manager fileprivate var sdlManager: SDLManager! // Singleton static let sharedManager = ProxyManager() private override init() { super.init() // Used for USB Connection let lifecycleConfiguration = SDLLifecycleConfiguration(appName: appName, fullAppId: appId) // Used for TCP/IP Connection // let lifecycleConfiguration = SDLLifecycleConfiguration(appName: appName, fullAppId: appId, ipAddress: "<#IP Address#>", port: <#Port#>) // App icon image if let appImage = UIImage(named: "<#AppIcon Name#>") { let appIcon = SDLArtwork(image: appImage, name: "<#Name to Upload As#>", persistent: true, as: .JPG /* or .PNG */) lifecycleConfiguration.appIcon = appIcon } lifecycleConfiguration.shortAppName = "<#Shortened App Name#>" lifecycleConfiguration.appType = .media let configuration = SDLConfiguration(lifecycle: lifecycleConfiguration, lockScreen: .enabled(), logging: .default(), fileManager: .default()) sdlManager = SDLManager(configuration: configuration, delegate: self) } func connect() { // Start watching for a connection with a SDL Core sdlManager.start { (success, error) in if success { // Your app has successfully connected with the SDL Core } } } } //MARK: SDLManagerDelegate extension ProxyManager: SDLManagerDelegate { func managerDidDisconnect() { print("Manager disconnected!") } func hmiLevel(_ oldLevel: SDLHMILevel, didChangeToLevel newLevel: SDLHMILevel) { print("Went from HMI level \(oldLevel) to HMI level \(newLevel)") } func didReceiveSystemInfo(_ systemInfo: SDLSystemInfo) -> Bool { print("Connected to system: \(systemInfo)") return true } }
You should now be able to connect to a head unit or emulator. For more guidance on connecting, see Connecting to an Infotainment System. To start building your app, learn about designing your interface. Please also review the best practices for building an SDL app.
View on GitHub.com