project/Kuring(공지알림)

[SwiftUI] UIActivityViewController를 SwiftUI로

lgvv 2022. 5. 31. 13:14

UIActivityViewController를 SwiftUI로

히스토리

  • 2022.05.31
    • 쿠링 사이드에서 공유하기 기능을 SwiftUI에서 사용하기 위한 포스팅
  • 2024.12.11
    • 해당 포스팅보다 더 좋은 방법을 발견하여 링크 추가

 

개선한 방법

 

해당 포스팅보다 더 좋은 방법을 발견하여 링크 추가

 

이번 제목도 UIKit스러움

이전에도 말했지만, 프로젝트에서 UIKit을 SwiftUI로 코드롤 전환하고 있는데 여기서 만난 문제를 해결하고자 내가 구글링한 단어 그대로 작성했다.

 

🟠 ActivityItem

import UIKit

/// `activitySheet` modifier 를 통해 `ActivityView`를 띄울 때 사용하는 activity
struct ActivityItem {
    var items: [Any]
    var activities: [UIActivity]
    var excludedTypes: [UIActivity.ActivityType]

    /// - Parameters:
    ///   - items: `UIActivityViewController` 를 통해 공유할 아이템
    ///   - activities: 시트에 포함시키고자 하는 커스텀 `UIActivity`
    init(
        items: Any...,
        activities: [UIActivity] = [],
        excludedTypes: [UIActivity.ActivityType] = []
    ) {
        self.items = items
        self.activities = activities
        self.excludedTypes = excludedTypes
    }
}

🟠 ActivityView

import SwiftUI
import LinkPresentation
import CoreServices

struct ActivityView: UIViewControllerRepresentable {

    @Binding var item: ActivityItem?
    var permittedArrowDirections: UIPopoverArrowDirection
    var completion: UIActivityViewController.CompletionWithItemsHandler?

    init(
        item: Binding<ActivityItem?>,
        permittedArrowDirections: UIPopoverArrowDirection,
        onComplete: UIActivityViewController.CompletionWithItemsHandler? = nil
    ) {
        _item = item
        self.permittedArrowDirections = permittedArrowDirections
        self.completion = onComplete
    }

    func makeUIViewController(context: Context) -> ActivityViewControllerWrapper {
        ActivityViewControllerWrapper(
            item: $item,
            permittedArrowDirections: permittedArrowDirections,
            completion: completion
        )
    }

    func updateUIViewController(_ controller: ActivityViewControllerWrapper, context: Context) {
        controller.item = $item
        controller.completion = completion
        controller.updateState()
    }

}

final class ActivityViewControllerWrapper: UIViewController {

    var item: Binding<ActivityItem?>
    var permittedArrowDirections: UIPopoverArrowDirection
    var completion: UIActivityViewController.CompletionWithItemsHandler?

    init(
        item: Binding<ActivityItem?>,
        permittedArrowDirections: UIPopoverArrowDirection,
        completion: UIActivityViewController.CompletionWithItemsHandler?)
    {
        self.item = item
        self.permittedArrowDirections = permittedArrowDirections
        self.completion = completion
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func didMove(toParent parent: UIViewController?) {
        super.didMove(toParent: parent)
        updateState()
    }

    func updateState() {
        let isActivityPresented = presentedViewController != nil

        if item.wrappedValue != nil {
            if !isActivityPresented {
                let controller = UIActivityViewController(
                    activityItems: item.wrappedValue?.items ?? [],
                    applicationActivities: item.wrappedValue?.activities
                )

                controller.excludedActivityTypes = item.wrappedValue?.excludedTypes
                controller.popoverPresentationController?.permittedArrowDirections = permittedArrowDirections
                controller.popoverPresentationController?.sourceView = view

                controller.completionWithItemsHandler = { [weak self] (activityType, success, items, error) in
                    self?.item.wrappedValue = nil
                    self?.completion?(activityType, success, items, error)
                }

                present(controller, animated: true, completion: nil)
            }
        }
    }

}

아래 코드도 꼭 넣어주세요!

import SwiftUI

extension View {
    /**
     관련`ActivityItem`이 존재할 때, 해당하는 activity sheet를 보여줍니다.

     - Parameters:
       - item: activity에 사용할 아이템입니다.
       - onComplete: sheet가 dimiss되었을 때, 결과과 호출됩니다.
     */
    func activitySheet(_ item: Binding<ActivityItem?>, permittedArrowDirections: UIPopoverArrowDirection = .any, onComplete: UIActivityViewController.CompletionWithItemsHandler? = nil) -> some View {
        background(ActivityView(item: item, permittedArrowDirections: permittedArrowDirections, onComplete: onComplete))
    }
}

✅ 사용법 (Usage)

import SwiftUI

struct ActivityTestView: View {
    @State private var item: ActivityItem?

    var body: some View {
        Button {
            item = ActivityItem(
                items: "This will be shared"
            )
        } label: {
            Text("Share")
        }
        .activitySheet($item)
    }
}

결과물들

마지막으로 present된 것이 중간 그리고 맨 윗부분까지 올라오는 것을 볼 수 있다.