Compose Multiplatform 이미지 피커 라이브러리 배포하기

Wonseok Kim
Preat
Published in
7 min readDec 11, 2023

--

안녕하세요 :)

이번에는 Preat에서 Compose Multiplatform 이미지 피커 라이브러리, peekaboo를 배포하게 된 과정과 그 이야기를 공유해보도록 하겠습니다.

  1. Preat에 새롭게 추가된 개인화된 리뷰 이미지 등록 기능

Preat 앱 버전 1.5.0 부터는 리뷰 등록 이미지를 업로드하는 기능이 추가되었습니다.

이미지 피커 사용 예시 (iOS)
App Store 버전 업데이트 기록

저희 서비스는 오로지 개인화에 초점을 두었기 때문에 단순한 리뷰 등록이 아닌 내가 업로드한 이미지를 앱 내의 모든 식당 데이터에서 대표 이미지로 볼 수 있도록 하였습니다.

하지만, 이 기능을 구현하기 위해서는 우선적으로 iOS, Android 디바이스 내의 저장소에 접근하여 이미지를 불러오는 공통된 로직이 필요했습니다.

2. 그래서 찾아본 라이브러리, Calf

Calf by Mohamed Rejeb

Calf 라이브러리는 Compose Multiplatform 앱을 위한 적응형 UI를 쉽게 만들 수 있게 해주는 라이브러리입니다.

각 플랫폼 별로 Native한 UI들을 쉽게 구현할 수 있고, 특히 개인적으로는 CircularProgressIndicator를 각 플랫폼(iOS, Android)에서 제공하는 컴포넌트 뷰에 맞게 UI를 그려주는 AdaptiveCircularProgressIndicator가 마음에 들었습니다.

AdaptiveCircularProgressIndicator 코드 예제

AdaptiveCircularProgressIndicator(
modifier = Modifier.size(50.dp),
color = Color.Red,
)

3. 하지만, 이미지 다중 선택 이슈 (iOS)

Preat의 리뷰 업로드 기능을 구현하기 위해, Calf의 calf-file-picker 모듈을 직접 사용해 보았습니다.

File Picker 이미지 타입일 때, iOS에서 README와 다른 화면이 나타남

하지만, iOS에서 Calf의 README에서 보이는 이미지 선택 화면이 나타나지 않고, 아이폰 사용자라면 다들 익숙한 파일 앱을 클릭했을 때의 화면(문서 선택기)이 나타나는 이슈가 있었습니다.

파일
아이폰에서 파일 앱에 진입했을 때의 화면

4. PHPicker???

Preat에서는 해당 이슈가 수정될 때까지 마냥 기다릴 수는 없었습니다.

결국 직접 구현을 해야하는 상황이 찾아왔는데…

다행히도 서칭을 해보니, iOS 14부터 새로운 Photo Picker인 PHPicker가 추가되었다는 사실을 알게 되었습니다.

https://developer.apple.com/videos/play/wwdc2020/10652/

5. 늦었다. 바로, 개발 시작

처음에는 당장 라이브러리를 배포할 생각없이, 과연 어떻게 하면 다중 이미지 선택을 위해 PHPickerKotlin Multiplatform의 공통 모듈(:shared)에서 expect로 추상화하고, Android 소스셋iOS 소스셋에서 구현을 할 수 있을 지 고민하기 시작했습니다.

먼저, 안드로이드 구현의 경우는 Android13 이후에 등장한 Photo pickerActivityResultContracts.PickVisualMedia를 통해 이미지 피커를 구현하였습니다.

isPhotoPickerAvalable()의 결과에 따라 문서 선택기 (ACTION_OPEN_DOCUMENT)

다만, 주의해야할 점이 Android SDK 버전에 따라 동작이 달라질 수 있다는 점입니다. 왜 그런지 내부 코드를 한 번 살펴보겠습니다.

PickVisualMedia 클래스 내부의 createIntent 메서드 (설명 추가)

그렇다면 Kotlin/Native에서 iOS 이미지 피커는 어떤식으로 구현을 했을까요?

코드를 간략하게 살펴보겠습니다.

// example #1

@OptIn(ExperimentalForeignApi::class)
val delegate =
object : NSObject(), PHPickerViewControllerDelegateProtocol {
override fun picker(
picker: PHPickerViewController,
didFinishPicking: List<*>,
) {
// TODO
}
}

먼저, delegate 를 통해 NSObject를 상속받고, PHPickerViewControllerDelegateProtocol 인터페이스를 구현하는 객체를 생성합니다.

이미지 선택 결과 처리는 오버라이드된 메서드의 didFinishPicking 매개변수를 활용하는데, 이는 사용자가 선택한 이미지 항목을 담고 있는 List<*> 형태입니다.

이 리스트를 PHPickerResult 객체들로 형변환 (as List<PHPickerResult>) 한 뒤, PHPickerResult에서 제공하는 itemProvider를 사용하여 실제 이미지 데이터에 접근합니다.

// example #2

@Suppress("UNCHECKED_CAST")
val results = didFinishPicking as List<PHPickerResult>

for (result in results) {
result.itemProvider.loadDataRepresentationForTypeIdentifier(
typeIdentifier = "public.image",
) { nsData, _ ->
// TODO
}
}

그렇게 접근하여 얻은 이미지 데이터(NSData)는 별도의 로직을 통해 Kotlin/Native에서 사용 가능한 ByteArray 타입으로 변환됩니다.

추가적으로, 이미지 데이터 로딩 작업의 동시성을 관리하기 위한 처리를 비롯한 더 자세한 코드는 깃허브 레포지토리에서 확인하실 수 있습니다.

5. 마치며…

이상으로, Android의 Photo Picker와 iOS의 PHPickerViewController를 활용하여 양 플랫폼에서 동작하는 이미지 피커 라이브러리의 구현 및 배포 과정을 아주 간략하게 함께 살펴보았습니다.

아직은 완벽하지 않은 코드이고, 앞으로 개선해 나가야 할 부분들이 많이 남아 있습니다.

컨트리뷰션은 물론, 사용하시면서 발견하신 이슈들을 등록해 주시면 큰 도움이 될 것 같습니다!

이 라이브러리를 시작으로 Kotlin Multiplatform 생태계에 조금이나마 기여할 수 있기를 바라며, 앞으로도 많은 관심과 지원 부탁드립니다.

감사합니다! 🙇🙇🙇

--

--