RN에서 핀치 줌(pinch zoom)이 지원되는 이미지/동영상 갤러리(스와이프)를 구현할일이 생겨 라이브러리를 서칭해보았다.
1. ascoders/react-native-image-viewer (star 2.1k)
이 라이브러리가 가장 많은 star를 보유하고 있었고, zoom과 swipe이벤트간 간섭에 대해 훌륭하게 처리해주고 있었다.
보통 스와이프 기능에는 FlatList를 쓰는데, 이 라이브러리는 transform으로 처리하였다.
renderHeader, renderFooter 등 커스터마이징 기능도 지원한다.
동영상 플레이어를 끼워넣기 위해 renderImage를 사용했는데, 동영상 컴포넌트만 로딩이 되지 않았다.
코드를 뜯어보니 Image.getSize를 이용해 사이즈를 가져오지 못하면 fail로 처리되기 때문이었다. 애초에 이미지가 아니면 화면 로딩이 되지 않는다는 뜻이다.
이미지 갤러리 용도로는 훌륭한 라이브러리 이지만, 커스텀 컴포넌트도 필요한 나에게는 적합하지 않았다.
2. leecade/react-native-swiper (star 9.1k)
엄밀히 말하면 이 라이브러리는 이미지 전용은 아니고, 스와이프(페이징?) 기능에 대한 라이브러리다. 하지만 swiper위에 image zoom과 video player를 얹으면 될꺼라 생각했고, 그렇게 적용해봤다.
그러나 onIndexChanged 를 활용해 현재 index 받아오는 코드를 구현하던중 콘솔창이 노란색 warning으로 채워지기 시작했다.
onIndexChanged produces Warning: Cannot update a component from inside the function body of a different component.
github.com/leecade/react-native-swiper/issues/1209
조금 검색하니 바로 원인과 해결책이 나왔다. 그럼에도 이 해결책이 적용되지 않고 있다는건 라이브러리가 죽어있다는 뜻이고, deprecated된 라이브러리 사용을 꺼리는 나는 저 이슈 답변중 누군가가 추천한 react-native-snap-carousel 을 사용해보기로 했다.
3. archriss/react-native-snap-carousel (star 8k)
여기까지 오게될줄은 몰랐지만, 스와이프기능의 끝판왕 처럼 보였다. 업데이트도 최근까지 이루어졌고, 안정적이라는 평이 많았다.
실제로 사용해보니 이쁘고, 생각한대로 잘 적용되었다.
이대로 끝나는가 싶었는데, 진짜 문제는 이미지 줌을 적용하던중 발견되었다. (snap-carousel만의 문제는 아니다. FlatList를 사용하는 모든 라이브러리에게 해당되는 문제다)
이미지 줌 라이브러리:
ascoders/react-native-image-zoom
이 라이브러리(1번 뷰어에 사용된 하위 라이브러리다)를 활용해 핀치 줌을 구현하던중, snap-carousel의 스와이프 제스쳐와 핀치줌 제스쳐가 충돌하여 하나만 작동하는 문제가 생겼다.
ascoders/react-native-image-zoom/issues/42
이슈목록을 뒤적거리다가 `Prevent FlatList scrolling while image zoomed` 라는 적절한 이슈를 찾아 해결책을 적용해봤다.
하지만 내 코드에선 작동하지 않았고, 더 안정적인 방법을 찾아 ImageViewer라는 커스텀 컴포넌트로 문제를 해결하였다.
(수정) 안드로이드에선 ImageViewer만으로 Carousel스크롤을 막을수 있지만, iOS에선 Carousel레벨에서 scale에 따라 scroll을 비활성화하는 로직이 필요함.
Carousel 적용예제
const [scrollEnabled, setScrollEnabled] = useState(true);
return (
<Carousel
scrollEnabled={scrollEnabled}
renderItem={() => (
<View>
<ImageViewer
onScaleChange={(scale) => setScrollEnabled(scale === 1)}
/>
</View>
)}
/>
)
ImageViewer 컴포넌트
import React, {FC, useRef} from 'react';
import {NativeBase, View} from 'native-base';
import ImageZoom, {ImageZoomProps} from 'react-native-image-pan-zoom';
import {Dimensions, Image} from 'react-native';
interface ImageViewerProps {
source: NativeBase.Image['source'];
width?: any;
height?: any;
style?: ImageZoomProps['style'];
onScaleChange?: (scale: number) => void;
}
const ImageViewer: FC<ImageViewerProps> = ({
source,
width = Dimensions.get('window').width,
height = Dimensions.get('window').height,
onScaleChange,
...props
}) => {
const scaleValue = useRef(1);
return (
<ImageZoom
cropWidth={Dimensions.get('window').width}
cropHeight={Dimensions.get('window').height}
imageWidth={width}
imageHeight={height}
minScale={1}
{...props}
onStartShouldSetPanResponder={(e) => {
return e.nativeEvent.touches.length === 2 || scaleValue.current > 1;
}}
onMove={({scale}) => {
scaleValue.current = scale;
onScaleChange && onScaleChange(scale);
}}>
<View
style={{width: '100%', height: '100%'}}
onStartShouldSetResponder={(e) => {
console.log(
scaleValue.current,
e.nativeEvent.touches.length < 2 && scaleValue.current <= 1,
);
return e.nativeEvent.touches.length < 2 && scaleValue.current <= 1;
}}>
<Image
source={source}
resizeMode="contain"
style={{width: '100%', height: '100%'}}
/>
</View>
</ImageZoom>
);
};
export default ImageViewer;
이미지 갤러리 커스터마이징으로 고통받을 누군가에게 도움이 되었으면...
'Front-end > React & React Native' 카테고리의 다른 글
[React Native] ios 빌드시 main.jsbundle does not exist. This must be a bug with 에러 해결방법 (0) | 2020.12.07 |
---|---|
[React Native] react-native-naver-map ios 빌드시 Undefined symbols for architecture x86_64: 에러 (0) | 2020.12.06 |
React Native 컴포넌트에서 children 스타일 덮어쓰는 방법 (0) | 2020.11.19 |
[React] ApolloClient 캐시 비우는 간단한 방법 (0) | 2020.11.19 |
[Strapi + React] ApolloClient로 초간단 pagination 구현하기 (0) | 2020.11.18 |