[iOS] Share Extension (1)

Picnic·2024년 12월 10일

iOS

목록 보기
1/5
post-thumbnail

안녕하세요 Picnic🧃입니다.

이번엔 제가 프로젝트에서 사용한 Share Extenison에 대해서 조금 더 제대로 알아보고자 글을 작성하려고 합니다.

바로 시작하겠습니다.🫠

이 글은 ShareExtension에 대한 기본적인 사용법을 다루고 있습니다.
커스텀 UI를 통한 ShareExtension 사용과 공유할 데이터를 이용하는 방법은 2편을 참고해주세요.

Share Extension이란?


애플에서 말하는 Share Extension은 다음과 같습니다.

Share Extension 프로그램은 사용자가 소셜 공유 웹사이트나 업로드 서비스와 같은 다른 엔티티에 콘텐츠를 간편하게 공유할 수 있도록 합니다.

예를 들어 우리가 유튜브 앱을 보다가 공유하고 싶은 영상이 있으면 공유 버튼을 누르고 Share Extension을 제공하는 SNS를 통해 공유할 상대를 정하는 과정이 해당됩니다. 아래 사진과 같은 시트를 한 번쯤은 보셨을거에요.

Share Extension은 다음을 수행해야 합니다.

  • 사용자가 콘텐츠를 간단히 게시할 수 있도록 지원
  • 사용자가 적절한 경우 콘텐츠를 미리보기, 편집, 주석 추가, 구성할 수 있도록 지원
  • 사용자의 콘텐츠를 전송 전에 검증

이 때 Share Extension 프로그램이 작업할 수 있는 콘텐츠 타입을 지정하는 것이 좋습니다.

위의 Key를 ShareExtension이 가지고 있는 Info.plist에 추가해주고 수, true / false 등으로 value를 지정해주면 됩니다.

이것을 설정하게 되면 Share Extension이 필요한 앱의 공유 시트에서만 자신의 앱이 나타나도록 할 수 있거나 특정 조건을 만족해야만 자신의 앱이 공유 시트에 나타나도록 설정할 수 있습니다.

사용자가 Share Extension 프로그램을 선택하면, 콘텐츠를 작성하고 게시할 수 있는 View를 표시합니다.
이 View는 시스템이 제공하는 compose view controller를 기반으로 하거나 커스텀 View를 만들 수 있습니다.

시스템이 제공하는 compose view controller는 위에서 수행해야 하는 일반적인 작업을 지원합니다.

(compose view controller는 뒤에서 ShareViewController로 불리기도 합니다.)

사용법


먼저 Share Extension을 생성하는 법은 다음과 같습니다.

  1. Xcode 프로젝트에서 File - New - Target을 선택해줍니다.

그러면 위와 같이 다양한 Extension들이 나오는데 이 중 Share Extension을 선택해주면 됩니다.

그 뒤에 원하는 Product Name을 입력해주고 Finish를 눌러 만들어주면 됩니다.

그러면 다음과 같이 새로운 폴더와 함께 기본적인 ShareViewController, Info.plist 파일, 인터페이스 파일(스토리보드)를 제공해줍니다.

먼저 기본적인 ShareViewController는 다음과 같은 모습입니다.

기본 ShareViewController는 SLComposeServiceViewController를 상속하며, didSelectPostisContentValid와 같은 메서드에 대한 설명이 포함됩니다.

공유 데이터에 접근하기


Share Extension은 기본 ViewController의 extensionContext 프로퍼티를 사용해 NSExtensionContext 객체에 접근합니다. 이 객체는 사용자가 작성한 초기 텍스트와 게시할 항목(ex: 링크, 이미지, 비디오 등)을 포함합니다.

또한 extension context 객체에는 게시 작업의 상태 정보도 포함됩니다.

기본 SLComposeServiceViewController 객체에는 사용자가 수정할 수 있는 텍스트 내용을 표시하는 텍스트 뷰가 포함되어 있습니다.

