今までのチャットの実装の難しさとiOS14からの手法
UIKitでチャットアプリを実装する際、UITableViewを使うことが多いかと思います。
LINEのようなチャットの場合は特に高さの計算、一番下へスクロールの実装が難しいですね。
今までの経験により、実装も複雑になりバグも多かったりしました。
iOS14になってからは、SwiftUIのScrollViewReaderが出てきて、だいぶチャットの実装が楽になりました。
環境
- Xcode12.2
 - iOS14.2(SwiftUI2)
 
ScrollViewReaderとは
プログラム的にスクロールできるようになります。
ScrollViewReader - Apple公式ドキュメント
ScrollView {
  ScrollViewReader { (proxy: ScrollViewProxy) in
    ...
  }
}
ScrollViewReaderのクロージャーでScrollViewProxyにアクセスが可能になります。これを保存しておき、
scrollToでプログラム的にスクロールします。
func scrollTo<ID>(_ id: ID, anchor: UnitPoint? = nil) where ID : Hashable
簡単なチャットで使ってみた
struct ContentView: View {
    
    @ObservedObject private var viewModel: ViewModel = .init()
    
    @State var text: String = ""
    @State var value: ScrollViewProxy?
    
    var body: some View {
        VStack {            
            ScrollView {
                ScrollViewReader { value in
                    LazyVStack(alignment: .center, spacing: 16) {
                        //チャットの表示
                        ForEach.init(self.viewModel.messages, id: \.id) { message in
                            ChatView.init(message: message)
                        }
                    }.onAppear {
                        self.value = value //ScrollViewProxyを保存する
                        self.value?.scrollTo(self.viewModel.messages.count, anchor: .bottom) //初めに表示された時に一番下までスクロールする
                    }.animation(.easeInOut)
                }
            }
            
            //テキスト入力欄
            VStack {
                ...
            }.background(Color(white: 0.95))
        }
    }
}
extension ContentView {
    
    //送信ボタンを押した時
    func sendText() {
        viewModel.send(text: text)
        text = ""
        guard let message = viewModel.messages.last else { return }
        debugPrint(message)
        //わずかにタイミングをずらさないと、スクロールできない
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
            withAnimation {
                //一番下にアニメーションする
                self.value?.scrollTo(message.id, anchor: .bottom)
            }
        })
    }
    
}
スクショ
ソースコード
参考
https://developer.apple.com/documentation/swiftui/scrollviewreader
https://qiita.com/giiiita/items/be38b9f0135a12bfd49c
https://medium.com/better-programming/build-a-chat-app-interface-with-swiftui-96609e605422
https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions
https://www.raywenderlich.com/5815412-getting-started-with-swiftui-animations