メッセンジャーアプリ

LINEのようなメッセージをやりとりするアプリを作ります。

講義の目標

  • mBaas(Nifty Cloud Mobile Backend)を使ったサーバ連携を利用を理解すること
  • プッシュ通知を使ったアプリ開発ができるようになること

NCMB(Nifty Cloud Mobile Backend)の利用

NCMBとは

NCMB(Nifty Cloud Mobile Backend)はスマートフォンアプリでよく利用される汎用的な機能をクラウドから提供するサービスです。

クラウド上に用意された機能をアプリ側でAPIで呼び出すだけで利用できるので、サーバー開発・運用不要でよりリッチなバックエンド機能をアプリに実装することができます。

このようなモバイルアプリ開発のサーバ連携をサポートするサービスの事と mBaas といいます。

NCMBでできること

NCMBはモバイルアプリ開発でよく使われる下記機能を利用することができます。
iOSはもちろn、AndroidやUnity、Javascriptにて利用できるようになっています。

  • プッシュ機能
  • 会員管理・認証
  • SNS連携(Facebook、Twitter、Googleなど)
  • データストア
  • ファイルストア
  • 位置情報検索

アカウント作成

NCMBを利用するにはniftyのアカウント登録が必要です。
下記ページよりアカウント登録してください。

アカウント登録

@nifty会員の登録(無料)をクリックします。

ユーザ名、パスワード、メールアドレスを入力して登録ボタンをクリックします。

登録内容を確認して登録するボタンをクリックします。

ログインボタンをクリックします。

先程登録したユーザ名とパスワードを入力してログインボタンをクリックします。

利用規約の画面が表示されます。規約内容を確認して同意のチャックをして、アカウント登録をクリックします。

アプリ新規作成になりますのでmessengerと入力して新規作成ボタンをクリックします。

次の画面でAPIキーが表示されます。アプリケーションキークライアントキーをコピーしてメモしておいてください。

これでNCMBのアカウント登録は完了です。

プロジェクト新規作成

MessengerでXcodeプロジェクトを新規作成してください。
使用するテンプレートはSingle View Applicationとなります。

入力項目 入力値
Product Name Messenger
Team None
Oganization Name 任意(”ALJ”など自分が所属している組織の名前を入れる)
Oganization Identifier jp.co.al-j
Language Swift
Devices iPhone
Use Core Data チェックを外す
include Unit Tests チェックを外す
include UI Tests チェックを外す

Cocoapods設定

プロジェクトフォルダをターミナルにドラッグします。

ターミナルの画面でpod initを実行します。

pod init

これでPodfileがプロジェクト内に作成されますので、openコマンドで開きましょう。

open Podfile

テキストエディタが開いてPodfileを編集できますので、下記内容になるように修正し、保存してください。

Podfile

platform :ios, '9.0'
use_frameworks!

target 'Messenger' do
pod 'NCMB', :git => 'https://github.com/NIFTYCloud-mbaas/ncmb_ios.git'
end

ターミナルに戻り下記コマンドでNCMBのライブラリをダウンロードします。

pod install

このように表示されればOKです。

一度Xcodeプロジェクトを閉じて、下図のとおりxcworkspaceからXcodeを開き直してください。

NCMB初期設定

次に、NCMBの初期設定をおこないます。

NCMBはObjective-Cという言語で作られており、Swiftで利用する為には、Objective-CからSwiftへ橋渡しする為のファイル(Bridging-Header.h)を作成し、Swiftで扱えるように設定する必要があります。

まずは橋渡しする為のファイル(Bridging-Header.h)を作成しましょう。

プロジェクトの「Messenger」フォルダーで右クリックをし、「New Files..」を選択します。

「Header File」を選択し、「Next」を押します。

Save Asに「Messenger-Bridging-Header.h」と入力して「Create」ボタンを押します。

「Messenger-Bridging-Header.h」を以下へ編集します。

Messenger-Bridging-Header.h

#import <NCMB/NCMB.h>

Xcodeへ「Messenger-Bridging-Header.h」を読み込ませる為の設定をします。

「Messenger」プロジェクトの「Build Settings」の「Objective-C Bridging Header」に「Messenger/Messenger-Bridging-Header.h」を指定します。

続いて、下記のようにAppDelegate.swiftimport NCMBを追加し、NCMB.setApplicationKeyメソッドの引数に、NCMBのアプリ作成で生成されたアプリケーションキーとクライアントキーを設定します。

AppDelegate.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import NCMB
//////////////// ▲▲ 追加 ▲▲ ////////////////

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

//////////////// ▼▼ 追加 ▼▼ ////////////////
NCMB.setApplicationKey("【アプリケーションキー】", clientKey: "【クライアントキー】")
//////////////// ▲▲ 追加 ▲▲ ////////////////
return true
}

これでNCMBと連携する準備は完了です。

ログイン・会員登録機能実装

