
Outline
事前準備編はこちら
ここから先は全てChatViewControllerの話です
特記がない場合はChatViewControllerの編集ととらえてください。
STEP2 WebSocketの実装
ログ出力
このハンズオンではログで動作を確認するシーンが多いため、最初にログを拡張します。
Seesaa京都さんの方法を使わせていただき下記の関数を追加します。
func LOG(_ body: String = "",
function: String = #function,
line: Int = #line)
{
print("[\(function) : \(line)] \(body)")
}
これでログに関数名と行番号が表示されるようになりました。
WebSocket接続
WebRTCによるP2P接続を実現するには接続情報の交換が必須です。今回はこの交換にWebSocketを利用します。
ブラウザの場合はコピペで接続情報を交換する素敵な方法があるのですが、iOSは最初からWebSocketです。
今回はハンズオン用にGoでWebSocketサーバを用意しました。
宣言
importにStarscreamを追加して
import Starscream
class ChatViewController: UIViewController,
WebSocketDelegate {
var websocket: WebSocket! = nil
...
WebSocketDelegateを継承させ、
クラスのメンバ変数にWebSocketを追加します。
WebSocketDelegate
WebSocketの接続、切断やメッセージ受信のイベントを受け取れるように、WebSocketDelegateの関数をクラス内に作ります。
func websocketDidConnect(socket: WebSocket) {
LOG()
}
func websocketDidDisconnect(socket: WebSocket,
error: NSError?) {
LOG("error: \(String(describing: error?.localizedDescription))")
}
func websocketDidReceiveMessage(socket: WebSocket,
text: String) {
LOG("message: \(text)")
}
func websocketDidReceiveData(socket: WebSocket,
data: Data) {
LOG("data.count: \(data.count)")
}
接続と切断
チャット画面を開いた際にWebSocketの接続するように、Closeボタンを押した時に切断するように、WebSocketの接続/切断処理を実装します。
URLの[YourID]は自分のだけの英数字のIDを決めて変えてください
override func viewDidLoad() {
super.viewDidLoad()
websocket = WebSocket(url: URL(string:
"wss://conf.space/WebRTCHandsOnSig/[YourID]")!)
websocket.delegate = self
websocket.connect()
}
...
@IBAction func closeButtonAction(_ sender: Any) {
// Closeボタンを押した時
websocket.disconnect()
_ = self.navigationController?.popToRootViewController(animated: true)
}
テスト
この状態で試しに実行してみましょう。
OutputにwebsocketDidConnectと表示されれば成功です。
この通りStarscreamを使うと、やりたい操作を記述するだけで、容易にWebSocketが利用できます。
これで、接続情報が交換できるようになりました。いよいよ、PeerConnectionを作成していきます。
STEP3 映像/音声の取得
最初と最後の呪文
AppDelegate.swiftを編集して、WebRTCをimportし
import WebRTC
起動時にRTCInitializeSSLを終了時にRTCCleanupSSLを呼び出すようにします。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
RTCInitializeSSL()
return true
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
RTCCleanupSSL()
}
WebRTC Native Clientを使う場合には必ず必要になる呪文です。ブラウザに対応する処理はありません。
RTCPeerConnectionFactoryの初期化
ChatViewController.swiftに戻って、いよいよWebRTCの処理を作っていきます。
クラスのメンバ変数にRTCPeerConnectionFactoryを追記します。
var peerConnectionFactory: RTCPeerConnectionFactory! = nil
RTCPeerConnectionFactoryはPeerConnectionを複数生成した場合でも親玉になります。
viewDidLoad内のwebsocketの初期化の前でインスタンスを作っておきましょう。
override func viewDidLoad() {
super.viewDidLoad()
// RTCPeerConnectionFactoryの初期化
peerConnectionFactory = RTCPeerConnectionFactory()
websocket = WebSocket(url: URL(string: "wss://conf.space/WebRTCHandsOnSig/[YourID]")!)
...
映像/音声の取得
映像、音声ソースの取得処理を書いていきます。ブラウザではgetUserMediaに相当する処理です。
これらのソースはPeerConnectio間で共有するため、メンバ変数に下記を追記します。
var peerConnectionFactory: RTCPeerConnectionFactory! = nil
var audioSource: RTCAudioSource?
var videoSource: RTCAVFoundationVideoSource?
取得処理はstartVideoというメンバ関数にまとめていきます。
func startVideo() {
// この中身を書いていきます
}
音声ソースの生成
音声のソースを生成してします。デバイス制御の記述は不要です。
// 音声ソースの設定
let audioSourceConstraints = RTCMediaConstraints(
mandatoryConstraints: nil, optionalConstraints: nil)
// 音声ソースの生成
audioSource = peerConnectionFactory
.audioSource(with: audioSourceConstraints)
映像ソースの生成
映像のソースも同じように生成してします。
// 映像ソースの設定
let videoSourceConstraints = RTCMediaConstraints(
mandatoryConstraints: nil, optionalConstraints: nil)
videoSource = peerConnectionFactory
.avFoundationVideoSource(with: videoSourceConstraints)
最後に映像ソースをcameraPreviewに設定します
// 映像ソースをプレビューに設定
cameraPreview.captureSession = videoSource?.captureSession
startVideoの呼び出し
Factoryの初期化後、WebSocketの初期化前で呼び出しましょう。
override func viewDidLoad() {
super.viewDidLoad()
// RTCPeerConnectionFactoryの初期化
peerConnectionFactory = RTCPeerConnectionFactory()
startVideo()
websocket = WebSocket(url: URL(string: "wss://conf.space/WebRTCHandsOnSig/[YourID]")!)
...
deinit
このままでは終了時にクラッシュしてしまうのでdeinitを追記しておきます。
deinit {
audioSource = nil
videoSource = nil
peerConnectionFactory = nil
}
WebRTCのプログラムは解放順に注意が必要です。
テスト
この状態で試しに実行してみましょう。
自画像が表示されれば成功です。
STEP4 PeerConnectionの作成
RTCPeerConnectionDelegate
WebSocket同様、PeerConnectionを複数のイベントが発生します。それを受け取る関数を書いておきましょう。
ChatViewControllerにRTCPeerConnectionDelegateを継承させ、メンバ関数を追記します。
class ChatViewController: UIViewController,
WebSocketDelegate, RTCPeerConnectionDelegate {
...
func peerConnection(_ peerConnection: RTCPeerConnection,
didChange stateChanged: RTCSignalingState) {
// 接続情報交換の状況が変化した際に呼ばれます
}
func peerConnection(_ peerConnection: RTCPeerConnection,
didAdd stream: RTCMediaStream) {
// 映像/音声が追加された際に呼ばれます
}
func peerConnection(_ peerConnection: RTCPeerConnection,
didRemove stream: RTCMediaStream) {
// 映像/音声削除された際に呼ばれます
}
func peerConnectionShouldNegotiate(_
peerConnection: RTCPeerConnection) {
// 接続情報の交換が必要になった際に呼ばれます
}
func peerConnection(_ peerConnection: RTCPeerConnection,
didChange newState: RTCIceConnectionState) {
// PeerConnectionの接続状況が変化した際に呼ばれます
}
func peerConnection(_ peerConnection: RTCPeerConnection,
didChange newState: RTCIceGatheringState) {
// 接続先候補の探索状況が変化した際に呼ばれます
}
func peerConnection(_ peerConnection: RTCPeerConnection,
didGenerate candidate: RTCIceCandidate) {
// Candidate(自分への接続先候補情報)が生成された際に呼ばれます
}
func peerConnection(_ peerConnection: RTCPeerConnection,
didOpen dataChannel: RTCDataChannel) {
// DataChannelが作られた際に呼ばれます
}
func peerConnection(_ peerConnection: RTCPeerConnection,
didRemove candidates: [RTCIceCandidate]) {
// Candidateが削除された際に呼ばれます
}
RTCPeerConnectionの作成
PeerConnectionを作っていきましょう。今回は処理をまとめるためにprepareNewConnectionという関数を作って書いていきます。
複数人との通話を実現する場合は、このPeerConnectionを複数作成します。なので戻り値で受け取るようにしました。
func prepareNewConnection() -> RTCPeerConnection {
// STUN/TURNサーバーの指定
let configuration = RTCConfiguration()
configuration.iceServers = [
RTCIceServer.init(urlStrings:
["stun:stun.l.google.com:19302"])]
// PeerConecctionの設定(今回はなし)
let peerConnectionConstraints = RTCMediaConstraints(
mandatoryConstraints: nil,
optionalConstraints: nil)
// PeerConnectionの初期化
peerConnection = peerConnectionFactory.peerConnection(
with: configuration, constraints: peerConnectionConstraints, delegate: self)
//...つづく...
}
音声の追加
先ほど作成したソースを元に音声トラックを作成し、PeerConnection側ではSenderを作って作成したトラックを設定します。
// 音声トラックの作成
let localAudioTrack = peerConnectionFactory
.audioTrack(with: audioSource!, trackId: "ARDAMSa0")
// PeerConnectionからAudioのSenderを作成
let audioSender = peerConnection.sender(
withKind: kRTCMediaStreamTrackKindAudio,
streamId: "ARDAMS")
// Senderにトラックを設定
audioSender.track = localAudioTrack
この状態で、音声送信が可能になりました。
映像の追加
ビデオチャットを作るので、同様に映像を追加します。呼び出す関数や設定する定数に変化はありますが、基本的には音声と変わりません。
// 映像トラックの作成
let localVideoTrack = peerConnectionFactory.videoTrack(
with: videoSource!, trackId: "ARDAMSv0")
// PeerConnectionからVideoのSenderを作成
let videoSender = peerConnection.sender(
withKind: kRTCMediaStreamTrackKindVideo,
streamId: "ARDAMS")
// Senderにトラックを設定
videoSender.track = localVideoTrack
return peerConnection
この状態で、映像送信も可能になりました。
最後に生成したpeerConnectionを返しておきましょう。
今回はクラスのメンバにpeerConnectionがある前提なので追加しておきましょう
var peerConnectionFactory: RTCPeerConnectionFactory! = nil
var peerConnection: RTCPeerConnection! = nil
終話処理の作成
終話処理も作っておきましょう。PeerConnectionのcloseを呼ぶと終話することができます。
今回はhangUpという関数にまとめました。
func hangUp() {
if peerConnection != nil {
if peerConnection.iceConnectionState != RTCIceConnectionState.closed {
peerConnection.close()
}
peerConnection = nil
LOG("peerConnection is closed.")
}
}
peerConnectionがclosedな状態ではない場合にclose()を呼ぶようにしてあります。また、この際にpeerConnectionの解放も行うようにしました。
hangUpの呼び出し
HangUpボタンとCloseボタンを押した際にhangUp関数を呼ぶようにしておきます。
@IBAction func hangupButtonAction(_ sender: Any) {
//HangUpボタンを押した時
hangUp()
}
@IBAction func closeButtonAction(_ sender: Any) {
// Closeボタンを押した時
hangUp()
websocket.disconnect()
_ = self.navigationController?.popToRootViewController(animated: true)
}
例によって、クラッシュ防止のためdeinitにも追記しておきましょう。
deinit {
if peerConnection != nil {
hangUp()
}
audioSource = nil
videoSource = nil
peerConnectionFactory = nil
}
PeerConnection状態変化
デバッグのためPeerConnectionの状態変化をログに出力するようにします。
また、closeされた時と失敗した時にhangUp()を呼ぶようにしておきましょう。
func peerConnection(_ peerConnection: RTCPeerConnection,
didChange newState: RTCIceConnectionState) {
// PeerConnectionの接続状況が変化した際に呼ばれます
var state = ""
switch (newState) {
case RTCIceConnectionState.checking:
state = "checking"
case RTCIceConnectionState.completed:
state = "completed"
case RTCIceConnectionState.connected:
state = "connected"
case RTCIceConnectionState.closed:
state = "closed"
hangUp()
case RTCIceConnectionState.failed:
state = "failed"
hangUp()
case RTCIceConnectionState.disconnected:
state = "disconnected"
default:
break
}
LOG("ICE connection Status has changed to \(state)")
}
prepareNewConnectionの呼び出し
作成したprepareNewConnectionを動作確認用にconnectボタンを押した際に呼び出すようにします。
@IBAction func connectButtonAction(_ sender: Any) {
// Connectボタンを押した時
if peerConnection == nil {
peerConnection = prepareNewConnection()
}
}
動作テスト
ここまで実装できたら、適当にConnect, Close, HangUpを押して操作してみましょう。
特に前回と画面の差異はありませんが、
アプリがクラッシュしなければOKです。
STEP5 かける処理の実装
処理の流れ
実装前に、接続処理の流れを確認しておきます。
かける処理は次のようなシーケンスになります。
sendSDP
はじめにofferやanswerなどのSDPを相手に送る処理を作っておきます。
SwiftyJSONを利用するのでimportに追加しておいてください
import SwiftyJSON
SDPの入っているRTCSessionDescription型のdescから必要な値を取り出してJSONに格納しwebsocket.writeで相手に送信します。
func sendSDP(_ desc: RTCSessionDescription) {
LOG("---sending sdp ---")
let jsonSdp: JSON = [
"sdp": desc.sdp, // SDP本体
"type": RTCSessionDescription.string(
for: desc.type) // offer か answer か
]
// JSONを生成
let message = jsonSdp.rawString()!
LOG("sending SDP=" + message)
// 相手に送信
websocket.write(string: message)
}
makeOffer
それではPeerConnectionを作ってからofferを相手に送るところまでをmakeOffer関数にまとめて作ってしまいましょう。
func makeOffer() {
// PeerConnectionを生成
peerConnection = prepareNewConnection()
// Offerの設定 今回は映像も音声も受け取る
let constraints = RTCMediaConstraints(
mandatoryConstraints: [
"OfferToReceiveAudio": "true",
"OfferToReceiveVideo": "true"
], optionalConstraints: nil)
let offerCompletion = {
(offer: RTCSessionDescription?, error: Error?) in
// Offerの生成が完了した際の処理
if error != nil { return }
self.LOG("createOffer() succsess")
let setLocalDescCompletion = {(error: Error?) in
// setLocalDescCompletionが完了した際の処理
if error != nil { return }
self.LOG("setLocalDescription() succsess")
// 相手に送る
self.sendSDP(offer!)
}
// 生成したOfferを自分のSDPとして設定
self.peerConnection.setLocalDescription(offer!,
completionHandler: setLocalDescCompletion)
}
// Offerを生成
self.peerConnection.offer(for: constraints,
completionHandler: offerCompletion)
}
シーケンス通りの処理順になっているか確認してみてください
ポイント: RTCMediaConstraints
映像、音声の送受信設定が出てきましたね。受け取ると言っているだけなので、相手が送ってくれるかは別の話です。
// Offerの設定 今回は映像も音声も受け取る
let constraints = RTCMediaConstraints(
mandatoryConstraints: [
"OfferToReceiveAudio": "true",
"OfferToReceiveVideo": "true"
], optionalConstraints: nil)
makeOffer の呼び出し
電話をかけた場合の処理になります。Connectボタンを押した時に呼ぶようにしましょう。
@IBAction func connectButtonAction(_ sender: Any) {
// Connectボタンを押した時
if peerConnection == nil {
LOG("make Offer")
makeOffer()
} else {
LOG("peer already exist.")
}
}
すでに、peerConnectionがある時には呼ばない配慮もあるといいですね。
setAnswer
シーケンス図によるとAnswerが帰ってくるので受け取る処理も必要になります。今回はsetAnswerにまとめておきましょう。
func setAnswer(_ answer: RTCSessionDescription) {
if peerConnection == nil {
LOG("peerConnection NOT exist!")
return
}
// 受け取ったSDPを相手のSDPとして設定
self.peerConnection.setRemoteDescription(answer,
completionHandler: {
(error: Error?) in
if error == nil {
self.LOG("setRemoteDescription(answer) succsess")
} else {
self.LOG("setRemoteDescription(answer) ERROR: " + error.debugDescription)
}
})
}
受け取ったanswerを相手のSDPとして設定するだけです。この時PeerConnectionはすでにあるはずなので、冒頭でチェックしておきましょう。
setAnswer の呼び出し
answerは相手からWebSocketで送られてくるため、届いた時にはwebsocketDidReceiveMessageが呼ばれます。
メッセージはJSONで送られてくるので、これをパースします。また、複数の異なるtypeのメッセージが届くため、typeによってswitchで処理を分けるようにしておきましょう。
func websocketDidReceiveMessage(
socket: WebSocket, text: String) {
LOG("message: \(text)")
// 受け取ったメッセージをJSONとしてパース
let jsonMessage = JSON.parse(text)
let type = jsonMessage["type"].stringValue
switch (type) {
case "answer":
// answerを受け取った時の処理
LOG("Received answer ...")
let answer = RTCSessionDescription(
type: RTCSessionDescription.type(for: type),
sdp: jsonMessage["sdp"].stringValue)
setAnswer(answer)
default:
return
}
}
answerが届いた際にはJSONのパース結果からRTCSessionDescriptionを生成して、setAnswerに渡してあげるようにすればOKです。
onaddstream
シーケンス図にonaddstreamという記載がありました。このイベントは受け取ったSDPの中に映像、音声のストリーム情報が含まれていた場合に発生します。iOSの場合は記述済みのPeerConnectionDelegateのイベントから下記が発火します。
func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
// 映像/音声が追加された際に呼ばれます
LOG("-- peer.onaddstream()")
DispatchQueue.main.async(execute: { () -> Void in
// mainスレッドで実行
if (stream.videoTracks.count > 0) {
// ビデオのトラックを取り出して
self.remoteVideoTrack = stream.videoTracks[0]
// remoteVideoViewに紐づける
self.remoteVideoTrack?.add(self.remoteVideoView)
}
})
}
発生したタイミングでストリームが渡されてきますが、ストリームには複数のトラックが格納できます。
今回はVideoTrackが1つ入っているはずなので、これを取り出して作成済みのremoteVideoViewに表示するようにしましょう。
呼び出し元がメインスレッドではないので、メインスレッドで実行するようにDispatchQueue.main.asyncで囲っています。
remoteVideoTrackが赤くなったはずです。Trackはメンバ変数として参照を保持しましょう。remoteVideoViewが更新されなくなってしまいます。
var peerConnectionFactory: RTCPeerConnectionFactory! = nil
var peerConnection: RTCPeerConnection! = nil
var remoteVideoTrack: RTCVideoTrack?
これで、受け取った映像がremoteVideoViewに表示されるようになります。
hangUp処理の追加
メンバ変数を追加すると必ず生じるのが解放処理の追加です。hangUpにremoteVideoTrackの解放処理を追記しましょう
func hangUp() {
if peerConnection != nil {
if peerConnection.iceConnectionState != RTCIceConnectionState.closed {
peerConnection.close()
}
if remoteVideoTrack != nil {
remoteVideoTrack?.remove(remoteVideoView)
}
remoteVideoTrack = nil
peerConnection = nil
LOG("peerConnection is closed.")
}
}
remoteVideoTrackが設定されていた場合、remoteVideoViewの設定を解除してpeerConnectionの解放前にremoteVideoTrackを解放するようにします。
candidateを送る処理
シーケンス図を見ると最後にcandidateを送りあっています。これには接続先候補情報が含まれていて、これがなければ相互接続は難しくなります。
これを相手に送るsendIceCandidate関数を書きましょう。
func sendIceCandidate(_ candidate: RTCIceCandidate) {
LOG("---sending ICE candidate ---")
let jsonCandidate: JSON = [
"type": "candidate",
"ice": [
"candidate": candidate.sdp,
"sdpMLineIndex": candidate.sdpMLineIndex,
"sdpMid": candidate.sdpMid!
]
]
let message = jsonCandidate.rawString()!
LOG("sending candidate=" + message)
websocket.write(string: message)
}
SDPと同様にRTCIceCandidate型で渡されたcandidateをJSONにして相手に送るようにします。
candidateの発生
candidateはsetLocalDescriptionを呼んだ段階から収集されていて、見つかると記述済みのPeerConnectionDelegateのイベントから下記が発火します。
引数でcandidateが渡されるので、これをsendIceCandidateで相手に送るようにしましょう。
func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
// Candidate(自分への接続先候補情報)が生成された際に呼ばれます
if candidate.sdpMid != nil {
sendIceCandidate(candidate)
} else {
LOG("empty ice event")
}
}
収集が終わると空のイベントが発火しますが、今回はこれは使いません。
candidateを受け取る処理
相手から送られてきたcandidateを受け取る処理も必要です。answer同様にwebsocketで送られてくるので、websocketDidReceiveMessageのtype別caseに下記を追記します。
case "candidate":
LOG("Received ICE candidate ...")
let candidate = RTCIceCandidate(
sdp: jsonMessage["ice"]["candidate"].stringValue,
sdpMLineIndex:
jsonMessage["ice"]["sdpMLineIndex"].int32Value,
sdpMid: jsonMessage["ice"]["sdpMid"].stringValue)
addIceCandidate(candidate)
answer同様にJSONからRTCIceCandidateに戻してaddIceCandidate関数に渡します。
addIceCandidate関数は下記のように実装します。
func addIceCandidate(_ candidate: RTCIceCandidate) {
if peerConnection != nil {
peerConnection.add(candidate)
} else {
LOG("PeerConnection not exist!")
}
}
渡されたcandidateをpeerConnectionがある時にaddするだけです。
動作テスト
これで、かける場合の一連の処理が実装できました。実際に試してみましょう。
下記のURLの末尾をwebsocketを初期化した際にURLに記述したIDに変更してChromeブラウザで開いてください。
https://conf.space/WebRTCHandsOn/**[YourID]**
[Start Video]を押して、カメラの取得を許可してください、自画像が表示されたら、iOS側は自画像が出ている状態で、[Connect]を押してください。映像音声が繋がるはずです。
現状ではiOS側の[Connect]しか使えません。
STEP6 かかってきた処理の実装
処理の流れ
長いハンズオンも、いよいよ最後です。
実装前に、接続処理の流れを確認しておきます。
かかってきた処理は次のようなシーケンスになります。
シーケンス通りに実装していきましょう
setOffer
setOfferはsetAnswerとほとんど同じですが下記の二点が異なります。
- peerConnectionの生成を行う
- setRemoteDescriptionに成功した場合にanswerを作る処理を呼ぶ
func setOffer(_ offer: RTCSessionDescription) {
if peerConnection != nil {
LOG("peerConnection alreay exist!")
}
// PeerConnectionを生成する
peerConnection = prepareNewConnection()
self.peerConnection.setRemoteDescription(offer, completionHandler: {(error: Error?) in
if error == nil {
self.LOG("setRemoteDescription(offer) succsess")
// setRemoteDescriptionが成功したらAnswerを作る
self.makeAnswer()
} else {
self.LOG("setRemoteDescription(offer) ERROR: " + error.debugDescription)
}
})
}
冒頭のpeerConnectionのチェックも異なるので注意してください。
makeAnswer
makeAnswerもまた、makeOfferと非常によく似ていますが、下記の点が異なります。
- RTCMediaConstraints がない
- PeerConnectionはある前提(作らない)
- offerじゃなくてanswerを作っている
func makeAnswer() {
LOG("sending Answer. Creating remote session description...")
if peerConnection == nil {
LOG("peerConnection NOT exist!")
return
}
let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
let answerCompletion = { (answer: RTCSessionDescription?, error: Error?) in
if error != nil { return }
self.LOG("createAnswer() succsess")
let setLocalDescCompletion = {(error: Error?) in
if error != nil { return }
self.LOG("setLocalDescription() succsess")
// 相手に送る
self.sendSDP(answer!)
}
self.peerConnection.setLocalDescription(answer!, completionHandler: setLocalDescCompletion)
}
// Answerを生成
self.peerConnection.answer(for: constraints, completionHandler: answerCompletion)
}
completionHandlerは同一なので共通化しても良いと思います。今回はログのために分けました。
setOfferの呼び出し
先にmakeAnswerを作ってしまいましたが、相手からofferがきた際にsetOfferを呼び出すようにしなければなりません。
例によってwebsocketDidReceiveMessageのcaseを追加します。answerとほとんど同じです。
case "offer":
// offerを受け取った時の処理
LOG("Received offer ...")
let offer = RTCSessionDescription(
type: RTCSessionDescription.type(for: type),
sdp: jsonMessage["sdp"].stringValue)
setOffer(offer)
これで、ブラウザ側でConnectを押した際にも繋がるようになりました。
一連の接続実装ができました。WebRTCの実装としては、ここまでで完了ですが、もう少しお付き合いください。
切断の高速化
ブラウザと同様の切断処理を実装しておきましょう。
hangUpが呼ばれた際にWebSocketでtype:closeを相手に送るようにします。
func hangUp() {
if peerConnection != nil {
if peerConnection.iceConnectionState != RTCIceConnectionState.closed {
peerConnection.close()
let jsonClose: JSON = [
"type": "close"
]
LOG("sending close message")
websocket.write(string: jsonClose.rawString()!)
}
if remoteVideoTrack != nil {
...
同様にwebsocketDidReceiveMessageにcloseがきた際にhangUp()を呼ぶようにしましょう。
case "close":
LOG("peer is closed ...")
hangUp()
アスペクト比の調整
先ほどテストした際には、iOS側で顔が縦長になって表示されました。これを修正する処理を入れておきます。
ChatViewControllerの継承元にRTCEAGLVideoViewDelegateを追加してください。
class ChatViewController: UIViewController,
WebSocketDelegate,
RTCPeerConnectionDelegate,
RTCEAGLVideoViewDelegate {
このDelegateはRTCEAGLVideoViewに送られてくるフレームのサイズが変わった際に下記のイベントを発火します。
func videoView(_ videoView: RTCEAGLVideoView,
didChangeVideoSize size: CGSize) {
let width = self.view.frame.width
let height =
self.view.frame.width * size.height / size.width
videoView.frame = CGRect(
x: 0,
y: (self.view.frame.height - height) / 2,
width: width,
height: height)
}
フレームサイズはsizeに入れて渡されるので、今回はフレームが横長の前提で画面の横幅に合わせて、アスペクト比をそのままになるよう縦幅を変えて、RTCEAGLVideoViewのframeを変えるようにします。
最後にviewDidLoad内でremoteVideoViewのdelegateにselfを指定しましょう
override func viewDidLoad() {
super.viewDidLoad()
remoteVideoView.delegate = self
// RTCPeerConnectionFactoryの初期化
peerConnectionFactory = RTCPeerConnectionFactory()
動作テスト
大変お疲れ様でした。最後の動作テストです。
ブラウザとiOSの双方で自画像が表示されている状態にして、[connect]を押してみてください。ブラウザ側からも繋がったかと思います。
また、iOS上の表示のアスペクト比も修正され、iOS側のHangUpを押せばブラウザの画面がリセットされるはずです。
以上で動作テストは完了です。
HangUpを押してもiOS側の表示が残るのは、このアプリでは仕様となります。
やっぱり辛い逃げよう
- Skyway
https://nttcom.github.io/skyway/
- 時雨堂
EOF
- FirebaseのデータをObjectMapperを用いてデータ構造を管理する方法
- Metal逆引きレシピ
- 1年遅れのWWDC 2015 Tour
- Swiftの静的コード解析ツールTailorの使い方
- Swift3でFacebook SDKを使う
- UITableViewのデリゲートメソッドまとめ
- iOS コーディングガイドライン
- Notification的にRxSwiftをクラス間の通知に使う
- React NativeでObjective-C/SwiftのAPIを扱う(Native Component編)
- Firebase Notificationを使用してiOS端末にPush通知を送信する
- Metalの恩恵は受けつつCore Imageで「手軽に」画像処理
- Swiftでのローカル通知・リモート通知の実装メモ
- UINavigationControllerと座標ズレの小ネタ
- [swift] Firebase Realtime Databaseのリスナーについて
- モジュール結合度について
- FacebookやTwitterのアプリで気になった表現を自分なりにトレースした際の実装ポイントまとめ(タイルレイアウトがサムネイル画像の枚数に応じて変わる表現)
- Widgets(Today Extension)のまとめ
- iOSアプリに審査なしでパッチを当てるライブラリを作ってみた
- Swift Evolution の Commonly Proposed がすごい勉強になる
- AndroidとiOSの実装を徹底比較する
- CALayer概要
- [Swift]0から作るXmasカウントダウンアプリ
- 猿でもできるKituraでJSONを生成する方法
- Visual Format Languageを使う【Swift3.0】
- User登録画面で活用できそうなProtocolを活用したDesignPattern3選
- RxSwift スレッドクイズ (解答・解説編)
- Kickstarter-iOSのViewModelの作り方がウマかった
- Swift で 2つの線分の交点を求める
- Metalでカメラからの動画入力をリアルタイム処理する
- iOSアプリ開発でアニメーションするなら押さえておきたい基礎
- iOS標準の写真アプリのように画像をズームしながら遷移させる
- プロトコル指向言語としてのSwift – OOPからPOPへのパラダイムシフトと注意点
- Actionを使って快適なViewModel生活を🏄
- 【Swift】最前面のUIViewControllerを取得する方法
- UIFeedbackGeneratorをiOS10未満対応アプリで楽に書けるUtility
- ViewControllerをObservableとして考える
- 【swift】イラストで分かる!具体的なDelegateの使い方。
- iPod20台を通信/制御/連携してみた【Max × Swift × OSC】
- iOSのMPSCNNによる手書き数字認識のサンプルを読む – 前編
- swift3でCGFloat.min, CGFloat.maxを利用したい
- Objective-CからSwiftへの移行でバグりやすいポイント
- コードレビューをチームにリクエストするSlack BotをSwift実装したお話🚢
- 誰でもわかるプログラミング入門を目指し隊〜Swift編〜
- Metal で 三角形を組み合わせて 2D の線を描く
- Swift中間言語の、ひとまず入り口手前まで
- MetalでiOSアプリに宿る生命
- Xcode 8.2, Swift 3.0でTwitterの認証を通してタイムラインを取得するまで
- Swift 3 以降の NotificationCenter の正しい使い方
- Windows 10でSwift開発環境構築 with Atom
- Observable.just()からはじめるRxSwift
- `as AnyObject` で何が起こるのか
- まだiOS Clean Architecture で消耗してるの? 爆速開発ツールを作ったのでご紹介
- Swiftにおけるアニメーションの世界観を掴む
- [Swift] [iOS] Watson Visual Recognitionを使って顔解析アプリを作ってみた
- 【Swift】いまさらですがiOS10でプッシュ通知を実装したサンプルアプリを作ってみた
- 【Swift3】日本語を含むURLをNSURLに
- スワイプすると出てくるメニューをSwiftで作る
- 脱・文字列ハードコーディング
- Objective-C の循環的複雑度を計測する
- 続:いろんなところでsushiを流す話
- iOSのMetalで畳み込みニューラルネットワーク – MPSCNNを用いた手書き数字認識の実装
- [iOS10] カメラを触ってみる
- ビルドで待たないための、Simulator 上で実行中の view をいじる方法
- Swift での UI テストの雑なまとめ
- iOSの新規プロジェクトでClean Architecture + Application Coordinatorの構成にした理由と感触
- [Swift] バリデーションチェックは、正規表現で!
- MkMapViewの使い方(swift版)
- UIKitにある機能でWebで見かけるようなUI達を作る
- Swift 3でSwiftBondを使ってMVVMしちゃおう
- [Swift] サンプルを見ながら、リファクタリングを勉強する
- Swift製のコマンドラインツールをbrewでインストールできるようにする
- 【Swift】アプリからメーラーを起動する方法
- Swiftで日記アプリを作ろう 〜その1 カレンダー表示編〜
- Carthageを使っていて”$(SRCROOT)/Carthage/Build/iOS/*.framework”を打ち飽きた方へ
- AWS + Nginx + Node.js + iOS(Swift) でリアルタイムチャットアプリを作ろう
- 【Xcode 8対応版】Xcodeプラグイン Alcatrazを導入しよう
- SwiftのAddress/Thread Sanitizer
- iOSで指先またはペン先の動きを記録
- Swiftで日記アプリを作ろう 〜その2 Realm導入編〜
- IBM OpenWhiskというサーバーレス実行環境を用いて、Swiftでおみくじ🎍
- 初心者のためのSprite Kit入門
- 【Swift】Realm BrowserでRealm Mobile Databaseの中身を確認する
- Swiftで日記アプリを作ろう 〜その3 Realm活用編〜
- iOSプロジェクトのテストバンドルに存在するファイルを取得する
- Swift3でDocumentディレクトリのファイルにアクセスする
- RxSwiftでUITextFiledのtextのオブザーバーがSwift3から変わっていた
- 【Swift】UITextFieldのカーソル非表示&コピー・ペーストを不可にする方法
- Alamofire, URLSessionの通信処理をMethod Swizzlingでスタブに置き換える
- 冬休みの8日間を使って、ダンス動画アプリを作った
- Swift で計算して Bezier 曲線を描く
- Swiftで小学生の頃ハマった定規でぐるぐるするやつを描く
- iOSアプリが何回もリジェクトされた後にAppleからフォローアップされる件
- ジェスチャーやカスタムトランジションを利用して入力時やコンテンツ表示時に一工夫を加えたUIの実装ポイントまとめ
- Xcodeの旧バージョンをインストールする方法
- Share Extensionでデータを共有する
- 【iOS10】Firebaseでサイレント通知を行う
- iOSで音声/ビデオ通話をWebRTCを使って実現する(調査編)
- iOSアプリ向け通信スタブの使い方
- Swift3.0対応 CoreGraphicsでPieを描く
- 【iOS】Tinder的アニメーション
- Instagramライクな画像フィルタライブラリをCocoaPodsで公開してみた
- SceneKitで扱える3Dモデルのフォーマット/アニメーションつき3DモデルをSceneKitで使う
- UITabBarにcustomなバッジをつける
- SwiftとJavascriptの変数宣言の比較
- RxSwiftをサクッと勉強してみた
- そういえばMacでAndroidアプリつくるために必要な準備ってなんだっけ?
- 吹き出しのようなViewを作ってみる
- Swiftのエラー4分類が素晴らしすぎるのでみんなに知ってほしい
- Swiftの型の限界を超える
- SwiftのString(文字列) APIとの付き合い方
- 恐怖!忍び寄るライブラリへのロックイン
- 関数型プログラミングの実際のところ
- Never GiveUp というタイトルで Swift Tweets で発表しました。 #swtws
- Clean Architecture 開発ツールの話
- iOSにおけるクリーンアーキテクチャよもやま話 #swtws
- 型推論のビルドが遅いらしいから調べてみる
- iOSエンジニアの正規表現入門
- UIViewPropertyAnimatorの基本まとめ①
- Swift3での日時に関する処理
- プッシュ通知を究める!その①〜普通のプッシュ通知の実装の仕方〜
- プッシュ通知を究める!その②〜リッチ通知(メディア付き)の実装の仕方〜
- KickStarter iOSアプリのStoryboardのenumでの管理のしかたに感動した
- iOSアプリからFirebase Cloud Messaging経由でプッシュ通知を送る
- Swift で Elixir のパイプ演算子を実装してみる
- Swiftの値型と参照型、値渡しと参照渡し
- Swiftのクロージャにおける循環参照
- [Swift] Validatorというライブラリを使ってValidationする
- KituraでWebSocketを動かす
- iOSアプリ開発基礎ハンズオン
- 素晴らしいSwiftのポインタ型の解説
- Swiftのメモリレイアウトを調べる
- Storyboard を使わずコードだけで画面を生成、遷移をしてみる
- DispatchQueueでthrottle/debounceを実現する
- Swiftのenumのメモリレイアウトの最適化が凄い
- RxSwiftでの実装練習の記録ノート(前編:Observerパターンの例とUITableViewの例)
- [Swift3]SegmentedControlでViewを切り替える
- SwiftLintのバージョンを固定する方法
- iOS 10.3で追加されたアプリレビューを投稿できるSKStoreReviewControllerを試してみた
- UIViewControllerAnimatedTransitioning によるカスタムトランジション
- CFSocket を使って iOS で socket 通信した話
- 画像をダブルタップとピンチイン・ピンチアウトで拡大・縮小する Swift3編
-
iOS 10.3からアプリ内レーティングが可能に!- SKStoreReviewController – - Swift3による自動更新購読のアプリ内課金(In-App Purchase)の実装 for 月額課金
- 【Swift】AppStoreのレビューフォームを開くパラメーターが新登場 – action=write-review –
- Swift EvolutionのAcceptedステータスまとめ (2017.1.27) – Protocol-oriented integers, Permit where clauses to constrain associated types, …etc
- 【Swift3.0】xibを使ったカスタムビューの作り方
- iOSの審査に関するまとめ
- Apple Watch に Core Motion を使って色々なデータを取得して表示させてみる
- [macOS]macOS Cocoaプログラミングをはじめる
- 値渡しと参照渡しと参照の値渡しと
- 【iOS】UILabelにPaddingをつける(Swift3対応)
- 【AutoLayout】UILabelの幅を文字列の長さに合わせて可変にする方法
- 【Swift3】ToDoアプリを作る【CoreData】
- Cloud9で始めるServer Side Swift (Vapor)
- UIViewPropertyAnimatorの基本まとめ②
- #Swift fastlaneの次に来る? Sourcery🔮でメタプログラミングする
- iOSのMetal Performance Shadersでニューラルネットを実行する際のモデルの渡し方
- iOSアプリ開発時に使う小ネタ
- 2点間の距離計算 (C, Clojure, Go, Haskell, Java, LOGO, OCaml, Ruby, Rust, Scratch, Swift)
- SwiftでBase64 Encode / Decodeする
- Today Extensionについて
- [Swift 3.0] ViewController単位で画面の向きを制御する
- RxSwiftでの実装練習の記録ノート(後編:DriverパターンとAPIへの通信を伴うMVVM構成のサンプル例)
- swiftのguardを使ってみる
- 乾電池型IoT”MaBeee”を使って簡単なおもちゃ制御アプリを作ってみる
- ReactiveSwift 基本要素の使い方 Signal / SignalProducer編
- [Swift|Kotlin]+Rxで作ったネイティブアプリのコードの違い
- 1行で全てのUIButton(UIView)の同時押しを無効にする
- ありそうでなかったSwiftでのPostリクエストの投げ方
- Swiftによるシリーズアプリの共通部分を切り出して一括管理する
- [iOS] Google VR SDK の代替案としての MetalScope
- iOS アプリの Unit Test – Swift 編
- Swift で Quadratic Bezier 曲線の長さを計算する
- 脳波でスカートをめくるP-WAVEを開発した話
- 今更聞けない?Struct と Class の使い分け方(補足)
- わーい、すごーい
- Swiftで「サクッと使える」通信ライブラリ【RapidFire】
- Swift GPUImage2で画像の平均色を抽出する
- 【Swift x PHP】iOS端末とPHPサーバでHTTP通信
- Auto Layout を使わずにわかりやすいコードでレイアウトが組めるフレームワーク NotAutoLayout
- Carthageで管理しているライブラリのライセンスをまとめてくれるスクリプト
- 【Swift 3】処理の完了を待ってから後続処理を行う
- 【Swift x Ruby】iOS端末とRuby on RailsサーバでHTTP通信
- Swift:UIColorを16進数カラーコードで初期化する
- SwiftLintのRules全まとめ
- Xcode 不使用リソースを抽出する
- ios10 シャッタースピードやホワイトバランスなどiPhoneカメラに特殊機能を実装する方法
- RxSwiftとAlamofireとObject Mapperで
- UITextViewにタップ可能なリンクを挿入する
- web制作者にもわかる、Swift 3が++と–を削除した理由
- iOSのMPSCNNに渡すモデルパラメータのフォーマット / TensorFlowからの書き出し
- [Swift] 三角形のボタンを作る
- [Swift]segueを使った画面遷移、segueを使わない画面遷移
- SwiftにおけるMethod Dispatchについて
- ZIG SIMが送信するセンサの種類とデータ構造について
- TwitterAPIとSwiftを使ってiOSアプリを作ろう! – 前編 – #dotsgirls
- TwitterAPIとSwiftを使ってiOSアプリを作ろう! – 後編 – #dotsgirls
- TwitterAPIとSwiftを使ってiOSアプリを作ろう! – カスタマイズ編 – #dotsgirls
- Google画像検索APIをiOSで利用する
- Apple公式のiPhoneアプリチュートリアルやってみた①(Build a Basic UI)
- Swift3対応をこれからする人が役立ちそうなこと!
- tableView(_:didSelectRowAt:)にて、modalなSegueをperformSegueすると描画が遅い件は、DispatchQueue.main.asyncで解決できる
- Custom URL Schemeでアプリ内の任意のページを表示する
- Swiftの強力な機能であるstaticメソッド制約の紹介と、Kotlin, TypeScript, Java, Scala, C++との比較
- [iOS] Swift + Kannaでtableをスクレイピング
- 激安NSAttributedString
- lazy var の遅延参照?という面白い動き
- UIButtonの画像をUIButtonに対してAspectFitする方法
- iOS の時間関数の精度
- [Swift] count == 0 より isEmpty を使うべき理由
- [swift] InterfaceBuilderで多角形のボタンを作る
- Swift3で消費型アプリ内課金(In-App-Purchase)を実装してみた
- UIStackViewを使って詳細ページの実装をシンプルにする
- Swift 3のUIScrollViewでカルーセルUI(ページング/画像などをスワイプで行き来できるView)をつくる
- iOS(Swift)でアラート画面のようにViewの背景透過をする
- Concourse CIでiOSアプリのCIを行う
- VO, DTO, POSO, DAO, Entity の違い
- Swift の文字列の長さ
- モックオブジェクトをより便利にする (Try! Swift2017)
- よくわかるUIGestureRecognizerDelegate
- Swift,iOS,Xcodeの設計についてメモ
- サーバサイドSwiftの現状考察 #tryswiftconf
- Try! Swift 2017のまとめ
- TableViewで複数セルを一気に複数削除する
- 新しいアプリを作るときによく使うSwift Extension集
- ユニットテストにも可読性を持たせる (Four Phase Test)
- try! Swift 2017 Tokyo 参加レポート
- UIImageのリサイズ方法と注意点
- 始めて見よう! SwiftでWebアプリ開発(基礎編)
- try! Swift Tokyo 2017 に参加した&リンク集
- 2017年におけるObjective-Cコミュニティの動向
- SpriteKitの衝突処理について(categoryBitMask collisionBitMask contactTestBitMask 使い方)
- 猿がようやく理解したSwiftのdeferの使いどころ
- [Swift3.0][iOS][SpriteKit]SpriteKitでテキストを落としたり、ぶっ飛ばしたりした!
- SwiftはどのようにJavaの検査例外を改善したか
- iOS 10以降のNotificationの基本
- 猿がついに理解できたSwiftのthrow・do・try・catchの意味
- print()やNSLogを書かずにconsoleにメッセージを出力する方法
- 猿がもがきまくって理解したSwiftのデリゲート(Delegate)という仕組み
- iOS Test Night_#3に参加できず悲しいのでまとめた
- iOS Test Night #3
- Firebase Cloud Messagingで画像つきプッシュ通知を送信する
- TypeというiOS用のMarkdownエディタを作った
- iOSのCIにモバイル向けCI、Bitriseを導入する(CocoaPods, GitHub Privateレポジトリ対応版)
- RxSwift 3.3.0で追加された3つのUnit(Single, Maybe, Completable)
- CAMPFIRE iOS #1 参加レポート
- 【iOS】ViewControllerを汚さずにUITableViewの下部からCellを追加する実装
- [Swift3]文字列が人名かどうかをバリデーションする方法
- [Swift] シンプルなカウントアップでSequenceに強くなる
- LINQライブラリまとめ
- iOSのBackground Transferがよくわからない人用に整理した
- [iOS][Swift]UITableViewのヘッダーとフッターを設定する方法
- 【Git】 project.pbxprojのコンフリクトの直し方
- Swiftのmutatingキーワードについて
- アプリにApple Payを導入する – 商品購入のハードルを下げる –
- Swiftでasync/awaitな書き方もできるPromiseライブラリHydra
- Playground で Carthage ライブラリを import する
- キリスト教の牧師(見習い)からWebエンジニアに転職した話
- [Swift 3.0]NSLayoutAnchorを用いたコードによるAuto Layout
- 俺の俺による俺のための iOS プログラム設計
- 和暦に関するメモ
- Interface Builderとソースコードで共通のカラーパターンを利用する方法
- SwiftyBeaver でアプリケーションログをメール送信しデバッグしやすい環境を作る
- [MVVM] kickstarter/ios-oss での画面遷移のやり方
-
図解 MemoryLayout
で解き明かす型のメモリー構成 - iOSのビルド高速化 7つの方法
- LINE NEWS風のUI実装
- カメラロールのようなシンプルなイメージピッカーの作成
- swiftLint 導入
- Instagramライクなパン/ピンチ操作できるイメージビューの作成
- 【iOS】アプリ内からレビューを依頼する 10.3未満も対応
- How to write basic UnitTestsを発表した際の参考資料
- Swiftのstatic、classキーワードの違いについて
- 心がHomebrewで旧バージョンのパッケージを入れたがってるんだ
- ただ画像を右側に表示したいだけなんだ・・・ – UIButtonの画像を右寄せにする
- 制約の修正なしで縦方向に要素を追加可能なビューの作成
- Storyboard上で置いてるUIVisualEffectViewのブラーのかかり具合を調節、アニメーションさせる [iOS10]
- DI(Dependency Injection)の概要
- SwiftRater を使って好きなタイミングで SKStoreReviewController を表示する
- Swift 3.1で死んだコードまとめ
- クラスや変数の名前とかを安易に英語にしたい
- Swiftのprint()をファイル名、行数、関数名を出力して分かりやすくする
- iPhoneのSFSpeechRecognizerとAVSpeechSynthesizerと発泡スチロールでボスっぽいなにかを作る
- [Swift] 順序付き辞書、DictionaryLiteral
- iOS/Androidアプリを突貫工事で開発するときにやって良かった!悪かった!こと まとめ
- APIKitでRequestのイニシャライザに渡すのはValueObjectやEntityじゃないほうが良い
- iOS 実装サンプルアプリ集
- iOSアプリを新規開発するときに最低限守っていること
- 日付関連クラスのまとめ(Swift3)
- Foundation.Operationの並列オペレーションがよくわからない人向けの説明
- Swiftで複数のフラグを管理するためにOptionSetを使うと便利だった
- Swiftで連番画像のコマ送りアニメーション
- Swift3でJSONパースを行う
- Swift3.0 NavigationBarをhidden(隠す)方法
- iOS 10 での通知処理について
- 【Swift3】RxSwift + APIKit + Himotokiで作るAPIクライアント
- Swiftでいい感じのViewModelを作るためのメモ
- IGListKitとRxSwiftを掛け合わせてみた🤔
- UIFeedbackGeneratorの使い方と便利に使えるライブラリ
- カスタムViewをNibから初期化の最新版
- 【iOS】ステータスバーのカスタマイズ【Swift3.0】
- carthageのバージョンを今すぐ上げよう
- SwiftでContainerViewを使ってみる
- Swift の struct の stored property は var にしよう
- アイコンを申請なしで変える(iOS10.3)
- SwiftのOptionalのベストプラクティス
- Vapor as a web framework
- モックを「差し込む方法」を考える
- Swift4で何がかわりそうなのか
- Swift Standard Library相関図を作ってみた・眺めてみた #swtws
- iOS(Swift)から3ステップでMastodonに投稿を行う
- なぜDelegateをプロパティに持つとweakを指定しなければいけないの?
- SlideMenuControllerSwiftの使い方とカスタマイズ
- あとで読むQiitaリーダーアプリをリリースしました
- null安全な言語でも、バグ検知を怠れば安心ではない
- 【ネタ】Swift で Sleep Sort
- [Swift] enumerated()はindexを返さない
- [SiriKit]Siriから料金の支払いをする
- 2017年版Realmのエレガントな使い方
- Swiftのプロパティとinout参照を組み合わせたときの挙動が面白い
- swiftのenumでStringがカスタムなときでもenumを諦めない
- 【iOS】Fluxを利用して画面遷移を制御する
- Swift3.0 で CoreGraphicsを使って見る
- Debug Memory Graph で Memory Leak を調査する
- Apple公式のiOS開発チュートリアルをやってみた(Andoridエンジニア視点)
- Swift 4の新しいreduceが素晴らしいので紹介する
- Alert,ActionSheetの表示処理がスッキリかけるUIAlertControllerのラッパーライブラリを作りました
- 体系的なSwift言語学習
- AppのIconからVersionやCommitを判別して混乱を防ぐ
- IB上で範囲の確認ができるUIButtonのタップエリアを拡大する実装
- RxSwift 用語解説
- 【Swift3.0】Alamofireで画像&パラメータを送信
- Swift Package Manager(SwiftPM)で作ったコマンドラインツールをHomebrewに登録する方法
- SwiftではバージョンはStructにして演算子オーバーロードで比較したらどうでしょう
- Swift3ではKVOにkeyPath()式を使っていくのが便利
- やはりお前らのboundingRectWithSizeは間違っている
- SwiftでTestコード(on Xcode)
- `typealias My` をススメようと思ったけどやめた件 あるいはstatic methodをどうやって呼ぶか
- Firebase iOS SDKが刷新されましたよっていう話し
- [2017年版]RxSwift + Alamofire + ObjectMapper + RealmのSwift実装について
- BluetoothをもちいたiOS同士の通信
- Rails 5 Action CableチャットアプリのiOSクライアント側を作る
- SpeakerDeckのスライドをPDF形式で表示できるiOSアプリを作った
- SwiftでArrayがnilか0件の場合に共通の処理を動作させたい時はnil結合演算子を使う
- fastlaneを導入してビルドを楽にする
- 10年間の iOS 機能のまとめ with WWDC
- 書評:Swiftの各機能が「なぜ」存在し「いつ」使うべきかを解説した技術書 – Swift実践入門
- iOS SwiftでBLEのサンプルを動かしてみる
- Swift3で日付(Date)の比較が超簡単になっていた件
- AppleWatchKitとSpriteKitでスペースシューティングゲームを作ってみた
- 様々な言語でMap, Filter, Reduceを実現してみた(1)
- アニメーション付きのボタンを実装するためのテクニック
- Setを使いこなしたい(願望)
- AVFoundationで動画のリアルタイム合成
- Swiftガイドライン的な
- 位置情報アプリ開発者必見!Energy Efficiency Guide for iOS AppsのReduce Location Accuracy and Durationを読んでみた
- Instagramのような画面UIを簡単に作れるPastelViewを試してみた
- Swift API デザインガイドライン
- インタフェースと型クラス、どちらでもできること・どちらかでしかできないこと
- サーバーレスサーバーサイドSwiftとHexaville
- RxSwiftのExamplesにしれっと入ってる双方向データバインディングの演算子がイケてた
- Swift3対応をしてハマった不具合
- iOSでアプリ間でデータをやり取りするためのNの試行
- Embedded framework使用時の肥大化問題
- Swiftならメモ化も最高にスッキリ書けます
- 詳解! ios-Charts
- Instagramのログイン画面みたいなグラデーションのアニメーションを自分で作る
- Swiftのmap, filter, reduce, etc…は(あたりまえだけど)for, if, switch, whileからできている
- iOS11で新しく導入されたFramework
- pageViewController(_: viewControllerBefore:) および pageViewController(_: viewControllerAfter:) が呼び出されるタイミングについて(UIPageViewController)
- What’s New in iOS11まとめ (Metal2以外)
- Xcode による iOS 開発で秘匿したい情報をどう管理するか
- iOS11のCoreNFCを使う
- 公式ドキュメントを追いながらARKitを試してみよう
- Swiftでクラス名や関数名等をログ出力する
- 【iOS 11】開発者ドキュメントから見る iOS 11 の新機能 #WWDC17
- WWDC2017で更新されたサンプルコードまとめ
- Xcode8のDebug Memory GraphでCFArrayのメモリリークの原因を探る
- ARKitを触ってみよう 〜第1話〜
- Swift4.0 で追加される Codable
- 引っ張って閉じることができるモーダルを実装する (UINavigationControllerの場合)
- iOS11 Swipe Actions
- [iOS][Swift3] ニュース系アプリのユーザインタフェース PageMenuKit の実装
- Swift4 CodableでJSONが扱いやすくなる?
- Swift4のCodableでフラットなJSONからネストしたオブジェクトにデコードする
- Swift4のCodableでISO8601の日付をデコードする
- 【iOS 11】【Core ML】pip install coremltools でエラーになった場合の対処法
- Swift4のJSONDecorderは、Date等のパース方法をカスタマイズできるみたい
- ReactorKit(Flux + Reactive Programming)を学ぶ1 入門編
- ReactorKit(Flux + Reactive Programming)を学ぶ2 基礎編
- Swift4のCodableに対応した、独自のDecoder(CSVDecoder)を実装してみよう
- 個人開発アプリでSwift 4に一足先に対応しました
- [Xcode 8] Swiftのドキュメントコメントについての簡潔なまとめ
- init の名は。
- iOS11で追加されたDeviceCheckについて
- SwiftでWebRTC実装ハンズオン 事前準備編
- iOS11で発表されたMusicKitについて
- iOS 11 WKWebView 3大新機能 (WWDC 2017)
- NSURLSessionがメモリリークしてしまうのをなんとかした
- Array や Dictionary にもモダンでオシャレな extension を実装する
- 99%くらいのSwiftプログラマーが使わないであろう演算子の話
- UIKitのView表示ライフサイクルを理解する
- 【まとめ】What’s New in Testing【WWDC 2017】
- Swift PlaygroundsでXcode projectのコードを動かしてみよう
- WWDC17で新しく発表された画像フォーマットHEIFを使ってみた
- iOSでの各回転検知方法とその結果
- SwiftでWebRTC実装ハンズオン 本編
- [Swift4] privateにextensionからアクセスできる
- iOS – CrashlyticsのrecordErrorでカスタムログを送る
- コードを一行も書かずにHello Worldする方法
- 画像を表示する際にモアレ(干渉縞)を抑制する方法
- APIKitとCodableでAPIクライアントを作る
- CodingKeyで、case名のcamelCase ⇄ stringValueのsnake_case を自動で変換する
- 【まとめ】Engineering for Testability (前半)【WWDC 2017】
- 【まとめ】Engineering for Testability (後半)【WWDC 2017】
- 【Swift】iOSで放置型育成ゲームを作りたい(0)
- [Swift]初心者のためのSwiftチートシート
- Swift Package Manager (SwiftPM) Version 4 概要
- ARKitで豆腐作り
- Swiftで複数の非同期処理の完了時に処理を行う
- 【Swift】hogehoge.delegate = self は何をしているのか。
- cmdshelfによるスクリプト運用のご提案
- mbed × BLE × iOSでとりあえず通信したい人のための記事
- iOS LINEでログイン(Line SDK)
- Swift:UserDefaultsで初期値を設定する方法
- ios, androidのローカライズファイルを共通管理して幸せになった話
- 型システムの理論からみるSwiftの存在型(Existential Type)
- Swift4.0でDictionaryが大幅にパワーアップした
- Swift4で変更されたStringAPIをいくつか試してみた
- NO MORE ビルド時間泥棒 ☕️❌【RxSwift編】
- 新規アプリサービスのためのログ実装とサービス選定
- SwiftのSelfキーワードの使い方まとめ
- [Swift]WinでやるC#erの為のSwift基本文法覚書
- Decoder, DecodingContainerの、デコード先の型を推論させたい!明示的に指定したくない!
- XCode9で追加されたAsset catalogsのNamed colors supportについて
- Swiftのattributeまとめ[Swift4対応]
- SwiftでもKotlinのif式を使いたい
- iOSでBeaconの振る舞いを確認する
- はじめてのSwiftアプリ制作4: StoryboardとAuto Layoutその1
- Swift4でのSingletonを用いた共通データの値渡し
- ルートが配列のJSONをCoadableでカスタムモデルにマッピングする
- iOSで楽にデバッグメニューをつける
- 【Swift】TextFieldのキーボードを閉じる方法3選
- SwiftとKotlinの文法を比較してみた(基礎パート)
- [コピペで使える]swift3/swift4でリアルタイム顔認識をする方法
- Swift4 [SE-160 Limiting @objc inference] 概要
- SwiftのExtensionによるクラス分割
- Xcodeのビルド待ちで消耗してたので見直したら50%以上削減できた話
- Swiftコンパイラ開発環境構築
- (初心者向け)Flickr Apiを使って画像を引っ張ってくる(1)
- Equatableとは?(swift) ~ object同士を比較できるようにしよう〜
- Swift4のCodableが内部で何をやっているか確認する
- SwiftでアプリのCPU使用率とメモリ使用量を取得する
- できるだけプログラムっぽくないプログラミングへの挑戦(Swift編)
- MVVMをベースにしつつCleanArchitectureを取り入れてみた
- GCD(Grand Central Dispatch)でキューの順番とスピードを制御する方法(1/2)
- GCD(Grand Central Dispatch)でキューの順番とスピードを制御する方法(2/2)
- Swiftコンパイラのテスト環境
- できるだけプログラムっぽくないプログラミングへの挑戦(Objective-C編)
- 特定のアプリがインストール済みかチェックする
- 「PythonとSwiftは結構似ている」説の検証
- ARKitでタップした座標を検出する方法
- Aspect Fill, Aspect Fit, Scale to Fillの違い
- APIKitとCodableとの連携
- ストーリーボードでUIを綺麗にレイアウトするネタ集
- Swift コンパイラのアーキテクチャ
- 最近Swift書いていて可読性を上げるために意識していること
- Swiftで静的DI(Mixin-Injection)
- UIImageView で cornerRadius と Shadow を同時に使いたい
- Enigmaの実装
- iOS開発でClean Architectureを採用した際のイイ感じのディレクトリ構成とは
- iOS開発で導入しているライブラリの一言説明
- 【メモ】Xcode9ビルドでCarthage経由で導入したライブラリに関してswift version errorが発生
- インクリメンタルサーチ【RxSwift/RxCocoa編】
- CotEditor を Swift に移行する
- Swift3.0でアニメーション1 ~ Animate()メソッド編~
- Swiftに息づくstructural types(構造的型)
- メソッドのhookは正しいタイミングで行おう【RxSwift/RxCocoa】
- 実践Swiftコンパイラ #swtws
- 純粋値型Swift
- Mac: 開発向け厳選ツール群(18/6/23更新)
- Swift でアニメーションの連続実行をしてみる話
- SwiftでiOS脱獄チェック
- iOSの機械学習フレームワークの比較 – Core ML / Metal Performance Shaders (MPSCNN) / BNNS
- 【初心者向け】Core Dataの使い方と説明swift3.0
- RxSwiftのshare*の早見表
- Swiftの有名画像キャッシュライブラリを比較してみた
- indexがArrayの範囲内かチェックする色々な書き方
- 【iOS】fastlaneでipaファイルを作成して、fabric crashlyticsでベータ版を配布し、Slackで完了通知を行う
- iOSアプリでMockを使ってUnitTestを書く
- iOSアプリでCIを始めようとサービスを調べた
- StubとSpyを使ってiOSのUnitテストを書いてみた(Clean architecture)
- RSKImageCropperの使い方とカスタマイズ
- アプリ内での Touch ID を利用したユーザ認証
- Automatically manage signingとxcconigで超効率化
- Instagram APIでOAuth認証する (Swift3版)
- [Swift] MainThreadで処理を実行する
- Carthage updateとCarthage bootstrapの違い
- iOSのクラッシュログをSymbolicate(復元)して解析する
- やさしいSwift単体テスト~テスト可能なクラス設計・前編~
- やさしいSwift単体テスト~テスト可能なクラス設計・後編~
- [iOS][Swift 4] CodableでJSONのパース
- [Swift] Dictionaryをこねくり回すネタ集
- Screen Recordingの録画開始・停止を取得する
- TDD ✕ Property-based Testing (SwiftCheck) で数学パズルを検証してみる
- 超効率化外伝: xcconfigの便利なところ&設定例
- Swiftで花火を作った話
- SwiftでHigher Kinded Polymorphismを実現する
- 絶対にやってはいけない「Apple IDをテストで13歳未満にすること・・」
- Spajam2017優秀賞「嫌われAIの命名」の発想プロセスからiOSアプリ実装まで
- RxSwift `a.withLatestFrom(a)` 同じ上流元の同期的合流問題
- Range が Codable に適合してなかったので後付けで適合させてみる話と、Codable のエラーハンドリングについて
- Swiftのfinalについて
- 【Swift】Dateの王道 【日付】
- サーバーレスとiOSアプリの連携 〜IBM Cloud Functionsを使ってサーバーサイドSwiftで試してみる
- ARKitで立方体の6面それぞれに異なるテクスチャを貼る方法
- iOSでlottie-iosを使ってリッチなアニメーションを簡単に実現してみる
- Swift の class の mutating func とは何か
- 余計なOptionalはやめてくれ
- 【プッシュ通知】Y Combinatorも投資するOneSignalがFirebaseより便利で素晴らしかった
- 【swiftエラー】clang: error: linker command failed with exit code 1 (use -vto see invocation)
- RxSwiftでwithLatestFromが最新じゃなくなることがあるorz
- RxSwiftについてようやく理解できてきたのでまとめることにした(1)
- RxSwiftについてようやく理解できてきたのでまとめることにした(2)
- RxSwiftについてようやく理解できてきたのでまとめることにした(3)
- RxSwiftについてようやく理解できてきたのでまとめることにした(4)
- iOSの消耗型課金のサーバーサイドTipsまとめ
- iOS 11 WKWebViewで広告などのコンテンツブロックをする
- タブスワイプで画面を切り替えるメニューUI
- 【MacOS】スクリーンレコーディング 【Swift】
- Codableについて色々まとめた[Swift4.x]
- FirebaseStorageの画像をSDWebImageで表示しようとして詰まった話
- [Swift] classにEquatableを実装するのは一筋縄ではいかない(ことがある)点に注意。
- ニュースアプリでAPIの記事をRealmにキャッシュして有効期限内だったらそれを表示する
- Swiftで参照型の値から生ポインタを作る方法
- Swift の protocol における Interface と Method の違いを理解しよう
- ARKitを扱う際の心構えとTips
- 脱Storyboardのすすめ
- Firebaseでアプリを開発するならClient Side Joinを前提にすること
- UILabelの文字色をグラデーションさせる
- [Swift3]ローカル通知の実装方法
- [Swift]iOSのデフォルトの関数を活用した Validation String Extension集
- Closureって美味しいの?
- [Swift3]アプリ内でレビューを依頼する
- プッシュ通知設定画面へ遷移させる為の実装
- iOS9, 10 WKWebView – Cookie操作
- iPad対応アプリを開発するときに、UI周りで気をつけることをまとめてみた
- Swift 4 で「プラマイ」範囲を作る
- ぼくのやっているVIPER(のようなもの)
- MVVMを勉強するときに参考になったリンク集 & 概要まとめ
- Xcode8でiOS11beta端末の動作確認がしたい(iOS11実機ビルドしたい)Could not locate device support files.エラー
- iOS11のバグ修正を行うに当たって気になったレイアウト関連の変更点(contentInsetAdjustmentBehavior, SafeAreaLayoutGuide)
- iOS 11 UITableViewでcontentOffsetを使ったスクロールが上手くいかない
- iOS 11 の Safe Area は Auto Layout だけでなくコードベースでも取れる
- iOS向けfastlaneアクションまとめ
- iPhone Xをネイティブ解像度から判定する
- Swift4 Stringのsubstring周りが変わっていた
- iPhone X Human Interface Guidelinesの要点
- iOSDC 2017 まとめ
- RxSwift の Observable とは何か
- Swaggerで始めるAPI定義管理とコードジェネレート
- iOS11で追加されるScreen Recordingについて
- iOS11 カメラとCoreML(Vision)で画像検出
- Swift初心者が3ヶ月でiOSアプリを公開するまでにやったこと、ハマったこと。
- iOSアプリ開発の全体像
- ARKitのコードによく出てくる4次元行列transformについて
- Swiftの @escaping と weak/unowned の理解
- iOSDC Japan 2017で「Auto Layoutのアルゴリズム」について発表しました
- Setは遅いのか
- iOSDC 2017 でさらっと出てきた Phantom Type さらっとやった話
- 超朗報。Xcode 9でやっとSwiftのリファクタリングが可能に
- iOS11から搭載されるスクリーンレコーディングでの録画を検知する方法
- Swift 4 マイグレーション、またはXcode9対応 メモ
- iOSと人工知能(AI) -GPU並列演算の仕組みと機械学習- というタイトルで、iOSDC2017に登壇しました
- 今度のiPhone Xは我々開発者をどれほど苦しめるのか #okamoba
- ARKitのサンプルコード集「ARKit-Sampler」
- iOS11のTwitter投稿対応(Social.framework → TwitterKit)
- iOS11で Grouped UITableView のセクションヘッダーに余分な高さが出る問題について
- [Swift] UserDefaults に画像を保存するとフリーズした
- iOS11 + Xcode9.0でedgesForExtendedLayoutの値を空にしていると、UITableViewのドリルダウンでアニメーションが崩れる
- 【iOSDC2017】MVC→MVP→MVVM→Fluxの実装の違いを比較してみる
- [Swift4]ARKitで球体をランダムに描画する
- 【Xcode9】ファイルヘッダーコメントをカスタマイズする
- [Swift] CharacterSetはCharacterのsetではありませんよ?
- iOS11のVision.frameworkを使ってみる
- iOS11からViewの一部だけを角丸にすることが簡単になった
- Swift 4の魅力の一面を3行で表す
- [Swift] 読み上げ機能、簡単に使えるライブラリつくったよ
- iOS11.0でUINavigationControllerのTitleViewのタッチイベントが呼ばれない現象について
- PDFKit を使ってみた
- Xcode の Debug Memory Graph が便利
- iOSアプリで紙吹雪を降らして画面を賑やかにする
- iOS 11ファイルAppにDocumentsフォルダを表示して他のアプリと共有する方法
- 【Swift】 画像を3種類も書き出したくないでござる
- Swift4 全予約語 (98語) の解説
- ライブラリを使わずにMV*の話(iOS)~ViewとModelの役割〜
- ライブラリを使わずにMV*の話(iOS)〜MVC, MVP, MVVM〜
- .ipa file を実機にインストールする方法(iTunes 12.7)
- UILabelをNSAttributedStringで文字装飾(Swift 4対応)
- AutoLayoutでiPhoneXのedge-to-edge対応

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。