하나 타자 연습 개발후기

Jung Kim
Jung Kim
Mar 15 · 13 min read

2주 방학이 끝나가는 주말이 시작하고 있었다. 초등학교 고학년이 되는 딸아이는 학교에서 타자 연습을 해본 적이 있다면서 한글 타자 프로그램을 설치해달라고 물었다.

추억의 타자 연습 게임 — 베네치아가 떠올랐다

검색해보니 무료 웹 버전도 있고 윈도우용 앱들은 있지만, 집에 있는 오래된 맥북에는 설치할 만한 앱이 없었다. 재택 기간이기도 하니까 몇 가지 테스트만 해보고 하나 만들어 보기로 했다. 기술적으로 고려한 것은 iOS 기반 UIKit 프레임워크를 맥 앱으로 바꿔주는 macOS Catalyst를 활용하기로 했다. 확실히 익숙한 것 뿐만 아니라, UIKit 방식이 AppKit 방식보다 생산성이 높은 것 같았다. 그렇지만 영문 자료도 별로 없었다.

가장 먼저 떠올린 기본 컨셉은 베네치아였다. 그래서 기본 기능만 기획해봤다.

  • 단어 목록을 갖고 있고,
  • 일정 간격마다 목록에서 하나씩 꺼내서 화면에 추가하고,
  • 다시 일정하게 애니메이션해서 아래로 떨어뜨리고,
  • 화면 아랫부분에 닿으면 실패해서 하트를 줄이고,
  • 입력한 단어가 화면에 표시중인 단어와 일치하면 해당 단어를 화면에서 제거

기본 동작만 구현해서 일요일 아침에 하나 고객님께 베타 테스트를 해봤다.

  • 우선 맞거나 틀리거나 소리가 안나니까 인지하기 어려웠다. → 효과음 추가
  • 그러면 시작하는 것도 인지할 수 있도록 시작음 추가
  • 효과음은 무료로 사용할 수 있는 효과음 사이트를 찾아서 게임 효과음을 찾았다.
  • 내가 만들면서 할 때보다 은근 어려워해서 단어를 추가하는 속도와 애니메이션 속도를 조정해야 했다 → 100점마다 타이머 속도 개선
  • 타이머 방식은 나도 처음 써보지만 만들면서 배우는 게 있어야 하니까 DispatchSource를 사용하기로 했다.
  • 첫째가 써보니 둘째도 해보려고 했는데, 해본 적이 없고 자리가 익숙하지 않으니 단어 하나 조차 입력하기 어려워했다. 그리고보니 타자연습이 자리연습 > 단어연습 > 짧은글 연습 > 긴글 연습 이렇게 이루어졌던 게 생각났다. → 메뉴 화면 추가
  • 페이스북에 사진만 올렸을 뿐인데, 호응이 괜찮아서 이거 앱 스토어에 올려야겠다. (아이 이름을 붙이는 앱을 만들어 주는 게 로망이기도 했다)

메뉴 화면

메뉴 화면을 추가하면서 전반적인 디자인을 고민하기 시작했다. 맥 앱이니까 다크 모드도 지원해야 하고, 그럴려면 색상도 골라야 하고, 디자인을 처음부터 그려서 하기는 어려우니까 메뉴 화면에서 배경 이미지도 필요했다.

메뉴 화면 배경은 연말에 키보드 키캡을 바꾸면서 찍어둔 키보드 사진을 활용하기로 했다.

키보드 일부만 메뉴 화면 배경으로 적용한 화면

타이머를 조정해도 어렵다는 딸아이의 피드백을 반영해서 초급, 중급, 상급으로 구분하고 타이머 시작 값과 빨라지는 주기도 조정했다. 그렇게 아이가 직접 몇 번 테스트를 해보니 쉬운 정도(5초마다 단어 추가, 3초마다 스크롤)와 어려워하는 정도(2초마다 단어 추가, 1.1초마다 스크롤)가 결정됐다.

낱말 연습

처음 사용하던 단어 연습을 순우리말로 순화해서 낱말 연습으로 바꿨다. 한글 타자 연습이니까 한자어보다 한글을 더 사용하는 게 취지에 맞을 것 같았다.

낱말 중에서 아이에게 익숙한 단어를 고민하다가 가장 먼저 떠올린 것은 위인들 이름이었다. 그래서 한국을 빛낸 100명의 위인들에서 60명을 가져왔다. 하다보니 이름이 어려울 뿐 아니마 여러 자리를 활용하지 못하는 것 같았다. 아이가 좋아하는 아이돌 그룹 이름을 또 다른 세트로 준비했다. 이건 또 영어 단어를 발음대로 쓴게 많아서 꼭 한글 연습은 아니지만, 더 호기심을 갖는 효과가 있었다.