画面デザイン

Main.storyboardを開いて、下図のとおりViewControllerを選択してEditor -> Embed In -> Navigation Controllerを選択してください。

そうすると下図のとおりViewControllerの前にNavigation Controllerが追加されます。

下図を参考にログイン画面・会員登録画面を作ってください。
ViewControllerを追加したので、下表のとおりにViewControllerのswiftファイルを作成して関連付けてください。

画面名 swiftファイル名
ログイン画面 LoginViewController.swift
ユーザ登録画面 UserRegisterViewController.swift

ユーザID、パスワード入力用のTextField、ログイン、新規ユーザ登録、新規登録のアクションができるようにButtonを配置してください。

ViewControllerからログイン画面にSegue(Show)を追加します。

ナビゲーション部分にタイトルを入れます。下図のようにNavigation Itemをナビゲーションバーの部分にドラッグすればタイトルを設定できるようになります。

SegueidentifiertoLoginにします。

次にログイン画面の新規ユーザ登録ボタンからユーザ登録画面でSegue(Show)を追加し、identifiertoRegisterにしてください。

IBAutletIBActionも下図のとおりに設定してください。

ログインチェック

最初にViewControllerにてログイン状況を判断し、まだログインしていなければLoginViewControllerに遷移するようにします。

ViewController.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import NCMB
//////////////// ▲▲ 追加 ▲▲ ////////////////

