AranAli
AranAli

Reputation: 47

SwiftUI ScrollViewReader does not scroll to last message when images are included

I am working on a SwiftUI chat application where I want the chat view to automatically scroll to the last message when the view appears and when new messages are added. The scrolling works fine for text messages, but when I include images in the chat messages, it stops scrolling to the last message.

My code in FirebaseManager:

struct UserData: Identifiable {
    var id = UUID()
    var username: String
    var profileImageURL: URL?
    var firstName: String
}

struct Message: Identifiable, Equatable, Hashable {
    var id = UUID()
    var text: String
    var senderID: String
    var recipientUsername: String
    var timestamp: Date // Används för att ordna meddelandena efter tidpunkt
    var imageURL: URL?
    
    var timeAgo: String {
        let formatter = RelativeDateTimeFormatter()
        formatter.unitsStyle = .abbreviated
        return formatter.localizedString(for: timestamp, relativeTo: Date())
    }
    
}

My code in ChatLogViewModel:

class ChatLogViewModel: ObservableObject {
    
    @Published var messages: [Message] = []
    
    func fetchMessages() {
            firebaseManager.fetchMessages { messages in
                DispatchQueue.main.async {
                    self.messages = messages
                }
            }
        }
    
}

My code in MessageView:

import SwiftUI

struct MessageView: View {
    
    let message: Message
    let isCurrentUser: Bool

    var body: some View {
        VStack(alignment: isCurrentUser ? .trailing : .leading) {
            if let imageURL = message.imageURL {
                AsyncImage(url: imageURL) { phase in
                    switch phase {
                    case .empty:
                        ProgressView()
                    case .success(let image):
                        image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(maxWidth: 200, maxHeight: 200)
                            .cornerRadius(10)
                    case .failure:
                        Text("Failed to load image")
                    @unknown default:
                        EmptyView()
                    }
                }
            }
            if !message.text.isEmpty {
                Text(message.text)
                    .foregroundColor(isCurrentUser ? .white : .black)
                    .padding()
                    .background(isCurrentUser ? Color.blue : Color.white)
                    .cornerRadius(10)
                    .overlay(
                        MessageOverlay(isCurrentUser: isCurrentUser)
                    )
            }
        }
    }
}

struct MessageOverlay: View {
    let isCurrentUser: Bool

    var body: some View {
        Image(systemName: "arrowtriangle.down.fill")
            .font(.title)
            .rotationEffect(.degrees(isCurrentUser ? -45 : 45))
            .offset(x: isCurrentUser ? 10 : -10, y: 10)
            .foregroundColor(isCurrentUser ? Color.blue : Color.white)
    }
}

My code in MessageRow:

import SwiftUI

struct MessageRow: View {
    let message: Message
    let isCurrentUser: Bool

    var body: some View {
        HStack {
            if isCurrentUser {
                Spacer()
                MessageView(message: message, isCurrentUser: isCurrentUser)
            } else {
                MessageView(message: message, isCurrentUser: isCurrentUser)
                Spacer()
            }
        }
    }
}

My code in ChatLogView:

import SwiftUI

struct ChatLogView: View {
    
    let user: UserData
    
    @StateObject var chatLogViewModel = ChatLogViewModel()
    
    var firebaseManager: FirebaseManager
    
    var body: some View {
        VStack {
            ScrollView {
                ScrollViewReader { scrollViewProxy in
                    VStack {
                        ForEach(chatLogViewModel.messages) { message in
                            MessageRow(message: message, isCurrentUser: message.senderID == firebaseManager.currentUserUID)
                                .padding(.horizontal)
                                .padding(.top, 8)
                        }
                        HStack { Spacer() }
                            .id("lastMessage")
                    }
                    .onAppear {
                        print("Chat view appeared, scrolling to last message")
                        scrollViewProxy.scrollTo("lastMessage", anchor: .bottom)
                    }
                    .onChange(of: chatLogViewModel.messages) { _ in
                        print("Messages changed, scrolling to last message")
                        withAnimation(.easeOut(duration: 0.5)) {
                            scrollViewProxy.scrollTo("lastMessage", anchor: .bottom)
                        }
                    }
                }
            }
        }
        .background(Color.gray.opacity(0.2))
        .navigationTitle(user.username)
        .navigationBarTitleDisplayMode(.inline)
        .toolbar(.hidden, for: .tabBar)
        ChatBarComponent(chatLogViewModel: chatLogViewModel, user: user)
    }
}


I can still see these two printed in the terminal when I enter the chat:

Chat view appeared, scrolling to last message

Messages changed, scrolling to last message

What I have tried (beside from the code):

  1. I have also tried ensuring each message has a unique ID using .id(message.id).
  2. Using the defaultScrollAnchor() modifier with an initial anchor of . bottom

Any help or suggestions would be greatly appreciated!

Upvotes: 0

Views: 72

Answers (0)

Related Questions