3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Multipeer ConnectivityによるiPad間通信で早押しボタンを作った

Last updated at Posted at 2020-03-02

#概要
2チーム対抗クイズイベントのために、早押しボタンのiOSアプリを簡単に実装しました。
二台のiPadを用いて、各チームがiPadを早押しボタンとして使います。

iOS端末間のP2P通信を可能にするフレームワーク、Multipeer Connectivityを用いています。
回答ボタンを押した時の時間を端末間で通信し、正確なタイミング判定を実現しました。

画面に出ている数字はスコアカウンターです。

#仕組み
Multipeer ConnectivityはBluetooth等を用いて通信しています。
通信のメッセージが届くのにラグがあるため、二つの起こりうるパターンが考えられます。
それぞれのパターンで先に押した方を判定するために、以下のような通信が行われます。

####1. 一方がもう一方より圧倒的に早く押した場合
(以下の「勝ち」「負け」は、どちらが先に押したかの判定を意味します。)
早く押された方の端末をAとした場合、以下の図のようになります。

  1. Aは、Bにボタンが押された時の時間を伝えます。
  2. Bの方はボタンがまだ押されていないので、Bは負けを画面上に表示します。
  3. しかし、この時点ではA端末側は自分が勝ったかどうかは分かりません。そのため、Bは負けを表示した後に、Aに「Bが負けたこと」を伝えます。
  4. Aは、そのメッセージを受け取ると自身が勝ったことが分かり、画面上に勝ちを表示します。
スクリーンショット 2020-03-02 14.00.06.png

####2. 二チームのタイミングが僅差だった場合
Aのメッセージが発信されてから届くまでの間に、Bが押された場合は、以下のようになります。

  1. Aが押されると、先ほどと同様にBに押された時の時間を送信します。
  2. それをBが受け取る前にBも押され、Bが押された時の時間をAに送信します。
  3. その後、BにAからの通信が届きます。Bは、自身が押された時間とAが押された時間を比較します。すると、Bは自身が負けたことが分かり、画面上に負けを表示します。
  4. 同様に、AにBからの通信が届くと、時間を比較することで自身が勝ったことが分かり、画面上に勝ちを表示します。
スクリーンショット 2020-03-02 14.27.54.png

#実装
全体はGithubに置いておきます。
https://github.com/blu3mo/QuizButton
以下は、MCService.swiftの一部です。

早押しボタンが押された時の送信/受信まわりの実装は以下のようになっています。
メッセージはData型にエンコードしてやりとりしています。

MCService.swift
extension MCService: MCSessionDelegate {
    
    func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
        // Debug用
        switch state {
        case .connected:
            print("connected: \(peerID.displayName)")
        case .connecting:
            print("connecting: \(peerID.displayName)")
        case .notConnected:
            print("not connected: \(peerID.displayName)")
            //self.state = .open
        }
    }
    
    func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
        let decodedString = String(data: data, encoding: .utf8)
        
        var message: Message
        switch decodedString {
        case "youwin":
            message = .youWin
        default: //date
            let date = dateFromString(string: decodedString!, format: dateStringFormat)
            message = .triedDate(date)
        }
        
        model.reactMessage(message: message)
    }
    
    func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
        
    }
    
    func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {
        
    }
    
    func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {
        
    }
    
}

extension MCService: JudgeModelConnectionOutput {
    
    var isConnected: Bool {
        get {
            return (mcSession.connectedPeers.count == 0)
        }
    }
    
    func sendMessage(message: Message) {
        var sendingString: String = ""
        
        switch message {
        case .youWin:
            sendingString = "youwin"
        case .triedDate(let date):
            sendingString = stringFromDate(date: date, format: dateStringFormat)
        }
            
        do {
            try mcSession.send(sendingString.data(using: .utf8)!, toPeers: mcSession.connectedPeers, with: .reliable)
        } catch {
            print("sending error") //TODO
        }
    }
    
    func advertise() {
        let mcAdvertiserAssistant = MCAdvertiserAssistant(serviceType: serviceTypeId, discoveryInfo: nil, session: self.mcSession)
        mcAdvertiserAssistant.start()
    }
    
    func getBrowser() -> UIViewController {
        let mcBrowser = MCBrowserViewController(serviceType: serviceTypeId, session: mcSession)
        mcBrowser.delegate = self
        mcBrowser.maximumNumberOfPeers = 1
        mcBrowser.minimumNumberOfPeers = 1
        
        return mcBrowser
    }

}

詳しい仕様については、Multipeer Connectivityの公式Documentationを確認してください。

#運用
実際のイベントでは2台のiPadを観客に向けて設置し、マウスをそれぞれに接続しました。
(最近iPadでもマウスが使えるようになりました)
回答者がマウスをクリックすると、ボタンが押された判定となります。

#備考
・Qiita初投稿&素人なので、改善点等あったら教えてください🙏
・今回は二端末間の通信となっていますが、接続数の制限を解除すれば同
じ仕組みで三台以上も問題ないはずです。
・Multipeer Connectivityについては、不安定であるという意見も存在します。
 MultipeerConnectivityって使えるの? by @YearCentury

#参考
iOS Swift Tutorial: Transfer Data with the Multipeer Connectivity Framework
https://www.youtube.com/watch?v=H5c4vo6p5Fg

ActionSheet Popover on iPad in Swift
https://medium.com/@nickmeehan/actionsheet-popover-on-ipad-in-swift-5768dfa82094

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?