ハンズオン開催
こちらの内容を元にオンラインハンズオンを開催します。1時間程度になりますので、ご興味があればぜひご参加ください。
NCMBのSwift SDKを使ってデモアプリを作ってみます。リアルタイム通信系は人気があるのですが、NCMBでは残念ながらWebSocketは使えません。そこで今回はPieSocketというWebSocketを提供するサービスと組み合わせて、Swift製のチャットアプリを作ってみます。
前回はチャットメッセージの送信について解説しました。今回はチャットメッセージの表示と、既存データの取得について解説します。
コードについて
今回のコードはNCMBMania/Swift_Chat_Demoにアップロードしてあります。実装時の参考にしてください。
チャットメッセージの表示
チャットメッセージの表示は ChatView
にて行います。WebSocketの接続やメッセージの送受信を管理している ChatScreenModel
を ObservedObject
として定義しています。
struct ChatView: View {
@ObservedObject var chat = ChatScreenModel()
そして chat.messages
にメッセージが追加されたらリストを更新しています。この時、ForEach の id は objectId になります。
ScrollView {
ScrollViewReader { proxy in
LazyVStack(spacing: 8) {
ForEach(chat.messages, id: \.objectId ) { message in
ChatMessageRow(message: message)
}
}
.onChange(of: chat.messages.count) { _ in
scrollToLastMessage(proxy: proxy)
}
}
}
scrollToLastMessage
はリストの最後のメッセージを追いかける形で自動スクロールするためのメソッドです。
// 自動スクロール用
private func scrollToLastMessage(proxy: ScrollViewProxy) {
if let lastMessage = chat.messages.last {
withAnimation(.easeOut(duration: 0.4)) {
proxy.scrollTo(lastMessage.objectId, anchor: .bottom)
}
}
}
自分と相手のメッセージで表示を分ける
自分のメッセージか否かで表示(色や配置)を分けています。これは ChatMessageRow
で実装しています。自分のメッセージかどうかはNCMBObjectにあるuserId(チャット投稿者のobjectId)を使って判定しています。
struct ChatMessageRow: View {
@State var message: NCMBObject
// 日付のフォーマット(時刻のみ)
static private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
return formatter
}()
var body: some View {
HStack {
if isMe() {
Spacer()
}
VStack(alignment: .leading, spacing: 6) {
HStack {
Text(message["displayName"]! as String)
.fontWeight(.bold)
.font(.system(size: 12))
Text(Self.dateFormatter.string(from: createDate()))
.font(.system(size: 10))
.opacity(0.7)
}
Text(message["body"]! as String)
}
.foregroundColor(isMe() ? .white : .black)
.padding(10)
.background(isMe() ? Color.blue : Color(white: 0.95))
.cornerRadius(5)
if !isMe() {
Spacer()
}
}
}
// 自分宛かどうか判定する関数
func isMe() -> Bool {
if let userId: String = message["userId"] {
let user = NCMBUser.currentUser
return userId == user!.objectId
}
return false
}
// 日付をフォーマットに沿って返す
func createDate() -> Date {
if let createDate: String = message["createDate"] {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return dateFormatter.date(from: createDate)!
}
return Date()
}
}
既存メッセージの取得
過去にやり取りされたメッセージは NCMB のChatクラスにあります。チャット画面が表示されたタイミングで、そのデータを取得します。データを受け取ったらメインスレッドで更新する必要があるので注意してください。メッセージはそのまま chat.messages
に入れてしまえば、表示に反映されます。
// NCMBに保存されているメッセージを取得する関数
func getPastMessages() {
// データ取得用のクエリオブジェクトを用意
var query = NCMBQuery.getQuery(className: "Chat")
// 並び順はcreateDateの昇順
query.order = ["createDate"]
// 20件取得
query.limit = 20
// 検索実行
query.findInBackground(callback: { result in
// 結果判定
switch result {
case let .success(ary):
// 取得できた場合は結果をチャットメッセージとして反映
DispatchQueue.main.async {
chat.messages = ary
}
break
case .failure(_): break // エラーの場合
}
})
}
反映をリアルタイムで行う
メッセージが追加された際に表示への反映をリアルタイムで行うため ChatScreenModel
の messages
に @Published
を付けておきます。
final class ChatScreenModel: ObservableObject {
private var webSocketTask: URLSessionWebSocketTask?
@Published var messages: [NCMBObject] = []
これで過去のメッセージも表示できるようになります。
まとめ
今回はNCMBの匿名認証とデータストア、そしてPieSocketを使ってチャットアプリを作りました。今回はチャンネルIDを1固定にしていますが、これを変えることで複数のチャットルームにも対応します。ぜひカスタマイズして、Swiftアプリの中にチャット機能を組み込んでください。