보통 단어장에 틀린 단어와 외운 단어를 구분하는 이유는 틀린 단어를 나중에 모아서 공부하기 위함입니다. 물론 지금도 단어의 색으로 틀린 단어만 모아서 공부하는 기능을 구현이 가능합니다만 틀린 단어가 몇개 밖에 없다면 그 단어들을 공부하기 위해서 지나치게 많은 스크롤을 하는 불편함이 존재합니다. 넘기다가 수 많은 초록색 단어들 중에 끼어 있는 노란색 단어를 놓칠 수도 있고요.
따라서 이번에는 맞은 단어를 제외하고 틀린 단어만 모아서 볼 수 있는 기능을 구현해보고자 합니다. 아래 캡쳐를 보면 필터링 버튼을 누르면 노란색으로 나오는 틀린 단어만 모아서 볼 수 있는 것을 볼 수 있습니다.
이 방법은 아주 간단합니다. 필터링을 DB가 하게 하는 것이죠. 필터링 버튼을 눌렀을 때 DB에 필터링 조건을 전달해서 새로운 데이터를 fetch 해오면 됩니다.
다만 이 방법의 경우 누를 때마다 새로운 네트워크 통신을 합니다. 하지만 이미 모든 단어의 데이터가 메모리에 있는 상황에서 굳이 새로운 네트워크 통신을 하는 것은 불필요해 보입니다.
메모리에는 이미 이 단어장에 있는 모든 단어의 데이터가 저장되어 있습니다. 이 데이터를 조건에 맞게 필터링해서 활용하면 추가적인 네트워크 통신 없이 필터링 기능을 구현할 수 있습니다. 이 방법을 통해 구현해보겠습니다.
먼저 필터링 여부를 결정하는 타입인 enum을 하나 선언합니다. 해당 enum에는 각각의 case 마다 토글버튼의 이름을 구현해둡니다.
enum StudyMode {
case all, excludeSuccess
var toggleButtonTitle: String {
switch self {
case .all: return "O제외"
case .excludeSuccess: return "전부"
}
}
}
StudyView의 ViewModel에 단어를 저장하는 Array를 두 개로 구분합니다. 첫 번째 Array는 DB에서 가져온 단어들의 데이터를 그대로 저장하는 배열입니다. 두 번째 Array는 @Published로 선언해서 View에서 화면을 그리는데 참고하도록 합니다.
private var rawWords: [Word] = []
@Published var words: [Word] = []
filter 함수를 통해 DB에서 불러온 rawWords를 통해 @Published로 선언한 words 배열에 필요한 단어를 할당합니다. 모든 단어를 보여줄 때는 rawWords를 그대로, 틀린 단어면 본다면 studyState가 success인 것은 제외합니다.
private func filterWords() {
switch studyMode {
case .all:
words = rawWords
case .excludeSuccess:
words = rawWords.filter { $0.studyState != .success }
}
}
위에 구현한 filter 함수를 활용해서 studyMode를 변경하는 함수를 구현합니다. studyMode를 변경하고 나면 filter를 통해 words를 다시 세팅합니다.
func toggleStudyMode() {
studyMode = studyMode == .all ? .excludeSuccess : .all
filterWords()
}
toggleStudyMode 까지 하게 되면 모든 위에 캡쳐한 기능은 모두 구현한 셈입니다. 하지만 아직 마지막 관문이 남아있습니다. 바로 단어의 성공 / 실패 여부를 저장하는 함수를 수정해야 합니다.
수정 전의 함수를 사용하게 되면 word의 데이터는 수정되지만 rawWords는 수정되지 않을 것입니다. 따라서 다시 filter를 하게되면 수정되지 않은 데이터가 보여질 것입니다.
또한 틀린 단어만 모아보기 페이지에서는 단어를 성공 처리하면 화면에서 사라져야 합니다. 하지만 수정 전의 함수를 사용하면 틀린 단어들 사이에 성공한 단어가 그대로 남아있을 것입니다.
따라서 수정한 함수에서는 일단 rawWords의 데이터를 수정한 후 filter를 통해서 다시 words를 세팅해줍니다. 이렇게 하면 rawWords에 데이터의 수정이 반영이 되고 다시 filter가 되면서 성공한 단어는 화면에서 사라진 것입니다.
// 수정 전
func updateToSuccess() {
guard let wordBookID = wordBook.id else { return }
guard let wordID = word.id else { return }
WordService.updateStudyState(wordBookID: wordBookID, wordID: wordID, newState: .success) { error in
// FIXME: handle error
if let error = error { print(error); return }
self.word.studyState = .success
}
}
// 수정 후
private func updateStudyState(id: String?, state: StudyState) {
guard let wordBookID = wordBook.id else { return }
guard let wordID = id else { return }
WordService.updateStudyState(wordBookID: wordBookID, wordID: wordID, newState: state) { [weak self] error in
// FIXME: handle error
if let error = error { print(error); return }
guard let index = self?.rawWords.firstIndex(where: { $0.id == wordID }) else { return }
self?.rawWords[index].studyState = state
self?.filterWords()
}
}