개요
이 글은 Share Extension을 통해 외부에서 앱으로 데이터를 공유하는 방법에 대해 알아본다
몇 가지 과정들을 나눠서 진행되는데, 위의 블로그를 참고했다
과정
- Share Extension 추가
- Activation Rule 설정
- ShareViewController에서 extensionContext를 통해 데이터 처리
- UIHostingController을 통해 SwiftUI View 사용
- 공유 데이터 설정 후, 앱과 데이터 공유
Share Extension 추가
Share Extension을 프로젝트에서 추가한다

위의 블로그에서 언급한대로, 애플은 Share Extension 기능과 관련해서 업데이트를 안한지 오래된 것 같다
공식문서는 옛날 형태로 존재하고, 기본적으로 SwiftUI를 사용하는 것이 아닌 UIKit을 사용한다
그렇지만 해당 글에서는 SwiftUI View를 Hosting하여 Share 시, 데이터 입력을 받도록 처리할 것이다
생성을 수행하면 기본적으로 다음과 같이 파일들이 생성된다

Activation Rule 설정
Extension을 추가하고, 기본적으로 Info.plist를 보면 다음과 같다
ActivationRule은 쉽게말해 우리들의 앱이 어떤 경우에 Share로 이용될 수 있는지를 설정하는 것이다
기본적으로 TRUEPREDICATE가 설정되어있는데, 이는 항상 공유 시 앱이 사용될 수 있음을 의미한다
실제 배포 시엔, 공유가 사용되는 경우를 아래와 같이 지정해줘야 한다
만약 간단하게 String에 대해서만 Share Extension을 사용하려면 다음과 같이 수정한다
- NSExtensionActivationRule의 String을 dictionary로 변경
- NSExtensionActivationRule dictionary의 key 설정

기본적으로 지원하는 키들은 위와 같이 존재한다
ShareViewController
이번엔 Share Extension에서 사용되는 뷰를 설정하기 위한 과정에 대해 알아본다
우선 이전 Info.plist에서 기본으로 설정된 스토리보드를 지우고, ViewController를 설정한다
ViewController 설정은 다음과 같이 NSExtensionPrincipalClass를 키 값으로 설정하고, <NameOfTheShareTarget>.ShareViewController를 값으로 설정한다
만약 현재 Share Extension 이름이 ShareContent라면 다음과 같이 Info.plist가 수정될 것이다

또한 다음과 같이 우선 ViewController를 가장 기본적인 형태로 바꾼다
import UIKit
class ShareViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Ensure access to extensionItem and itemProvider
guard
let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = extensionItem.attachments?.first else {
close()
return
}
}
/// Close the Share Extension
func close() {
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}
NSExtensionContext는 Share Extension을 통해 공유하려는 데이터를 얻어오는 데 사용된다
extension UIViewController : NSExtensionRequestHandling {
@available(iOS 8.0, *)
open var extensionContext: NSExtensionContext? { get }
}
이에 대해 좀 더 자세히 알아보기 위해 UIViewController 코드를 살펴보면 NSExtensionContext인 extensionContext가 존재한다

