앱을 만들다 보면 대부분 화면은 세로(portrait)로 고정하고 싶은데, 영상 플레이어나 이미지 뷰어 같은 특정 화면만 가로(landscape)를 허용하고 싶을 때가 있다. 그냥 app.json에 "orientation": "portrait"를 박아두면 전역으로 세로가 고정돼서 영상 화면에서도 돌아가질 않는다. 반대로 "default"로 풀어두면 모든 화면이 기기 방향을 따라가서 리스트 화면이 눕는 꼴을 봐야 한다.
이 간극을 메우는 게 expo-screen-orientation이다. 전역 기본값은 세로로 잠가두고, 필요한 화면에서만 코드로 가로를 열었다 닫는 식이다.
설치와 기본 잠금
npx expo install expo-screen-orientation
app.json에는 기본값만 세로로 둔다.
{
"expo": {
"orientation": "portrait"
}
}
영상 화면에서만 가로 허용하기
핵심은 화면이 열릴 때 가로를 풀고, 떠날 때 다시 세로로 잠그는 것. useEffect의 클린업에서 원복하는 게 포인트다.
import { useEffect } from 'react';
import * as ScreenOrientation from 'expo-screen-orientation';
function VideoScreen() {
useEffect(() => {
// 이 화면에서는 가로/세로 모두 허용
ScreenOrientation.unlockAsync();
return () => {
// 나갈 때 다시 세로로 고정
ScreenOrientation.lockAsync(
ScreenOrientation.OrientationLock.PORTRAIT_UP
);
};
}, []);
return /* 플레이어 UI */;
}
unlockAsync()는 기기 설정과 물리적 방향을 그대로 따라간다. "전체화면 버튼을 눌렀을 때만 강제로 눕히고 싶다"면 unlock 대신 가로로 직접 잠그면 된다.
async function enterFullscreen() {
await ScreenOrientation.lockAsync(
ScreenOrientation.OrientationLock.LANDSCAPE
);
}
async function exitFullscreen() {
await ScreenOrientation.lockAsync(
ScreenOrientation.OrientationLock.PORTRAIT_UP
);
}
현재 방향 감지하기
레이아웃을 방향에 따라 바꿔야 한다면 리스너를 건다.
useEffect(() => {
const sub = ScreenOrientation.addOrientationChangeListener((e) => {
const o = e.orientationInfo.orientation;
const isLandscape =
o === ScreenOrientation.Orientation.LANDSCAPE_LEFT ||
o === ScreenOrientation.Orientation.LANDSCAPE_RIGHT;
// isLandscape로 UI 분기
});
return () => ScreenOrientation.removeOrientationChangeListener(sub);
}, []);
주의할 점
iOS 시뮬레이터에서는 app.json의 orientation 설정이 캐시돼서 코드로 잠가도 안 먹힐 때가 있다. 앱을 완전히 지웠다 다시 빌드하면 해결된다. 그리고 lockAsync는 비동기라 화면 전환 애니메이션과 타이밍이 겹치면 살짝 덜컹거릴 수 있는데, 전환이 끝난 뒤 InteractionManager.runAfterInteractions로 미루면 부드럽다.
전역은 세로로 잠그고 화면 단위로만 예외를 두는 이 패턴이, 방향 관련 코드를 한 화면 안에 가둬두기 때문에 나중에 관리하기 제일 깔끔했다.