project/개발 업무

iOS 공유하기 LPLinkMetadata (UIKit, SwiftUI) 디자인패턴 적용

lgvv 2021. 6. 7. 02:16

iOS 공유하기 LPLinkMetadata (UIKit, SwiftUI) 디자인패턴 적용

iOS 시스템 공유하기 기능을 개발

SwiftUI에서 지원하는 ShareLink도 있지만, 더 범용성있게 사용하기 위해 해당 형태로 구현


예제코드

ArchitectureExample.zip
0.50MB

 

히스토리

  • 2021.06.17.: 초기버전
    • Swift 5, Xcode 12, iOS minimum Target 9.0
  • 2024.12.11: 개선버전
    • Swift 5.10, Xcode 16.1, minimum Target 13.0
    • 빌더 패턴 적용
    • Mixin and Trait 개념 적용
    • 모듈화
    • LinkMetaData 적용

 

코드 구현에 사용된 개념

 

목차

  • 결과 스크린샷
  • 구현부 전체 코드
  • Usage
    • SwiftUI
    • UIKit + MVC
    • UIKit + MVVM

 

결과 스크린샷

액티비티 아이템에는 빌더 패턴을 통해 여러 값들을 넣어줄 수 있음. 시뮬레이터 예제로 실기기에서 테스트하면 더 다양한 옵션이 존재.

  • 왼쪽이미지
    • URL만 넣어준 경우 웹에서 세팅된 값을 자동으로 설정
  • 오른쪽 이미지
    • URL 뿐만아니라 썸네일, 타이틀 등 내가 필요한 것들을 명시적으로 세팅

스크린샷


구현부 전체 코드

구현에 대한 전체 코드

public protocol SystemSharingFeature {
    func show(_ activityViewController: UIActivityViewController)
}

public extension SystemSharingFeature {
    func show(_ activityViewController: UIActivityViewController) {
        if let keyWindow = UIWindow.keyWindow {
            let topViewController = keyWindow.rootViewController?.topMostViewController()
            topViewController?.present(activityViewController, animated: true, completion: nil)
        }
    }
}

// MARK: - SystemSharingBuildable

public protocol SystemSharingBuildable {
    func excludedActivityTypes(_ excludedActivityTypes: [UIActivity.ActivityType]) -> Self
    func activityItemSource(_ activityItemSource: ActivityItemSource) -> Self
    func activityItem(_ activityItems: [Any]) -> Self

    func build() -> UIActivityViewController
}

public final class SystemSharingBuilder: SystemSharingBuildable {
    var excludedActivityTypes: [UIActivity.ActivityType]?
    var activityItemSource: ActivityItemSource?
    var activityItems: [Any]?

    public init() {}

    public func excludedActivityTypes(_ excludedActivityTypes: [UIActivity.ActivityType]) -> Self {
        self.excludedActivityTypes = excludedActivityTypes
        return self
    }

    public func activityItemSource(_ activityItemSource: ActivityItemSource) -> Self {
        self.activityItemSource = activityItemSource
        return self
    }

    public func activityItem(_ activityItems: [Any]) -> Self {
        self.activityItems = activityItems
        return self
    }

    public func build() -> UIActivityViewController {
        let activityViewController = UIActivityViewController(
            activityItems: activityItems ?? [activityItemSource as Any],
            applicationActivities: nil
        )
        activityViewController.excludedActivityTypes = excludedActivityTypes
        return activityViewController
    }
}

 

Usage

사용법 정리

  • SwiftUI
  • UIKit + MVC
  • UIKit + MVVM

빌더 패턴을 적용하여 필요한 것들을 붙여서 사용할 수 있음

  • 윈도우 위에 띄우는 구현 방식으로 View가 없는 객체에서도 사용할 수 있음
  • 즉, RIBs에서 View가 없는 RIB에서 해당 행위 호출 가능하다는 의미

빌더패턴

// MARK: - Usage (SwiftUI)
struct SwiftUIView: View, SystemSharingFeature {
    var body: some View {
        Button("공유하기 버튼 실행") {
            let builder = SharingFeatureBuilder()
                .activityItemSource(.init(title: "당근", urlString: "https://rldd.tistory.com/720", image: .init()))
                .build()
            show(builder)
        }
    }
}

// MARK: - Usage (UIKit - MVC)

final class ViewController_MVC: UIViewController, SystemSharingFeature {

    init() {
        super.init(nibName: nil, bundle: nil)

        configureUI()
    }

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

    private lazy var shareButton: UIBarButtonItem = {
        let btn = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(didTapShareButton))
        return btn
    }()

    @objc private func didTapShareButton() {
        let builder = SharingFeatureBuilder()
            .activityItemSource(.init(title: "당근", urlString: "https://rldd.tistory.com/720", image: .init()))
            .build()

        show(builder)
    }

    private func configureUI() {
        navigationItem.rightBarButtonItem = shareButton
    }
}

// MARK: - Usage (UIKit - MVVM)

final class ViewModel: SystemSharingFeature {
    enum Action {
        case shareButtonTapped
    }

    func send(action: Action) {
        switch action {
        case .shareButtonTapped:
            let builder = SharingFeatureBuilder()
                .activityItemSource(.init(title: "당근", urlString: "https://rldd.tistory.com/720", image: .init()))
                .build()

            show(builder)
        }
    }
}

final class ViewController_MVVM: UIViewController {
    private let viewModel: ViewModel

    init() {
        self.viewModel = .init()
        super.init(nibName: nil, bundle: nil)

        configureUI()
    }

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

    private lazy var shareButton: UIBarButtonItem = {
        let btn = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(didTapShareButton))
        return btn
    }()

    @objc private func didTapShareButton() {
        viewModel.send(action: .shareButtonTapped)
    }

    private func configureUI() {
        navigationItem.rightBarButtonItem = shareButton
    }
}