fix(chat): Release 빌드에서 타이핑 인디케이터 미갱신 문제 수정
- 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/렌더링이 꼬인 것이 원인.
This commit is contained in:
@@ -125,7 +125,7 @@ struct ChatRoomView: View {
|
|||||||
let message = viewModel.messages[index]
|
let message = viewModel.messages[index]
|
||||||
if message.mine {
|
if message.mine {
|
||||||
UserMessageItemView(message: message)
|
UserMessageItemView(message: message)
|
||||||
.id(index)
|
.id("msg_\(index)")
|
||||||
} else {
|
} else {
|
||||||
AiMessageItemView(
|
AiMessageItemView(
|
||||||
message: message,
|
message: message,
|
||||||
@@ -138,7 +138,7 @@ struct ChatRoomView: View {
|
|||||||
viewModel.selectedMessageIndex = index
|
viewModel.selectedMessageIndex = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.id(index)
|
.id("msg_\(index)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,11 +146,11 @@ struct ChatRoomView: View {
|
|||||||
ChatQuotaNoticeItemView(remainingTime: viewModel.countdownText) {
|
ChatQuotaNoticeItemView(remainingTime: viewModel.countdownText) {
|
||||||
viewModel.purchaseChatQuota()
|
viewModel.purchaseChatQuota()
|
||||||
}
|
}
|
||||||
.id(viewModel.messages.count)
|
.id("quota_\(viewModel.messages.count)")
|
||||||
.padding(.bottom, 12)
|
.padding(.bottom, 12)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
withAnimation(.easeOut(duration: 0.3)) {
|
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,
|
characterName: viewModel.characterName,
|
||||||
characterProfileUrl: viewModel.characterProfileUrl
|
characterProfileUrl: viewModel.characterProfileUrl
|
||||||
)
|
)
|
||||||
.id(viewModel.messages.count)
|
.id("typing_\(viewModel.messages.count)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 24)
|
.padding(.horizontal, 24)
|
||||||
.frame(minHeight: geometry.size.height, alignment: .bottom)
|
.frame(minHeight: geometry.size.height, alignment: .bottom)
|
||||||
}
|
}
|
||||||
.onChange(of: viewModel.messages.count) { _ in
|
.onChange(of: viewModel.messages.count) { _ in
|
||||||
if !viewModel.messages.isEmpty {
|
guard !viewModel.messages.isEmpty else { return }
|
||||||
withAnimation(.easeOut(duration: 0.3)) {
|
withAnimation(.easeOut(duration: 0.3)) {
|
||||||
proxy.scrollTo(
|
let targetId = viewModel.showSendingMessage
|
||||||
viewModel.showSendingMessage ?
|
? "typing_\(viewModel.messages.count)"
|
||||||
viewModel.messages.count :
|
: "msg_\(viewModel.messages.count - 1)"
|
||||||
viewModel.messages.count - 1,
|
proxy.scrollTo(targetId, anchor: .bottom)
|
||||||
anchor: .bottom
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ final class ChatRoomViewModel: ObservableObject {
|
|||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
characterImageId: self.chatRoomBgImageId
|
characterImageId: self.chatRoomBgImageId
|
||||||
)
|
)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
@@ -200,6 +201,7 @@ final class ChatRoomViewModel: ObservableObject {
|
|||||||
|
|
||||||
func getMemberInfo() {
|
func getMemberInfo() {
|
||||||
userRepository.getMemberInfo()
|
userRepository.getMemberInfo()
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
@@ -233,6 +235,7 @@ final class ChatRoomViewModel: ObservableObject {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
repository.purchaseMessage(roomId: roomId, messageId: selectedMessage.messageId)
|
repository.purchaseMessage(roomId: roomId, messageId: selectedMessage.messageId)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
@@ -277,6 +280,7 @@ final class ChatRoomViewModel: ObservableObject {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
repository.purchaseChatQuota(roomId: roomId)
|
repository.purchaseChatQuota(roomId: roomId)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
@@ -320,6 +324,7 @@ final class ChatRoomViewModel: ObservableObject {
|
|||||||
isResetting = true
|
isResetting = true
|
||||||
|
|
||||||
repository.resetChatRoom(roomId: roomId)
|
repository.resetChatRoom(roomId: roomId)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
|
|||||||
Reference in New Issue
Block a user