앱 개발을 시작하자마자 처음 한 작업은 레이아웃이었습니다.
회사에서 비즈니스 미팅용으로 "프로토타입"이 필요했기 때문입니다. 투자 미팅이나 파트너사 미팅에서 실제로 동작하는 걸 보여줘야 했던 거죠.
그래서 서버 개발보다 더미 데이터로 동작하는 프론트엔드 화면을 먼저 만드는 것부터 시작했습니다. 탭 네비게이션, 홈 화면과 같은 기본 구조를 잡았죠. 그리고 첫 관문이 나타났습니다.
"탭 아이콘 색 변환이 잘 안되는데?"
1. 탭 아이콘은 그냥 SVG 쓰면 되는 거 아닌가요?
웹 개발할 때는 아이콘을 당연히 SVG로 썼습니다. 확대해도 깨지지 않고, fill 속성으로 색상도 쉽게 바꿀 수 있으니까요.
디자인팀에게 받은 아이콘을 Figma에서 SVG로 가져왔습니다.
[react-native-svg 설치]
npx expo install react-native-svg
React Native에서 SVG를 쓰려면 react-native-svg 라이브러리가 필요하다는 걸 알았습니다.
"웹에서는 그냥 됐는데, 왜 라이브러리를 설치해야 하지?"
그 이유를 알기 위해선 PNG와 SVG의 차이에 대해 좀 더 이해할 필요가 있었습니다.
[PNG와 SVG]
PNG는 래스터(Raster) 이미지로, 픽셀(점)들의 집합입니다.
각 점마다 "이 위치는 빨간색", "저 위치는 파란색" 같은 정보가 저장되어 있습니다.
- 복잡한 이미지(사진)도 표현 가능
- 확대하면 픽셀이 보임 (계단 현상, 깨짐)
- 색상 변경 불가 (이미 특정 색상으로 그려진 상태)
- 파일 크기: 이미지가 복잡할수록, 해상도가 높을수록 커짐
SVG는 벡터(Vector) 그래픽으로, 수학 공식과 명령어로 이루어져 있습니다.
"[0,10] 지점에서 [50,50] 지점까지 빨간 선을 그어라", "[30,30] 지점에 반지름 20인 파란 원을 그려라" 와 같은 공식입니다.
- 복잡한 이미지(사진)는 표현 어려움
- 확대하더라도 선명함 유지(공식대로 다시 그림)
- 색상 변경 쉬움 ( stroke="red" → stroke="blue" )
- 파일 크기: 단순한 도형은 작음, 복잡한 경로는 커질 수 있음
[웹과 앱의 차이]
웹 브라우저는 SVG를 태생부터 지원합니다. HTML 표준의 일부이기 때문에, <svg> 태그를 넣으면 브라우저가 알아서 렌더링합니다.
하지만 React Native는 다릅니다. React Native의 <Image> 컴포넌트는 PNG, JPG 같은 래스터 이미지만 기본 지원하고, SVG 같은 벡터 그래픽은 지원하지 않습니다.
'react-native-svg' 라이브러리는 SVG의 <path> , <circle> 같은 명령어를 각 플랫폼의 네이티브 그래픽 API로 변환해줍니다.
탭 아이콘에는 SVG가 적합해보였고, react-native-svg 라이브러리도 설치했습니다.
이제 클릭한 탭은 색상이 변하도록 컴포넌트를 만들 시간입니다.
2. 내가 쓰려던 SVG는, 내가 알던 SVG가 아니었다
컴포넌트로 변환하기 전에, SVG 파일을 텍스트 문서로 열어봤습니다. 보통 SVG는 이렇게 생겼습니다.
<svg>
<path d="M10 20 L30 40..." fill="#000"/>
</svg>
그런데 받은 파일은 달랐습니다.
<svg>
<image href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..." />
</svg>
<path>가 아니라 <Image> 태그였습니다. 그리고 그 안에 엄청나게 긴 base64 문자열이 들어있었습니다.
"이게 뭐지?"
[SVG 안에 숨어있던 PNG]
알고 보니, 디자인팀에서 준 파일은 진짜 벡터 SVG가 아니었습니다. PNG 이미지를 SVG 파일 형식으로 감싼 것이었습니다.
이런 일이 왜 발생했을까요?
제가 다니는 회사는 원래 개발을 외주로 맡기던 곳이었습니다. 저를 처음으로 정규 개발자로 뽑은 거였죠. 디자인팀도 카페24 같은 곳에 외주는 맡겨봤지만, 한 공간에서 개발자와 직접 협업하는 건 아마 처음이었을 겁니다.
Figma에서 아이콘을 내보낼 때 SVG로 Export를 선택했지만, 실제로는 래스터 이미지(PNG)가 SVG 컨테이너 안에 임베드된 형태였습니다. PNG가 임베드된 SVG는 색상을 코드로 제어할 수 없기 때문에 다른 방법을 생각해야 했습니다.
방법 1) 디자인팀에 요청하기
"벡터 SVG로 아이콘 다시 내보내주세요"라고 요청할 수도 있었습니다.
하지만 당시 디자인팀은 다른 업무로 바빠 보였고, 무엇보다 "꼭 SVG여야만 하나?"라는 생각이 들었습니다.
웹에서는 SVG가 당연한 선택이었지만, 앱에서도 그래야 할까하는 의문이 생겼습니다.
방법 2) PNG 2장 전략
비슷한 사례를 찾아보니 실무에서는 PNG 두 장을 준비해서 상태에 따라 교체하는 방식도 자주 쓴다는 걸 알 수 있었습니다.
home_inactive.png // 비활성 상태 (회색)
home_active.png // 활성 상태 (검정색)
이 방법은 react-native-svg와 같은 추가 라이브러리가 필요하지 않고, React Native의 기본 컴포넌트인 <Image>를 사용합니다.
디자인팀의 추가 작업 없이 기존 파일을 활용할 수 있고, 그라데이션, 그림자 같은 복잡한 효과도 그대로 적용할 수 있다는 장점이 있습니다.
[문제 해결]
최종적으로 PNG 2장 전략을 선택했습니다.
const ICONS = {
home: {
on: require("../../assets/icons/tab_icons/home_active.png"),
off: require("../../assets/icons/tab_icons/home_inactive.png"),
},
...
}
const ICON_SIZE = 24;
function TabBarImage(props: {
iconName: keyof typeof ICONS;
focused: boolean;
size: number;
}) {
const { iconName, focused, size } = props;
const source = focused ? ICONS[iconName].on : ICONS[iconName].off;
return (
<Image
source={source}
style={{ width: size, height: size, resizeMode: "contain" }}
/>
);
}
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen
name="index"
options={{
title: "홈",
tabBarIcon: ({ focused }) => (
<TabBarImage iconName="home" focused={focused} size={ICON_SIZE} />
),
}}
/>
...
</Tabs>
);
}
이후 만든 즐겨찾기 버튼도 같은 방식으로 처리했습니다. 채워진 하트와 빈 하트, PNG 두 장으로요.
네이티브 환경에서 PNG는 안정적이었지만 SVG는 변환하는 비용이 생긴다는 점에서, 내가 웹이 아닌 앱 개발을 하고있구나 실감했습니다. SVG는 애니메이션 벡터나 확대/축소가 많은 경우 처럼 코드 제어가 필요할 때 고려할 생각입니다.
3. 외주에서 인하우스로 맞춰가는 과정
이 SVG 이슈를 겪으면서 처음으로 느낀 건, 디자인팀 산출물과 개발 환경의 전제가 다를 수 있다는 것입니다.
디자이너 입장에서는 "SVG 맞아요", 개발자 입장에서는 "근데 fill도 안 먹고, path도 없는데..."의 상황이 발생한 것이죠.
회사는 그동안 앱·웹 개발을 외주로 진행해왔고, 제가 처음으로 채용된 인하우스 개발자였습니다. 그러다 보니 “SVG면 된다”, “이미지면 된다” 같은 말의 의미가 각자 생각하는 환경 기준에서 조금씩 달랐던 것입니다.
이 상황에서 선택지는 두 가지였습니다.
- 디자인팀에 벡터 SVG로 다시 요청하기
- 개발 쪽에서 해결 가능한 방법을 찾기
고민 끝에, 이 문제는 구조를 바꾸지 않고도 내 선에서 해결 가능하다고 판단했습니다.
탭 아이콘은 애니메이션이나 확대, 축소가 필요한 영역이 아니었고,
PNG 두 장을 교체하는 방식이면 충분히 요구사항을 만족할 수 있었기 때문입니다.
그래서 디자인 수정 요청 없이, PNG 2장 전략으로 정리했습니다.
[의사결정의 이유를 공유하기]
협업하면서 느낀건 단순히 "이렇게 해주세요"보다, "왜 그래야 하는지"를 설명하는 게 훨씬 효과적이었습니다.
- "SVG 다시 주세요" → 디자인팀: "왜요? 지금 SVG인데요?"
- "이 아이콘은 상태에 따라 색이 바뀌어야 하는데, 지금 SVG 구조로는 코드로 색 제어가 어려워요. PNG 두 장이면 개발 쪽에서 바로 처리 가능합니다." → 디자인팀: "아 그렇군요. 다음부턴 미리 물어볼게요"
[ 디자인 협업에서 자주 나눴던 이야기들 ]
이후 디자인팀과 협업하면서, 단순히 “이렇게 만들어 주세요”보다는 함께 기준을 맞춰가는 대화를 많이 하게 됐습니다.
예를 들면 이런 것들이었습니다.
- 특정 상태만 있는 디자인을 보고
→ “이 화면이 비로그인이거나, 데이터가 없을 때는 어떻게 보여야 할까요?” - 구현 난이도에 비해 사용자 동선이 복잡한 디자인을 보고
→ “이 과정을 조금 더 단순하게 바꿔도 괜찮을까요?” - 샵백 앱을 벤치마킹한 UI를 구현하면서
→ “같은 기능의 버튼이 이 페이지에 두 번 노출될 필요가 있을까요?”
단순히 개발을 쉽게 하기 위한 요청이라기보다는, 사용자 입장에서 정말 필요한 요소인지를 기준으로 함께 고민해나갔습니다.
4. 프로토타입의 예상 밖 효과
재미있는 건 이렇게 만든 프로토타입이 비즈니스 미팅에서 큰 반응을 얻었다는 것입니다.
그동안 회사는 문서와 말로만 서비스를 설명했었는데, 실제로 동작하는 앱을 보여주니 설득력이 완전히 달랐다고 합니다. 투자자나 파트너사 입장에서도 이해하기 훨씬 쉬웠던 거죠.
더미데이터를 넣어 동작하게 한 프로토타입은 꽤나 현명한 선택이었습니다.
'개발 > 주니어 개발자의 캐시백 앱 단독 개발기' 카테고리의 다른 글
| 5. 인프라 입문: Android HTTP 차단과 HTTPS 적용기 (1) | 2026.01.21 |
|---|---|
| 4. 갑자기 해외 서비스도 추가된다고? (0) | 2026.01.19 |
| 3. 앱 소셜 로그인의 첫 관문 - SHA-1과 키 해시 (0) | 2026.01.07 |
| 2. 브랜드마다 다른 캐시백 정책, 어떻게 DB에 담을까? (0) | 2026.01.05 |
| 0. 기록을 남기기로 한 이유 (5) | 2025.12.22 |