공유 데이터는 inputItems로 부터 얻을 수 있는데, 이 데이터를 얻어서 SwiftUI 뷰를 통해 앱으로 전달하는 과정을 정리하면 다음과 같다
import UIKit
import UniformTypeIdentifiers
class ShareViewController: UIViewController {
override func viewDidLoad() {
/// Ensure access to extensionItem and itemProvider
guard
let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = extensionItem.attachments?.first else {
close()
return
}
/// Check type identifier
let textDataType = UTType.plainText.identifier
guard itemProvider.hasItemConformingToTypeIdentifier(textDataType) else {
close()
return
}
/// Load the item from itemProvider
itemProvider.loadItem(forTypeIdentifier: textDataType , options: nil) { (providedText, error) in
if let _ = error {
self.close()
return
}
guard let text = providedText as? String else {
self.close()
return
}
print(text)
}
/// Close the Share Extension
func close() {
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}
SwiftUI를 통한 데이터 입력 뷰 구현
SwiftUI의 UIHostingController(rootView:)를 사용하면, View를 UIKit에서 사용할 수 있다
입력받은 데이터에 처리과정을 거친 text를 다음과 같이 SwiftUI의 뷰에 전달한다
...
print(text)
DispatchQueue.main.async {
/// host the SwiftU view
let contentView = UIHostingController(rootView: ShareExtensionView(text: text))
self.addChild(contentView)
self.view.addSubview(contentView.view)
/// set up constraints
contentView.view.translatesAutoresizingMaskIntoConstraints = false
contentView.view.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
contentView.view.bottomAnchor.constraint (equalTo: self.view.bottomAnchor).isActive = true
contentView.view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
contentView.view.rightAnchor.constraint (equalTo: self.view.rightAnchor).isActive = true
}
// add observer for close notification
NotificationCenter.default.addObserver(forName: NSNotification.Name("close"), object: nil, queue: nil) { _ in
DispatchQueue.main.async {
self.close()
}
}
}
...
}
struct ShareExtensionView: View {
@State var text: String = ""
var body: some View {
VStack {
Text(text)
Button("close") {
close()
}
}
}
func close() {
NotificationCenter.default.post(name: NSNotification.Name("close"), object: nil)
}
}
이 둘(View와 ViewController)의 상호작용은 NotificationCenter를 통해 수행한다

앱과 데이터 공유
그렇다면 Share Extension에서 앱과 데이터는 어떻게 공유할 수 있는가?
Widget처럼 AppGroup을 통해 데이터 공유 메커니즘을 통해 앱에 데이터를 전달하면 된다
이때 가장 간단한 방법은 App Groups과 SwiftData를 이용하는 것일 것이다
우선 앱과 Share Extnesion에 App Groups를 추가하여 데이터 공유의 장을 마련한다

이후 앱에서 모델 컨테이너를 정의하고, SwiftData 모델을 정의하여, 확장과 공유한다
import SwiftUI
import SwiftData
@main
struct GCG_DictApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([ShareContentDTO.self])
do {
return try ModelContainer(for: schema)
} catch {
fatalError("Failed to create ModelContainer: \\(error)")
}
}()
var body: some Scene {
WindowGroup {
HomeView()
.modelContainer(sharedModelContainer)
}
}
}

마찬가지로 Share Extension에서도 컨테이너를 만들고, Hosting하는 SwiftUI 뷰에 전달하면 앱으로 데이터를 저장시킬 수 있다
class ShareViewController: UIViewController {
private var modelContainer: ModelContainer!
private func setupModelContainer() {
let schema = Schema([ShareContentDTO.self])
do {
self.modelContainer = try ModelContainer(for: schema)
} catch {
fatalError("Failed to create ModelContainer: \\(error)")
}
}
override func viewDidLoad() {
setupModelContainer()
...
DispatchQueue.main.async {
let shareView = ShareExtensionView(text: text)
.modelContainer(self.modelContainer)
/// host the SwiftU view
let contentView = UIHostingController(rootView: shareView)
self.addChild(contentView)
self.view.addSubview(contentView.view)
...
struct ShareExtensionView: View {
@Environment(\\.modelContext) var modelContext
let text: String
var body: some View {
VStack {
Text(text)
Button("추가") {
modelContext.insert(ShareContentDTO(content: text, createdAt: .now))
do {
try modelContext.save()
} catch {
print("Failed to save: \\(error)")
close()
}
}
Button("close") {
close()
}
}
}
...
이후 문자열을 선택 후, Share를 누르면 ShareExtensionView가 나타난다
여기서 추가 버튼을 클릭 시, 앱과 공유되는 SwiftData 컨테이너에 데이터를 저장하고
우측 사진과 같이 앱에서도 해당 데이터를 확인할 수 있다

'iOS+' 카테고리의 다른 글
On-Device AI with CoreML(feat. DeepSeek-R1) (0) | 2025.02.09 |
---|---|
인증서와 프로파일 (0) | 2025.02.02 |
#Preview를 위한 환경 구분 방법 (2) | 2024.12.29 |
Xcode Cloud와 Sparkle framework (2) | 2024.09.29 |
Objective-C (2) | 2024.09.22 |