40.1 안드로이드에선 됐는데 아이폰에선 왜 안 될까요?
PWA를 처음 배포하고 나면 다들 비슷한 경험을 해요. 안드로이드 크롬에선 설치 배너도 뜨고 홈 화면에 척척 깔리는데, 아이폰을 꺼내 든 순간부터 이상해지죠. 배너는 안 뜨고, 코드로 넣어둔 설치 버튼을 눌러도 아무 반응이 없어요. 저도 처음엔 제 코드가 잘못된 줄 알고 한참을 뒤졌어요.
결론부터 말하면 코드 문제가 아니에요. iOS 사파리(아이폰 웹 브라우저)는 PWA를 대하는 방식이 안드로이드와 근본적으로 달라요. 이번 편은 제가 아이폰에서 실제로 겪은 "안 되는 것들"을 하나씩 풀어볼게요. 미리 알면 삽질을 안 하니까요.
40.2 설치 버튼을 눌러도 왜 아무 일이 없죠?
안드로이드 크롬에서는 beforeinstallprompt라는 이벤트가 있어요. 브라우저가 "이 앱 설치할 만하네" 하고 이 이벤트를 던져주면, 그걸 잡아서 내 맘대로 설치 버튼을 띄울 수 있죠. 다음 편에서 다룰 바로 그 기능이에요.
그런데 iOS 사파리에는 이 이벤트가 아예 없어요. 그래서 아래 코드는 안드로이드에선 잘 돌지만 아이폰에선 콜백 안이 영원히 실행되지 않아요.
window.addEventListener('beforeinstallprompt', (e) => {
// 아이폰에서는 이 안으로 절대 안 들어와요
showMyInstallButton();
});
처음 이걸 몰랐을 때 저는 "왜 내 버튼이 안 뜨지" 하며 이벤트 이름 오타부터 의심했어요. 그런데 알고 보니 사파리가 이 이벤트를 지원 안 하는 거였죠. 아무리 코드를 고쳐도 뜰 수가 없던 거예요.
40.3 그럼 아이폰에선 어떻게 설치하나요?
iOS에서 PWA를 설치하는 방법은 딱 하나, 사용자가 직접 손으로 하는 거예요. 사파리 아래쪽 공유 버튼을 누르고, 메뉴를 내려서 홈 화면에 추가(Add to Home Screen)를 눌러야 해요. 이 과정을 자동으로 띄우거나 코드로 대신 눌러줄 방법은 없어요.
그래서 아이폰 사용자에겐 "공유 누르고 홈 화면에 추가 눌러주세요"라고 그림까지 곁들여 안내하는 게 사실상 유일한 방법이에요. 이 안내 UX는 다음 편에서 제대로 만들어볼게요. 지금은 "아이폰은 자동 설치가 원천적으로 막혀 있다"만 기억하면 돼요.
40.4 푸시 알림은 진짜 안 되나요?
예전엔 "아이폰 웹에서 푸시는 그냥 포기"가 정설이었어요. 그런데 이건 지금 기준으로는 반만 맞아요. iOS 16.4(2023년 3월)부터 웹 푸시가 열렸거든요. 다만 조건이 까다로워요.
가장 중요한 조건은 이거예요. 홈 화면에 추가해서 설치한 웹앱에서만 푸시가 돼요. 사파리 탭에서 그냥 열어둔 상태로는 안 돼요. 즉 사용자가 40.3의 그 귀찮은 홈 화면 추가를 끝까지 해줘야, 그다음에 알림 권한을 켜야, 비로소 푸시 하나를 보낼 수 있는 거죠.
안드로이드는 탭에서 열어둔 상태로도 권한만 주면 푸시가 오는데, 아이폰은 이 설치라는 관문을 반드시 통과해야 해요. 그래서 실무에선 "iOS 푸시는 된다, 단 설치한 사람에게만"이라고 정확히 알고 있어야 해요. 이걸 모르고 "아이폰도 푸시 됩니다"라고 기획서에 적으면 나중에 곤란해져요.
게다가 실제로 푸시 권한을 요청하는 코드도 아무 데서나 부르면 무시돼요. iOS에선 사용자가 버튼을 누르는 것 같은 직접적인 동작 안에서만 권한 창을 띄울 수 있어요. 페이지 열리자마자 자동으로 권한을 물으면 그냥 조용히 씹혀요. 저는 이걸 몰라서 "권한 창이 왜 안 뜨지" 하며 또 반나절을 날렸어요.
40.5 저장해둔 데이터가 사라진 적 있나요?
이건 제가 제일 크게 데인 부분이에요. 아이폰의 웹 저장 공간은 넉넉하지 않아요. 기기 여유 용량에 따라 제한이 걸리고, 안드로이드처럼 넉넉하게 캐시를 쌓아두기가 어려워요. 대용량 이미지를 캐시로 잔뜩 깔아두는 설계는 아이폰에서 잘 안 통해요.
더 무서운 건 자동 삭제예요. 사파리는 한동안(대략 일주일 넘게) 방문이 없는 사이트의 저장 데이터를 스스로 비워버릴 수 있어요. IndexedDB(브라우저 내장 데이터베이스), 캐시 스토리지 같은 게 어느 날 조용히 날아가 있는 거죠. 저는 로그인 상태를 로컬에 저장해뒀다가, 오랜만에 연 사용자들이 죄다 로그아웃돼 있어서 원인을 못 찾고 헤맸어요.
그래서 아이폰을 고려한다면 "저장한 건 언제든 사라질 수 있다"를 전제로 설계해야 해요. 중요한 데이터는 서버가 원본이고 로컬은 잠깐 쓰는 캐시일 뿐이라고 생각하는 게 안전해요.
40.6 아이콘이랑 시작 화면은 왜 따로 챙겨야 해요?
manifest(설정 파일)에 아이콘을 잘 넣었는데도 아이폰 홈 화면엔 엉뚱한 캡처 이미지가 아이콘으로 박히는 일이 있어요. iOS는 manifest만으로는 부족하고, 애플 전용 태그를 HTML head에 따로 넣어줘야 안전하거든요.
<link rel="apple-touch-icon" href="/icon-180.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
apple-touch-icon은 홈 화면 아이콘을, 그 아래 두 줄은 주소창 없이 전체 화면 앱처럼 뜨게 하고 상태 표시줄 색을 정해줘요. 최신 iOS는 manifest의 display 값도 어느 정도 읽지만, 아이콘만큼은 이 apple-touch-icon을 넣어두는 게 제일 확실해요. 이거 하나 빠뜨려서 홈 화면에 흐릿한 스크린샷이 박히는 건 흔한 실수예요.
40.7 이 밖에 아이폰에서 안 되는 게 또 있나요?
아쉽게도 몇 가지 더 있어요. 안드로이드 크롬에서 잘 쓰던 백그라운드 동기화(background sync, 앱이 꺼진 사이 데이터를 몰래 맞춰주는 기능)나 웹 블루투스 같은 하드웨어 접근 기능은 iOS 사파리에서 거의 안 돼요. "안드로이드에서 되니까 아이폰도 되겠지" 하고 넘어가면 십중팔구 여기서 걸려요.
설치한 웹앱 안에서 바깥 링크를 누를 때도 묘한 일이 생겨요. 앱처럼 뜨던 화면이 갑자기 사파리로 튀어나가면서, 어렵게 만들어둔 로그인 상태나 화면 흐름이 툭 끊기는 거죠. 그래서 아이폰을 겨냥한다면 외부 링크를 어떻게 처리할지도 미리 정해둬야 해요.
정리하면 아이폰의 PWA는 "웹으로 할 수 있는 것 중에서도 애플이 허락한 만큼만" 된다고 보는 게 마음이 편해요. 안 되는 걸 억지로 되게 하려다 시간을 버리느니, 처음부터 되는 범위 안에서 설계하는 편이 훨씬 빨라요.
40.8 오늘 겪은 제약을 정리해볼게요
아이폰의 PWA 제약은 크게 다섯 가지예요. beforeinstallprompt가 없어 자동 설치 유도가 안 되고, 설치는 사용자가 공유 → 홈 화면에 추가로 직접 해야 해요. 푸시는 iOS 16.4부터 되지만 홈 화면에 설치한 앱에서만이고요. 저장 공간은 빡빡한 데다 안 쓰면 비워질 수 있어서 서버를 원본으로 삼아야 해요. 마지막으로 아이콘은 apple-touch-icon을 따로 챙겨야 흐릿하게 안 박혀요.
이걸 미리 알고 설계하면 "아이폰에서만 왜 이러지" 하며 밤새우는 일을 크게 줄일 수 있어요. 제약을 안다는 건 그만큼 덜 데인다는 뜻이니까요.