달력을 직접 만들었습니다
라이브러리를 쓰지 않고 달력을 처음부터 만든 이야기. 무한 스크롤, 타임라인 뷰, 드래그앤드롭, 그리고 끝없는 크래시와의 싸움.
달력을 직접 만들었습니다
달력은 라이브러리를 쓰면 되는 거 아닌가. 저도 처음에는 그렇게 생각했습니다.
근데 쓰고 싶은 라이브러리가 원하는 대로 움직여주지 않더라고요. 주 단위 무한 스크롤이 필요한데 지원이 안 되거나, 셀 안에 들어갈 내용이 복잡해지면 커스터마이징이 한계에 부딪혔습니다.
결국 처음부터 만들기로 했습니다. 지금 돌아보면 겁이 없었다기보다는 몰랐던 것 같습니다. 뭘 모르는지를요.
주 단위 무한 스크롤
달력의 기본 구조는 세로로 끝없이 스크롤되는 주 단위 리스트입니다. 오늘을 기준으로 위아래 60주를 미리 만들어 두고, 끝에 다다르면 20주씩 더 붙입니다.
말은 간단한데, 리스트 위쪽에 항목을 추가하면 스크롤 위치가 점프합니다. 아래에 붙이는 건 자연스러운데, 위에 붙이면 갑자기 화면이 튀어요. 이걸 잡는 데 시간이 꽤 들었습니다.
그리고 데이터. 화면에 보이는 주변 ±12주의 할 일 데이터만 미리 가져오고, 나머지는 스크롤에 따라 동적으로 불러옵니다. 데이터가 많아지면 느려지니까, 보이는 범위만 관리하는 게 중요했습니다.
크래시와의 전쟁
솔직히 고백하면, 달력에서 크래시가 제일 많이 났습니다.
가장 심했던 건 Hermes 가비지 컬렉터 관련 문제였습니다. 데이터가 아직 안 왔는데 리스트가 먼저 그려지면, 빈 배열에서 데이터가 채워지는 순간 초기 스크롤 위치가 꼬이면서 앱이 죽었습니다. 재현도 어렵고, 원인을 찾는 데도 오래 걸렸습니다.
해법은 데이터가 준비될 때까지 리스트를 아예 안 그리는 것이었습니다. 단순한 해법이지만 거기까지 가는 길이 멀었습니다. SQLite 동기 로딩 문제도 있었고, 렌더 함수가 매번 새로 만들어지면서 생기는 문제도 있었고. 하나를 고치면 다른 게 터지고, 다시 고치면 또 다른 게 터지고.
이때가 제일 힘들었던 것 같습니다. “내가 이걸 왜 직접 만들겠다고 했지” 하는 생각이 하루에 세 번은 들었습니다.
타임라인 뷰
리스트로 할 일을 보는 것과 시간대별로 보는 건 느낌이 다릅니다. 오전에 뭐가 있고, 오후에 뭐가 있는지 한눈에 보고 싶었습니다.
그래서 24시간 타임라인 뷰를 만들었습니다. 시간 눈금이 세로로 쭉 있고, 할 일이 해당 시간대에 블록으로 표시됩니다. 지금 시간에는 빨간 선이 실시간으로 움직입니다.
겹치는 일정 처리가 어려웠습니다. 같은 시간대에 여러 할 일이 있으면 가로로 나눠서 보여줘야 하는데, 몇 개가 동시에 겹치느냐에 따라 열의 수가 달라집니다. 시간 기반으로 겹침을 감지하고, 그룹별로 열을 나누는 알고리즘을 짰습니다.
완벽하진 않을 수 있지만, 하루의 흐름이 한눈에 보인다는 것만으로도 꽤 유용하다고 느꼈습니다.
드래그앤드롭
타임라인에 블록이 있으면 자연스럽게 드는 생각이 있습니다. 이거 끌어서 옮기면 안 되나?
길게 눌러서 드래그하면 15분 단위로 딸깍 맞춰지면서 이동합니다. 위아래 가장자리를 잡으면 시간을 늘리거나 줄일 수 있습니다. 드래그하는 동안 실시간으로 “09:30 – 10:45” 같은 시간이 표시됩니다.
진동 피드백도 넣었습니다. 드래그를 시작할 때 살짝 울리는데, 이 작은 피드백이 “잡았다”는 느낌을 줍니다. 없으면 뭔가 허전하더라고요.
블록을 드래그하는 동안 원래 위치에 흐릿한 잔상이 남게도 했습니다. “여기서 저기로 옮기는 중”이라는 맥락이 유지돼야 헷갈리지 않으니까요.
요일이 유지되어야 합니다
날짜 상세 화면에서 주를 좌우로 넘길 수 있는데, 이때 요일이 유지되어야 합니다. 수요일을 보다가 다음 주로 넘기면 다음 주 수요일이 나와야지, 일요일로 돌아가면 안 됩니다.
당연한 것 같은데, 주의 시작일이 일요일이라 단순히 인덱스로 처리하면 어긋납니다. 별것 아닌 문제 같지만, 이런 것들이 쌓이면 “이 앱 뭔가 이상해”가 되거든요.
공휴일의 빨간 글씨
공휴일에 빨간 글씨를 넣는 건 작은 기능인데, 이게 있고 없고의 차이가 생각보다 큽니다. 달력을 보는 순간 “아, 이날 쉬는 날이구나”가 바로 들어오니까요.
공휴일 데이터를 가져와서 날짜별로 맵에 넣고, 셀 렌더링 할 때 확인하는 구조입니다. 별로 어렵지 않았지만, 이걸 넣은 후에 달력이 훨씬 달력다워졌습니다.
셀 하나하나의 성능
달력은 한 화면에 셀이 수십 개입니다. 날짜를 선택할 때마다 전체 셀이 다시 그려지면 버벅입니다.
그래서 셀마다 작은 파생 상태를 만들었습니다. “이 셀이 선택된 날짜인가”, “이 셀이 오늘인가”, “이 셀이 공휴일인가”를 각각 독립적으로 계산합니다. 날짜를 바꿔도 실제로 값이 바뀐 셀만 다시 그려집니다.
이런 최적화가 없으면 스크롤할 때 미세하게 끊깁니다. 눈에 보일 듯 말 듯 한 차이인데, 느끼는 사람은 느낍니다.
돌아보며
달력 관련 커밋이 400개가 넘습니다. 제가 만든 기능 중에서 가장 오래, 가장 많이 손이 갔습니다.
라이브러리를 쓰지 않은 게 맞는 선택이었는지는 아직도 모르겠습니다. 분명히 더 빨리 만들 수 있었을 거예요. 하지만 원하는 대로 움직이는 달력을 갖게 된 건 사실입니다.
크래시를 하나씩 잡아가면서 느낀 건, 달력처럼 단순해 보이는 것이 제일 까다롭다는 거였습니다. 모두가 매일 쓰는 거라 기대 수준이 높고, 조금만 어색해도 바로 티가 납니다.
아직 부족한 부분이 있을 겁니다. 쓰시다가 이상한 점이 보이면 알려주세요.
매일 쓰는 달력이니까, 매일 조금씩 더 좋아지면 좋겠습니다.