import UIKit import Social import MobileCoreServices import Photos class ShareViewController: SLComposeServiceViewController { // TODO: IMPORTANT: This should be your host app bundle identifier let hostAppBundleIdentifier = "com.chengyouhudong.hibok" let sharedKey = "ShareKey" var sharedMedia: [SharedMediaFile] = [] var sharedText: [String] = [] let imageContentType = kUTTypeImage as String let videoContentType = kUTTypeMovie as String let textContentType = kUTTypeText as String let urlContentType = kUTTypeURL as String override func isContentValid() -> Bool { return true } override func viewDidLoad() { // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. if let content = extensionContext!.inputItems[0] as? NSExtensionItem { if let contents = content.attachments { for (index, attachment) in (contents as! [NSItemProvider]).enumerated() { if attachment.hasItemConformingToTypeIdentifier(imageContentType) { handleImages(content: content, attachment: attachment, index: index) } else if attachment.hasItemConformingToTypeIdentifier(textContentType) { handleText(content: content, attachment: attachment, index: index) } else if attachment.hasItemConformingToTypeIdentifier(urlContentType) { handleUrl(content: content, attachment: attachment, index: index) } else if attachment.hasItemConformingToTypeIdentifier(videoContentType) { // handleVideos(content: content, attachment: attachment, index: index) } } } } } override func didSelectPost() { print("didSelectPost"); } override func configurationItems() -> [Any]! { // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. return [] } private func handleText (content: NSExtensionItem, attachment: NSItemProvider, index: Int) { attachment.loadItem(forTypeIdentifier: textContentType, options: nil) { [weak self] data, error in if error == nil, let item = data as? String, let this = self { this.sharedText.append(item) // If this is the last item, save imagesData in userDefaults and redirect to host app if index == (content.attachments?.count)! - 1 { let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)") userDefaults?.set(this.sharedText, forKey: this.sharedKey) userDefaults?.synchronize() this.redirectToHostApp(type: .text) } } else { self?.dismissWithError() } } } private func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) { attachment.loadItem(forTypeIdentifier: urlContentType, options: nil) { [weak self] data, error in if error == nil, let item = data as? URL, let this = self { this.sharedText.append(item.absoluteString) // If this is the last item, save imagesData in userDefaults and redirect to host app if index == (content.attachments?.count)! - 1 { let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)") userDefaults?.set(this.sharedText, forKey: this.sharedKey) userDefaults?.synchronize() this.redirectToHostApp(type: .text) } } else { self?.dismissWithError() } } } private func handleImages (content: NSExtensionItem, attachment: NSItemProvider, index: Int) { attachment.loadItem(forTypeIdentifier: imageContentType, options: nil) { [weak self] data, error in if error == nil, let url = data as? URL, let this = self { // Always copy let fileExtension = this.getExtension(from: url, type: .image) let newName = UUID().uuidString let newPath = FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")! .appendingPathComponent("\(newName).\(fileExtension)") let copied = this.copyFile(at: url, to: newPath) if(copied) { this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .image)) } // If this is the last item, save imagesData in userDefaults and redirect to host app if index == (content.attachments?.count)! - 1 { let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)") userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey) userDefaults?.synchronize() this.redirectToHostApp(type: .media) } } else { self?.dismissWithError() } } } // private func handleVideos (content: NSExtensionItem, attachment: NSItemProvider, index: Int) { // attachment.loadItem(forTypeIdentifier: videoContentType, options: nil) { [weak self] data, error in // // if error == nil, let url = data as? URL, let this = self { // // // Always copy // let fileExtension = this.getExtension(from: url, type: .video) // let newName = UUID().uuidString // let newPath = FileManager.default // .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")! // .appendingPathComponent("\(newName).\(fileExtension)") // let copied = this.copyFile(at: url, to: newPath) // if(copied) { // guard let sharedFile = this.getSharedMediaFile(forVideo: newPath) else { // return // } // this.sharedMedia.append(sharedFile) // } // // // If this is the last item, save imagesData in userDefaults and redirect to host app // if index == (content.attachments?.count)! - 1 { // let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)") // userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey) // userDefaults?.synchronize() // this.redirectToHostApp(type: .media) // } // // } else { // self?.dismissWithError() // } // } // } private func dismissWithError() { print("GETTING ERROR") let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert) let action = UIAlertAction(title: "Error", style: .cancel) { _ in self.dismiss(animated: true, completion: nil) } alert.addAction(action) present(alert, animated: true, completion: nil) extensionContext!.completeRequest(returningItems: [], completionHandler: nil) } private func redirectToHostApp(type: RedirectType) { let url = URL(string: "SharePhotos://dataUrl=\(sharedKey)#\(type)") var responder = self as UIResponder? let selectorOpenURL = sel_registerName("openURL:") while (responder != nil) { if (responder?.responds(to: selectorOpenURL))! { let _ = responder?.perform(selectorOpenURL, with: url) } responder = responder!.next } extensionContext!.completeRequest(returningItems: [], completionHandler: nil) } enum RedirectType { case media case text } func getExtension(from url: URL, type: SharedMediaType) -> String { let parts = url.lastPathComponent.components(separatedBy: ".") var ex: String? = nil if (parts.count > 1) { ex = parts.last } if (ex == nil) { switch type { case .image: ex = "PNG" case .video: ex = "MP4" } } return ex ?? "Unknown" } func copyFile(at srcURL: URL, to dstURL: URL) -> Bool { do { if FileManager.default.fileExists(atPath: dstURL.path) { try FileManager.default.removeItem(at: dstURL) } try FileManager.default.copyItem(at: srcURL, to: dstURL) } catch (let error) { print("Cannot copy item at \(srcURL) to \(dstURL): \(error)") return false } return true } // private func getSharedMediaFile(forVideo: URL) -> SharedMediaFile? { // let asset = AVAsset(url: forVideo) // let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded() // let thumbnailPath = getThumbnailPath(for: forVideo) // // if FileManager.default.fileExists(atPath: thumbnailPath.path) { // return SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) // } // // var saved = false // let assetImgGenerate = AVAssetImageGenerator(asset: asset) // assetImgGenerate.appliesPreferredTrackTransform = true // // let scale = UIScreen.main.scale // assetImgGenerate.maximumSize = CGSize(width: 360, height: 360) // do { // let img = try assetImgGenerate.copyCGImage(at: CMTimeMakeWithSeconds(1.0, 600), actualTime: nil) // try UIImagePNGRepresentation(UIImage(cgImage: img))?.write(to: thumbnailPath) // saved = true // } catch { // saved = false // } // // return saved ? SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) : nil // // } private func getThumbnailPath(for url: URL) -> URL { let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "==", with: "") let path = FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")! .appendingPathComponent("\(fileName).jpg") return path } class SharedMediaFile: Codable { var path: String; // can be image, video or url path. It can also be text content var thumbnail: String?; // video thumbnail var duration: Double?; // video duration in milliseconds var type: SharedMediaType; init(path: String, thumbnail: String?, duration: Double?, type: SharedMediaType) { self.path = path self.thumbnail = thumbnail self.duration = duration self.type = type } } enum SharedMediaType: Int, Codable { case image case video } func toData(data: [SharedMediaFile]) -> Data { let encodedData = try? JSONEncoder().encode(data) return encodedData! } } extension Array { subscript (safe index: UInt) -> Element? { return Int(index) < count ? self[Int(index)] : nil } }