macOS Catalyst에서 첫 번째 난관은 이쯤에서 찾아왔다. ViewController 화면 로딩부터 표시까지 실제로 그려지는 시점과 First Responder가 활성화되는 시점이 iOS와 조금 달랐다. macOS에서는 단어를 입력하는 TextField에 키보드 입력이 가능하려면 becomeFirstResponder()를 호출해야 하는데 ViewDidAppear() 보다 이전에 호출하면 안되는 경우가 발생했다.

기본적으로 iPad 가로 화면 크기를 기준으로 작업하게 되기 때문에 아이패드 비율이라고 생각했는데, macOS는 가변 윈도를 가진다. 그래서 비율도 실제로는 다르다. 윈도우 비율이 다르기 때문에 오토레이아웃을 지정할 수 밖에 없었는데, 전체 화면 비율이 너무 다르면 어색할 것 같았다. 그래서 macOS 경우에도 화면 비율을 아이패드 비율과 같도록하고 크기 조정을 제한했다.


짧은글 연습

그렇게 주말이 지나고 낱말 연습은 게임 형태로 동작하도록 구현했다. 월요일부터는 짧은글 연습을 고민하기 시작했다. 우선 화면 디자인을 고민하면서 다크모드와 라이트모드 모두에서 무채색이지만 깔끔한 화면 구성을 하고 싶었다. 마침 뉴모피즘에 대한 글을 읽었었는데 색상은 비슷하게 하면서 쉐도우로 양각, 음각을 드러내는 방법을 적용해보고 싶었다.

UIView에는 그림자Shadow를 붙이기 쉬운 편이지만, 뷰 바깥쪽이 아니라 안쪽으로 그림자가 그려지는 InnerShadow 구현은 조금 귀찮았다. 결국 CAShapeLayer를 추가하고, 레이어에 Path로 그림자를 그리는 방법을 선택했다. 0번째 레이어에 InnerShadow를 추가하고, 그 위에 하위 뷰를 그리는 방식이 됐다.

다만 원하는 색상과 그림자를 직접 그림자를 그려야하다보니 코드로 구현해야 하는데, 오토레이아웃이 아니라 frame 방식으로 그려지는 영역을 계산해야 했다. 그러다 보니 오토레이아웃으로 계산이 끝나지 않은 ViewDidLoad에서 그림자를 그리는 코드를 추가하지 해도 원하는 범위로 그려지지 않아서 ViewDidAppear() 시점까지 늦춰졌다.

라이트모드와 다크모드 모두에서 적정한 배경 색상과 그림자 색상을 정하는 게 꽤 시간이 걸렸다. 컬러 값이 확정되고 나니 Assets에 하나의 색상에 Dark Appearance를 추가해서 표시하는 게 효율적이었다. 이전, 현재, 다음 입력값을 표시하는 배경이 자연스럽게 보이도록 그림자가 이어지도록 표시했다. 아래 라이트모드일 때 좀 더 잘 드러난다.

다크모드
라이트 모드

짧은글 연습에서 중요한 기능은 타수를 측정하는 것과 정확도, 문장에서 틀린 단어를 찾아내서 표시하는 것이었다.

타수측정은 입력한 키보드 타수와 시간을 측정해서 계산하면 된다. 다만 문제는 iOS 프레임워크에서는 키보드 입력에 대한 이벤트가 아직 없다. UIKeyCommand 가 있긴 한데 특수키만 되고, iOS 13 베타버전에 UIKey가 도입되지만 아직 적용할 수 없었다.

그래서 TextField 입력한 문자열 값만으로 타수를 측정하기로 했다. 그럴려면 어떤 키보드 배치에서 어떤 문자값을 입력했는 지 문자 코드를 기반으로 측정해야 했다. 조합되어 있는 문자를 초성, 중성, 종성으로 분리하고 마지막 문자 코드를 보고 몇 번 눌린 것인지 계산했다. 중성의 이중 모음 `ㅚ`, `ㅢ` 나 종성의 `ᆭ`, `ᆶ` 이중 받침 코드를 보고 +1 처리를 해주는 게 필요했다.

키를 입력하는 시간 측정은 처음에는 입력 화면을 표시하는 시점부터 측정했었는데, 입력하지 않고 기다리는 시간까지 타수 계산에 포함되다보니 첫 글자를 입력하는 시점부터 측정하도록 개선했다.