한 번 시뮬레이터를 통해 살펴볼게요.

Share Extension을 만들었다면 위와 같이 실행시킬 타겟을 정할 수 있습니다.

방금 만든 Share Extension을 선택해서 실행을 하면

위와 같이 어느 앱에서 시작을 할건지 선택할 수 있습니다.

저는 Safari를 통해 시작을 해볼게요.

apple 홈페이지에 들어가서 공유 아이콘을 눌러봤습니다.

그러면 위와 같이 제가 실행한 프로젝트가 나타나게 됩니다.

그리고 앱을 터치하게 되면 오른쪽 같이 기본적인 ComposeView가 나타나게 됩니다.

그리고 위에서 설명한 것처럼 기본적인 TextView가 포함되어 있는 것을 볼 수 있습니다.

사용자가 Post를 선택하면, Share Extension은 TextView의 내용(필요한 경우 첨부 파일 포함)을 유효성 검사를 하고 NSExtensionContext의 completeRequestReturningItems:expirationHandler:completion: 메서드를 호출합니다.

코드를 보면 다음과 같습니다.

  override func didSelectPost() {
        // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
    
        // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
        self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }



UI 설계


일반적으로 단순한 작업을 지나치게 복잡하게 만들지 않는 것이 좋지만, 사용자가 기대하는 옵션을 제공하는 것도 중요합니다.

예를 들어, 사용자가 간단한 메모를 쉽게 게시할 수 있도록 하면서도 첨부 파일을 미리 보거나, 게시물에 태그를 추가하거나, 개인적보 설정 또는 사용할 앨범과 같은 세부 사항을 지정할 수 있도록 지원하는 것이 좋습니다.

추가로 표시해야 할 콘텐츠가 있는 경우, Auto Layout 제약 조건을 사용하여 적절하게 뷰의 높이를 조정할 수 있습니다. Auto Layout을 사용하지 않는 경우, UIViewController의 preferredContentSize 속성을 사용하여 뷰의 새로운 높이를 지정할 수 있습니다.

콘텐츠 게시하기


Share Extension의 주요 목적은 사용자가 콘텐츠를 게시하도록 돕는 것입니다.

사용자가 Share Extension에서 Post 또는 Send 버튼을 선택하면, 시스템이 제공하는 애니메이션이 작업이 처리 중임을 피드백으로 제공합니다. 이후 시스템은 SLComposeServiceViewController 클래스의 didSelectPost 메서드를 호출합니다.

사용자가 게시를 취소하거나, 다른 이유로 게시가 취소되면, 시스템은 피드백 애니메이션이 완료된 후 Share Extension의 didSelectCancel 메서드를 호출합니다. 이 메서드 구현이 의미가 있다면 Extension Context의 완료 작업을 커스터마이징하면 됩니다.

입력 유효성 검사


Share Extension은 사용자의 콘텐츠를 게시하기 전에 유효성을 검사해야 합니다.

Compose View에서 사용자의 콘텐츠에 대해 즉각적인 피드백을 제공하면 좋습니다.

예를 들어 특정 조건이 완료 되어야지만 Post 버튼을 활성화하거나, 현재 문자 수를 표시하는 방법 등이 있습니다.

기본 Compose View Controller를 사용하는 경우(SLComposeServiceViewController를 상속받는 클래스를 사용하는 경우), 사용자가 현재 입력한 콘텐츠의 유효성을 확인하려면 isContentValid 메서드를 구현하면 됩니다. 사용자가 표준 Compose View에서 텍스트를 변경하면 시스템이 isContentValid 메서드를 구현하므로, 원하는 조건일 때 Post 버튼을 활성화 할 수 있습니다.

override func isContentValid() -> Bool {
    // Do validation of contentText and/or NSExtensionContext attachments here
    return true
}

Share Extension에서 콘텐츠를 사용자 정의 방식으로 검증해야 하는 경우, validateContent 메서드 구현에서 검증 작업을 수행하세요. 결과에 따라 isContentValid 메서드에서 적절한 값을 반환할 수 있습니다.

