0

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!

4
  • Can you include a minimal reproducible example? Commented May 17, 2024 at 20:27
  • I now updated my code, mostly the structure of it to be more understandable @jnpdx Commented May 19, 2024 at 12:49
  • But no one can run it without a minimal reproducible example Commented May 19, 2024 at 13:32
  • Looks like ui doesn’t know your final sizes of views. And it calculated while drawing. Try to set hardcoded frames for images and check behavior again Commented Jan 11 at 7:56

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.