- JapaneseBenkyo
- JapaneseBenkyo
- Main
일본어 단어장, 일본어 한자 메뉴 화면이다. - IndexTableViewCell
거의 모든 화면에서 사용되는 목차 TableView의 Cell이다. - Vocabulary
- Vocabulary.swift
Vocabulary와 VocabularyForCell을 정의해놓은 파일이다.
struct Vocabulary는 단어, 발음, 뜻을 가지고 있다.
VocabularyForCell은 Vocabulary와 음 보기, 뜻 보기, 즐겨찾기가 되어있는지를 저장해놓은 변수를 갖고 있다. - Vocabulary
단어 난이도를 고를 수 있는 화면이다. - VocabularyDay
Day 몇의 단어를 공부할지 고를 수 있는 화면이다. - VocabularyStudy
단어 공부를 할 수 있는 화면이다. - VocabularyTest
암기한 단어들을 테스트할 수 있는 화면이다. - VocabularyTestResult
테스트의 결과를 보여주는 화면이다. 틀린 단어를 다시 테스트할 수도 있다. - VocabularyTableViewCell
단어 암기 TableView에 사용되는 Cell에 관련된 폴더이다.- VocabularyTableViewHandler.swift
단어 암기 TableView의 Delegate와 DataSource를 구현한 파일이다. - VocabularyTableViewCell.swift
- VocabularyTableViewCell.xib
- VocabularyTableViewHandler.swift
- Vocabulary.swift
- Kanji
- Kanji.swift
Kanji와 KanjiForCell을 정의해놓은 파일이다.
struct Kanji는 칸지(일본 신자체 한자), 한자(한국 정자체 한자), 일본 음, 일본 훈, 한국 음훈, 한자 활용 예시 단어들을 가지고 있다.
KanjiForCell은 Kanji와 한국 정자체 보기, 일본 음 보기, 일본 훈 보기, 한국 음훈 보기, 한자 활용 예시 단어들 보기, 즐겨찾기가 되어있는지를 저장해놓은 변수를 갖고 있다. - Kanji
한자 난이도를 고를 수 있는 화면이다. - KanjiDay
Day 몇의 한자를 공부할지 고를 수 있는 화면이다. - KanjiStudy
한자 공부를 할 수 있는 화면이다. - KanjiTest
암기한 한자들을 테스트할 수 있는 화면이다. - KanjiTestResult
테스트의 결과를 보여주는 화면이다. 틀린 한자를 다시 테스트할 수도 있다. - KanjiTableViewCell
한자 암기 TableView에 사용되는 Cell에 관련된 폴더이다.- KanjiTableViewHandler.swift
단어 암기 TableView의 Delegate와 DataSource를 구현한 파일이다. - KanjiTableViewCell.swift
- KanjiTableViewCell.xib
Expandable한 Cell이다. - ExpandableAreaView.swift
- ExpandableAreaView.xib
한자 활용 예시 단어를 표시할 label을 담고 있는 view이다. KanjiTableViewCell의 StackView에 예시 단어 개수만큼 들어간다.
- KanjiTableViewHandler.swift
- Kanji.swift
- Extension
- UIViewController+Ext.swift
모든 Scene들을 Enum으로 관리한다. - UINavigationController+Ext.swift
가장 최근 ViewController를 없애고 새로운 ViewController를 추가하는 replace함수를 구현했다.
이는 TestResult Scene에서 틀린 단어 다시 테스트하기를 눌렀을 때 TestResult에서 TestView로 Replace될 때 사용된다. - UIStackView+Ext.swift
StackView를 한번에 비우는 기능의 함수를 구현했다.
이는 ExpandableCell에서 사용된다.
- UIViewController+Ext.swift
- Singleton
- JSONManager.swift
JSON 데이터를 Kanji, KanjiForCell, Vocabulary, VocabularyForCell로 변환하거나
Kanji, KanjiForCell, Vocabulary, VocabularyForCell를 JSON 데이터로 변환한다. - TTSManager.swift
일본어 발음을 들려주는 TTS를 실행한다. - UserDefaultManager.swift
즐겨찾기한 단어들, 학습 진행도를 UserDefault에 저장한다. - CommonConstant.swift 프로젝트에서 공통적으로 사용하는 상수 값을 관리한다.
- JSONManager.swift
- res
- AppDelegate.swift
- Assets.xcassets
- Base.lproj
- LaunchScreen.storyboard
스플래시뷰이다.
- LaunchScreen.storyboard
- Info.plist
- Main
- JapaneseBenkyoTests
- JapaneseBenkyoUITests
- JapaneseBenkyo.xcodeproj
- JapaneseBenkyo
- Image
AppStore에 올린 미리보기 이미지이다.
앱의 대부분의 화면은 UITableView를 활용하여 구성하고 있다.
3000개 정도의 단어나 150 정도의 Days를 다뤄야 하기 때문에 아래와 같은 코드로 reusableCell을 이용하여 최적화해주었다.
VocabularyTableViewCell은 그저 껍데기일 뿐이다. IBOutlet변수들과 IBAction함수들을 연결만 해주었다.
// VocabularyTableViewCell.swift
import UIKit
class VocabularyTableViewCell: UITableViewCell {
@IBOutlet weak var lbWord: UILabel!
@IBOutlet weak var btnSound: UIButton!
@IBOutlet weak var lbSound: UILabel!
@IBOutlet weak var btnMeaning: UIButton!
@IBOutlet weak var lbMeaning: UILabel!
@IBOutlet weak var btnBookmark: UIButton!
var onClickSound: ((_ sender: UIButton) -> Void)?
var onClickMeaning: ((_ sender: UIButton) -> Void)?
var onClickBookmark: ((_ sender: UIButton) -> Void)?
var onClickPronounce: ((_ sender: UIButton) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
btnMeaning.titleLabel?.textAlignment = .center
lbWord.adjustsFontSizeToFitWidth = true
btnSound.titleLabel?.adjustsFontSizeToFitWidth = true
}
@IBAction func onClickSound(_ sender: UIButton) {
onClickSound?(sender)
}
@IBAction func onClickMeaning(_ sender: UIButton) {
onClickMeaning?(sender)
}
@IBAction func onClickBookmark(_ sender: UIButton) {
onClickBookmark?(sender)
}
@IBAction func onClickPronounce(_ sender: UIButton) {
onClickPronounce?(sender)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
VocabularyTableViewHandler가 실질적인 알맹이다. VocabularyTableViewCell에서 연결해준 IBOutlet변수들과 IBAction함수들을 활용하여 실질적으로 구현하였다.
VocabularyTableView는 여러번 사용되기 때문에 VocabularyTableViewHandler.swift파일을 만들어 UITableViewDataSoure, UITableViewDelegate와 VocabularyTable 관련 로직들을 따로 빼내주었다.
// VocabularyTableViewHandler.swift
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: VocabularyTableViewCell
let vocabularyForCell: VocabularyForCell = vocabulariesForCell[indexPath.row]
if let reusableCell = tableView.dequeueReusableCell(withIdentifier: String(describing: VocabularyTableViewCell.self), for: indexPath) as? VocabularyTableViewCell {
cell = reusableCell
} else {
let objectArray = Bundle.main.loadNibNamed(String(describing: VocabularyTableViewCell.self), owner: nil, options: nil)
cell = objectArray![0] as! VocabularyTableViewCell
}
cell.onClickSound = { [weak self] sender in
self?.onClickSound(cell, sender, vocabularyForCell: vocabularyForCell)
}
cell.onClickMeaning = { [weak self] sender in
self?.onClickMeaning(cell, sender, vocabularyForCell: vocabularyForCell)
}
cell.onClickBookmark = { [weak self] sender in
self?.onClickBookmark(cell, sender, vocabularyForCell: vocabularyForCell)
}
cell.onClickPronounce = { [weak self] sender in
self?.onClickPronounce(cell, sender, vocabularyForCell: vocabularyForCell)
}
initializeCell(cell: cell, vocabularyForCell: vocabularyForCell)
return cell
}
...
VocabularyTable 사용 예시
// VocabularyTestResultViewController.swift
import UIKit
class VocabularyTestResultViewController: UIViewController {
...
private var vocabularyTableDataSource: VocabularyTableViewHandler?
...
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
vocabularyTableDataSource = VocabularyTableViewHandler(vocabulariesForCell: vocabulariesForCell)
tableView.delegate = vocabularyTableDataSource
tableView.dataSource = vocabularyTableDataSource
tableView.rowHeight = CGFloat(CommonConstant.cellSize)
tableView.register(UINib(nibName: String(describing: VocabularyTableViewCell.self), bundle: nil), forCellReuseIdentifier: String(describing: VocabularyTableViewCell.self))
...
ExpandableCell은 매일 일본어 개발 중 가장 어려웠던 부분이었다.
한자 활용 예시 단어 개수에 따라 유동적으로 높이가 변해야하고 reusableCell을 사용할 수 있어야 했기 때문에 적절한 방법을 찾기 힘들었다.
라이브러리, NSLayoutConstraint 활용, cell insert 등 여러가지 방법들을 시도했지만 실패했다.
// KanjiTableViewHandler.swift
...
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
...
private func onClickExpand(_ cell: KanjiTableViewCell, _ sender: UIButton, kanjiForCell: KanjiForCell, indexPath: IndexPath) {
kanjiForCell.isExpanded = !kanjiForCell.isExpanded
initializeCell(cell: cell, kanjiForCell: kanjiForCell)
tableView.reloadRows(at: [indexPath], with: .automatic)
}
...
기존 KanjiTableViewCell 밑에 Intrinsic Size를 Placeholder로 설정한 StackView를 추가하고 cellHeight도 autometicDimension으로 설정하였다. 확장 버튼이 눌리면 stackView에 활용 단어 예시들을 추가해주고 reloadRows를 한다. 그러면 알아서 높이를 새롭게 계산하여 애니메이션과 함께 reload되기 때문에 ExpandableCell이 된다.
이 방법도 문제가 아예 없지는 않았다. 높이 계산이 다른 작업보다 오래 걸리는지, 버튼을 누르고 Expand 되기 전 잠시동안 활용 예시 단어들이 밑바닥에 겹쳐서 깔려있는 것이 보이는 이슈가 있었다.
// ExpandableAreaView.swift
...
lbTitle.clipsToBounds = true
...
clipsToBounds를 통해 삐져나오는 부분을 invisible하게 만들어주어 해결했다.