App Architecture
- MVP design pattern
- Xcode / Version 13.2.1 (13C100)
- without storyboard
- swift 5 100%
Identity
- Bundle Identifier : aeron-kim.BibleTyping
- Version : 2.8.5
- Build : 1
- https://apps.apple.com/kr/app/bibletyping/id1638674270?platform=iphone
GitHub
- https://github.com/kmjmarine/BibleTyping.git
- SourceTree
Open API
- https://ibibles.net
- Snapkit (spm)
- Alamofire (spm)
- Toast (spm)
- Lottie (spm)
ETC
- simulator - iPhone 12 pro max
2022.7.5
2022.7.9
2022.7.12
2022.7.13
@objc func didTabCancelButton() {
self.present(TypingListViewController(), animated: true, completion: nil) //오류
dismiss(animated: true, completion: nil) //오류
self.navigationController?.pushViewController(TypingListViewController(), animated: <#T##Bool#>) //오류
}
//TypingListViewController
navigationController?.pushViewController(typingDetailViewController, animated: true)
//TypingDetailViewController
self.navigationController?.popViewController(animated: true)
2022.7.19
enum BibleType: String {
case old
case new
}
self.bookkind == BibleType.old.rawValue
2022.7.21
2022.7.22
2022.7.25
func formattedDateString(dateString: String) -> String {
//String -> Date -> String
//2022-07-25 -> 7/25
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
if let date = formatter.date(from: dateString) {
formmatter.dateFormat = "M/d"
return formmater.string(from: date)
} else {
return ""
}
}
2022.7.26
2022.7.29
2022.8.1
2022.8.2
progressView.layer.cornerRadius = 6
progressView.clipsToBounds = true
progressView.transform = progressView.transform.scaledBy(x: 1, y: 6) // 양옆 모서리가 뾰족하게 표현되어
주석 처리 후 해당 snp에서 높이를 지정함
let arrQuote = quote.components(separatedBy: " ")
2022.8.3
view.makeToast("짝짝짝! 정답이에요.")
view.makeToast("앗! 세번째에 들어갈 낱말과 다른 낱말이에요.")
2022.8.6
2022.8.7
2022.8.8
func setupNavigationBar() {
navigationItem.rightBarButtonItem = rigthBarButtonItem
}
let startIdx: String.Index = quoteText.index(quoteText.startIndex, offsetBy: 4) //장, 절 삭제
2022.8.9
attribtuedString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.label, range: NSRange(location: chrSourceText, length: 1))
2022.8.10
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
guard text == "\n" else { return true }
presenter.didTabConfirmButton(sourceText: sourceQuoteLabel.text, writeText: writeQuoteTextView.text)
textView.resignFirstResponder()
return true
}
@objc func didTabConfirmButton() {
presenter.didTabConfirmButton(sourceText: sourceQuoteLabel.text, writeText: writeQuoteTextView.text)
self.view.endEditing(true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
timeLeft = 5
progressTime = 0.0
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
2022.8.13
2022.8.14
func getMakeQuote(_ chpater: Int, _ verse: Int) -> Int {
let chapterLength: Int = String(chpater).count
let verseLength: Int = String(verse).count
return chapterLength + verseLength + 2 //"1:10 (장+절) + 공백+콜론 2 더함"
}
2022.8.15
if let indexJson = bibleList.Bibles.firstIndex(where: { $0.bookName == bookname && $0.chapter == chapter }) {
doneChapter = bibleList.Bibles[indexJson].chapter
doneVerse = bibleList.Bibles[indexJson].verse
}
2022.8.16
func showCloseAlertController() {
let alertController = UIAlertController(title: "데이터 읽기에 실패 했습니다.\n잠시 후에 다시 시도해 주세요.", message: nil, preferredStyle: .alert)
let closeAction = UIAlertAction(title: "닫기", style: .destructive) {[weak self] _ in
self?.dismiss(animated: true)
}
[closeAction].forEach { action in
alertController.addAction(action)
}
present(alertController, animated: true)
}
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.portrait
}
2022.8.17
self.tabBarController?.navigationController?.isNavigationBarHidden = true //tabBarController의 back버튼 영역 hidden 처리
2022.8.18
@objc func didTabConfirmButton() {
///TypingDetailViewController로 이동 구현 필요
}
2022.8.22
2022.8.27
guard let info = Bundle.main.infoDictionary,
let currentVersion = info["CFBundleShortVersionString"] as? String else { return }
versionLabel.text = "v" + currentVersion
private extension UIDevice {
static func vibrate() {
AudioServicesPlaySystemSound(1005)
}
}
2022.8.28
2022.8.30
2022.8.31
func delBookmark(_ newValue: Bookmark) {
var currentBookmakrs: [Bookmark] = getBookmark()
if let index = currentBookmakrs.firstIndex(where: { $0.bookname == newValue.bookname && $0.chapter == newValue.chapter && $0.verse == newValue.verse
}) {
currentBookmakrs.remove(at: index)
UserDefaults.standard.setValue(
try? PropertyListEncoder().encode(currentBookmakrs),
forKey: Key.bookmarks.value
)
}
}
2022.9.4
다국어 추가 (영어)
Localizable.strings 작성
PROJECT에 Korean 추가
언어설정 가져오기
폰의 언어 설정을 가져와 다른 데이터 노출
let quoteList: [Puzzle] = languageCode == "ko" ? Puzzle.puzzleKOList : Puzzle.puzzleENList
button.setPreferredSymbolConfiguration(.init(pointSize: 30, weight: .regular, scale: .default), forImageIn: .normal)
- String Extension 추가 (앱 종료, 재시작없이 actionSheet에 따라 설정된 언어로 변경)
var localized: String { //computed property
var language = UserDefaults.standard.array(forKey: "language")?.first as? String
if language == nil {
let str = String(NSLocale.preferredLanguages[0]) // 언어코드-지역코드 (ex. ko-KR, en-US)
language = String(str.dropLast(3)) // ko-KR => ko, en-US => en
}
let path = Bundle.main.path(forResource: language, ofType: "lproj") ?? Bundle.main.path(forResource: "en", ofType: "lproj")
let bundle = Bundle(path: path!)
return (bundle?.localizedString(forKey: self, value: nil, table: nil))!
}
2022.9.6
if language == "zh" {
language = "zh-Hans" //zh-Hans.lproj 파일명으로 재할당
}
2022.9.8
2022.9.13
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style: .normal, title: nil) { [weak self] (action, view, completion) in
let bookmarks = self!.bookmark[indexPath.row]
self!.userDefaultsManager.delBookmark(bookmarks)
self!.bookmark.remove(at: indexPath.row)
self!.viewController?.setupDoneView(bookmarks: self!.bookmark)
tableView.deleteRows(at: [indexPath], with: .automatic)
completion(true)
}
action.backgroundColor = .red
action.image = UIImage(systemName: "trash")
let configuration = UISwipeActionsConfiguration(actions: [action])
configuration.performsFirstActionWithFullSwipe = false
return configuration
}
let configuration = UISwipeActionsConfiguration(actions: [deleteAction, copyAction])
2022.9.16
//TypingDetailViewController
override func viewDidLoad() {
super.viewDidLoad()
TTSManager.shared.synthesizer.delegate = self
presenter.viewDidLoad()
}
extension TypingDetailViewController: AVSpeechSynthesizerDelegate {
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
TTSButton.setImage(UIImage(systemName: "speaker.wave.3"), for: .normal)
TTSManager.shared.stop()
}
}
bookLabel.snp.makeConstraints {
$0.width.equalTo(90.0) //가로 사이즈를 지정해야 UILabel의 가로 사이즈 넘어갈 경우 label.adjustsFontSizeToFitWidth 작동
}
let utterance = AVSpeechUtterance(string: string)
utterance.voice = AVSpeechSynthesisVoice(language: languageCode)
2022.9.18
actionSheet의 모달스타일은 UIModalPresentationPopover라고 설명을 해주면서 UIModalPresentationPopover을 사용 할 때는 barButtonItem또는 팝업에 대한 위치를 설정해줘야 된다고 명시
if UIDevice.current.userInterfaceIdiom == .pad { //디바이스 타입이 iPad일때
if let popoverController = alertController.popoverPresentationController {
// ActionSheet가 표현되는 위치를 저장해줍니다.
popoverController.sourceView = self.view
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.maxY, width: 0, height: 0)
popoverController.permittedArrowDirections = []
self.present(alertController, animated: true, completion: nil)
}
} else {
self.present(alertController, animated: true, completion: nil)
}
2022.9.19
func sortBookmark(mode: String) {
if mode == "time" {
bookmark.sort { $0.date! > $1.date! }
} else {
bookmark.sort { $0.bookname < $1.bookname }
}
viewController?.reloadTableView()
}
2022.9.20
2022.9.21