문제 상황
웹과 앱 환경에서 하단바를 다르게 표시해야 했다
const BottomNav = () => {
return <nav>하단 내비게이션</nav>;
};
이걸 어떻게 웹/앱 환경에 따라 다르게 보여줄까?
1. 환경 감지 방법
1.1 User-Agent 활용
User-Agent 문자열을 분석하여 앱 환경인지 확인하는 방법이다.
iOS나 Android 앱의 웹뷰에서 접속 시 특정 문자열(MyCustomApp)을 추가하도록 설정해 감지한다.
const useCheckEnvironment = () => {
const [isApp, setIsApp] = useState(false);
useEffect(() => {
// User-Agent 문자열 가져오기
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
// 1. iOS 앱 체크
const isIOS = userAgent.includes('iPhone') || userAgent.includes('iPad');
// 2. 커스텀 User-Agent 체크
const isCustomApp = userAgent.includes('MyCustomApp');
setIsApp(isIOS && isCustomApp);
}, []);
return isApp;
};
처음에는 User-Agent로 환경을 체크했다. 이게 좋아보였는데 iOS, Android 각각 다르게 처리해야 해서 생각보다 User-Agent 처리가 까다로웠다. 물론 현재 프로젝트에서는 ios 단일이었지만 추후에 확장성을 위해 다른 방법을 찾았다.
1.2 URL 파라미터 활용
URL에 ?platform=app 파라미터를 추가해 환경을 감지한다.
이 방식은 명시적이고 안정적이며, User-Agent 대체 또는 보완 용도로 적합하다.
const useCheckPlatform = () => {
const [platform, setPlatform] = useState<'web' | 'app'>('web');
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const platformParam = params.get('platform');
if (platformParam === 'app') {
setPlatform('app');
}
}, []);
return platform;
};
그래서 URL 파라미터 방식도 시도해봤다. 모든 URL에 파라미터를 붙여야 한다는 게 불편했지만 일단 이걸 쓰기로 했다. 이후에는 user-agent와 url을 같이 썼는데 그건 추후에 포스팅 해보겠음
2. 컴포넌트 구현
2.1 환경별 레이아웃 분기
const AppLayout = () => {
const isApp = useCheckEnvironment();
const platform = useCheckPlatform();
// 두 가지 방법 중 선택
const shouldShowBottomNav = !isApp || platform === 'web';
return (
<StyledLayout>
<main>
<Content />
</main>
{shouldShowBottomNav && (
<BottomNav>
<NavItem to="/home" icon={<HomeIcon />} />
<NavItem to="/search" icon={<SearchIcon />} />
<NavItem to="/profile" icon={<ProfileIcon />} />
</BottomNav>
)}
</StyledLayout>
);
};
const StyledLayout = styled.div`
position: relative;
min-height: 100vh;
main {
padding-bottom: ${({ theme }) => theme.bottomNavHeight};
}
`;
const BottomNav = styled.nav`
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: ${({ theme }) => theme.bottomNavHeight};
background: white;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-around;
align-items: center;
// iOS 노치 대응
padding-bottom: env(safe-area-inset-bottom);
`;
3. iOS 앱 설정
이 부분은 iOS 개발자가 설정하는 부분으로 대충 이런 식으로 동작하는구나~ 정도만 알면 될 것 같다.
다음은 iOS 개발자가 웹뷰 설정에서 User-Agent와 URL 파라미터를 추가하는 방법이다.
3.1 Swift로 User-Agent 설정
let webView = WKWebView(frame: self.view.bounds)
// 기존 User-Agent 가져오기
webView.evaluateJavaScript("navigator.userAgent") { (result, error) in
if let currentAgent = result as? String {
// 커스텀 식별자 추가
let newAgent = "\(currentAgent) MyCustomApp"
webView.customUserAgent = newAgent
}
}
3.2 URL 파라미터 추가
// URL에 platform 파라미터 추가
let baseUrl = "https://myapp.com"
let urlWithPlatform = "\(baseUrl)?platform=app"
if let url = URL(string: urlWithPlatform) {
let request = URLRequest(url: url)
webView.load(request)
}
4. iOS 개발자와 협업 과정
iOS 웹뷰와 웹 브라우저에서 서로 다른 동작을 구현하기 위해 iOS 개발자와 협업하며 아래와 같은 과정을 거쳤다.
우선 미리 iOS 개발자분과 요구사항을 정리했다.
- 웹뷰에서 접속 시
- User-Agent 감지를 통해 하단바를 숨기도록 설정
- User-Agent 문자열에 고유 값을 추가(예: MyCustomApp)하여 환경 구분
- 브라우저 접속 시
- 지정된 경로(/home, /search, /profile)에서만 하단바 표시
- 나머지 경로에서는 하단바 숨김.
- iOS 노치 및 화면 안전 영역 대응
- env(safe-area-inset-bottom)를 사용하여 iOS의 노치 디자인에도 대응
- Swift에서 하단 영역의 안정적인 렌더링을 위한 설정 요청
iOS 개발자분께 UserAgent 설정을 요청했고 테스트 시나리오는 다음과 같다.
1) 웹뷰에서 접속 테스트
- UserAgent.includes('MyCustomApp')로 식별.
- 하단바 숨김 확인:
- 특정 경로에서 하단바가 나타나지 않아야 함.
2) 브라우저에서 접속 테스트
- User-Agent가 기본 값일 경우 platform=web로 식별.
- 지정된 경로에서만 하단바 표시 확인:
- /home, /search, /profile에서 하단바 표시.
- /other-path에서는 하단바 숨김.
3) iOS 노치 대응 테스트
- env(safe-area-inset-bottom)이 적용되는지 확인.
- 안전 영역 외부로 UI가 침범하지 않도록 점검.
5. 실제 적용 시 고려사항
추가로 고려해야 할 자잘한 스타일링들이다.
5.1 스타일링
const BottomNav = styled.nav<{ isApp: boolean }>`
// 앱에서는 하단 여백 추가
${({ isApp }) => isApp && css`
margin-bottom: env(safe-area-inset-bottom);
`}
// 웹에서는 고정 높이
${({ isApp }) => !isApp && css`
height: 60px;
`}
`;
5.2 성능 최적화
const useEnvironment = () => {
const isApp = useMemo(() => {
const userAgent = navigator.userAgent;
return userAgent.includes("MyCustomApp");
}, []);
return isApp;
};
참고 문서
- [WKWebView 공식 문서](https://developer.apple.com/documentation/webkit/wkwebview)
- [safe-area-inset MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/env)
- React Router v6 문서
'React' 카테고리의 다른 글
| [React/리액트] 프로젝트에 Sentry 도입하기 / 모니터링 시스템 구축하기 (1) | 2025.01.08 |
|---|---|
| React에서 WebSocket 채팅 구현하기 / SockJS 사용하기 (0) | 2025.01.07 |
| TanStack Router v7로 타입 안전한 라우팅 구현하기 (3) | 2024.11.17 |
| [React/리액트] 스크롤 감지 버튼 구현하기 / 스크롤 이벤트와 버튼 스타일 동적 변경 (5) | 2024.10.13 |
| [React/리액트] 상태 관리 추천 / Zustand 사용법 (1) | 2024.08.03 |