🤔 번역 기능은 어디에 구현해야하나?
현재 검색 결과의 소개(overview)가 영어로 표시되고 있기 때문에
이를 Google Translate API를 사용하여 한국어로 변환하려고 함
🔷 방법
1️⃣ SearchViewModel 에서 overview를 번역 후 @Publised 에 저장
2️⃣ SearchResultCell 에서 configure(with: ) 시점에 번역 후 실행
🔷 방법 비교
방법 | 장점 | 단점 |
SearchViewModel에서 미리 번역 | ✅ ViewModel에서 모든 데이터 관리 ✅ 데이터 변경 시 UI 자동 업데이트 |
❌ 검색 결과가 많으면 API 요청이 많아짐 ❌ 한 번의 API 요청 지연이 전체 UI 업데이테 영향 |
SearchResultCell에서 번역 실행 | ✅ 필요한 경우에만 번역 요청 ✅ UI 렌더링 속도 향상 |
❌ 비동기 API 요청으로 인해 셀의 UI 업데이트 시점이 다를 수 있음 |
✅ 가장 최적의 해결 방법
1️⃣ SearchViewModel에서 overview 번역을 관리하지만, 비동기로 요청하여 UI가 멈추지 않도록 처리
2️⃣ configure(with:)에서 번역된 결과를 확인하고 즉시 업데이트
✅ SearchViewModel에서 번역 실행 (비동기 처리)
class SearchViewModel: ObservableObject {
...
@Published var translatedMovieOverviews: [Int: String] = [:] // 영화 overview 번역 저장
@Published var translatedTVOverviews: [Int: String] = [:] // TV overview 번역 저장
// MARK: - 검색 실행
func search(query: String) {
currentQuery = query
resetState()
Task {
do {
let results = try await searchService.searchAll(with: query, page: 1)
DispatchQueue.main.async {
self.movies = results.movies.results
self.tvShows = results.tvShows.results
self.people = results.people.results
self.totalMoviesCount = results.movies.totalResults
self.totalTVShowsCount = results.tvShows.totalResults
self.totalPeopleCount = results.people.totalResults
self.translateOverviews(for: .movie(self.movies))
self.translateOverviews(for: .tv(self.tvShows))
self.updateLoadMoreStatus()
}
} catch {
print("❌ 검색 요청 실패: \(error.localizedDescription)")
}
}
}
...
private func translateOverviews(for media: SearchTranslatedItem) {
switch media {
case .movie(let movies):
for movie in movies {
Task {
let translatedText = await GoogleTranslateAPI.translateText(movie.overview ?? "정보 없음 😅")
DispatchQueue.main.async {
self.translatedMovieOverviews[movie.id] = translatedText
}
}
}
case .tv(let tvs):
for tv in tvs {
Task {
let translatedText = await GoogleTranslateAPI.translateText(tv.overview ?? "정보 없음 😅")
DispatchQueue.main.async {
self.translatedTVOverviews[tv.id] = translatedText
}
}
}
}
}
}
✅ SearchResultCell에서 번역된 데이터 적용
class SearchResultCell: UICollectionViewCell, SelfConfiguringSearchCell {
// MARK: - Variable
static var reuseIdentifier: String = "SearchResultCell"
private var viewModel: SearchViewModel?
private var cancellable = Set<AnyCancellable>()
...
// MARK: - Function
func setViewModel(_ viewModel: SearchViewModel) {
self.viewModel = viewModel
}
func configure(with data: SearchItem) {
switch data {
case .movie(let movie):
...
if let translatedOverview = viewModel?.translatedMovieOverviews[movie.id] {
overviewLabel.text = translatedOverview
} else {
viewModel?.$translatedMovieOverviews
.receive(on: DispatchQueue.main)
.sink { [weak self] translatedDict in
if let translatedText = translatedDict[movie.id] {
self?.overviewLabel.text = translatedText
}
}
.store(in: &cancellable)
}
...
case .tv(let tv):
...
if let translatedOverview = viewModel?.translatedTVOverviews[tv.id] {
overviewLabel.text = translatedOverview
} else {
viewModel?.$translatedTVOverviews
.receive(on: DispatchQueue.main)
.sink { [weak self] translatedDict in
if let translatedText = translatedDict[tv.id] {
self?.overviewLabel.text = translatedText
}
}
.store(in: &cancellable)
}
...
}
}
📍 if let ~ else 구문에서 viewModel.$translatedMovieOverviews를 구독하는 이유
if let translatedOverview = viewModel.translatedMovieOverviews[movie.id] {
overviewLabel.text = translatedOverview
} else {
viewModel.$translatedMovieOverviews
.receive(on: DispatchQueue.main)
.sink { [weak self] translatedDict in
if let translatedText = translatedDict[movie.id] {
self?.overviewLabel.text = translatedText
}
}
.store(in: &cancellables)
}
✅ 1. 이 코드의 역할
1️⃣ 먼저 viewModel.translatedMovieOverviews에서 해당 영화 ID의 번역된 값이 있는지 확인
- 이미 번역된 텍스트가 있으면 즉시 overviewLabel에 적용 (UI 즉각 업데이트)
2️⃣ 없다면 viewModel.$translatedMovieOverviews를 구독하여 값이 변경될 때 업데이트하도록 설정
- 번역이 완료되면 자동으로 overviewLabel이 업데이트되도록 비동기 처리
📍if let 문이 필요한 이유
🔹 번역이 이미 완료된 경우
👉 즉시 번역된 텍스트를 사용하여 UI 업데이트
if let translatedOverview = viewModel.translatedMovieOverviews[movie.id] {
overviewLabel.text = translatedOverview
}
📌 이렇게 하면, 네트워크 요청 없이 빠르게 UI를 업데이트할 수 있음.
🔹 번역이 아직 완료되지 않은 경우
👉 Combine을 사용하여 번역된 텍스트가 도착할 때 overviewLabel을 업데이트
else {
viewModel.$translatedMovieOverviews
.receive(on: DispatchQueue.main)
.sink { [weak self] translatedDict in
if let translatedText = translatedDict[movie.id] {
self?.overviewLabel.text = translatedText
}
}
.store(in: &cancellables)
}
📌 이렇게 하면, translatedMovieOverviews가 변경될 때마다 UI가 자동으로 업데이트됨.
📌 즉, 비동기적으로 API 응답을 기다리고 있음을 의미.
📍만약 if let 없이 바로 viewModel.$translatedMovieOverviews를 구독하면?
viewModel.$translatedMovieOverviews
.receive(on: DispatchQueue.main)
.sink { [weak self] translatedDict in
if let translatedText = translatedDict[movie.id] {
self?.overviewLabel.text = translatedText
}
}
.store(in: &cancellables)
🚨 문제점:
- 이미 번역된 데이터가 있음에도 불필요하게 구독하게 됨
- 즉각적으로 UI 업데이트하지 못하고 비동기 처리가 필요
👉 이렇게 하면 불필요한 구독이 발생하고, UI 업데이트 속도가 느려질 수 있음.

'Project > MovieClip' 카테고리의 다른 글
🤔 reloadData() 함수의 역할 및 동작 방식 (0) | 2025.02.24 |
---|---|
🔥 MVVM + Combine을 통한 검색기능 구현 1편 패턴 비교 (0) | 2025.02.24 |
❌ 에러 분석 - UICollectionView 2번 dequeue... (0) | 2025.02.23 |
🚜 서치 기능 개선 (MVVM + Combine) (0) | 2025.02.22 |
🔍 클로저를 저장하는 변수로? (0) | 2025.02.21 |