Optimal In-App Remote SDK for iOS は、iOS アプリの遠隔支援を実現するための開発キットです。 この SDK をアプリに組み込むことで、そのアプリを Optimal Remote で遠隔支援できるようになります。
- アプリ動作環境
- iOS 12 〜 iOS 18
- 上記 OS で動作している iPhone または iPad
- 英語、日本語
- 上記以外の言語環境では英語表記になります
- インターネットに接続できるネットワーク環境
- 開発環境
- Xcode 16.0 以降
SDK を組み込んだアプリの画面をオペレーターがリアルタイムに閲覧できます。
SDK を組み込んだアプリをオペレーターが操作できます。
SDK を組み込んだアプリの画面にオペレーターが赤ペンで書き込むことでユーザーに操作を指示することができます。
SDK を組み込んだアプリの画面にオペレーターから指マークを表示することでユーザーに操作を指示することができます。
オペレーターがユーザーと VoIP で音声通話しながら遠隔支援を効果的に実施することができます。
以下の手順を進める前に、開発者登録をしていただき以下をご用意ください。
- SDK を利用するためのプロファイル・キーペア
- オペレーターツール (Windows 版)
- オペレーターツールを利用するためのアカウント (ID・パスワード)
ZIP としてダウンロードすると OptimalRemote.xcframework の構成が不正になるので Git レポジトリとしてチェックアウトしてください。
次にチェックアウトしたディレクトリにある「OptimalRemote.xcframework.zip」を解凍してください。
OptimalRemote.xcframework には、SDK を利用するのに必要なヘッダファイル・静的ライブラリファイルファイルが含まれています。OptimalRemote.xcframework をプロジェクトに追加するには、この Git レポジトリに含まれる OptimalRemote.xcframework ディレクトリを以下を参考に追加してください。
- Embedding Frameworks In An App : Embedding a Framework in iOS, macOS, watchOS, and tvOS Apps
- Project Navigator Help: Adding an Existing File or Folder
OptimalRemoteResources ディレクトリには、SDK を利用するのに必要な文字列ファイル・画像ファイル一式が含まれています。OptimalRemoteResources ディレクトリをプロジェクトに追加するには、この Git レポジトリに含まれる OptimalRemoteResources ディレクトリを以下を参考に追加してください。
SDK を利用したアプリをビルドするには、以下の Framework へのリンクを追加する必要があります。
- AudioToolbox.framework
- AVFoundation.framework
- CoreMedia.framework
- CoreVideo.framework
- OpenGLES.framework
- SystemConfiguration.framework
- Security.framework
- libsqlite3.tbd
SDK はカテゴリクラスを利用しているため、リンカフラグに-ObjC
を追加してビルドする必要があります。また-lc++ -lstdc++ -L$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME)
を追加してビルドする必要があります。リンカフラグを追加するには以下を参考にしてください。
Build Settings
のLibrary Search Paths
に$(SDKROOT)/usr/lib/swift
を追加してください。- SDK を利用するアプリの
Minimum Deployments
がiOS 12.1
以下の場合、Build Settings
のRunpath Search Paths
に/usr/lib/swift
を追加してください。
SDK を利用するには、いくらかお決まりのコードを記述する必要があります。
アプリが端末の画面対応に対応していない場合、以下の対応は不要です。
SDK が表示する画面は、keyWindow とは別のウィンドウに表示されます。そのため、端末の画面対応に手動で対応する必要があります。
App-Based Life-Cycle のアプリの場合、UIApplicationDelegate
プロトコルの application:willChangeStatusBarOrientation:duration:
メソッドに以下のようにコードを追加することで対応することができます。
Swift
...
// 1. SDK を利用するためのヘッダをインポートする
import OptimalRemote
...
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(
_ application: UIApplication,
willChangeStatusBarOrientation newStatusBarOrientation: UIInterfaceOrientation,
duration: TimeInterval
) {
...
// 2. 画面が回転した時に SDK が表示する画面を回転させる
ORIAWindow.setOrientation(newStatusBarOrientation, withDuration: duration)
}
}
...
Objective-C
...
// 1. SDK を利用するためのヘッダをインポートする
#import "OptimalRemote/OptimalRemote.h"
...
- (void)application:(UIApplication *)application willChangeStatusBarOrientation:
(UIInterfaceOrientation)newStatusBarOrientation duration:(NSTimeInterval)duration {
...
// 2. 画面が回転した時に SDK が表示する画面を回転させる
[ORIAWindow setOrientation:newStatusBarOrientation withDuration:duration];
}
...
Scene-Based Life-Cycle のアプリの場合、UIWindowSceneDelegate
プロトコルの windowScene:didUpdateCoordinateSpace:interfaceOrientation:traitCollection:
メソッドに以下のようにコードを追加することで対応することができます。
Swift
...
// 1. SDK を利用するためのヘッダをインポートする
import OptimalRemote
...
func windowScene(
_ windowScene: UIWindowScene,
didUpdate previousCoordinateSpace: any UICoordinateSpace,
interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation,
traitCollection previousTraitCollection: UITraitCollection
) {
...
// 2. 画面が回転した時に SDK が表示する画面を回転させる
ORIAWindow.setOrientation(windowScene.interfaceOrientation, withDuration: 0)
}
...
Objective-C
...
// 1. SDK を利用するためのヘッダをインポートする
#import "OptimalRemote/OptimalRemote.h"
...
- (void)windowScene:(UIWindowScene *)windowScene
didUpdateCoordinateSpace:(id<UICoordinateSpace>)previousCoordinateSpace
interfaceOrientation:(UIInterfaceOrientation)previousInterfaceOrientation
traitCollection:(UITraitCollection *)previousTraitCollection {
...
// 2. 画面が回転した時に SDK が表示する画面を回転させる
[ORIAWindow setOrientation:[windowScene interfaceOrientation] withDuration:0];
}
...
App-Based Life-Cycle や Scene-Based Life-Cycle については以下を参考にしてください。
ここでは例として、UIViewController
クラスの派生クラスの viewDidLoad
メソッドで ORIASession
クラスのインスタンスを作ります。ORIASession
クラスは、SDK で iOS アプリの遠隔支援を実現するためのコアクラスのひとつです。
Swift
...
// 3. SDK を利用するためのヘッダをインポートする
import OptimalRemote
...
class XxxViewController: UIViewController, ORIASessionControllerAppDelegate {
...
// 5. ORIASession の制御クラスをプロパティに追加する
var controller: ORIASessionController? = nil
// 6. ORIASession クラスをプロパティに追加する
var session: ORIASession? = nil
...
override func viewDidLoad() {
super.viewDidLoad()
...
// 7. .profile の内容を以下に貼り付ける
let PROFILE = "XXXXXXXX"
// 8. .key の内容を以下に貼り付ける
let KEY = "XXXXXXXX"
// 9. ORIASession の制御クラスのインスタンスを作る
self.controller = ORIASessionController.defaultController()
self.controller?.appDelegate = self
// 10. ORIASession クラスのインスタンスを作る
self.session = ORIASession.sessionForProfile(PROFILE, signedBy: KEY)
// VoIP を有効にする (既定では NO)
self.session?.voiceChatEnabled = true
// ヘッドフォンがない場合にスピーカーから音声を出力する (既定では NO)
self.session?.voiceChatOverridesSpeakerWhenNoHeadphones = true
self.session?.delegate = self.controller
self.session?.loadDefaultPointerImages()
}
...
}
Objective-C
...
// 3. SDK を利用するためのヘッダをインポートする
#import "OptimalRemote/OptimalRemote.h"
...
// 4.
@interface XxxViewController () <ORIASessionControllerAppDelegate>
...
// 5. ORIASession の制御クラスをプロパティに追加する
@property (nonatomic, strong) ORIASessionController *controller;
// 6. ORIASession クラスをプロパティに追加する
@property (nonatomic, strong) ORIASession *session;
...
@end
...
- (void)viewDidLoad {
[super viewDidLoad];
...
// 7. .profile の内容を以下に貼り付ける
NSString *PROFILE = @"XXXXXXXX";
// 8. .key の内容を以下に貼り付ける
NSString *KEY = @"XXXXXXXX";
// 9. ORIASession の制御クラスのインスタンスを作る
self.controller = [ORIASessionController defaultController];
self.controller.appDelegate = self;
// 10. ORIASession クラスのインスタンスを作る
self.session = [ORIASession sessionForProfile:PROFILE signedBy:KEY];
// VoIP を有効にする (既定では NO)
self.session.voiceChatEnabled = YES;
// ヘッドフォンがない場合にスピーカーから音声を出力する (既定では NO)
self.session.voiceChatOverridesSpeakerWhenNoHeadphones = YES;
self.session.delegate = self.controller;
[self.session loadDefaultPointerImages];
}
ここでは例として、UIViewController
クラスの派生クラスが helpMeButton
という UIButton
クラスのプロパティを持っていると仮定し、そのボタンがタップされたら ORIASession を開始する、というコードを追加します。
Swift
...
// 11. ORIASession が開始したときのメソッド
func controllerDidOpen(_ controller: ORIASessionController) {
// ORIASession が完了するまでボタンを無効化する
self.helpButton.isEnabled = false
}
// 12. ORIASession が完了したときのメソッド
func controllerDidComplete(
_ controller: ORIASessionController,
remoteConnectionHasEstablished: Bool
) {
// ORIASession が完了したのでボタンを有効化する
self.helpButton.isEnabled = true
// 完了画面を表示する
if remoteConnectionHasEstablished {
ORIAUISplashWindow.showForCompletion()
}
}
// 13. helpMeButton がタップされたときのメソッド
@IBAction func helpMeButtonDidTouchUpInside(_ sender: UIButton) {
// 開始画面を表示して ORIASession を開始する
if self.controller?.canOpen == true {
ORIAUISplashWindow.showWithBlock {
self.session?.open()
}
}
}
...
Objective-C
...
// 11. ORIASession が開始したときのメソッド
- (void)controllerDidOpen:(ORIASessionController *)controller {
// ORIASession が完了するまでボタンを無効化する
self.helpMeButton.enabled = NO;
}
// 12. ORIASession が完了したときのメソッド
- (void)controllerDidComplete:(ORIASessionController *)controller remoteConnectionHasEstablished:
(BOOL)remoteConnectionHasEstablished {
// ORIASession が完了したのでボタンを有効化する
self.helpMeButton.enabled = YES;
// 完了画面を表示する
if (remoteConnectionHasEstablished) {
[ORIAUISplashWindow showForCompletion];
}
}
// 13. helpMeButton がタップされたときのメソッド
- (IBAction)helpMeButtonDidTouchUpInside:(id)sender {
// 開始画面を表示して ORIASession を開始する
if (self.controller.canOpen) {
[ORIAUISplashWindow showWithBlock:^{
[self.session open];
}];
}
}
...
iOS 9 から追加された App Transport Security(ATS) へ optim.co.jp とそのサブドメインを例外として設定する必要があります。
次の設定を行うことで SDK の通信が ATS によって中断されることを回避します。 Info.plist の plist 要素へ ATS の設定を追加してください。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>optim.co.jp</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
...
</dict>
</plist>
次の設定を行うことで、iOS アプリがバックグラウンド時に VoIP が切断されることを回避します。 Info.plist の plist 要素へ Background Modes の設定を追加してください。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
...
</dict>
</plist>
これで iOS アプリ側の準備は完了です。
アプリをビルドしたら、インターネットに接続された端末でアプリを実行し、helpMeButton
をタップすると「受付番号」が表示されます。オペレーターツールでこの受付番号を入力すると、オペレーターツールとアプリが接続され、オペレーターツールにアプリの画面が表示されます!
iOS 11 以上にて UIAlertView を用いたダイアログ表示を行うと以下の事象が発生します。
- 切断確認ダイアログが表示されない。これに伴い、端末から切断が行えない。
- オペレーターツールから遠隔操作をリクエストした場合に、遠隔操作許可ダイアログが表示されない。これに伴い、遠隔操作が行えない。
原因は keyWindow の設定が意図せず変更されており、これの影響を受けて上記の事象が発生しております。 UIAlertView は iOS 8 以降は非推奨となっているモジュールで、ダイアログの表示には UIAlertController を用いることが推奨されています。 UIAlertView
ですので iOS 11 以上に対応される場合には、ダイアログ表示には UIAlertController を使用し、 iOS 11 未満にも対応される場合には必要に応じて分岐の処理を実装いただけますようお願い致します。
以上でチュートリアルは完了です。うまくオペレーターツールと接続できない場合、お問い合わせください。
2024 年春以降、新規アプリまたはアプリのアップデートを App Store に提出する場合、SDK の Privacy Manifest を含める必要があります。Privacy Manifest については以下の資料を参考にしてください。
この SDK は Privacy Manifest(PrivacyInfo.xcprivacy)を含んでいますが、SDK を組み込んだアプリに SDK の Privacy Manifest が含まれていないことを確認しています。
そのため、アプリの Privacy Manifest に以下の項目の記載をお願い致します。
アプリまたはサードパーティ SDK が、App Tracking Transparency フレームワークで定義されているトラッキングのためにデータを使用するかどうかを示すブール値です。
SDK では、トラッキングのためにデータを使用していないため NO(false) となります。
アプリまたはサードパーティ SDK が接続する、トラッキングを行うインターネットドメインの一覧を示す文字列の配列です。
SDK ではトラッキングしていないため、空となります。
アプリまたはサードパーティ SDK が収集するデータタイプを記述する辞書の配列です。
SDK では、以下のデータを収集しています。以下のキーと値については、こちらを参照してください。
NSPrivacyCollectedDataType | NSPrivacyCollectedDataTypeLinked | NSPrivacyCollectedDataTypeTracking | NSPrivacyCollectedDataTypePurposes |
---|---|---|---|
NSPrivacyCollectedDataTypeAudioData | false | false | NSPrivacyCollectedDataTypePurposeAppFunctionality |
NSPrivacyCollectedDataTypeCustomerSupport | false | false | NSPrivacyCollectedDataTypePurposeAppFunctionality |
アプリまたはサードパーティ SDK がアクセスする API タイプのうち、アクセスに理由が必要な API として指定されているものを記述する辞書の配列です。
SDK では、以下の API タイプにアクセスしています。以下のキーと値については、こちらを参照してください。
NSPrivacyAccessedAPIType | NSPrivacyAccessedAPITypeReasons |
---|---|
NSPrivacyAccessedAPICategoryUserDefaults | CA92.1 |
NSPrivacyAccessedAPICategoryFileTimestamp | C617.1 |
NSPrivacyAccessedAPICategorySystemBootTime | 35F9.1 |
NSPrivacyAccessedAPICategoryDiskSpace | E174.1 |
新規アプリまたはアプリのアップデートを App Store 提出する場合、アプリのプライバシー方針(サードパーティパートナーのコードをアプリに組み込む場合は、そのパートナーの方針も含む)に関する情報が必要となります。
アプリの提出時には、以下の「データの種類」と「データの使用」について記載してください。
データの種類 | データの使用 |
---|---|
オーディオデータ | アプリの機能 |
カスタマーサポート | アプリの機能 |
上記の「1. OptimalRemote.xcframework ディレクトリをプロジェクトに追加する」にて、プロジェクトに追加する xcframework を OptimalRemoteNoVoIP.xcframework としていただくことで音声通話機能を除いた SDK を利用できます。