예를 들어, 사용자가 업로드하기 전에 asset을 축소해야 한다면, 축소 작업이 완료될 때까지 Post 버튼을 활성화하지 않아야 합니다. 축소 작업이 완료되었는지 확인하려면 isContentValid 메서드에서 validateContent를 호출하고 적절한 결과를 반환하세요.

기본 Compose View Controller(SLComposeServiceViewController를 상속하는 ShareViewController)를 사용하면 위와 같이 validateContent() 메서드를 사용할 수 있습니다.

예를 들면 위와 같이 사용할 수 있겠죠?

사용자가 콘텐츠를 수정하면 isContentValid() 메서드가 호출되고 그 때마다 유효성 검사를 하여 Post 버튼을 활성화할지 결정할 수 있습니다.

콘텐츠 미리보기(iOS 전용)


사용자가 선택한 콘텐츠를 미리 볼 수 있도록, 시스템에서 제공하는 Compose View Controller(SLComposeServiceViewController)는 기본적으로 사진, 비디오, 웹페이지와 같은 표준 데이터 유형의 미리보기를 자동으로 표시할 수 있습니다. 비표준 데이터 유형을 처리해야 하는 경우, Share Extension에서 loadPreviewView 메서드를 구현하여 사용자 정의 미리보기 뷰를 제공할 수 있습니다.

iOS에서는 preview를 공유 UI의 텍스트 편집 영역 옆에 표시합니다. 가능한 한 작은 preview를 만들어 텍스트 영역이 지나치게 작아지지 않도록 해야 합니다. Share UI는 높이를 확장할 수 있지만, 지나치게 높아지면 콘텐츠가 키보드 뒤에 표시되어 사용자가 스크롤해야 할 수 있습니다.

만약 다음과같이

override func loadPreviewView() -> UIView! {
    return nil
}

를 설정해주면

위와 같이 아까 있던 Preview가 없어진 것을 볼 수 있습니다.


Configuring a Post(iOS 전용)


SLComposeSheetConfigurationItem 클래스는 iOS Share Extension이 사용자가 게시를 구성할 수 있도록 항목 목록을 쉽게 제공할 수 있게 합니다.

예를 들어, 사용자가 게시할 계정을 선택하거나, 개인정보 설정을 지정하거나, 사용자 정의 텍스트 입력(예: Twitter 멘션)을 자동 완성하도록 도울 수 있습니다. 기본적으로 표준 Compose View Controller(SLComposeServiceViewController)는 공유 UI 하단에 구성 항목을 테이블 뷰로 표시합니다.

이 부분은 디스코드 앱을 보면 쉽게 이해할 수 있을 것 같아요.

위와 같이 Share in, Share to 부분이 configurationItems 메서드를 통해 만들어진 부분인 것을 알 수 있습니다.

Share Extension은 SLComposeServiceViewController 클래스의 configurationItems 메서드를 사용해 사용자가 설정할 수 있는 구성 유형을 식별하는 SLComposeSheetConfigurationItem 인스턴스 배열을 반환합니다. 사용자가 configuration item을 탭하면, 해당 항목은 커스텀 뷰 컨트롤러를 표시하여 사용자가 구성을 수행할 수 있습니다.

커스텀 Configuration view controller를 표시하려면, 일반적으로 SLComposeSheetConfigurationItemTapHandler 타입의 블록(여기서 뷰 컨트롤러를 생성)을 정의한 다음, pushConfigurationViewController:를 호출해 이를 표시합니다. 표준 Compose View Controller는 UINavigationController 인스턴스를 사용하여 구성 뷰 컨트롤러를 표시하므로, 사용자는 Back 버튼을 눌러 공유 UI로 돌아갈 수 있습니다. 또한, 다른 사용자 작업에 응답하여 popConfigurationViewController를 호출하여 공유 UI로 돌아갈 수 있습니다.

