LNMediaPicker.swift 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. //
  2. // LNMediaPicker.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/11/26.
  6. //
  7. import Foundation
  8. import UIKit
  9. import Photos
  10. enum LNMediaPickerType: CaseIterable {
  11. case camera
  12. case photo
  13. var title: String {
  14. switch self {
  15. case .camera: .init(key: "A00011")
  16. case .photo: .init(key: "A00012")
  17. }
  18. }
  19. }
  20. extension LNBottomSheetMenu {
  21. static func showImageSelectMenu(view: UIView? = nil,
  22. options: LNMediaPickerOptions = .init(),
  23. handler: @escaping LNMediaPickerHandler) {
  24. let panel = LNBottomSheetMenu()
  25. let types: [LNMediaPickerType] = [.camera, .photo]
  26. panel.update([
  27. LNMediaPickerType.camera.title,
  28. LNMediaPickerType.photo.title,
  29. .init(key: "A00003")
  30. ]) { index, _ in
  31. guard index < types.count else { return }
  32. let type = types[index]
  33. LNMediaPicker.shared.pick(from: view, type: type, options: options, handler: handler)
  34. }
  35. panel.popup()
  36. }
  37. }
  38. extension LNCommonAlertView {
  39. static func showPhotoLibraryPermissionAlert() {
  40. let alert = LNCommonAlertView()
  41. alert.titleLabel.text = .init(key: "A00287")
  42. alert.showConfirm(.init(key: "A00289")) {
  43. openAppSetting()
  44. }
  45. alert.showCancel()
  46. alert.popup()
  47. }
  48. static func showCameraPermissionAlert() {
  49. let alert = LNCommonAlertView()
  50. alert.titleLabel.text = .init(key: "A00288")
  51. alert.showConfirm(.init(key: "A00289")) {
  52. openAppSetting()
  53. }
  54. alert.showCancel()
  55. alert.popup()
  56. }
  57. }
  58. typealias LNMediaPickerHandler = (_ image: UIImage?, _ videoUrl: URL?) -> Void
  59. struct LNMediaPickerOptions {
  60. let allowEdit: Bool
  61. let allowVideo: Bool
  62. let videoMaximumDuration: TimeInterval
  63. init(allowEdit: Bool = false,
  64. allowVideo: Bool = false,
  65. videoMaximumDuration: TimeInterval = 60) {
  66. self.allowEdit = allowEdit
  67. self.allowVideo = allowVideo
  68. self.videoMaximumDuration = videoMaximumDuration
  69. }
  70. }
  71. private var mediaHandlerKey: UInt8 = 0
  72. extension UIViewController {
  73. fileprivate var mediaPickerHandler: LNMediaPickerHandler? {
  74. get {
  75. objc_getAssociatedObject(self, &mediaHandlerKey) as? LNMediaPickerHandler ?? nil
  76. }
  77. set {
  78. objc_setAssociatedObject(self, &mediaHandlerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  79. }
  80. }
  81. }
  82. class LNMediaPicker: NSObject {
  83. static let shared = LNMediaPicker()
  84. private override init() {
  85. super.init()
  86. }
  87. }
  88. extension LNMediaPicker {
  89. func pick(from view: UIView? = nil,
  90. type: LNMediaPickerType,
  91. options: LNMediaPickerOptions,
  92. handler: @escaping LNMediaPickerHandler) {
  93. switch type {
  94. case .photo:
  95. handlePhotoAuthorization(view: view, options: options, handler: handler)
  96. case .camera:
  97. handleCameraAuthorization(view: view, options: options, handler: handler)
  98. }
  99. }
  100. private func handlePhotoAuthorization(view: UIView?,
  101. options: LNMediaPickerOptions,
  102. handler: @escaping LNMediaPickerHandler) {
  103. // 申请相册权限(iOS 10+ 需授权)
  104. LNPermissionHelper.requestPhotoLibraryAuthorization { [weak self] status in
  105. guard let self else { return }
  106. switch status {
  107. case .authorized: // 已授权,打开相册
  108. self.presentPicker(from: view, sourceType: .photoLibrary, options: options, handler: handler)
  109. case .denied, .restricted: // 拒绝授权或受限制
  110. LNCommonAlertView.showPhotoLibraryPermissionAlert()
  111. handler(nil, nil)
  112. case .notDetermined: // 首次请求,系统会自动弹出授权框(无需额外处理)
  113. break
  114. case .limited:
  115. break
  116. @unknown default:
  117. break
  118. }
  119. }
  120. }
  121. private func handleCameraAuthorization(view: UIView?,
  122. options: LNMediaPickerOptions,
  123. handler: @escaping LNMediaPickerHandler) {
  124. let status = LNPermissionHelper.cameraAuthorizationStatus()
  125. switch status {
  126. case .authorized: // 已授权
  127. presentPicker(from: view, sourceType: .camera, options: options, handler: handler)
  128. case .notDetermined: // 未决定,请求权限
  129. LNPermissionHelper.requestCameraAccess { [weak self] granted in
  130. guard let self else { return }
  131. guard granted else {
  132. handler(nil, nil)
  133. return
  134. }
  135. self.presentPicker(from: view, sourceType: .camera, options: options, handler: handler)
  136. }
  137. case .denied, .restricted: // 拒绝/受限
  138. LNCommonAlertView.showCameraPermissionAlert()
  139. handler(nil, nil)
  140. @unknown default:
  141. handler(nil, nil)
  142. }
  143. }
  144. }
  145. extension LNMediaPicker {
  146. private func presentPicker(from view: UIView?,
  147. sourceType: UIImagePickerController.SourceType,
  148. options: LNMediaPickerOptions,
  149. handler: @escaping LNMediaPickerHandler) {
  150. let vc = view?.viewController ?? UIView.appKeyWindow?.rootViewController
  151. let picker = UIImagePickerController()
  152. picker.delegate = self
  153. picker.sourceType = sourceType
  154. picker.allowsEditing = options.allowEdit
  155. picker.mediaTypes = options.allowVideo
  156. ? [UTType.image.identifier, UTType.movie.identifier]
  157. : [UTType.image.identifier]
  158. if sourceType == .camera {
  159. picker.videoMaximumDuration = options.videoMaximumDuration
  160. }
  161. picker.mediaPickerHandler = handler
  162. vc?.present(picker, animated: true)
  163. }
  164. }
  165. extension LNMediaPicker: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
  166. func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
  167. // 关闭相册
  168. picker.dismiss(animated: true)
  169. if let videoURL = info[.mediaURL] as? URL {
  170. picker.mediaPickerHandler?(nil, videoURL)
  171. return
  172. }
  173. // 获取选择的图片(editedImage:编辑后的图片,originalImage:原始图片)
  174. let image: UIImage?
  175. if let editedImage = info[.editedImage] as? UIImage {
  176. image = editedImage
  177. } else if let originalImage = info[.originalImage] as? UIImage {
  178. image = originalImage
  179. } else {
  180. image = nil
  181. }
  182. picker.mediaPickerHandler?(image, nil)
  183. }
  184. // 取消选择
  185. func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
  186. picker.dismiss(animated: true)
  187. picker.mediaPickerHandler?(nil, nil)
  188. }
  189. }