정확도 계산은 입력한 문자열과 기준 문자열을 순서대로 비교해서, 일치하지 않은 글자 개수를 전체 글자로 나눈 백분율로 계산했다. 짧은글 입력할 때마다 비교하고 정확도를 리턴하고 전체 글자와 미일치 글자 개수를 누적해서 전체 정확도를 계산했다.

입력한 문자열을 비교할 때마다 일치하지 않은 글자와 위치값을 배열로 리턴해주고 있었다. 틀린 글자는 AttributedString으로 일치하지 않은 글자의 위치에 속성을 바꿔주는 방식을 사용했다.


중대한 버그 발견 🤦🏻‍♂️

여기까지 개발하고 수요일에 다시 딸아이에게 검수를 받았다. 메뉴를 왔다갔다 하면서 테스트를 하다보니 낱말 연습을 몇 번 들어갔다 나와서 새 게임을 하면, 한 번만 틀려서 게임이 종료되는 버그를 발견했다 🤯

디버깅을 하다보니 처음 사용해보는 DispatchSourceTimer 핸들러 클로저 핸들러가 ViewController를 retain해서 사라지지 않고 이벤트를 중복해서 발생시키는 문제였다. 타이머를 멈추려면 아무때나 cancel만 하면 되는 줄 알았는데 꼭 resume한 상태에서 cancel을 해야만 핸들러 클로저를 정상적으로 없앨 수 있었다.


마지막 관문 : 자리 연습

자리 연습을 가장 마지막에 개발했던 것은 디자인 리소스 때문이었다. 솔직히 말해서 손을 그릴 자신이 없었다. 아이패드로 손 그림들을 따라서 그려봤지만 원하는 형태가 나오지 않았다. 그러다가 결국 손 모델을 고용(?)하기로 했다. 왼쪽은 둘째 녀석의 손이고, 오른쪽은 셋째 녀석의 손이다. 사진을 비교해보니 셋째는 너무 작아서 탈락

그래서 둘째 아이 손모양을 본떠서 그림을 그렸다. 이 과정에서도 원하는 형태로 커브를 그릴 수 있는 방법을 여러 방식으로 시도해봤는데, 최종적으로 맘에 들었던 커브는 원하는 만큼 점을 추가해서 합칠 수 있었던 키노트였다.

이렇게 해서 오른쪽 손을 그리고, 뒤집어서 왼손으로 만들었다. 특히 다른 손에 가려지는 부분을 점선으로 별도로 그렸는 데 닫힌 경로(closed path)가 아니라서 배경을 투명하게 만드는 게 조금 귀찮은 작업이었다. 손 모양 그림도 라이트모드, 다크모드 둘 다 준비해야 하는데 손가락 부분이 불투명해서 확대해서 지우는 작업이 필요했다 👨🏻‍🎨

이렇게 해서 손을 키보드 위에 올리는 애니메이션을 구현할 준비가 완료됐다. 키보드 레이아웃 두벌식을 기준으로 자리를 연습할 데이터 모델을 만들고, 레벨이 올라가면 랜덤하게 위치를 표시하도록 구현했다. 자리 연습할 때는 안내 문구를 추가해서, 시프트를 누르지 않거나, 옆자리를 누르거나 상황에 따라 안내를 표시했다. 10개 키를 입력하고 나면 손모양은 사라지도록 구현했다.

특히 자리연습은 꼼수가 하나 있는데, UIKit 특성상 키 이벤트를 직접 받을 수 없어서 화면 바깥에 보이지 않는 TextField를 만들어 놓고 거기에 한 글자를 입력할 때마다 지우고 다시 입력하는 동작을 반복하도록 구현했다.


맥 앱 스토어 올리기

맥 앱 스토어에 올리는 과정이 iOS 앱과 거의 동일해졌고, macOS Catalyst 기반으로 만들었기 때문에 특별히 해줄 게 별로 없이 편리해졌다.(고 믿었다 ㅎㅎ) 그래도 왠만한 것은 업로드 과정에서 다 검증이 된다.

iOS 앱이 아니라 맥 앱을 올릴 때 스크린샷 크기는 몇이어야 할까? 그것도 모르고 있었다 ㅎㅎ 꼭 전체 화면일 필요는 없고 16:10 비율로 된 이미지로 잘라야 했다. 다른 앱들도 살펴보니 배경 화면 가운데 띄워놓고 비율에 맞춰 잘랐다는 걸 알았다.

