2024. 10. 6. 11:51ㆍFramework, Library
1️⃣ Share Sheet, Share Extension 기본 개념 이해하기
보통 자신의 앱에서 보여주는 콘텐츠 (URL 주소, 텍스트, 이미지, 동영상 등)를 타 앱으로 공유할 수 있도록 할 때,
아래와 같은 공유 버튼을 사용해서 콘텐츠를 앱 외부로 내보내거나-타앱에서 데이터를 처리할 수 있도록 하는 액션을 만들고는 한다.
타 앱에서 "공유하기" 버튼을 클릭했을 때, 우리의 앱과 쉽게 상호작용할 수 있도록 만드는 기능이 바로 Share Extension이고,
추가로 버튼 클릭 시에 나오는 바텀시트 화면을 Share Sheet라고 부른다.
*HIG에서 설명하고 있는 Share Sheet의 정식 명칭은 Activity View로, UIActivityViewController로 만들게 된다. (해당 내용은 다음 글에서 다루게 될 예정)
<토스터 TOASTER>는 링크 아카이빙 서비스로 "외부 앱에서 보던 링크를 우리 앱에 손쉽게 저장할 수 있는 기능"이 필요했고,
그에 따라서 2차 스프린트에 이 Share Extension 기능을 포함하는 업데이트를 진행하게 되었다.
여담으로, 3차 스프린트에는 우리 앱의 웹 화면에서도 다른 앱에 대한 접근이 가능하게 하는 Activity View를 추가하게 될 예정이다.
다른 앱에서도 우리 앱에 대한 접근이 쉬워졌으니 / 우리 앱에서도 다른 앱에 대한 접근을 쉽게 만드는 개념이랄까..? 아무튼 우리 앱 <토스터 TOASTER>와 다른 앱 간의 상호작용이 갈수록 원활하게 만들어가지는 셈!
이번 글은 Share Extension을 구현하는 방법 하나하나에 대해 살펴보고자 쓰게 된 것은 아니다.
구현 방법을 설명하기보다, Share Extension이 다른 앱에서 표출되는 조건을 정의하는 Info.plist 파일의 속성인 NSExtensionActivationRule과 외부로부터 우리 앱의 Extension으로 넘어오는 정보의 자료형에 해당하는 NSExtensionItem에 대해 집중적으로 다루고자 글을 쓰게 되었다.
2️⃣ NSExtensionActivationRule - Share Extension이 표출될 규칙을 정해보자
우리 앱의 익스텐션 타깃으로 Share Extension을 처음 추가하면,
기본적으로 어느 상황에서 우리 앱의 Share Extension을 표출시키도록 할지를 정하는 NSExtensionActivationRule을 지정해줘야 한다.
쉽게 말하면 기본적으로 Share Extension은 콘텐츠를 공유하는 상황에서 사용할 수 있는 기능이기 때문에,
어떤 유형의 콘텐츠를 공유할 때 동작하고자 할지, 어떤 상황에서 사용자에게 표시될지를 하나의 규칙으로 정해놓는다는 의미다.
우리 토스터 앱을 기준으로 하면,
동영상이나 이미지, 텍스트, 파일 등의 다른 콘텐츠를 공유하고자 하는 상황은 제외하고 / 웹 주소에 해당하는 URL을 공유하고자 할 때만 우리 앱의 Share Extension이 표출되도록 규칙으로 정해놓게 되었다.
NSExtensionActivationRule에서 지원하고 있는 유형은 아래와 같은 것들이 있다.
- NSExtensionActivationSupportsText : 텍스트에 대한 공유를 지원할지, 말지를 Boolean 값으로 지정한다.
- NSExtensionActivationSupportsImageWithMaxCount : 이미지 공유를 지원, 최대 이미지를 몇 개 지원할지 Int 값으로 지정한다.
- NSExtensionActivationSupportsMovieWithMaxCount : 동영상 공유를 지원, 최대 동영상 개수를 Int 값으로 지정한다.
- NSExtensionActivationSupportsFileWithMaxCount : 파일 공유를 지원, 최대 파일 지원 개수를 Int 값으로 지정한다.
- NSExtensionActivationSupportsFileTypeIdentifiers : 특정 파일 형식을 String 타입으로 지정해, Array 형으로 묶어서 지정한다.
- NSExtensionActivationSupportsWebURLWithMaxCount : HTTP URL 주소를 지원, 최대 지원 가능한 URL의 개수를 Int 값으로 지정한다.
우리 앱은 웹 링크를 저장하는 서비스이므로,
처음에는 WebURL에 대해서만 Share Extension이 동작하도록 NSExtensionActivationSupportsWebURLWithMaxCount 유형을 포함시켜 Info.plst 내용을 작성해 주면 될 것이라 생각했다.
*여기서 설명하는 Info.plst는 기본 앱의 Info.plst 파일이 아닌, 별도의 Share Extension에서 생성해 준 Info.plst 파일을 의미한다.
3️⃣ 각 외부 서비스별 NSExtensionItem의 자료형 이해하기
그러나, URL 권한을 열어줬음에도 사파리를 제외한 다른 앱에 대해서는 링크를 받아올 때 조금씩 다른 부분이 있어 어려움이 겪었다.
여기서 말하는 다른 부분이란,
1. Activation Rule이 웹 링크임에도 URL이 아닌, 다른 자료형으로 반응하여 Share Extension이 작동되지 않는 문제
2. NSExtensionItem이 "public.url"의 형태가 아니라 다른 형태로 받아오게 되는 문제 등을 의미한다.
이게 무슨 말인지는 아래 예시를 보면 이해할 수 있다.
우선, 아이폰 기본 웹 브라우저인 Safari나 카카오 기반 서비스의 웹 뷰 (카카오톡, 카카오맵, 티스토리 등)에서는 예상했던 시나리오대로 URL로 인식하고, 주소 값을 받아올 때도 "public.url"이라는 URL 값으로 처리할 수 있도록 넘어온다. -> 우리는 이 url을 받아서 그냥 저장할 수 있도록 하면 되었다!
하지만, 네이버 기반의 앱에서는 아래 보이는 것과 같이 Text와 URL이 함께 넘어오거나, Text만 가지고 넘겨주는 경우가 있어
현재 설정한 NSExtensionActivationSupportsWebURLWithMaxCount 권한에 더해 NSExtensionActivationSupportsText 권한을 추가로 설정할 필요가 있었다.
-> 기존 URL로만 Activation Rule을 설정하면, 네이버 기반 앱에서는 "어? URL이 아니라 Text잖아?"하고 우리 앱의 Share Extension을 작동시키지 않았던 것!
여기서 네이버 앱은 Text로 반응하고, 위의 로그와 같이 Text와 URL을 함께 넘겨준다는 것의 의미는 뭘까?
- Text에 해당하는 내용은 현재 콘텐츠의 제목이 되는 거다. (URL 주소를 Text로 넘겨주는 개념이 아니다!)
- URL에 해당하는 내용이 내게 필요한 웹 링크 주소 값이 되는 거다.
이제 Share Extension의 ViewController에 해당하는 ShareViewController 코드로 넘어와서 Item 값을 받아오는 처리를 해주겠다.
여기서 먼저 Share Extension에서 넘어오는 데이터를 받을 때 사용되는 NSExtensionItem이라는 객체에 대해 간단하게 설명해 보겠다.
NSExtensionItem은 콘텐츠의 제목을 표현하는 NSAttributedString 타입의 arrtibutedTitle,
공유하는 콘텐츠의 본문이나 설명을 포함하고 있는 NSAttributedString 타입의 attributedContextText,
그리고 해당 앱이 공유할 수 있도록 만든 실제 데이터를 담고 있는 NSItemProvider 객체의 배열 타입인 attachments가 있다. (위에서 봤던 내용물들이 이 attachments 배열에 속해있는 항목이었던 거다. -> 카카오는 url만 보내고 / 네이버는 text와 url을 모두 보내고)
나는 이 attachments라는 배열 속에 URL 타입에 해당하는 "public.url" 값으로 URL 주소 값을 인식하고, 해당하는 Item만 가지고 우리 앱의 링크 저장을 수행하는 로직을 만들어주게 되었다. 아래와 같이!
쉽게 말해 URL이 아니라 Text와 URL이 함께 배열로 속해있던 네이버 앱의 로그를 참고로, URL 값만 받아서 처리할 수 있도록 만든 것!
func getUrl() {
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProviders = item.attachments {
itemProviders.forEach { itemProvider in
if itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url") { [weak self] (url, error) in
if let shareURL = url as? URL {
self?.urlString = shareURL.absoluteString
} else {
print("Error loading URL: \(error?.localizedDescription ?? "")")
}
}
}
}
}
}
카카오나 네이버 기반이 아닌 다른 앱에 대해서는 많은 테스트를 돌려보는 중이다.
유튜브 앱의 경우에는 애초에 URL 값을 내보내지 않고, Text 값으로만 반응하고 값을 내보내고 있었기에 처리를 해주는 데 있어 아직도 어려움을 겪고 있다. 해당 경우에 대해서는 어떻게 URL 값을 받아오고 처리할 수 있을지 조금 더 찾아봐야 할 것 같다...:)
아무튼 이렇게 다양한 앱에서 링크 URL 값을 받아오고, 우리의 링크 저장 앱에 손쉽게 저장할 수 있는 기능을 구현할 수 있었다는 점!
오늘 글은 여기까지😊