Hibok
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

284 lines
12 KiB

  1. import UIKit
  2. import Social
  3. import MobileCoreServices
  4. import Photos
  5. class ShareViewController: SLComposeServiceViewController {
  6. // TODO: IMPORTANT: This should be your host app bundle identifier
  7. let hostAppBundleIdentifier = "com.chengyouhudong.hibok"
  8. let sharedKey = "ShareKey"
  9. var sharedMedia: [SharedMediaFile] = []
  10. var sharedText: [String] = []
  11. let imageContentType = kUTTypeImage as String
  12. let videoContentType = kUTTypeMovie as String
  13. let textContentType = kUTTypeText as String
  14. let urlContentType = kUTTypeURL as String
  15. override func isContentValid() -> Bool {
  16. return true
  17. }
  18. override func viewDidLoad() {
  19. // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
  20. if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
  21. if let contents = content.attachments {
  22. for (index, attachment) in (contents as! [NSItemProvider]).enumerated() {
  23. if attachment.hasItemConformingToTypeIdentifier(imageContentType) {
  24. handleImages(content: content, attachment: attachment, index: index)
  25. } else if attachment.hasItemConformingToTypeIdentifier(textContentType) {
  26. handleText(content: content, attachment: attachment, index: index)
  27. } else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
  28. handleUrl(content: content, attachment: attachment, index: index)
  29. } else if attachment.hasItemConformingToTypeIdentifier(videoContentType) {
  30. // handleVideos(content: content, attachment: attachment, index: index)
  31. }
  32. }
  33. }
  34. }
  35. }
  36. override func didSelectPost() {
  37. print("didSelectPost");
  38. }
  39. override func configurationItems() -> [Any]! {
  40. // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
  41. return []
  42. }
  43. private func handleText (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
  44. attachment.loadItem(forTypeIdentifier: textContentType, options: nil) { [weak self] data, error in
  45. if error == nil, let item = data as? String, let this = self {
  46. this.sharedText.append(item)
  47. // If this is the last item, save imagesData in userDefaults and redirect to host app
  48. if index == (content.attachments?.count)! - 1 {
  49. let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
  50. userDefaults?.set(this.sharedText, forKey: this.sharedKey)
  51. userDefaults?.synchronize()
  52. this.redirectToHostApp(type: .text)
  53. }
  54. } else {
  55. self?.dismissWithError()
  56. }
  57. }
  58. }
  59. private func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
  60. attachment.loadItem(forTypeIdentifier: urlContentType, options: nil) { [weak self] data, error in
  61. if error == nil, let item = data as? URL, let this = self {
  62. this.sharedText.append(item.absoluteString)
  63. // If this is the last item, save imagesData in userDefaults and redirect to host app
  64. if index == (content.attachments?.count)! - 1 {
  65. let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
  66. userDefaults?.set(this.sharedText, forKey: this.sharedKey)
  67. userDefaults?.synchronize()
  68. this.redirectToHostApp(type: .text)
  69. }
  70. } else {
  71. self?.dismissWithError()
  72. }
  73. }
  74. }
  75. private func handleImages (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
  76. attachment.loadItem(forTypeIdentifier: imageContentType, options: nil) { [weak self] data, error in
  77. if error == nil, let url = data as? URL, let this = self {
  78. // Always copy
  79. let fileExtension = this.getExtension(from: url, type: .image)
  80. let newName = UUID().uuidString
  81. let newPath = FileManager.default
  82. .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
  83. .appendingPathComponent("\(newName).\(fileExtension)")
  84. let copied = this.copyFile(at: url, to: newPath)
  85. if(copied) {
  86. this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .image))
  87. }
  88. // If this is the last item, save imagesData in userDefaults and redirect to host app
  89. if index == (content.attachments?.count)! - 1 {
  90. let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
  91. userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
  92. userDefaults?.synchronize()
  93. this.redirectToHostApp(type: .media)
  94. }
  95. } else {
  96. self?.dismissWithError()
  97. }
  98. }
  99. }
  100. // private func handleVideos (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
  101. // attachment.loadItem(forTypeIdentifier: videoContentType, options: nil) { [weak self] data, error in
  102. //
  103. // if error == nil, let url = data as? URL, let this = self {
  104. //
  105. // // Always copy
  106. // let fileExtension = this.getExtension(from: url, type: .video)
  107. // let newName = UUID().uuidString
  108. // let newPath = FileManager.default
  109. // .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
  110. // .appendingPathComponent("\(newName).\(fileExtension)")
  111. // let copied = this.copyFile(at: url, to: newPath)
  112. // if(copied) {
  113. // guard let sharedFile = this.getSharedMediaFile(forVideo: newPath) else {
  114. // return
  115. // }
  116. // this.sharedMedia.append(sharedFile)
  117. // }
  118. //
  119. // // If this is the last item, save imagesData in userDefaults and redirect to host app
  120. // if index == (content.attachments?.count)! - 1 {
  121. // let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
  122. // userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
  123. // userDefaults?.synchronize()
  124. // this.redirectToHostApp(type: .media)
  125. // }
  126. //
  127. // } else {
  128. // self?.dismissWithError()
  129. // }
  130. // }
  131. // }
  132. private func dismissWithError() {
  133. print("GETTING ERROR")
  134. let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)
  135. let action = UIAlertAction(title: "Error", style: .cancel) { _ in
  136. self.dismiss(animated: true, completion: nil)
  137. }
  138. alert.addAction(action)
  139. present(alert, animated: true, completion: nil)
  140. extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
  141. }
  142. private func redirectToHostApp(type: RedirectType) {
  143. let url = URL(string: "SharePhotos://dataUrl=\(sharedKey)#\(type)")
  144. var responder = self as UIResponder?
  145. let selectorOpenURL = sel_registerName("openURL:")
  146. while (responder != nil) {
  147. if (responder?.responds(to: selectorOpenURL))! {
  148. let _ = responder?.perform(selectorOpenURL, with: url)
  149. }
  150. responder = responder!.next
  151. }
  152. extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
  153. }
  154. enum RedirectType {
  155. case media
  156. case text
  157. }
  158. func getExtension(from url: URL, type: SharedMediaType) -> String {
  159. let parts = url.lastPathComponent.components(separatedBy: ".")
  160. var ex: String? = nil
  161. if (parts.count > 1) {
  162. ex = parts.last
  163. }
  164. if (ex == nil) {
  165. switch type {
  166. case .image:
  167. ex = "PNG"
  168. case .video:
  169. ex = "MP4"
  170. }
  171. }
  172. return ex ?? "Unknown"
  173. }
  174. func copyFile(at srcURL: URL, to dstURL: URL) -> Bool {
  175. do {
  176. if FileManager.default.fileExists(atPath: dstURL.path) {
  177. try FileManager.default.removeItem(at: dstURL)
  178. }
  179. try FileManager.default.copyItem(at: srcURL, to: dstURL)
  180. } catch (let error) {
  181. print("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
  182. return false
  183. }
  184. return true
  185. }
  186. // private func getSharedMediaFile(forVideo: URL) -> SharedMediaFile? {
  187. // let asset = AVAsset(url: forVideo)
  188. // let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded()
  189. // let thumbnailPath = getThumbnailPath(for: forVideo)
  190. //
  191. // if FileManager.default.fileExists(atPath: thumbnailPath.path) {
  192. // return SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video)
  193. // }
  194. //
  195. // var saved = false
  196. // let assetImgGenerate = AVAssetImageGenerator(asset: asset)
  197. // assetImgGenerate.appliesPreferredTrackTransform = true
  198. // // let scale = UIScreen.main.scale
  199. // assetImgGenerate.maximumSize = CGSize(width: 360, height: 360)
  200. // do {
  201. // let img = try assetImgGenerate.copyCGImage(at: CMTimeMakeWithSeconds(1.0, 600), actualTime: nil)
  202. // try UIImagePNGRepresentation(UIImage(cgImage: img))?.write(to: thumbnailPath)
  203. // saved = true
  204. // } catch {
  205. // saved = false
  206. // }
  207. //
  208. // return saved ? SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) : nil
  209. //
  210. // }
  211. private func getThumbnailPath(for url: URL) -> URL {
  212. let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "==", with: "")
  213. let path = FileManager.default
  214. .containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
  215. .appendingPathComponent("\(fileName).jpg")
  216. return path
  217. }
  218. class SharedMediaFile: Codable {
  219. var path: String; // can be image, video or url path. It can also be text content
  220. var thumbnail: String?; // video thumbnail
  221. var duration: Double?; // video duration in milliseconds
  222. var type: SharedMediaType;
  223. init(path: String, thumbnail: String?, duration: Double?, type: SharedMediaType) {
  224. self.path = path
  225. self.thumbnail = thumbnail
  226. self.duration = duration
  227. self.type = type
  228. }
  229. }
  230. enum SharedMediaType: Int, Codable {
  231. case image
  232. case video
  233. }
  234. func toData(data: [SharedMediaFile]) -> Data {
  235. let encodedData = try? JSONEncoder().encode(data)
  236. return encodedData!
  237. }
  238. }
  239. extension Array {
  240. subscript (safe index: UInt) -> Element? {
  241. return Int(index) < count ? self[Int(index)] : nil
  242. }
  243. }