스크린샷을 올리고 메타 데이터를 입력하고 금요일 저녁에 첫 번째 버전 리뷰를 요청했다. 간만에 설레이는 밤이 지나고 반가운(?) 리젝 사유를 받았다.

첫 번째 리젝 사유는 기능 미비였다. 응(?) 구현 안된 기능이 있다고? 설명을 보니 맥 앱은 도움말 Help 메뉴를 눌렀을 때 동작이 필수 구현이었다. 아차…🙀

맥 앱에서 Help 메뉴는 예전부터 Help 번들을 추가해주는 방식을 사용했었는데, iOS 앱만 다루다보니 전혀 기억을 못하고 있었다. 요즘은 보통 Help 메뉴를 누르면 도움말 홈페이지를 띄워주는 방식으로 많이들 구현한다.

추억을 되살려(?) Help 번들을 직접 만들어보기로 했다. 도움말 기능은 Help Book 이라고 부르는 HTML 문서와 이미지가 들어있는 번들 패키지다. 자세한 내용은 공식문서를 참고하자. 사실상 버려진 규격이 아닐까 (이게 필요하다면 Xcode에서 보여주기라도 해야하는 데 그것도 지원안한다 ㅜㅜ) https://developer.apple.com/library/archive/documentation/Carbon/Conceptual/ProvidingUserAssitAppleHelp/authoring_help/authoring_help_book.html

두 번째 리젝 사유는 앱 이름 때문이었다.

ProductName과 앱 스토어에 표시하는 앱 이름이 동일해야 하는 데, 한글로 입력한 곳도 있고 아닌 곳도 있고 달랐다. macOS 앱에서 앱 이름 메뉴를 클릭했을 때 보이는 앱 이름과 Hide, Quit 메뉴에 보이는 이름과 앱 스토어 이름까지 모두 동일해야만 한다. 가볍게 수정 완료 GTG

그렇게 우당탕당 우여곡절 끝에 앱 스토어에 올라갔다. 👏🏻

다행히도 지인 분들이 받아주셔서 한국 맥 앱 스토어에 순위권에도 올라갔다. 🎉

여전히 일부 맥에서는 TextField가 first responder가 되지 않는 현상이 있었고, 짧은글 연습에서 마지막 연습은 표시가 안되는 버그도 있었다. 버그를 고치면서 짧은글 문구도 좀 더 추가했다.

iOS 앱 아이콘과 다르게 맥 앱은 투명처리가 가능한데, 스토어에 올라간 버전을 보니까 배경이 투명하지 않고 회색처리가 되어 있었다. 다시 찾아보니 앱 아이콘도 맥 앱용을 추가할 수 있었다. 아래 화면처럼 아이패드 프로젝트를 기반으로 작업하고 투명한 맥용 아이콘을 적용할 수 있다.

업데이트하면서 버전 정보를 확인하는 About 메뉴도 어떻게 처리되는지 궁금했다. About 메뉴는 Credits.rtfd 리소스 번들을 만들면 리치 텍스트 기반으로 라이센스 영역에 표시된다. 링크는 넣을 수 없지만 다양한 형식의 텍스트와 이미지는 포함할 수 있다.


찐후기

  • iOS 개발자가 일주일 정도 투자하면 맥 앱을 만들 수 있다.
  • macOS Catalyst는 모든 것을 다 해주지는 않지만, iOS 앱을 정말 쉽게 포팅할 수 있다.
  • 아이폰 앱과 아이패드 앱이 다른 것처럼 맥 앱은 또 다른 경험을 줄 수 있다. 그래서 iOS 개발자 입장에서는 커진 화면을 채울 UX와 디자인은 늘 고민꺼리다.
  • iOS도 Assert는 안 이쁘지만 macOS Assert는 더 안 이쁘다.
  • 맥 앱 스토어라고 리뷰가 빠르지는 않다. 체감상 거의 비슷하다.
  • Help 와 Credits를 미리 준비하자. 아이콘도 따로 준비하면 좋다.
  • 맥 앱 스토어는 역시 시장이 작다. 그만큼 더 많은 앱을 만들 기회가 있다.
  • 다음 버전에서는 영문 QWERTY, 세벌식 자판을 지원할 예정이다.
  • 맥 앱은 용량이 확실히 작다. v1.0 버전이 3MB, v1.1 4.9MB 수준이다. (도대체 Xcode는?)
  • 4월초부터 유료로 바뀝니다. 유료로 구매해주셔도 더 좋습니다 😍

More From Medium

Related reads

Related reads

Also tagged Swift

101

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade