- 投稿日:2019-02-27T23:00:00+09:00
【SwiftChaining】UIとバインディングする
SwiftChainingの解説記事その2です。
前回の記事はこちら -> Swiftでデータバインディングするライブラリを作ったiOSアプリでデータバインディングをするからには、UIと値を同期できないと始まりません。
今回は例として、
UISwitch
とバインディングする例を紹介します。UISwitchとバインディングする
UISwitch
のスイッチのON・OFFの状態であるisOn
と、前回の記事で紹介したValueHolder
の値をバインディングしてみます。コードは以下の通りです。import UIKit import Chaining class ExampleViewController: UIViewController { @IBOutlet weak var mySwitch: UISwitch! lazy var isOnAdapter = { KVOAdapter(self.mySwitch, keyPath: \UISwitch.isOn )}() lazy var valueChangedAdapter = { UIControlAdapter(self.mySwitch, events: .valueChanged) }() let isOnHolder = ValueHolder(true) var pool = ObserverPool() override func viewDidLoad() { super.viewDidLoad() self.pool += self.isOnHolder.chain().sendTo(self.isOnAdapter).sync() self.pool += self.valueChangedAdapter.chain().map { $0.isOn }.sendTo(self.isOnHolder).end() } }解説
KVOに対応したプロパティを
SwiftChaining
で扱えるようにするためのクラスがKVOAdapter
です。KVOAdapter(self.mySwitch, keyPath: \UISwitch.isOn )対象となるオブジェクトとプロパティの
keyPath
を渡して生成し、値を監視したり更新したりできます。
ValueHolder
同士の場合と同じく、以下のようにsendTo
にKVOAdapter
を渡すことでValueHolder
からの値を受け取ることができます。self.isOnHolder.chain().sendTo(self.isOnAdapter).sync()逆に
KVOAdapter
からisOn
の変更を受け取るのには、以下のように逆方向に繋げれば良いのですが、今回の場合、残念ながらうまくいきません。// うまくいかない self.isOnAdapter.chain().sendTo(self.isOnHolder).sync()
UISwitch
をタップしてON・OFFの状態を変更したときはKVOでは通知されないので、UIControl
のイベントを監視するようにします。
UIControl
のイベントを監視するクラスとしてUIControlAdapter
を用意しています。UIControlAdapter(self.mySwitch, events: .valueChanged)
events
で監視したいUIControl
のイベントを指定します。
UIControlAdapter
からイベントを監視してValueHolder
に値を反映させるには以下のように書きます。self.valueChangedAdapter.chain().map { $0.isOn }.sendTo(self.isOnHolder).end()
UIControlAdapter
から送られる値はUIControl
(ここではUISwitch
)なので途中でmap
関数を使ってisOn
の値に変換してValueHolder
が受け取れるようにしています。このように、それぞれ変更があった時に双方向に値を送り合うことで、値を同期することができるという感じになります
SwiftChaining
では値の送信が循環しても延々とループにならないように内部でロックしているので、それこそchain
したオブジェクトをsendTo
にそのまま渡すようなことをしてもハングアップしたりはしません。ちなみに、今回のコードで出てきたクラスに
ObserverPool
というものがあります。var pool = ObserverPool() ... self.pool += self.isOnHolder.chain().sendTo(self.isOnAdapter).sync()複数のObserverを保持しておかないといけない時に別々に管理するのは面倒なので、Observerをまとめて保持しておけるものとして用意しています。
ObserverPool
を破棄したりinvalidate()
を呼んだら、保持しているObserverがまとめて無効になります。Observerの追加を関数でやると扱いにくそうだったので、
+=
で追加できるようにしています。補足
なお今回紹介したコードだと、
ValueHolder
からUISwitch
へ値を反映させる時にアニメーションをしなかったり、UISwitch
のisOn
をコードで直接変更した時にValueHolder
へ値が反映されません。それらが必要であれば、さらにコードを追加・変更することになるでしょう。
SwiftChaining
はUIKit
をラップして簡単にするものではないので、このようなUIKit
の対応は通常と変わらず考える必要があります。
- 投稿日:2019-02-27T23:00:00+09:00
【SwiftChaining】 UIとバインディングする
SwiftChainingの解説記事その2です。
前回の記事はこちら -> Swiftでデータバインディングするライブラリを作ったiOSアプリでデータバインディングをするからには、UIと値を同期できないと始まりません。
今回は例として、
UISwitch
とバインディングする例を紹介します。UISwitchとバインディングする
UISwitch
のスイッチのON・OFFの状態であるisOn
と、前回の記事で紹介したValueHolder
の値をバインディングしてみます。コードは以下の通りです。import UIKit import Chaining class ExampleViewController: UIViewController { @IBOutlet weak var mySwitch: UISwitch! lazy var isOnAdapter = { KVOAdapter(self.mySwitch, keyPath: \UISwitch.isOn )}() lazy var valueChangedAdapter = { UIControlAdapter(self.mySwitch, events: .valueChanged) }() let isOnHolder = ValueHolder(true) var pool = ObserverPool() override func viewDidLoad() { super.viewDidLoad() self.pool += self.isOnHolder.chain().sendTo(self.isOnAdapter).sync() self.pool += self.valueChangedAdapter.chain().map { $0.isOn }.sendTo(self.isOnHolder).end() } }解説
KVOに対応したプロパティを
SwiftChaining
で扱えるようにするためのクラスがKVOAdapter
です。KVOAdapter(self.mySwitch, keyPath: \UISwitch.isOn )対象となるオブジェクトとプロパティの
keyPath
を渡して生成し、値を監視したり更新したりできます。
ValueHolder
同士の場合と同じく、以下のようにsendTo
にKVOAdapter
を渡すことでValueHolder
からの値を受け取ることができます。self.isOnHolder.chain().sendTo(self.isOnAdapter).sync()逆に
KVOAdapter
からisOn
の変更を受け取るのには、以下のように逆方向に繋げれば良いのですが、今回の場合、残念ながらうまくいきません。// うまくいかない self.isOnAdapter.chain().sendTo(self.isOnHolder).sync()
UISwitch
をタップしてON・OFFの状態を変更したときはKVOでは通知されないので、UIControl
のイベントを監視するようにします。
UIControl
のイベントを監視するクラスとしてUIControlAdapter
を用意しています。UIControlAdapter(self.mySwitch, events: .valueChanged)
events
で監視したいUIControl
のイベントを指定します。
UIControlAdapter
からイベントを監視してValueHolder
に値を反映させるには以下のように書きます。self.valueChangedAdapter.chain().map { $0.isOn }.sendTo(self.isOnHolder).end()
UIControlAdapter
から送られる値はUIControl
(ここではUISwitch
)なので途中でmap
関数を使ってisOn
の値に変換してValueHolder
が受け取れるようにしています。このように、それぞれ変更があった時に双方向に値を送り合うことで、値を同期することができるという感じになります
SwiftChaining
では値の送信が循環しても延々とループにならないように内部でロックしているので、それこそchain
したオブジェクトをsendTo
にそのまま渡すようなことをしてもハングアップしたりはしません。ちなみに、今回のコードで出てきたクラスに
ObserverPool
というものがあります。var pool = ObserverPool() ... self.pool += self.isOnHolder.chain().sendTo(self.isOnAdapter).sync()複数のObserverを保持しておかないといけない時に別々に管理するのは面倒なので、Observerをまとめて保持しておけるものとして用意しています。
ObserverPool
を破棄したりinvalidate()
を呼んだら、保持しているObserverがまとめて無効になります。Observerの追加を関数でやると扱いにくそうだったので、
+=
で追加できるようにしています。補足
なお今回紹介したコードだと、
ValueHolder
からUISwitch
へ値を反映させる時にアニメーションをしなかったり、UISwitch
のisOn
をコードで直接変更した時にValueHolder
へ値が反映されません。それらが必要であれば、さらにコードを追加・変更することになるでしょう。
SwiftChaining
はUIKit
をラップして簡単にするものではないので、このようなUIKit
の対応は通常と変わらず考える必要があります。
- 投稿日:2019-02-27T21:50:51+09:00
[Flutter] Flutter 1.2発表。
こんにちは!
MWC2019で、Flutter1.2が発表されました。Tag-Trendsによると、Flutterは1月17日頃からの上がってます。
Flutter1.2からの変更点
1.Improved stability, performance and quality of the core framework.
2.Work to polish visual finish and functionality of existing widgets.
3.New web-based tooling for developers building Flutter applications.The plug-in team has also been busy in Flutter 1.2, with work well underway to support in-app purchases, as well as many bug fixes for video player, webview, and maps. And thanks to a pull request contributed by a developer from Intuit, we now have support for Android App Bundles, a new packaging format that helps in reducing app size and enables new features like dynamic delivery for Android apps.
Dart DevTools
https://flutter.github.io/devtools/
1.A widget inspector, which enables visualization and exploration of the tree hierarchy that Flutter uses for rendering.
2.A timeline view that helps you diagnose your application at a frame-by-frame level, identifying rendering and computational work that may cause animation 'jank' in your apps.
3.A full source-level debugger that lets you step through code, set breakpoints and investigate the call stack.
4.A logging view that shows activity you log from your application as well as network, framework and garbage collection events.Dart2.2の発表
2011に発表された、DartはMWC2019を迎えてDart2.2になります。
Flutter Create
これすごいですよ! 参加してみませんか?
iMac Proも貰えるようです。
- 投稿日:2019-02-27T19:18:36+09:00
【Xcode】開発途中に手動でディレクトリ構成を変更する
概要
Xcodeでのディレクトリ構成変更方法についてです。初歩的な話ですね。
開発してる途中で「ディレクトリの構成変えたくなったんだけどどうしたらええんや?!」となって調べても今のバージョンで詳しいやり方が載っている記事等がなかったので書いてみました。
僕はちゃんと知らなくて適当にやったらビルドエラーでめちゃくちゃはまってしまったので、同じようにはまった人のためになれば嬉しいです。自分の調べ方が悪かっただけで、もっとしっかり説明されている記事等があるかもしれないのであれば教えていただけると嬉しいです!
環境
【Xcode】 Version 10.1
正しいやり方
この状態からディレクトリの構成を変更していきます!
今回はMVCモデルに沿ってディレクトリ構成を変更してみます。
まず、Finderにて新規フォルダを作成します。
作成するのはModel
,View
,Controller
の3つのフォルダです。
作成したらそれぞれのフォルダにファイルを振り分けていきます。
先ほどまでなんともなかったファイルたちが赤くなっていますね。
赤い状態は「ファイルの参照先にファイルがなくなってるよ〜」と知らせてくれているものです。一つファイルを選択してinspectorのFullPathをみてみるとファイルのパスが指定されています。がしかし、そのパスにファイルが存在しないので怒られるんですね。
ということでこの赤くなっているファイルを消しても参照が消えるだけなので削除しちゃいます。
最後に先ほど作成したフォルダたちをXcode上に追加していきます。
FinderからDrag&Dropで追加すると以下のような表示が出ます。
大事なのがここで
Create Groups
を選択することです。
この状態でFinishを押すと黄色のフォルダがXcode上に現れます。
他のフォルダも同様に入れていき、ビルドができるかどうかの確認が完了すれば…
ディレクトリ構成の変更完了です!
誤ったやり方
僕が何も知らずにやった方法です。
Finder場でフォルダ作成してファイルを分けるところまでは同じ。
この後
Create Group
ではなくCreate folder References
を選択すると以下の感じになります。
一見大丈夫そうですが、ビルドして実行するとエラーが生じるようになります。
あくまでも参照はファイルに対して行うものであって、フォルダに対して行うことはできないのだと思います。(見当違いなこと言っていたらごめんなさい)
最後に
他にこういうやり方あるよ!とかこんなやり方しなくてもこうやりゃいいじゃろが等あれば教えていただけると幸せになれます!
以上です。
- 投稿日:2019-02-27T18:25:32+09:00
Twilio Voice Swift Quickstart for iOS(日本語訳)
Twilio Voice Swift Quickstart for iOS
iOSでVoiceを始めよう:
- Quickstart - クイックスタートアプリを実行する
- Access Tokens - アクセストークンを使用
- Managing Audio Interruptions - 音声の中断の管理
- Managing Push Credentials - プッシュ認証情報の管理
- More Documentation - Voice iOS SDKに関連するその他のドキュメント
- Issues and Support - ファイリングの問題と一般的なサポート
Quickstart
クイックスタートアプリケーションを使い始めるには、次の手順に従います。
手順1〜6により、アプリケーションは電話をかけることができます。
残りの手順7〜10では、アプリケーションはAppleのVoIPサービスを使用してプッシュ通知の形式で着信通話を受信できるようになります。
- TwilioVoice frameworkをインストールする
- Voice API keyを作成する
- アプリで使用するアクセストークンを生成するようにサーバーを構成する
- TwiML applicationを作成する
- アプリケーションサーバーを設定する
- アプリを実行する
- VoIP Service Certificateを作成する
- VoIP Service Certificateを使ってPush Credentialを作成する
- XcodeのプロジェクトにVoIPpush通知の設定をする
- 着信を受ける
- clientからclientに電話をかける
- clientからPSTNに電話をかける
1. TwilioVoice frameworkをインストールする
Carthage
Cartfileに次の行を追加します。
github "twilio/twilio-voice-ios"
carthage bootstrap
を実行します (SDKをアップデートしている場合はcarthage update
を実行します)対象のapplication targetの
Build Phases
タブにて,+
アイコンをクリックして、New Run Script Phase
を選びます。 Run Scriptにshell(ex:/bin/sh
)を指定し , 以下の内容のscriptを追加します:/usr/local/bin/carthage copy-frameworks“Input Files”の下に、使用したいframeworkのパスを追加します:
$(SRCROOT)/Carthage/Build/iOS/TwilioVoice.frameworkCocoapods
quickstartのフォルダ内で
pod install
を実行すると、Cocoapodsがworkspaceを作成します。
また、必ず Cocoapods v1.0 以降を使用してください。
Cocoapodsのインストールが完了し,SwiftVoiceQuickstart.xcworkspace
を開くとSwiftを用いた基本的なquickstartプロジェクトとCallKit quickstartプロジェクトが見つかります。Note: TwilioVoice の最新版を取得するには、
pod repo update master
を実行し CocoaPods Master Spec Repo から最新版を取得する必要があるかもしれません。2. Voice API keyを作成する
Voice API Keys ページに移動して新しいAPI keyを作成してください:
作成した
API_KEY
とAPI_KEY_SECRET
をメモをとってください。 次のステップで必要となります。3. アプリで使用するアクセストークンを生成するようにサーバーを構成する
サーバー用のスタータープロジェクトの1つをダウンロードします。
- voice-quickstart-server-java
- voice-quickstart-server-node
- voice-quickstart-server-php
- voice-quickstart-server-python
サーバーのREADMEに記載されている手順に従って、アプリケーションサーバーをローカルで起動し、パブリックインターネット経由でアクセスできるようにします。consoleから取得できる Twilio Account SID と先ほどメモをとった
API_KEY
とAPI_SECRET
を置き換えてください。ACCOUNT_SID = 'AC***' API_KEY = 'SK***' API_KEY_SECRET = '***'4. TwiML applicationを作成する
次にTwiMLアプリケーションを作成する必要があります。TwiML applicationは TwiML呼び出し先に設定されているパブリックURLを参照します。
iOSアプリがTwilioを呼び出すと、TwilioはこのURLにWebhookを用いてリクエストを送信し、アプリケーションサーバーはTwiMLを生成してResponseを返し、返ってきたResponseに沿ってTwilioは応答します。
TwiMLアプリケーションを作成するには TwiML app pageにアクセスしてください。
新しいTwiML applicationを作成し, アプリケーションサーバーの/makeCall
エンドポイントをVoice Request URLに設定します。(あなたのアプリケーションサーバーがPHPで書かれている場合、最後に.php
拡張子が必要です)。ご覧の通りRequest URLにはngrokのパブリックアドレスを使用しています。
TwiML Applicationの設定を保存し, TwiML Application SID(AP
から始まる長い識別子)を取得します。5. アプリケーションサーバーを設定する
残りの
APP_SID
設定情報をサーバーのコードに入れましょう。ACCOUNT_SID = 'AC***' API_KEY = 'SK***' API_KEY_SECRET = '***' APP_SID = 'AP***'それが終わったら、サーバーを再起動して新しい設定情報を使用します。今度はテストする時が来ました。
ブラウザを開き、アプリケーションサーバーのAccess Tokenを取得するためのURLをご覧ください。
Access Token endpoint:https://{YOUR_SERVER_URL}/accessToken
(あなたのアプリケーションサーバーが、PHPで書かれている場合は、最後に.php拡張子をつける必要があります)
すべてが正しく設定されていれば、長い文字列と数字が表示されるはずです。これがTwilioアクセストークンです。iOSアプリはTwilioに接続するためにこのようなトークンを使用します。6. アプリを実行する
それでは
SwiftVoiceQuickstart.xcworkspace
に戻りましょう。baseURLString
をngrokのパブリックURLで更新します。import UIKit import AVFoundation import PushKit import TwilioVoice let baseURLString = "https://3b57e324.ngrok.io" let accessTokenEndpoint = "/accessToken" let identity = "alice" let twimlParamTo = "to" class ViewController: UIViewController, PKPushRegistryDelegate, TVONotificationDelegate, TVOCallDelegate, AVAudioPlayerDelegate, UITextFieldDelegate {アプリをビルドして実行します
テキストフィールドを空のままにして、callボタンを押して通話を開始します。お祝いメッセージが聞こえます。別のclientまたは番号への発信は、手順11および12で説明されています。切断するには、"Hang Up"をタップします。
7. VoIP Service Certificateを作成する
Programmable Voice SDKはAppleのVoIPサービスを使用して、着信呼び出しを受信したことをアプリケーションに通知します。ユーザーに着信を受けさせる場合は、アプリケーションでVoIPサービスを有効にしてVoIP Services Certificateを生成する必要があります。
Apple Developer portalへアクセスして、以下を行う必要があります:
- 証明書を作成できるようにApple Developer Programの会員になる。
- 対象のApp IDで“Push Notifications”サービスが有効になっていることを確認してください。
- App IDに対応するプロビジョニングプロファイルを作成します。
- Certificates - > Productionに移動し、右上の
+
をクリックして新しい証明書を追加して、このアプリのApple VoIP Services Certificateを作成します。8. VoIP Service Certificateを使ってPush Credentialを作成する
Keychain Accessを使用してVoIP Service Certificateを生成したら、それをTwilioにアップロードして、Twilioがあなたに代わってアプリにプッシュ通知を送信できるようにする必要があります。
VoIPサービス証明書をKeychain Accessから.p12ファイルとしてエクスポートしてから、opensslコマンドを使用して.p12ファイルから証明書と秘密鍵を抽出します。
.p12が書き出しのオプションではない場合は、Keychain Accessの検索バーにvoip
を入力して、証明書を書き出すときに必ず両方の項目を選択してください。$> openssl pkcs12 -in PATH_TO_YOUR_P12 -nocerts -out key.pem $> openssl rsa -in key.pem -out key.pem $> openssl pkcs12 -in PATH_TO_YOUR_P12 -clcerts -nokeys -out cert.pemPush Credentials pageに移動して新しいPush Credentialを作成します。証明書と秘密鍵を貼り付けます。plaintextとしてキーを貼り付ける必要があります:
cert.pem
の-----BEGIN CERTIFICATE-----
から-----END CERTIFICATE-----
まで貼り付ける必要があります。key.pem
の-----BEGIN RSA PRIVATE KEY-----
から-----END RSA PRIVATE KEY-----
まで貼り付ける必要があります。「Sandbox」オプションを必ずチェックしてください。これは重要。生成したVoIP Service Certificateは、本番環境でもAppleのsandbox infrastructureでも使用できます。このチェックボックスをオンにすると、開発プロビジョニングプロファイルに適したApplesandbox infrastructureにプッシュが送信されます。
アプリのストア送信準備が整ったら、「APS Environment:production」でplistを更新し、同じVoIP Certificateを使用して「Sandbox」オプションをチェックせずに別のPush Credentialを作成します。
それでは、サーバーのコードに戻り、Push Credential SIDを更新しましょう。Push Credential SIDがアクセストークンに埋め込まれます。
PUSH_CREDENTIAL_SID = 'CR***'9. Xcodeプロジェクト設定にプッシュ通知の設定をする
プロジェクトの Capabilities タブで,“Push Notifications”を有効化します。
Xcode 8以前では、バックグラウンドモードで “Voice over IP” と “Audio, AirPlay Picture in Picture” の両方の機能を有効にします。Xcode 9以降では、“Audio, AirPlay and Picture in Picture”機能が有効になっていることと、"audio"と"voip"を含む"UIBackgroundModes"がアプリのplistにあることを確認してください。
<key>UIBackgroundModes</key> <array> <string>audio</string> <string>voip</string> </array>10. 電話を受ける
これで着信を受ける準備が整いました。アプリをRebuildして、アプリケーションサーバーのエンドポイント /placeCall にアクセスします。
https://{YOUR_SERVER_URL}/placeCall
(あなたのアプリケーションサーバーがPHPで書かれている場合、最後に.php
拡張子が必要です)
これによりTwilio REST APIリクエストがトリガーされ、モバイルアプリへのインバウンドコールが行われます。アプリが通話を受け付けると、お祝いのメッセージが聞こえます。11. client から client に発信する
clientからclientへの発信を行うには、2つのデバイスでアプリケーションを実行する必要があります。 追加のデバイスでアプリケーションを実行するには、新しいデバイスを登録するときに必ずアクセストークンに別のIDを使用してください。例えば,
identity
をbob
に変更してアプリを実行します。let accessTokenEndpoint = "/accessToken" let identity = "bob" let twimlParamTo = "to"テキストフィールドを使用して通話受信者のIDを指定してから、“Call”ボタンをタップして発信します。
TwilioVoice.call()
メソッドで使用されるTwiMLパラメーターはサーバーで使用されている名前と一致する必要があります。12. client から PSTN に発信する
検証済電話番号を一つ用意します。検証済電話番号はTwilioから外線発信できるあなたの電話番号が使えます。この番号はTwilioに移植されていないので、この電話番号のためにTwilioに支払う必要はありません。
clientから発信するためには, まずhttps://jp.twilio.com/console/phone-numbers/verified から検証済電話番号を取得します。 サーバーコードの
CALLER_NUMBER
を検証済電話番号に置き換えます。サーバーを再起動させて、新しい設定値を使用します。TwiML applicationのVoice Request URLはパブリックなアプリケーションサーバーのエンドポイント/makeCall
を指定する必要があります。Access Tokens
access tokenはサーバーコンポーネントのjwtにより作成されます。
作成されたaccess tokenにはProgrammable Voiceのgrant
、あなたが指定したidentity
、有効期限を設定するtime-to-live
が含まれます。time-to-live
の初期値は1時間でTwilio helper ライブラリを使用して最大24時間まで設定することができます。用途
iOS SDKでは、access tokenは次の目的で使用されます:
TwilioVoice.call(...)
経由で外線発信するTwilioVoice.registerWithAccessToken(...)
とTwilioVoice.unregisterWithAccessToken(...)
経由で着信時のVoIP Push Notificationsの登録、解除。登録されると着信時にTVOCallInvite
経由で承認するか拒否するか選ぶことができます。着信を受けるときはaccess tokenは必要ありません。TVOCallInvite
は内部的にaccess tokenを保持しており、それは私たちのインフラストラクチャにアクセスすることができます。有効期限切れ
前述のように、アクセストークンは最終的に期限切れになります。アクセストークンが期限切れになった場合、私たちのインフラストラクチャは登録時に
TVOCallDelegate
を介してTVOErrorAccessTokenExpired
/20104
エラーまたはcompletionエラーを返します。アクセストークンの有効期限を適切に管理するテクニックは数多くあります:
- 発信呼び出しを行う前に、必ずアクセストークンサーバーから新しいアクセストークンを取得してください。
TVOErrorAccessTokenExpired
/20104
エラーが発生するまでアクセストークンを保持し、エラーが発生した後に新しいトークンを取得してください。- アクセストークンを要求されたときのタイムスタンプと一緒に保存しておくと、サーバーで使用されている「有効期限」に基づいて、トークンがすでに期限切れになっているかどうかを事前に確認できます。
- 発信呼び出しに関連付けられた
UIApplication
またはUIViewController
が作成されるたびにアクセストークンをフェッチします。音声割り込みの管理
iOSのバージョンが異なると AVAudioSession 割り込みの処理方法が若干異なります。このセクションでは、Programmable Voice iOS SDKが音声の中断を管理し、中断が終了した後に通話の音声を再開する方法について説明します。 iOSでは、中断が終了したことを示すために必要な通知が提供されないため、SDKが通話音声を自動的に再開できない場合があります。
Programmable Voice iOS SDKがオーディオの中断をどのように処理するか
- SDKは自身を
TwilioVoice.initialize()
の中でAVAudioSessionInterruptionNotification
のオブザーバーとして登録します。- 通知が発生し、中断の種類が
AVAudioSessionInterruptionTypeBegan
の場合、SDKは自動的にオーディオデバイスを無効にし、アクティブな通話を保留にします。- SDKが
AVAudioSessionInterruptionTypeEnded
で通知を受け取ると、SDKはオーディオデバイスを再度有効にしてアクティブな通話のオーディオを再開します。- iOS 8と9では、
AVAudioSessionInterruptionTypeEnded
による割り込み通知が常に発生するわけではないため、SDKは通話音声を自動的に再開できません。これは既知の問題であり、別の方法はUIApplicationDidBecomeActiveNotification
を使用して中断後にアプリが再びアクティブになったときに音声を再開することです。異なるiOSバージョンでの通知
以下は、音声の中断を引き起こし、Voice SDKの通話中に再開するためのさまざまな手順で受信したシステム通知の一覧です。 (アプリがアクティブなVoice SDK通話中であると仮定します)
シナリオ 中断通知の開始 中断終了の通知 通話の再開? Note PSTN割り込み A.
PSTN割り込み
PSTN着信通話を受け入れる
電話相手が電話を切るiOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11B.
PSTN割り込み
PSTN着信通話を受け入れる
自分が電話を切るiOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11C.
PSTN割り込み
PSTNを拒否iOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11D.
PSTN割り込み
PSTNを無視iOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11E.
PSTN割り込み
自分が応答する前に相手が電話を切るiOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11そのほかのAudio Interruption
(例えばYouTubeアプリ)F.
YouTubeアプリに切り替えてビデオを再生する
ビデオを停止
Voiceアプリに戻るiOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11中断終了通知はiOS 9では発生しません。
iOS 10/11でVoiceアプリに切り替えた後、数秒まで割り込み終了通知は発生しません。AVAudioSessionInterruptionOptionShouldResume
フラグはfalse
です。G.
YouTubeアプリに切り替えてビデオを再生する
ビデオを停止せずにVoiceアプリに戻るiOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11中断終了通知はiOS 9では発生しません。
iOS 10/11でVoiceアプリに切り替えた後、数秒まで割り込み終了通知は発生しません。AVAudioSessionInterruptionOptionShouldResume
フラグはfalse
です。H.
YouTubeアプリに切り替えてビデオを再生する
ホームボタンを2回押してYouTubeアプリを終了する
Voiceアプリに戻るiOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11iOS 9
iOS 10
iOS 11Voiceアプリがアクティブ状態に戻るまで、中断終了通知は発生しません。 AVAudioSessionInterruptionOptionShouldResume
フラグはfalse
です。CallKit
iOS 10以降では、CallKit(統合されている場合)は、一連のデリゲートメソッドを提供することで中断を処理し、アプリケーションが適切なオーディオデバイスの処理と状態の遷移で応答できるようにします。
Notifications & 割り込み中のコールバック
CallKitフレームワークへの呼び出しを報告するときに
CXCallUpdate
オブジェクトのsupportsHolding
フラグを有効にすることで、別のPSTNまたはCallKit対応の呼び出しがあるときに"Hold&Accept" オプションが表示されます。 “Hold&Accept” オプションを押すと、一連のシナリオとコールバックが起こります:
provider:performSetHeldCallAction:
delegateメソッドはCXSetHeldCallAction.isOnHold = YES
で呼び出されます。ここで音声通話を保留にして、操作を実行します。AVAudioSessionInterruptionNotification
はAVAudioSessionの中断が始まったことを示すために起動されます。- CallKitはあなたのアプリのAVAudioSessionを無効にし、
provider:didDeactivateAudioSession:
コールバックを起動します。あなたはTwilioVoice.audioEnabled = NO
を呼び出してSDKオーディオデバイスを無効にする必要があります。- 割り込んだ電話が終了した時、
AVAudioSessionInterruptionNotification
を通知します。システムがそれを通知した時、provider:performSetHeldCallAction:
メソッドを再度呼ぶことで、あなたは中断された通話を再開することができます。Note 割り込みコールがリモートパーティによって切断された場合、このコールバックは実行されません。- AVAudioSessionが再度有効化されます。あなたは
provider:didActivateAudioSession:
メソッド内にて、TwilioVoice.audioEnabled = YES
を行い、再度SDKのaudio deviceを有効化する必要があります。
シナリオ 中断後に音声を再開しますか? Note A.
保留 & 承認
自分が電話を切るiOS 10
iOS 11B.
保留 & 承認
相手が電話を切るiOS 10
iOS 11割り込みが終了しても provider:performSetHeldCallAction:
は呼ばれません。C.
保留 & 承認
システムUIで音声通話に戻るiOS 10
iOS 11D.
拒否するiOS 10
iOS 11割り込まれた通話に応答しないため、通話の中断は発生しません。 E.
無視するiOS 10
iOS 11割り込まれた通話に応答しないため、通話の中断は発生しません。 ケース2の場合、CallKitは
provider:performSetHeldCallAction:
メソッドを呼び出して通話音声を自動的に再開しませんが、システムUIは音声通話がまだ保留中であることを示します。あなたは"Hold"ボタンを使って呼び出しを再開することができます、あるいはCXSetHeldCallAction
を使ってプログラムで保留状態を解除することができます。アプリはまた、ユーザーの混乱を避けるために通話の「保留」状態を示すためにUI状態を更新する責任があります。// Resume call audio programmatically after interruption CXSetHeldCallAction *setHeldCallAction = [[CXSetHeldCallAction alloc] initWithCallUUID:self.call.uuid onHold:holdSwitch.on]; CXTransaction *transaction = [[CXTransaction alloc] initWithAction:setHeldCallAction]; [self.callKitCallController requestTransaction:transaction completion:^(NSError *error) { if (error) { NSLog(@"Failed to submit set-call-held transaction request"); } else { NSLog(@"Set-call-held transaction successfully done"); } }];Push認証情報の管理
push認証情報はプッシュ通知チャネルのレコードです。iOSの場合、このpush認証情報はAPNS VoIPのプッシュ通知チャネルレコードです。push認証情報はMobile Push Credentialsで管理されます。
iOS SDKの
TwilioVoice.registerWithAccessToken:deviceToken:completion
を介して登録が実行されるたびに、JWTベースのアクセストークンよりidentity
とPush Credential SID
が提供されます。電話をかけるたびにidentity
、Push Credential SID
とdevice token
をユニークなアドレスとしてAPNS VoIP push通知を送信します。TwilioVoice.unregisterWithAccessToken:deviceToken:completion
を使うと、そのidentity
に対する関連付けが削除されます。Push Credentialの更新
Appleから提供された認証情報を変更または更新する必要がある場合は、consoleでPush Credentialを選択し、次に示すPush Credentialページのテキストボックスに新しい
certificate
とprivate key
を追加します。Push Credentialの削除
作成したアプリケーションが使用されていない場合を除き、プッシュ資格情報を削除することはお勧めしません。
APNS VoIP証明書がもうすぐ期限切れになるか期限切れになった場合は、Push Credentialを削除しないでください。 代わりに、「Push Credentialの更新」セクションに従ってPush Credentialを更新する必要があります。
Push Credentialが削除されるとこのPush Credentialで行われた関連登録はすべて削除されます。将来的にこの削除されたPush CredentialのPush Credential SIDを使用して登録された
identity
を参照しようとすると失敗するでしょう。確実にPush Credentialを削除したい場合は、選択したPush Credentialページで
Delete this Credential
をクリックしてください。Push Credentialを削除した後、新しいアクセストークンを生成するときには必ずPush Credential SIDを削除または置き換えてください。
More Documentation
あなたは以下のgetting startedとAppleの最新ドキュメントより、たくさんのドキュメントを見つけることができます。
Twilio Helper Libraries
TwiMLとProgrammable Voice Calls APIの使用方法の詳細については、TwiMLクイックスタートをご覧ください。:
- TwiML Quickstart for Python
- TwiML Quickstart for Ruby
- TwiML Quickstart for PHP
- TwiML Quickstart for Java
- TwiML Quickstart for C#
Issues and Support
あなたが見つけた問題をGithubで提出してください: Voice Swift Quickstart.
問題を報告する際にPersonally Identifiable Information(PII)または機密アカウント情報(APIキー、資格情報など)を共有していないことを確認してください。Voice SDKに関する一般的なお問い合わせはfile a support ticketをご覧ください。
License
MIT
- 投稿日:2019-02-27T16:05:05+09:00
NavigationControllerの画面遷移で
概要
ナビゲーションコントローラーでの画面遷移アニメーションを変更する。
実装環境
Xcode 10.1
Swift 4.2.1コード
先ずは通常の画面遷移
AppDelegate.swiftimport UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var navigationController: UINavigationController? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let viewController: ViewController = ViewController() //set viewController as rootViewController of navi navigationController = UINavigationController(rootViewController: viewController) //hide navigation bar navigationController?.setNavigationBarHidden(true, animated: false) //set window self.window = UIWindow(frame: UIScreen.main.bounds) self.window!.backgroundColor = .white //set navi as rootViewController self.window!.rootViewController = navigationController self.window!.makeKeyAndVisible() return true } func applicationWillResignActive(_ application: UIApplication) { } func applicationDidEnterBackground(_ application: UIApplication) { } func applicationWillEnterForeground(_ application: UIApplication) { } func applicationDidBecomeActive(_ application: UIApplication) { } func applicationWillTerminate(_ application: UIApplication) { } }ViewController.swiftimport UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() createImage() createButton() } func createImage() { let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)) imageView.image = UIImage(named: "image1") imageView.contentMode = .scaleAspectFill self.view.addSubview(imageView) } func createButton(){ let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50)) button.center = self.view.center button.setTitle("next", for: .normal) button.backgroundColor = UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 1.0) button.addTarget(self, action: #selector(onButtonTapped), for: .touchUpInside) self.view.addSubview(button) } @objc func onButtonTapped(){ /* if you want to use default transitions, please uncomment out below. */ //let transition = CATransition() //transition.duration = 0.5 //transition.type = CATransitionType.fade //following types are used with subtype //transition.type = CATransitionType.moveIn //transition.type = CATransitionType.push //transition.type = CATransitionType.reveal //example of subtype //transition.subtype = CATransitionSubtype.fromBottom //navigationController?.view.layer.add(transition, forKey: nil) let view = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)) view.alpha = 0 view.backgroundColor = .white self.view.addSubview(view) UIWindow.animate(withDuration: 1.0, animations: { view.alpha = 1.0 }) { (Bool) in DispatchQueue.main.asyncAfter(deadline: .now()+0.2, execute: { //Attention "animated" is "false" self.navigationController?.pushViewController(nextViewController(), animated: false) }) DispatchQueue.main.asyncAfter(deadline: .now()+1.0, execute: { view.removeFromSuperview() }) } } }nextViewController.swiftimport UIKit class nextViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() createImage() createButton() } func createImage() { let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)) imageView.image = UIImage(named: "image2") imageView.contentMode = .scaleAspectFill self.view.addSubview(imageView) } func createButton(){ let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50)) button.center = self.view.center button.setTitle("back", for: .normal) button.backgroundColor = UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 1.0) button.addTarget(self, action: #selector(onButtonTapped), for: .touchUpInside) self.view.addSubview(button) } @objc func onButtonTapped(){ let view = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)) view.alpha = 0 view.backgroundColor = .white self.view.addSubview(view) UIWindow.animate(withDuration: 1.0, animations: { view.alpha = 1.0 }) { (Bool) in DispatchQueue.main.asyncAfter(deadline: .now()+0.2, execute: { //Attention not pushViewController //popToViewController is back ViewController method self.navigationController?.popToViewController(self.navigationController!.viewControllers[0], animated: false) }) DispatchQueue.main.asyncAfter(deadline: .now()+1.0, execute: { view.removeFromSuperview() }) } } }結果
解説
今回の実装ではstoryboardを使わなかったためstoryboardのファイルは削除している。
それなのでInfo.plistからstoryboard file base nameを削除しAppDelegateでwindowを設定した。遷移アニメーションの変更部分
navigationControllerのanimatedをfalseにすることで横にスライドするアニメーションを行わないようにする。
self.navigationController?.pushViewController(nextViewController(), animated: false)その他のやり方
コメントアウトしているところを参考にしてもらえれば分かるようにnavigationControllerのtypeを変更することで遷移方法を変更できる。
segueのcross dissolveのようなanimationであればtypeをfadeにすることで簡単に実装できるだろう。
参考
CATransition
How do I cross dissolve when pushing views on a UINavigationController in iOS 7?
How do I create a new Swift project without using Storyboards?
- 投稿日:2019-02-27T13:08:51+09:00
CocoaPods でビルド済みバイナリを使う
iOS アプリ開発でサードパーティライブラリを導入する場合、CocoaPods か Carthage かのどちらかを使うのが定番です。
CocoaPods は、ライブラリのソースコードをワークスペースに組み込んで Xcode でビルドを行う仕組みです。このため、アプリのビルド時にライブラリのビルドが始まってしまうことがあります。これにより、ビルド時間が増加するため、開発効率が落ちるという問題があります。この問題は、先にライブラリをビルドしてバイナリを使う Carthage に比べて、CocoaPods が劣る点のひとつです。
しかし実は、CocoaPods でもビルド済みバイナリを使うことが可能です。以下、その方法を述べます。
cocoapods-binary プラグイン
cocoapods-binary プラグインは、先にライブラリをビルドして Xcode でビルド済みバイナリを使うようにする、CocoaPods のプラグインです。サードパーティ製のプラグインですが、CocoaPods のドキュメントで紹介されています(Pre-compiling dependencies)。
pod install
のとき、cocoapods-binary はライブラリのソースコードをダウンロードしてビルドします。そして、ビルドしたバイナリをワークスペースに組み込みます。したがって、Xcode はビルド済みバイナリを使います。これにより、ビルド時間を短縮でき、開発効率が上がります。
プラグインの使い方
cocoapods-binary は RubyGems で提供されています。CocoaPods と同様に
gem install
やbundle install
でインストールします。次に、
Podfile
の先頭に以下を追加します。plugin 'cocoapods-binary'そして、どの pod でバイナリを使うか指定します。
pod "ExpectoPatronum", :binary => trueまたは、次のように書けば全ての pod でバイナリを使います。
all_binary!この場合、バイナリを使いたくない pod を
:binary => false
で指定することもできます。注意事項
このプラグインを使う場合、
use_frameworks!
指定が必要になります。この指定は Dynamic framework を使う指定です。現在の Xcode や CocoaPods は Static framework に対応しており、Static を使うほうが Dynamic に比べて起動時間が早いなどのメリットがありますが、このプラグインとの併用はできないようです。
バイナリ指定すると何が起こるか
pod install
を行うと、バイナリ指定していない場合は、Pods
ディレクトリに各ライブラリのソースコードが配置されます。一方、バイナリ指定した場合は、Pods
ディレクトリに各ライブラリのバイナリが配置されます。より正確には、
Pods/_Prebuild
ディレクトリ以下に各ライブラリのバイナリが配置され、各ライブラリのディレクトリPods/ExpectoPatronum
には_Prebuild
へのシンボリックリンクが作成されます。オプション
バイナリ指定すると、
Pods
ディレクトリにはバイナリのみが置かれ、ソースコードは置かれなくなります。ただ、それはpod install
の際に毎回ソースコードをダウンロードしてくるということでもあります。pod install
の時間を短縮したい場合は、keep_source_code_for_prebuilt_frameworks!
を指定すれば、ソースコードも置いておくようにできます。また、bitcode が必要な場合は
enable_bitcode_for_prebuilt_frameworks!
を指定します。まとめ
CocoaPods でビルド済みバイナリを使う方法を紹介しました。CocoaPods でビルドを早くしたい方は一度試してみると良いと思います。
なお、詳細は cocoapods-binary のページを参照してください。