[ios-Swift] Share Extension 사용하기
이번 글에서는 Share Extension을 커스텀 뷰로 꾸며 사용하는 글입니다.
1. 설정
ShareExtensionExample 프로젝트를 생성한 후
File -> Target -> Share Extension을 추가해 주도록 합니다.
Extension -> App 간 데이터 사용은 동일한 그룹을 사용해야 데이터를 공유할 수 있는 것으로 알고 있어 그룹 설정을 먼저 하도록 하겠습니다.
프로젝트의 Capacapabilities -> App Grouops를 선택 후 그룹을 설정해 주도록 합니다.
그룹명은 group.Bundle identifier.Share를 사용했습니다.
TARGETS -> 프로젝트에서 추가한 그룹을 셰어 익스텐션에서 체크해주도록 합니다.
이제 그룹 설정이 완료되었습니다.
이제 우리는 커스텀 뷰에서 어떤 데이터를 받을 것 인지를 설정해야 하는데 Extension의 info.plist 에서 설정하겠습니다.
NSExtensionActivationRule -> Dictionary로 변경 후 키와 밸류를 설정합니다.
NSExtensionActivationSupportsWebURLWithMaxCount : 허용할 HTTP URL 최대 수
NSExtensionActivationSupportsImageWithMaxCount : 허용할 IMAGE 최대 수
NSExtensionActivationRule의 키 값은 이곳에서 확인해 보시면 됩니다.
2. 소스
MainInterface.stroryboard에서 오브젝트들을 추가 및 ShareViewController에서 SLComposeServiceViewController -> UIViewController로 변경했습니다.
import UIKit
import Social
class ShareViewController: UIViewController {
override func viewDidLoad() {
}
}
이제 앱이 아닌 익스텐션을 실행시켜 사파리 브라우저를 실행시키면 아래와 같이 커스텀 뷰가 나타나는 것을 확인할 수 있습니다.
커스텀 뷰의 버튼 및 동작을 추가하도록 하겠습니다.
import UIKit
import Social
import MobileCoreServices
class ShareViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
let extensionItems = extensionContext?.inputItems as! [NSExtensionItem]
for extensionItem in extensionItems {
if let itemProviders = extensionItem.attachments as? [NSItemProvider] {
for itemProvider in itemProviders {
// 해당 객체가 있는지 식별
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
itemProvider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil, completionHandler: { result, error in
var image: UIImage?
if result is UIImage {
image = result as? UIImage
}
if result is URL {
let data = try? Data(contentsOf: result as! URL)
image = UIImage(data: data!)!
}
if result is Data {
image = UIImage(data: result as! Data)!
}
DispatchQueue.main.async {
if let image = image {
self.imageView.image = image
}
}
})
}
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
itemProvider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { result, error in
let data = NSData.init(contentsOf:result as! URL)
DispatchQueue.main.async {
if let urlStr = result {
self.textView.text = "\(urlStr)"
}
}
})
}
}
}
}
}
}
kUTTypeImage 외 다른 타입도 많으니 필요하신 걸 찾아서 변경 후 사용하면 됩니다.
kUTTypeImage as string는 "public.image"와 동일합니다.
// 익스텐션의 버튼 이벤트
@IBAction func btnSend(_ sender: UIButton) {
// 처리후 종료
if let userDefaults = UserDefaults(suiteName: "위에서 설정한 그룹") {
if let image = imageView.image {
userDefaults.set(image.pngData(), forKey: "image")
}
if let text = textView.text {
userDefaults.set(text, forKey: "text")
}
}
self.hideExtensionWithCompletionHandler(completion: { _ in
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
})
}
@IBAction func btnDismiss(_ sender: UIButton) {
self.hideExtensionWithCompletionHandler(completion: { _ in
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
})
}
func hideExtensionWithCompletionHandler(completion: @escaping (Bool) -> Void) {
UIView.animate(withDuration: 0.3, animations: {
self.navigationController?.view.transform = CGAffineTransform(translationX: 0, y:self.navigationController!.view.frame.size.height)
}, completion: completion)
}
익스텐션 뷰를 닫으려고 해도 그냥 dismiss를 사용하면 뷰가 사라지지 않아 hideExtensionWithCompletionHandler를 사용하도록 합니다.
//앱 뷰 컨트롤러
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
if let userDefaults = UserDefaults(suiteName: "group.com.fostep.Share") {
if let data = userDefaults.data(forKey: "image") {
imageView.image = UIImage(data: data)
}
if let text = userDefaults.string(forKey: "text") {
textLabel.text = text
}
}
}
익스텐션과 앱과의 데이터 전송은 UserDefaults를 사용해 저장 후 앱을 실행 UserDefaults를 확인하는 방식을 사용합니다.
꼭 UserDefaults를 사용하지 않아도 되며 익스텐션에서 서버에 전송 후 받는 방법을 사용하셔도 될 겁니다.