class ViewController: UIViewController {
:
:
override func viewDidLoad() {
super.viewDidLoad()
}

//////////////// ▼▼ 追加 ▼▼ ////////////////
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// ログインチェック
self.loginCheck()
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

//////////////// ▼▼ 追加 ▼▼ ////////////////
func loginCheck(){
if (NCMBUser.current() != nil) {
print("ログイン済み")
} else {
print("未ログイン")
// ログイン画面へ遷移
self.performSegue(withIdentifier: "toLogin", sender: nil)
}
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

この状態でRunしてシュミレータで確認してみましょう。まだログインしていませんので、起動後すぐにログイン画面へ遷移すればOKです。

会員登録機能実装

次にユーザ登録画面にてユーザ登録する処理を追加します。
UserRegisterViewControllerNCMBモジュールをimportします。

UserRegisterViewController.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import NCMB
//////////////// ▲▲ 追加 ▲▲ ////////////////
class UserRegisterViewController: UIViewController {

そしてIBActionuserRegisterButtonTappedメソッドの箇所にNCMBにユーザ登録する処理を追加します。

UserRegisterViewController.swift

@IBAction func userRegisterButtonTapped(_ sender: AnyObject) {
//////////////// ▼▼ 追加 ▼▼ ////////////////
// NCMBユーザインスタンス生成
let user = NCMBUser()
user.userName = self.userIdTextField.text
user.password = self.passwordTextField.text
// ACL設定(全員を読み込み可能とする)
let acl = NCMBACL()
acl.setPublicReadAccess(true)
user.acl = acl
// ユーザ登録
user.signUpInBackground({(error) in
if (error != nil){
print("ユーザ登録失敗:\(error)")
}else{
print("ユーザ登録完了")
// アラート通知
let alert = UIAlertController(title: "登録完了", message: "ユーザ登録完了しました。", preferredStyle: .alert)
alert.addAction(UIAlertAction(
title: "OK",
style: .default,
handler: {(action:UIAlertAction) -> Void in
_ = self.navigationController?.popToRootViewController(animated: true)
}))
self.present(alert, animated: true, completion: nil)
}
})
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

NCMBにユーザを作成する場合にはNCMBUserインスタンスを生成します。
NCMBUserインスタンスにユーザID(userName)とパスワード(password)をセットし、
さらにACL(アクセス権限)設定をセットし、signUpInBackgroundWithBlockメソッドでユーザ登録を行います。

これですでにNCMBにてユーザ登録できるようになっています。

パスワード入力欄は「●」と表示されるように設定しましょう。以下の処理を追加します。

UserRegisterViewController.swift

override func viewDidLoad() {
super.viewDidLoad()
//////////////// ▼▼ 追加 ▼▼ ////////////////
//passwordの入力文字を「●」で表示する
passwordTextField.isSecureTextEntry = true
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

Runを実行してユーザ登録してみましょう。ユーザID、パスワードはとりあえずともにuser1でユーザを作成してください。

ユーザ作成完了すると下図のようにアラート画面が表示されるはずです。

NCMBの管理画面でユーザ登録しているか確認してみましょう。
NCMBにログインしてください。

アプリをMessengerにして会員管理の箇所をクリックします。
すると下図のようにuser1のレコードが1行追加されていると思います。

このようにNCMBを使えば会員機能を簡単に実装する事ができます。

ログアウト機能実装

ユーザ作成が完了するとアプリはログイン状態になっています。
今回は擬似的に複数のユーザを作成したり、切り替えたりしたいので、まずログアウト機能を実装しておきます。

Main.storyboardを開き、ViewControllerBar Button Itemを追加し、IBActionlogoutButtonTappedメソッドを追加します。

追加したlogoutButtonTappedボタンにログアウト機能を追加します。

ViewController.swift

func loginCheck(){
if (NCMBUser.current() != nil) {
print("ログイン済み")
} else {
print("未ログイン")
// ログイン画面へ遷移
self.performSegue(withIdentifier: "toLogin", sender: nil)
}
}

@IBAction func logoutButtonTapped(_ sender: AnyObject) {
//////////////// ▼▼ 追加 ▼▼ ////////////////
NCMBUser.logOut()
self.loginCheck()
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

NCMBUser.logOutでログアウトします。
ログアウトしたらまた自動的にログイン画面を表示するようにloginCheckメソッドを実行しています。

それではRunしてログアウトできるか確認しましょう。

ログイン機能実装

ユーザ登録できるようになりましたので、続いてログイン機能を実装していきましょう。
次はLoginViewControllerIBActionloginButtonTappedメソッドにログイン部分のコードを追加します。

LoginViewController.swift


import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import NCMB
//////////////// ▲▲ 追加 ▲▲ ////////////////
class LoginViewController: UIViewController {


@IBAction func loginButtonTapped(_ sender: AnyObject) {
//////////////// ▼▼ 追加 ▼▼ ////////////////
// NCMBログイン
let userId = userIdTextField.text!
let password = passwordTextField.text!
NCMBUser.logInWithUsername(inBackground: userId, password: password, block:({(user, error) in
if (error != nil){
print("ログイン失敗:\(error)")
}else{
print("ログイン成功:\(user)")
// トップ画面へ戻る
_ = self.navigationController?.popToRootViewController(animated: true)
}
}))
//////////////// ▲▲ 追加 ▲▲ ////////////////
}
}

画面にあるユーザIDとパスワードを入力するテキストフィールド(userIdTextField、passwordTextField)の値をNCMBUser.logInWithUsernameInBackground の引数にセットして実行する事によりNCMBに対してログインを実行します。
ユーザIDとパスワードが正しければ、userの中にユーザ情報が格納されます。ログイン失敗時はerrorに値が入っていますので、if (error != nil)で条件分岐して判断する事ができます。

それでは先ほど作成したuser1でログインできるか確認してみましょう。
下記のようにデバッグエリアにログイン成功と表示されればOKです。

パスワード入力欄は「●」と表示されるように設定しましょう。以下の処理を追加します。

LoginViewController.swift

override func viewDidLoad() {
super.viewDidLoad()
//////////////// ▼▼ 追加 ▼▼ ////////////////
//passwordの入力文字を「●」で表示する
passwordTextField.secureTextEntry = true
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

ユーザを追加

今回のアプリは複数ユーザを想定したものですので、複数ユーザを予め作成しておきます。
会員登録、ログアウトを繰り返し、user2user3でユーザID、パスワードでユーザを作成してください。

このようにNCMB管理画面の会員管理でユーザが作成されているか確認しておきましょう。

ユーザ一覧画面

ユーザ一覧を確認できる画面を作っていきます。今回はその機能をViewController.swiftで実装していきます。

まずはMain.storyboardでViewControllerにTableViewを配置し、Prototype Cell1にし、IBOutlettableViewとして関連付けます。

TableCellのidentifierCellに設定します。

続いて下記コードを実装します。

ViewController.swift

//////////////// ▼▼ プロトコル追加 ▼▼ ////////////////
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
//////////////// ▲▲ プロトコル追加 ▲▲ ////////////////
//////////////// ▼▼ 追加 ▼▼ ////////////////
var users = NSArray() // ユーザデータ
//////////////// ▲▲ 追加 ▲▲ ////////////////
@IBOutlet weak var tableView: UITableView!

override func viewDidLoad() {
super.viewDidLoad()
//////////////// ▼▼ 追加 ▼▼ ////////////////
self.tableView.delegate = self
self.tableView.dataSource = self
//////////////// ▲▲ 追加 ▲▲ ////////////////
}
:
:
:
//////////////// ▼▼ 追加 ▼▼ ////////////////
// MARK: - TableView Datasource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath)
// ユーザIDをセルに表示
let user = users.object(at: indexPath.row) as! NCMBUser
cell.textLabel!.text = user.userName
return cell
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
:
:
}

続いて、ユーザリストをNCMBから取得します。fetchUserListメソッドを作って実装します。

//////////////// ▼▼ 追加 ▼▼ ////////////////
// ユーザ取得
func fetchUserList(){
// 取得対象のテーブルを"user"に指定
let query = NCMBQuery(className: "user")
// 取得条件として自分以外の条件追加
query.whereKey("objectId", notEqualTo: NCMBUser.currentUser().objectId)
// データ取得
query.findObjectsInBackgroundWithBlock({(NSArray objects, NSError error) in
if (error != nil){
print("ユーザ取得失敗:\(error)")
} else {
print("ユーザ取得成功:\(objects)")
self.users = objects
self.tableView.reloadData()
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

NCMBのデータストアのデータを取得する場合にはNCMBQueryを使います。NCMBQueryを初期化するときにclassNameを指定して取得するデータストアのテーブルを指定します。今回は会員のテーブルなのでuserを指定します。

NCMBQuery.whereKeyにて取得する条件を追加する事ができます。今回はログインしているユーザは対象外にする条件を追加しています。

そしてNCMBQuery.findObjectsInBackgroundWithBlockにてデータ取得を行っています。
この処理も処理完了時の処理を記述できるようになっていますので、errorの有無で処理成功有無を確認し、成功している場合にはusersのメンバ変数にセットし、tableViewの更新処理をしています。

fetchUserListメソッドをloginCheckメソッドで呼び出し、画面が表示する度にユーザリストを取得するようにします。

func loginCheck(){
if (NCMBUser.currentUser() != nil) {
print("ログイン済み")
//////////////// ▼▼ 追加 ▼▼ ////////////////
// ユーザ取得
fetchUserList()
//////////////// ▲▲ 追加 ▲▲ ////////////////
} else {
print("未ログイン")
// ログイン画面へ遷移
self.performSegueWithIdentifier("toLogin", sender: nil)
}
}

この状態でRunしてみましょう。ログアウトしている場合はログインすれば、下記のようにユーザIDがTableViewに表示されます。

これでユーザ一覧機能は完了です。

ユーザ選択時にメッセージルームを作成

ユーザ一覧のセルをタップしたら、ユーザ双方でメッセージを交換できるようにルーム(部屋)を作成します。
まずNCMBにどのようなテーブル構成にすべきか考えましょう。
今回は、メッセージをやりとりするために下記のテーブルをデータストア作成します。

テーブル名 説明
Room メッセージをやりとりするルーム(部屋)を管理するテーブル、メッセージをやりとりするユーザを管理
Message メッセージ内容を管理するテーブル。RoomのIDを保持

ユーザ一覧からユーザを選択すると、自ユーザ(ログインユーザ)と選択ユーザを含めたルームを作り、そのルーム単位でメッセージを送信できるようにします。

Roomデータ保存

それではまず、ユーザを選択したときにRoomテーブルに保存する処理を追加します。

//////////////// ▼▼ 追加 ▼▼ ////////////////
// MARK: - TableView Delegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 選択したユーザ
let selectedUser = users.object(at: indexPath.row) as! NCMBUser
// 自ユーザ
let myUser = NCMBUser.current()
// Roomデータ保存
self.createRoom(myUser, selectedUser: selectedUser)
}

// Roomデータ保存
func createRoom(myUser:NCMBUser,selectedUser:NCMBUser){
// Roomテーブル登録用NCMBオブジェクト生成
let room = NCMBObject(className: "Room")
// 部屋の名前は選択したユーザを連結したものにする
room?.setObject("\(myUser.userName) <--> \(selectedUser.userName)", forKey: "roomName")
// Roomメンバー設定
room?.setObject([myUser,selectedUser], forKey: "users")
// Roomデータ保存
room?.saveInBackground({(error) in
if (error != nil) {
print("Room保存失敗:\(error)")
}else{
print("Room保存成功:\(room)")
// RoomUserにもデータ保存
self.saveRoomUserTable(
room,
myUser:myUser,
selectedUser: selectedUser
)
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

Roomを作成完了時にどのユーザがルーム登録するのかを管理するためにRoomUserデータも登録します。

これで一度Runしてユーザ一覧画面でユーザを選択してください。
下図のようにRoomのデータが保存されれば成功です。

既にRoomデータ作成済みかチェック

一度選択したユーザをまた選択した場合にはルームを作らないようにします。

//////////////// ▼▼ 追加 ▼▼ ////////////////
// すでにルーム作成されているかチェック
func checkRoomExist(myUser:NCMBUser,selectedUser:NCMBUser){
let query = NCMBQuery(className: "Room")
query?.whereKey("users", containsAllObjectsIn: [myUser,selectedUser])
query?.findObjectsInBackground({(objects, error) in
if (error != nil){
print("RoomUser取得失敗:\(error)")
} else {
print("RoomUser取得成功:\(objects)")
if (objects?.count)! > 0 {
print("Room作成済")
} else {
print("Room未作成")
// Roomデータ保存
self.createRoom(myUser: myUser, selectedUser: selectedUser)
}
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

checkRoomExistメソッドを作り、TableViewのセルタップ時にそのメソッドで確認してからRoomテーブルを作成するようにします。

// MARK: - TableView Delegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// 選択したユーザ
let selectedUser = users.objectAtIndex(indexPath.row) as! NCMBUser
// 自ユーザ
let myUser = NCMBUser.currentUser()
//////////////// ▼▼ 削除 ▼▼ ////////////////
// Roomデータ保存
self.createRoom(myUser, selectedUser: selectedUser)
//////////////// ▲▲ 削除 ▲▲ ////////////////
//////////////// ▼▼ 変更 ▼▼ ////////////////
// 既にRoomが作成されているかチェック
self.checkRoomExist(myUser: myUser!, selectedUser: selectedUser)
//////////////// ▲▲ 変更 ▲▲ ////////////////
}

この状態でRunして動作確認しておきましょう。
下図のように同じユーザを選択した場合にはRoom作成済が出力され、RoomRoomUserのテーブルが追加されない事を確認してください。

メッセージ画面

それでは選択したユーザ通しでメッセージをやりとりできるようにします。

画面デザイン

Main.storyboardを開き、1つViewControllerを追加し、ユーザ選択画面からSegue(Show)でSegueを追加して接続してください。

SegueのidentifiertoMessageにします。

ユーザ一覧画面と同様にTableViewを配置し、TableViewCellのidentifierもCellにしておきます。

また、TextFieldButtonを下図のように配置してください。

AutoLayoutの設定をしておきます。

これで画面デザインは完了です。新しくViewControllerを追加しましたのでMessageViewController.swiftを新規作成し、画面の関連付けをしておいてください。

IBOutlet、IBActionをしておきます。

メッセージ受信

まずはメッセージ受信の処理を追加していきます。

MessageViewController.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import NCMB
//////////////// ▲▲ 追加 ▲▲ ////////////////

//////////////// ▼▼ プロトコル追加 ▼▼ ////////////////
class MessageViewController: UIViewController,UITableViewDataSource,UITextFieldDelegate {
//////////////// ▲▲ プロトコル追加 ▲▲ ////////////////

//////////////// ▼▼ 追加 ▼▼ ////////////////
var room = NCMBObject() // ユーザ一覧から取得するルーム
var messages = NSArray()
//////////////// ▲▲ 追加 ▲▲ ////////////////

@IBOutlet weak var bottomMargin: NSLayoutConstraint!
@IBOutlet weak var messageTextField: UITextField!
@IBOutlet weak var tableView: UITableView!

override func viewDidLoad() {
super.viewDidLoad()
//////////////// ▼▼ 追加 ▼▼ ////////////////
tableView.dataSource = self
messageTextField.delegate = self
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

//////////////// ▼▼ 追加 ▼▼ ////////////////
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// メッセージ取得
fetchMessages()
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

//////////////// ▼▼ 追加 ▼▼ ////////////////
// MARK: - TableView Datasource
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let msg = messages.objectAtIndex(indexPath.row) as! NCMBObject
cell.textLabel!.text = msg.objectForKey("text") as? String
return cell
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

//////////////// ▼▼ 追加 ▼▼ ////////////////
// メッセージ取得
func fetchMessages(){
let query = NCMBQuery(className: "Message")
query.whereKey("room", equalTo: room)
query.orderByAscending("createDate")
query.findObjectsInBackgroundWithBlock({(NSArray objects, NSError error) in
if (error == nil) {
if(objects.count > 0) {
self.messages = objects;
self.tableView.reloadData()
} else {
print("エラー")
}
} else {
print(error.localizedDescription)
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

基本的にはユーザ一覧画面と同じように、NCMBQueryMessageテーブルの内容を取得しています。取得条件は前の画面(ユーザ一覧画面)から渡されるRoom情報をもとに取得しています。
取得した内容をTableViewに更新して表示させる処理を実装しています。

また前の画面(ユーザ一覧画面)から画面遷移するようにしておきます。

ViewController.swift

// すでにルーム作成されているかチェック
func checkRoomExist(myUser:NCMBUser,selectedUser:NCMBUser){
let query = NCMBQuery(className: "RoomUser")
query.whereKey("user", containedIn: [myUser,selectedUser])
query.findObjectsInBackgroundWithBlock({(NSArray objects, NSError error) in
if (error != nil){
print("RoomUser取得失敗:\(error)")
} else {
print("RoomUser取得成功:\(objects)")
if objects.count > 0 {
print("Room作成済")
//////////////// ▼▼ 追加 ▼▼ ////////////////
// 次の画面へ遷移(roomを次の画面に渡す)
let room = objects[0] as! NCMBObject
self.performSegueWithIdentifier("toMessage", sender: room)
//////////////// ▲▲ 追加 ▲▲ ////////////////
} else {
print("Room未作成")
self.createRoom(myUser,selectedUser: selectedUser)
}
}
})
}

ViewController.swift

//////////////// ▼▼ 追加 ▼▼ ////////////////
func createRoom(myUser:NCMBUser,selectedUser:NCMBUser){
// Roomテーブル登録用NCMBオブジェクト生成
let room = NCMBObject(className: "Room")
// 部屋の名前は選択したユーザを連結したものにする
room.setObject("\(myUser.userName) <--> \(selectedUser.userName)", forKey: "roomName")
// Roomメンバー設定
room.setObject([myUser,selectedUser], forKey: "users")
// Roomデータ保存
room.saveInBackgroundWithBlock({(NSError error) in
if (error != nil) {
print("Room保存失敗:\(error)")
}else{
print("Room保存成功:\(room)")
//////////////// ▼▼ 追加 ▼▼ ////////////////
// 次の画面へ遷移(roomを次の画面に渡す)
self.performSegueWithIdentifier("toMessage", sender: room)
//////////////// ▲▲ 追加 ▲▲ ////////////////
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

prepareForSegueメソッドを追加して画面遷移のタイミングでroomデータを次の画面に送るようにしています。

ViewController.swift

//////////////// ▼▼ 追加 ▼▼ ////////////////
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toMessage"{
let vc = segue.destinationViewController as! MessageViewController
vc.room = sender as! NCMBObject
}
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

メッセージ送信処理

メッセージを取得して表示する機能は実装しましたが、送信して保存する処理を実装していませんので実装します。IBActionのsendMessageButtonTappedに送信処理を記述していきます。

MessageViewController.swift

// MARK: - IBAction
@IBAction func sendMessageButtonTapped(sender: AnyObject) {
//////////////// ▼▼ 追加 ▼▼ ////////////////
let msgStr = messageTextField.text
// 文字を何かしら入力していたら送信
if msgStr?.characters.count > 0 {
// メッセージ送信
let msg = NCMBObject(className: "Message")
msg.setObject(msgStr, forKey: "text")
msg.setObject(room, forKey: "room")
// データストアに保存
msg.saveInBackgroundWithBlock({(NSError error) in
if (error != nil) {
print("Message:保存失敗:\(error)")
}else{
print("Message:保存成功:\(msg)")
// TableView更新
self.fetchMessages()
// テキストフィールドの入力内容をリセット
self.messageTextField.text = ""
}
})
}
messageTextField.resignFirstResponder()
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

この状態でメッセージを保存する事ができましたが、この状態では、テキストフィールドのキーボードが表示しっぱなしになりますので、エンターキーを入力したらキーボードを閉じるようにします。

MessageViewController.swift

//////////////// ▼▼ 追加 ▼▼ ////////////////
// 改行ボタンタップ時
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

この状態で実行してみましょう。送信ボタンをクリックしたらメッセージが保存され、TableViewに保存されればOKです。

NCMB管理画面にもMessageテーブルが作成され、送信内容が登録されている事を確認しましょう。

これでメッセージ画面の基本的な処理は完了です。

プッシュ通知設定

LINEアプリのようにユーザがメッセージを送信したら、Push通知送信し、相手のユーザに通知してあげる機能を実装してみましょう。

プッシュ通知の流れ

プッシュ通知についての概要はApple公式ドキュメント「Local および Push Notification プログラミングガイド」で解説されていますが、単純化すると下記の流れとなります。

  • iPhone・iPadのアプリでプッシュ通知を許可し、APNSからデバイストークンを取得
  • デバイストークンを何らかの方法でプロバイダに送信
  • プロバイダよりプッシュ許可したデバイスを対象にAPNS通知を依頼
  • APNSからデバイスへ通知し、デバイスでサウンド・バッジ・アラート等を出す

プッシュ通知の前提

プッシュ通知を実装するには下記の前提条件があります。

  • iOS Developer Programの登録が必要
  • iOS実機を持っている

プッシュ通知事前設定

プッシュ通知を利用するには下記の作業が必要です。

  • App IDの登録
  • プッシュ通知の有効化と証明書のダウンロード
  • 証明書を所定の形式(*.pem)に変換

App IDの登録

iOS Dev Centerにアクセスし、開発者登録されているIDでログインします。

https://developer.apple.com/membercenter/

開発者登録しているアカウントでログインします。

Certificates,Identifiers & Profilesを開きます。

App IDsを選択してから+ボタンをクリックします。

項目 概要
App ID Description AppIDsの一覧で表示される名称です。アプリの概要を表す名称を入力します。
App ID Suffix Explicit App IDを選択し、アプリのBundle Identifierと同様の名称を入力します。例)com.terakoya.Messenger
App Services アプリで使用するサービスです。プッシュ通知を利用するため「Push Notification」をチェックします。

プッシュ通知の有効化と証明書のダウンロード

App IDで「Configurable」となっているプッシュ通知を有効にするため、認証局より証明書を取得します。
MACでキーチェーンアクセスを開き、証明書アシスタント⇒認証局に証明書を要求を開きます。

証明書アシスタントで証明書情報を入力し、「続ける」を押下します。

項目 概要
ユーザのメールアドレス デベロッパ登録したメールアドレス
通称 デベロッパの名前(デベロッパ登録したものと同じでなくてもOK)
要求の処理 ディスクに保存を選択

CertificateSigningRequest.certSigningRequestの保存画面となるため、任意の場所に保存します。

iOS Developer Centerに戻り、登録したApp IDを選択し、Editより編集画面を開きます。

編集画面のPush Notificationsより、証明書を作成します。
証明書は開発用と製品用をそれぞれ必要に応じて作成する必要がありますが、手順は同じです。
Create Certificateを押下します。

Continueを押下します。

Choose Fileを押下するとファイル選択画面となるため、保存しておいたCertificateSigningRequest.certSigningRequestを指定します。

CertificateSigningRequest.certSigningRequestが選択されたらGernerateボタンを押下します。

証明書の作成に成功すると証明書がダウンロード可能になるため、Downloadボタンよりダウンロードします。

ダウンロードした証明書をダブルクリックすると、キーチェーンに登録されます。
今回は開発用(Development)の証明書のため、Apple Development IOS Push Servicesとなっています。

iOS Dev CenterのApp IDsより該当App IDを確認すると、Push NotificationsEnabledになっています。
※製品用(Distribution)の証明書も同様の手順で作成するとEnabledになります。

証明書を所定の形式(*.pem)に変換

キーチェーンアクセスの左ペインより「証明書」を開き、該当証明書を選択した状態で、Apple Development IOS Push Services:アプリID”を書き出すを選択します。

任意の場所へ、「個人情報交換(.p12)」の形式で保存します。
便宜上、今回はデスクトップに下記の名前で保存します。push_develop.p12で保存しましょう。

書き出した証明書のパスワード入力画面となるため、何も入力せずそのままOKを押下します。

キーチェーンにアクセスするためのパスワードを聞かれた場合、入力します。
(通常Macのログインパスワードです。)

デスクトップにファイルが保存されたことを確認します。

NCMBにプッシュ通知設定

アプリ側プッシュ通知設定

AppDelegate.swift

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// NCMB設定
NCMB.setApplicationKey("e1fe98cf51709607c3c500507dd03305bd8298034ee4297d629f5a7a7704dcaa", clientKey: "b045fab67b58b1eca59d20d65787cf350eb1332e632f24a48cd1a8dae2bdff5a")
//////////////// ▼▼ 追加 ▼▼ ////////////////
// プッシュ通知設定
if NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1 {
// プッシュ通知のタイプを選択(バッジ、サウンド、アラート)
let types: UIUserNotificationType = [.Alert, .Badge, .Sound,]
let settings = UIUserNotificationSettings(forTypes: types, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
}
// アプリ起動時のプッシュ通知
if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary {
print("Remote Notification \(remoteNotification)")
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
return true
}

//////////////// ▼▼ 追加 ▼▼ ////////////////
// MARK: - プッシュ通知
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
print("Device token:\(deviceToken)")
let installation = NCMBInstallation.currentInstallation()
installation.setDeviceTokenFromData(deviceToken)
installation.saveInBackgroundWithBlock(nil)
}

// 起動中に受け取ったプッシュ通知
func application(application: UIApplication, didReceiveRemoteNotification userInfo:[NSObject : AnyObject],
fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void){
print("User Info \(userInfo)")
NSNotificationCenter.defaultCenter().postNotificationName("remotePush", object: nil)
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

この状態でRunしてみましょう。下図のようにプッシュ通知の許可を求めるアラートが出ます。OKを押す事によりデバイストークンがNCMBに登録されます。
※シュミレータではプッシュ通知のデバイストークンは作成されませんので、実機で確認してください。

実機でプッシュ通知の許可をするとNCMB管理画面のデータストア -> installationのテーブルに端末の情報が保存されます。

NCMB管理画面からプッシュ通知を送る

プッシュ通知の準備が整いましたので、NCMB画面からプッシュ通知をしてみたいと思います。
NCMB管理画面でプッシュ通知をクリックし新しいプッシュ通知ボタンをクリックします。

タイトル、メッセージ欄に任意に文字を入力して、配信日時は今すぐ配信をエランでください。

iOS端末に配信するのチェックボックスを有効にしてプッシュ通知を作成するをクリックしてください。

しばらくすると、アプリ端末にプッシュ通知が届きます。
実機でないと確認できません。

メッセージ送信時にルームメンバーにプッシュ通知

それではメッセージ送信時にメンバーにプッシュ通知をするようにしたいと思います。
下記のとおりpushChannelSetと、pushNotificationメソッドを追加します。

MessageViewController.swift

//////////////// ▼▼ 追加 ▼▼ ////////////////
// MARK: - プッシュ通知
// チャネル登録有無を確認後、未登録ならチャネル登録
func pushChannelSet(){
// ログインユーザのinstallation取得
let installation = NCMBInstallation.currentInstallation()
print("installation.channels:\(installation.channels)")
// 既にチャネル登録されていないかチェック
var isRegistered = false
// チャネル登録有無をチェック(今のRoomのObjectIdでチェネル登録されていないか確認)
if (installation.channels != nil) {
for channel in installation.channels {
if String(channel) == String(room.objectId){
isRegistered = true
print("チャネルは既に登録済み")
}
}
}
if !isRegistered {
print("チャネルは未登録")
let newChannel = NSMutableArray()
// RoomのObjectIdでチェネル登録
newChannel.addObject(room.objectId)
installation.channels = newChannel
installation.saveInBackgroundWithBlock({(NSError error) in
if (error != nil) {
print("チャネル登録失敗")
} else {
print("チャネル登録成功")
}
})
}
}
// チャネルのユーザに対してプッシュ通知
func pushNotification(){
let push = NCMBPush()
push.setPushToIOS(true)
push.setPushToAndroid(false)
push.setChannel(room.objectId)
push.setMessage("Message sent!")
push.setImmediateDeliveryFlag(true)
push.saveInBackgroundWithBlock({(NSError error) in
if (error != nil) {
print("Push通知失敗:\(error)")
}else{
print("Push通知成功")
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

pushChannelSetメソッドでは、ログインユーザのinstallation(プッシュ通知配信端末)に対してチャネルの登録をしています。チャネルとはプッシュ通知を配信する端末をグルーピングするものです。今回はメッセージをやりとりするルーム単位でチャネルを作りたいため、ログインユーザがこの画面にアクセスしたときにRoomのIDをチャネルとしてinstallationに追加しています。

pushNotificationメソッドでは、そのチャネルに対してプッシュ通知を送信する処理を記述しています。
つぎに、viewDidLoadでチャネル登録処理が実行されるようにします。

MessageViewController.swift

override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
messageTextField.delegate = self
//////////////// ▼▼ 追加 ▼▼ ////////////////
// ルームに入ったユーザはプッシュ通知を受けるためにチャネル登録
self.pushChannelSet()
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

メッセージ送信後にプッシュ通知されるようにします。

MessageViewController.swift

@IBAction func sendMessageButtonTapped(sender: AnyObject) {
let msgStr = messageTextField.text
if msgStr?.characters.count > 0 {
// メッセージ送信
let msg = NCMBObject(className: "Message")
msg.setObject(msgStr, forKey: "text")
msg.setObject(room, forKey: "room")
// データストアに保存
msg.saveInBackgroundWithBlock({(NSError error) in
if (error != nil) {
print("Message:保存失敗:\(error)")
}else{
print("Message:保存成功")
self.fetchMessages()
self.messageTextField.text = ""
//////////////// ▼▼ 追加 ▼▼ ////////////////
// プッシュ通知
self.pushNotification()
//////////////// ▲▲ 追加 ▲▲ ////////////////
}
})
}
messageTextField.resignFirstResponder()
}

プッシュ通知受信を検知

この画面でメッセージをやりとりしているときに、相手側がメッセージを送信したのを検知するための仕組みを追加します。画面が表示されるタイミングで呼ばれるviewDidAppearメソッドにNSNotificationCenter.defaultCenter().addObserverメソッドを使ってプッシュ通知を検知するようにしています。また画面が非表示になったときにはその検知を解除するようにもしています。

MessageViewController.swift

override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// メッセージ取得
fetchMessages()
//////////////// ▼▼ 追加 ▼▼ ////////////////
// プッシュ通知検知設定
NSNotificationCenter.defaultCenter().addObserver(self,selector: "update:", name: "remotePush", object: nil)
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
//////////////// ▼▼ 追加 ▼▼ ////////////////
// NotificationCenter解除
NSNotificationCenter.defaultCenter().removeObserver(self, name: "update:", object: nil)
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

//////////////// ▼▼ 追加 ▼▼ ////////////////
// プッシュ通知受信時
func update(notification: NSNotification?) {
// メッセージ再読み込み
fetchMessages()
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

NSNotificationCenter.defaultCenter().addObserverremotePushの名前のイベント(AppDelegateでプッシュ通知受信時に通知されるよう定義しています)が発生したら、updateメソッドを呼びさす設定をし、updateメソッドではfetchMessagesメソッドを実行してメッセージの再取得をしています。これを実装することによりこの画面でメッセージをやりとりがほぼリアルタイムに画面の再表示をせずにメッセージのやりとりができるようになります。

これでメッセンジャーアプリは完成です。
是非、複数端末にこのアプリをインストールして、メッセージのやりとりができる事を確認してみてください。

また、メッセージをやり取りする画面MessageViewControllerのUIもLineアプリのように吹き出しのイメージにするなどして改善していくといいでしょう。
メッセンジャーのUIを手軽に構築するにはJSQMessagesViewControllerというライブラリが有名ですので、そのライブラリを取り込んでみるといいでしょう。

JSQMessagesViewController