From da78b43f64bc62a645af8b81dc0b090a26d73e94 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Fri, 12 Sep 2025 00:26:07 +0900 Subject: [PATCH] =?UTF-8?q?fix(chat):=20Release=20=EB=B9=8C=EB=93=9C?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=83=80=EC=9D=B4=ED=95=91=20=EC=9D=B8?= =?UTF-8?q?=EB=94=94=EC=BC=80=EC=9D=B4=ED=84=B0=20=EB=AF=B8=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SwiftUI diff 꼬임 원인 제거: LazyVStack 아이템 id 네임스페이스 분리 - 메시지 셀: "msg_\(index)" - 쿼터 안내: "quota_\(messages.count)" - 타이핑 인디케이터: "typing_\(messages.count)" - 스크롤 타깃 id도 동일 네임스페이스로 일관화 - Combine 체인 메인 스레드 보장: sendMessage/enterRoom에 receive(on: .main) 적용 - 성공/실패/디코드 실패 모든 경로에서 showSendingMessage 정상 복구 왜: 디버그에서는 보였으나 Release(TestFlight)에서 UI 반영이 유실됨. 동일 id 충돌 및 메인 스레드 미보장으로 SwiftUI diff/렌더링이 꼬인 것이 원인. --- .../Sources/Chat/Talk/Room/ChatRoomView.swift | 25 ++++++++----------- .../Chat/Talk/Room/ChatRoomViewModel.swift | 5 ++++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/SodaLive/Sources/Chat/Talk/Room/ChatRoomView.swift b/SodaLive/Sources/Chat/Talk/Room/ChatRoomView.swift index c4c634b..ce63ca2 100644 --- a/SodaLive/Sources/Chat/Talk/Room/ChatRoomView.swift +++ b/SodaLive/Sources/Chat/Talk/Room/ChatRoomView.swift @@ -125,7 +125,7 @@ struct ChatRoomView: View { let message = viewModel.messages[index] if message.mine { UserMessageItemView(message: message) - .id(index) + .id("msg_\(index)") } else { AiMessageItemView( message: message, @@ -138,7 +138,7 @@ struct ChatRoomView: View { viewModel.selectedMessageIndex = index } } - .id(index) + .id("msg_\(index)") } } @@ -146,11 +146,11 @@ struct ChatRoomView: View { ChatQuotaNoticeItemView(remainingTime: viewModel.countdownText) { viewModel.purchaseChatQuota() } - .id(viewModel.messages.count) + .id("quota_\(viewModel.messages.count)") .padding(.bottom, 12) .onAppear { withAnimation(.easeOut(duration: 0.3)) { - proxy.scrollTo(viewModel.messages.count, anchor: .bottom) + proxy.scrollTo("quota_\(viewModel.messages.count)", anchor: .bottom) } } } @@ -160,22 +160,19 @@ struct ChatRoomView: View { characterName: viewModel.characterName, characterProfileUrl: viewModel.characterProfileUrl ) - .id(viewModel.messages.count) + .id("typing_\(viewModel.messages.count)") } } .padding(.horizontal, 24) .frame(minHeight: geometry.size.height, alignment: .bottom) } .onChange(of: viewModel.messages.count) { _ in - if !viewModel.messages.isEmpty { - withAnimation(.easeOut(duration: 0.3)) { - proxy.scrollTo( - viewModel.showSendingMessage ? - viewModel.messages.count : - viewModel.messages.count - 1, - anchor: .bottom - ) - } + guard !viewModel.messages.isEmpty else { return } + withAnimation(.easeOut(duration: 0.3)) { + let targetId = viewModel.showSendingMessage + ? "typing_\(viewModel.messages.count)" + : "msg_\(viewModel.messages.count - 1)" + proxy.scrollTo(targetId, anchor: .bottom) } } } diff --git a/SodaLive/Sources/Chat/Talk/Room/ChatRoomViewModel.swift b/SodaLive/Sources/Chat/Talk/Room/ChatRoomViewModel.swift index 4603567..4db4a36 100644 --- a/SodaLive/Sources/Chat/Talk/Room/ChatRoomViewModel.swift +++ b/SodaLive/Sources/Chat/Talk/Room/ChatRoomViewModel.swift @@ -151,6 +151,7 @@ final class ChatRoomViewModel: ObservableObject { roomId: roomId, characterImageId: self.chatRoomBgImageId ) + .receive(on: DispatchQueue.main) .sink { result in switch result { case .finished: @@ -200,6 +201,7 @@ final class ChatRoomViewModel: ObservableObject { func getMemberInfo() { userRepository.getMemberInfo() + .receive(on: DispatchQueue.main) .sink { result in switch result { case .finished: @@ -233,6 +235,7 @@ final class ChatRoomViewModel: ObservableObject { isLoading = true repository.purchaseMessage(roomId: roomId, messageId: selectedMessage.messageId) + .receive(on: DispatchQueue.main) .sink { result in switch result { case .finished: @@ -277,6 +280,7 @@ final class ChatRoomViewModel: ObservableObject { isLoading = true repository.purchaseChatQuota(roomId: roomId) + .receive(on: DispatchQueue.main) .sink { result in switch result { case .finished: @@ -320,6 +324,7 @@ final class ChatRoomViewModel: ObservableObject { isResetting = true repository.resetChatRoom(roomId: roomId) + .receive(on: DispatchQueue.main) .sink { result in switch result { case .finished: