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