iOS) Share and Action Extension 사용해보기(2) - Action Extension
Creating an iOS 10 Action Extension
위의 튜토리얼을 큰틀로 잡고 따라가지만 코드는 수정하였습니다
1️⃣ Action Extension 추가
- [File] > [New] > [Target] 에서 Action Extension 을 선택해준다.
2️⃣ Action Extension 사용
- Action extension 템플릿은 principal view controller class (ActionViewController라고 함) 과 Info.plist 파일 및 인터페이스 파일(즉, 스토리보드 또는 xib 파일)에 대한 기본 소스 파일을 제공합니다.
추가했으니 잘 나오는지 확인해봐야겠죠?
- 사진앱에서 acivity view 를 살펴보면 맨 아래에 ActionExtension 이 추가된 것을 볼 수 있다.
선택해볼까요?
- 기본적으로 Action Extension 은 다음과 같이 이미지를 가져오도록 구현되어 있습니다.
🪓 activity view 띄우기
우리는 해당 Action Extension 을 선택하면 text view 의 텍스트를 바꿔주는 Action 을 만들어볼 예정입니다.
- UITextView 를 추가해주고 텍스트를 “변경 전” 으로 설정한다. activity view 를 띄울 버튼도 추가한다.
- 버튼에 다음과 같은 화면전환 코드를 추가해준다.
// ✅ init(activityItems:applicationActivities:)
let activityViewController = UIActivityViewController(activityItems: [textView.text ?? ""], applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
✅ init(activityItems:applicationActivities:)
activityItems
activity 를 수행할 data 객체의 배열. 예를 들어, 데이터는 현재 선택된 콘텐츠를 나타내는 하나 이상의 문자열 또는 이미지 개체로 구성될 수 있다.
실제 데이터 객체 대신 UIActivityItemProvider 개체와 같이 UIActivityItemSource 프로토콜을 채택하는 개체일 수 있다.
이 배열은 nil 이 아니여야 하며 하나의 객체를 반드시 포함해야 한다.
applicationActivities
애플리케이션이 지원하는 커스텀 서비스를 나타내는 UIActivity 객체의 배열. 이 파라미터는 nil 일 수 있다.
activity view 가 정상적으로 등장하는지 확인해볼까요?
- 해당 액션을 가진 버튼을 탭하게되면 아래처럼 activity view 가 등장하고 추가한 ActionExtension 가 나온다. (현재 context 와 관련된 extension 이 등장한다. 텍스트 → Copy)
3️⃣ MainInterface 변경
우리는 해당 Action Extension 을 선택하면 text view 의 텍스트를 바꿔주는 Action 을 만들어볼 예정입니다.
- 아래의 기본으로 있는 UIImageView 를 삭제한다. UITextView 를 추가해고 오토레이아웃을 잡아준다.
- 인터페이스 빌더에서 Behavior 의 Editable 을 체크 해제한다.
4️⃣ host app 에서 컨텐츠 받기
Action Extension 은 NSExtensionContext 메서드를 사용하여 host app 의 컨텐츠를 extesion 으로 보낼 수 있습니다.
- Action Extension 에서 기존의 “이미지 뷰에 이미지를 추가하는 코드” 대신 다음의 코드로 수정해보자.
// ✅ extensionContext : Returns the extension context of the view controller. 자료형 NSExtensionContext
// ✅ inputitems : The list of input NSExtensionItem objects associated with the context. 자료형 NSExtensionItem
// ✅ attachments : An optional array of media data associated with the extension item. 자료형 [NSItemProvider]?
guard let textItem = self.✅extensionContext?.✅inputItems.first as? NSExtensionItem,
let textItemProvider = textItem.✅attachments?.first else { return }
// ✅
if textItemProvider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
// ✅
textItemProvider.loadItem(forTypeIdentifier: UTType.text.identifier, options: nil) { item, error in
guard let text = item as? String else { return }
DispatchQueue.main.async {
self.myTextView.text = text
}
}
}
✅ hasItemConformingToTypeIdentifier(_:)
item provider 에 지정된 UIT(파일 및 데이터 전송을 위한 유형 식별자) 를 준수하는 데이터 표현이 포함되어 있는지 여부를 나타내는 Boolean 값 리턴.
✅ loadItem(forTypeIdentifier:options:completionHandler:)
item 의 데이터를 가져오고 필요할 경우 지정된 유형으로 강제 변환.
- host app 의 text view 의 “변경 전” 이라는 텍스트를 extension 으로 가져온 모습
5️⃣ host app 으로 컨텐츠 보내기
Action Extension 은 NSExtensionContext 메서드를 사용하여 사용자의 편집 내용을 host app 으로 보낼 수 있습니다.
- Action Extension 을 처음에 만들 때 부터 이미 기본적으로
Done
이라는 버튼이 연결이 되어 있습니다.
@IBAction func done() {
// Return any edited content to the host app.
// This template doesn't do anything, so we just echo the passed in items.
self.extensionContext!.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil)
}
텍스트를 수정해서 host app 으로 보낼 것이기 때문에 수정해보겠습니다.
- 아래처럼 extension context 부터 접근해서 값을 가져왔기 때문에 내보낼때는 거꾸로 접근해야합니다!
// ✅ extensionContext 자료형 : NSExtensionContext
// ✅ inputitems 자료형 : NSExtensionItem
// ✅ attachments 자료형 : [NSProvider]?
guard let textItem = self.✅extensionContext?.✅inputItems.first as? NSExtensionItem,
let textItemProvider = textItem.✅attachments?.first else { return }
extension context > input items(extension item) > attachments(item provider)
- item provider > extension item > extension context
@IBAction func done() {
let returnItemProvider = NSItemProvider(item: myTextView.text as NSSecureCoding?, typeIdentifier: UTType.text.identifier)
let returnItem = NSExtensionItem()
returnItem.attachments = [returnItemProvider]
// ✅
self.extensionContext?.completeRequest(returningItems: [returnItem], completionHandler: nil)
}
✅ completeRequest(returningItems:completionHandler:)
host app 에 result itme 배열로 app extension 요청을 완료하도록 지시.
보내줬으니 host app 에서 받아야 겠죠?
6️⃣ host app 에서 받은 컨텐츠 활용
- 앞서 extension item 에서 컨텐츠를 가져왔듯이 completionWithItemsHandler 의 파라미터 items(
NSExtensionItem
) 을 사용해서 값을 가져오면 된다.
// ...
// UTType 를 사용하기 위해서
import UniformTypeIdentifiers
// ...
@objc private func presentToActivityVC() {
let activityViewController = UIActivityViewController(activityItems: [textView.text ?? ""], applicationActivities: nil)
// ✅ 기본제공 서비스 중 사용하지 않을 ActivityType 제거할 수 있다.(에어드랍 제거)
// activityViewController.excludedActivityTypes = [UIActivity.ActivityType.airDrop]
// ✅ activity 가 완료되거나 activity view controller 가 해제되면 completion block 이 실행.
activityViewController.completionWithItemsHandler = { activity, success, items, error in
if success {
guard let textItem = items?.first as? NSExtensionItem, let itemProvider = textItem.attachments?.first else { return }
if itemProvider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
itemProvider.loadItem(forTypeIdentifier: UTType.text.identifier, options: nil) { item, error in
DispatchQueue.main.async {
self.textView.text = item as? String
}
}
}
} else {
print(error)
}
}
self.present(activityViewController, animated: true, completion: nil)
}
✅ UIActivityViewController.CompletionWithItemsHandler
Delclaration
typealias CompletionWithItemsHandler = ([UIActivity](https://developer.apple.com/documentation/uikit/uiactivity).[ActivityType](https://developer.apple.com/documentation/uikit/uiactivity/activitytype)?, [Bool](https://developer.apple.com/documentation/swift/bool), [Any]?, [Error](https://developer.apple.com/documentation/swift/error)?) -> [Void](https://developer.apple.com/documentation/swift/void)
activity 가 완료되거나 activity view controller 가 해제되면 completion block 이 실행된다. 이 block 을 사용해서 최종 코드를 실행할 수 있다. 매개변수는 다음과 같다.
activityType
사용자가 선택한 service 의 타입. 커스텀 서비스의 경우, UIActivity 객체의 activityType 메서드에서 반환된 값. 시스템 정의 activities 의 경우, UIActivity 의 내장 Activity Type 중 하나.(앞서 사용하지 않을 ActivityType 에서 살펴본 타입들 의미한다.)
completed
서비스가 수행된 경우 true
. 수행되지 않은 경우 false
. 이 매개변수는 사용자가 서비스를 선택하지 않고 뷰 컨트롤러를 닫을 때도 false
이다.
returnedItems
NSExtensionItem
객체 배열. 이 배열의 item 을 활용해서 original data 에서 extension 에 의해 변경된 사항을 가져온다. 수정된 항목이 없으면 nil
이다.
activityError
activity 가 완료되지 않은 경우 error
객체. activity 가 완료된 경우 nil
.
결과
🪓 Declaring the Supported Content Type
진행하면서 다음과 같은 워닝이 뜨는것을 볼 수 있었을 거에요..
앱을 App Store에 제출하기 전에 TRUEPREDICATE의 모든 사용을 특정 술어 문 또는 NSExtensionActivationRule 키로 대체해야 합니다. 포함하는 앱의 확장 프로그램에 TRUEPREDICATE가 포함되어 있으면 앱이 거부됩니다.
info.plist 를 보면 진짜 TRUEPREDICATE 로 된 부분이 있네요..!
NSExtensionActivationRule
NSExtensionActivationRule 에 해당하는 value 를 넣어주면 됩니다! 저희는 text 를 다뤘기 때문에 NSExtensionActivationSupportsText
를 사용해주면 적합해요!
NSExtensionActivationSupportsText
: app extension 이 text 를 지원하는지의 여부.
NSExtensionActivationRule
을 Dictionary
으로 타입을 바꾸고 key-value 를 추가해주면 워닝이 없어집니다!
🪓 Changing the Extension Display Name
액션의 이름을 바꾸고싶다면 아래의 Bundle display name
에서 바꿀 수 있습니다!