말로는 너무 어려우니…

한 번 위의 이미지와 비슷한 간단한 예제를 만들어 보겠습니다.

import UIKit
import Social

class ShareViewController: SLComposeServiceViewController {
    private let items = ["Family", "Friends", "Study"]
    let shareInItem = SLComposeSheetConfigurationItem()
    
    override func isContentValid() -> Bool {
        return true
    }
    
    override func didSelectPost() {
        self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }
    
    override func configurationItems() -> [Any]! {
        // 1. Compose View 밑에 tableview로 나타날 Item 정의
        shareInItem?.title = "Share in"
        shareInItem?.value = items[0]
        shareInItem?.tapHandler = { [weak self] in // 2. tapHandler에서 커스텀 뷰 호출
            guard let self = self else { return }
            self.showSelectionViewController()
        }
        
        // 3. Item 리턴
        return [shareInItem].compactMap { $0 }
    }
    
    private func showSelectionViewController() {
        // 선택을 위한 커스텀 뷰 컨트롤러
        let selectionVC = SelectionViewController()
        selectionVC.selectedItem = shareInItem!.value
        selectionVC.items = items
        selectionVC.onSelected = { [weak self] selectedItem in
            self?.shareInItem?.value = selectedItem
            self?.popConfigurationViewController() // 이전 화면으로 돌아감
        }
        
        // 구성 뷰 컨트롤러 표시
        pushConfigurationViewController(selectionVC)
    }
    
}

// 선택을 위한 CustomView 작성
class SelectionViewController: UIViewController {
    var selectedItem: String = ""
    var onSelected: ((String) -> Void)?
    var items: [String] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tableView = UITableView(frame: .zero, style: .plain)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.backgroundColor = .clear
        tableView.dataSource = self
        tableView.delegate = self
        view.addSubview(tableView)
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
    }
}

extension SelectionViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
        cell.backgroundColor = .clear
        cell.textLabel?.text = items[indexPath.row]
        cell.accessoryType = items[indexPath.row] == selectedItem ? .checkmark : .none
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let selected = items[indexPath.row]
        onSelected?(selected)
    }
}

순서는 대략적으로 다음과 같습니다.
(아래 순서는 주석의 순서가 아닙니다!)

  1. SLComposeSheetConfigurationItem를 통해 Item을 만들어줍니다.
    만들어주기만 한다면 ComposeView 아래에 셀의 형태로 아이템이 나타나게 됩니다.
  2. Item을 선택하기 위한 커스텀 뷰를 만들어줍니다.
    여기선 UITableView를 통해 만들어 주었습니다.
  3. tapHandler 메서드에서 ViewController를 띄워주면 되는데, 커스텀 뷰 컨트롤러에 대한 설정을 위해 showSelectionViewController() 함수를 만들어서 호출해줍니다.
  4. showSelectionViewController() 함수에서 pushConfigurationViewController(_:) 메서드를 호출하여 뷰 컨트롤러를 호출해주면 됩니다.
  5. 아이템을 선택했다면 popConfigurationViewController() 메서드로 되돌아가준다.

다시 정리하면

  • configurationItems() 메서드에서 SLComposeSheetConfigurationItem 들을 만들어주어 배열로 리턴한다!
    ⇒ ComposeView의 하단에 셀의 형태로 나타난다.
  • Item의 tapHandler 를 설정해주어 커스텀 뷰를 띄울 수 있도록 한다.
    이 때 뷰는 pushConfigurationViewController(_:) 를 통해 띄워야 한다.
  • 되돌알 갈 땐 popConfigurationViewController() 를 이용한다.

로 간단히 말할 수 있을 것 같습니다.

그러면 위와 같은 결과가 나오게 됩니다!


여기까지 해서 1편을 마치도록 하겠습니다.

커스텀 뷰로 ShareExtenision을 사용하는 방법과 데이터를 어떻게 이용하는지는 다음 2편에서 계속…!

0개의 댓글