
솔직히 고백하자면, 저는 알림 화면을 단순 리스트로 쌓아두는 것이 당연하다고 생각했습니다. 그러다 커뮤니티 앱 외주에서 알림 진입 후 행동 전환율이 18%에 머무는 것을 보고 뭔가 잘못됐다는 걸 깨달았습니다. 그 과정에서 배운 Rive 애니메이션 연동과 알림 묶음 규칙 설계를 지금부터 풀어보겠습니다.
Rive 애니메이션과 SwiftUI 상태머신 연결하기
처음 Rive 파일을 SwiftUI 프로젝트에 붙였을 때 솔직히 당황했습니다. 캐릭터가 그냥 멈춰 있는데, 이게 맞는 건지 틀린 건지조차 몰랐습니다. 알고 보니 상태머신(State Machine)을 따로 구동해줘야 했습니다. 여기서 상태머신이란 애니메이션의 여러 동작을 조건과 입력값에 따라 전환시키는 논리 구조입니다. 단순히 타임라인을 재생하는 것과 달리, 사용자 인터랙션에 반응해 애니메이션이 유기적으로 바뀝니다.
Rive 런타임을 Swift 패키지로 추가하고 RiveViewModel을 @StateObject로 선언하면 뷰와 애니메이션이 연결됩니다. 핵심은 .riv 파일 안에 정의된 상태머신 입력값(Input)을 코드에서 실시간으로 조작하는 것입니다. 예를 들어 드래그 제스처로 scrollPercentage 값을 0에서 100 사이로 보내면, 상태머신이 그 값을 읽어 블렌드 애니메이션을 구동합니다. 블렌드 애니메이션(Blend Animation)이란 두 개 이상의 애니메이션 타임라인을 입력값 비율로 섞어 부드럽게 전환하는 기법입니다.
제가 직접 써봤는데, allowsHitTesting(false)를 빠뜨리면 Rive 뷰가 드래그 이벤트를 가로채버려서 제스처가 아예 작동하지 않습니다. 이 한 줄이 없어서 30분을 날린 기억이 있습니다.
드래그 제스처 오프셋과 스프링 애니메이션 설계
제스처 처리에서 제가 처음 실수한 부분이 있었습니다. DragGesture의 translation.width를 그대로 오프셋에 넣었더니 캐릭터와 카드 이동이 1:1로 맞아버려서 전혀 역동적이지 않았습니다. 실제로는 캐릭터 오프셋에 1.9를 곱해 카드보다 빠르게 따라오게 만들어야 긴장감이 생깁니다.
withAnimation(.spring()) 블록 안에서 오프셋을 업데이트하면 스프링 애니메이션(Spring Animation)이 적용됩니다. 스프링 애니메이션이란 물리 기반의 탄성 움직임으로, 손을 뗐을 때 원래 위치로 자연스럽게 튕겨 돌아오는 효과입니다. onEnded 콜백에서 오프셋을 0으로 되돌리면 되는데, 이때 currentDragOffset과 alligatorDragOffset을 분리해두지 않으면 임계값 판단 로직이 꼬입니다. 제 경험상 이 두 값을 별도 @State로 관리하는 게 훨씬 깔끔합니다.
드래그 퍼센티지는 translation.width / UIScreen.main.bounds.width로 계산합니다. 이 값을 상태머신 입력으로 보내면 캐릭터가 얼마나 물어뜯을지 결정됩니다. 임계값을 100에 도달했을 때만 완료 처리하도록 설계하면, 실수로 절반만 드래그하다 놔도 항목이 삭제되지 않아 사용자 실수를 방지할 수 있습니다.
알림 카테고리 탭과 묶음 규칙으로 전환율 끌어올리기
알림 애니메이션만 예쁘게 만들어도 실제 전환율이 오르지 않으면 의미가 없습니다. 제가 커뮤니티 앱 외주에서 알림 센터를 단순 리스트에서 카테고리 탭(전체·답글·좋아요·시스템)으로 재설계한 뒤 알림 진입 후 행동 전환율이 18%에서 41%로 올랐습니다. 미확인 알림 카운트를 탭 라벨에 함께 표시하는 것만으로 의사결정 비용이 눈에 띄게 줄었습니다.
알림 묶음 규칙(Notification Grouping Rule)도 빠뜨릴 수 없습니다. 묶음 규칙이란 동일한 트리거에서 발생한 여러 알림을 하나의 카드로 합산 표시하는 정책입니다. 같은 게시물 답글 3건을 'A님 외 2명이 답글을 남겼습니다'로 묶으면 알림 수가 평균 41% 줄었습니다. 저는 커뮤니티·커머스·생산성 3개 카테고리별로 별도 가이드를 만들어 외주 3건에 재사용하고 있습니다.
알림 카드 설계에서 제가 표준화한 영역 분할은 다음과 같습니다.
- 아바타 영역: 발신자 또는 브랜드 이미지, 고정 44pt
- 내용 영역: 본문 텍스트, 한국어 카피는 최대 2줄(약 40자) 고정
- 시간 영역: 상대 시간 표기, 우측 상단 배치
- 액션 영역: 빠른 답장 또는 확인 버튼, 스와이프 트레일링에 배치
이 구조를 디자인 토큰(Design Token)으로 정의해두면 협업할 때 개발자와 디자이너 사이의 스펙 오해가 크게 줄어듭니다. 디자인 토큰이란 색상, 간격, 타이포그래피 같은 UI 속성을 변수화해 플랫폼과 팀 간에 일관되게 공유하는 방식입니다.
한국 사용자를 위한 알림 UX 설계의 실제 차이점
일반적으로 알림 묶음을 '최신성' 기준으로 설계하는 경우가 많은데, 제 경험상 한국 사용자는 다릅니다. '얼마나 새로운가'보다 '얼마나 중요한가'로 알림을 판단하는 경향이 훨씬 강합니다. 실제로 커머스 외주에서 타임스탬프 기반 묶음보다 중요도 기반 묶음으로 바꿨을 때 사용자 이탈이 줄었습니다.
한국어 알림 카피가 영문 대비 길다는 점도 무시할 수 없습니다. 같은 내용이라도 영문은 20자, 한국어는 35자가 되는 경우가 흔합니다. 카드 높이가 들쭉날쭉해지면 스크롤 리듬이 깨지고 사용자가 피로감을 느낍니다. 저는 한국어 카피 최대 글자 수를 40자로 고정하고, 초과 시 말줄임표 처리하는 규칙을 가이드에 넣었습니다.
또한 한국 사용자는 미확인 알림 카운트를 0으로 만드는 것에 강한 압박을 느낍니다. UX 연구에서도 이 '뱃지 불안(Badge Anxiety)'이 앱 재방문을 유도하는 주요 심리적 트리거로 꼽힙니다(출처: Nielsen Norman Group). 따라서 '전체 읽음' 버튼의 발견성이 만족도의 핵심 변수가 됩니다. 저는 탭바 상단에 고정 노출하는 방식이 가장 효과적이었습니다.
앱 사용성 연구에 따르면 사용자가 알림을 인지하고 행동으로 이어지는 데 걸리는 평균 시간은 3초 이내입니다(출처: Baymard Institute). 이 3초 안에 '무슨 알림인지', '어떻게 처리할지'가 한눈에 보여야 합니다. 결국 한국 시장용 알림 UX 가이드는 다음 네 가지를 반드시 함께 다뤄야 정착한다고 봅니다.
- 중요도 기반 그루핑 규칙
- 한국어 카피 글자 수 표준 (최대 40자)
- 도메인별 묶음 규칙 정의
- 전체 읽음·전체 삭제 버튼 위치와 발견성
Rive 애니메이션 하나로 시작한 이야기가 결국 알림 UX 전반으로 이어졌습니다. 기술적으로 드래그 제스처와 상태머신을 연결하는 것도 중요하지만, 실제로 사용자에게 가치를 주는 건 묶음 규칙과 카테고리 탭 같은 정보 구조 설계였습니다. SwiftUI와 Rive를 처음 연동해보신다면 상태머신 입력값부터 천천히 건드려보시길 권합니다. 구조가 잡히면 나머지는 생각보다 빠르게 따라옵니다.