// // LNMediaPicker.swift // Lanu // // Created by OneeChan on 2025/11/26. // import Foundation import UIKit import Photos enum LNMediaPickerType: CaseIterable { case camera case photo var title: String { switch self { case .camera: .init(key: "A00011") case .photo: .init(key: "A00012") } } } extension LNBottomSheetMenu { static func showImageSelectMenu(view: UIView? = nil, options: LNMediaPickerOptions = .init(), handler: @escaping LNMediaPickerHandler) { let panel = LNBottomSheetMenu() let types: [LNMediaPickerType] = [.camera, .photo] panel.update([ LNMediaPickerType.camera.title, LNMediaPickerType.photo.title, .init(key: "A00003") ]) { index, _ in guard index < types.count else { return } let type = types[index] LNMediaPicker.shared.pick(from: view, type: type, options: options, handler: handler) } panel.popup() } } extension LNCommonAlertView { static func showPhotoLibraryPermissionAlert() { let alert = LNCommonAlertView() alert.titleLabel.text = .init(key: "A00287") alert.showConfirm(.init(key: "A00289")) { openAppSetting() } alert.showCancel() alert.popup() } static func showCameraPermissionAlert() { let alert = LNCommonAlertView() alert.titleLabel.text = .init(key: "A00288") alert.showConfirm(.init(key: "A00289")) { openAppSetting() } alert.showCancel() alert.popup() } } typealias LNMediaPickerHandler = (_ image: UIImage?, _ videoUrl: URL?) -> Void struct LNMediaPickerOptions { let allowEdit: Bool let allowVideo: Bool let videoMaximumDuration: TimeInterval init(allowEdit: Bool = false, allowVideo: Bool = false, videoMaximumDuration: TimeInterval = 60) { self.allowEdit = allowEdit self.allowVideo = allowVideo self.videoMaximumDuration = videoMaximumDuration } } private var mediaHandlerKey: UInt8 = 0 extension UIViewController { fileprivate var mediaPickerHandler: LNMediaPickerHandler? { get { objc_getAssociatedObject(self, &mediaHandlerKey) as? LNMediaPickerHandler ?? nil } set { objc_setAssociatedObject(self, &mediaHandlerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } class LNMediaPicker: NSObject { static let shared = LNMediaPicker() private override init() { super.init() } } extension LNMediaPicker { func pick(from view: UIView? = nil, type: LNMediaPickerType, options: LNMediaPickerOptions, handler: @escaping LNMediaPickerHandler) { switch type { case .photo: handlePhotoAuthorization(view: view, options: options, handler: handler) case .camera: handleCameraAuthorization(view: view, options: options, handler: handler) } } private func handlePhotoAuthorization(view: UIView?, options: LNMediaPickerOptions, handler: @escaping LNMediaPickerHandler) { // 申请相册权限(iOS 10+ 需授权) LNPermissionHelper.requestPhotoLibraryAuthorization { [weak self] status in guard let self else { return } switch status { case .authorized: // 已授权,打开相册 self.presentPicker(from: view, sourceType: .photoLibrary, options: options, handler: handler) case .denied, .restricted: // 拒绝授权或受限制 LNCommonAlertView.showPhotoLibraryPermissionAlert() handler(nil, nil) case .notDetermined: // 首次请求,系统会自动弹出授权框(无需额外处理) break case .limited: break @unknown default: break } } } private func handleCameraAuthorization(view: UIView?, options: LNMediaPickerOptions, handler: @escaping LNMediaPickerHandler) { let status = LNPermissionHelper.cameraAuthorizationStatus() switch status { case .authorized: // 已授权 presentPicker(from: view, sourceType: .camera, options: options, handler: handler) case .notDetermined: // 未决定,请求权限 LNPermissionHelper.requestCameraAccess { [weak self] granted in guard let self else { return } guard granted else { handler(nil, nil) return } self.presentPicker(from: view, sourceType: .camera, options: options, handler: handler) } case .denied, .restricted: // 拒绝/受限 LNCommonAlertView.showCameraPermissionAlert() handler(nil, nil) @unknown default: handler(nil, nil) } } } extension LNMediaPicker { private func presentPicker(from view: UIView?, sourceType: UIImagePickerController.SourceType, options: LNMediaPickerOptions, handler: @escaping LNMediaPickerHandler) { let vc = view?.viewController ?? UIView.appKeyWindow?.rootViewController let picker = UIImagePickerController() picker.delegate = self picker.sourceType = sourceType picker.allowsEditing = options.allowEdit picker.mediaTypes = options.allowVideo ? [UTType.image.identifier, UTType.movie.identifier] : [UTType.image.identifier] if sourceType == .camera { picker.videoMaximumDuration = options.videoMaximumDuration } picker.mediaPickerHandler = handler vc?.present(picker, animated: true) } } extension LNMediaPicker: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { // 关闭相册 picker.dismiss(animated: true) if let videoURL = info[.mediaURL] as? URL { picker.mediaPickerHandler?(nil, videoURL) return } // 获取选择的图片(editedImage:编辑后的图片,originalImage:原始图片) let image: UIImage? if let editedImage = info[.editedImage] as? UIImage { image = editedImage } else if let originalImage = info[.originalImage] as? UIImage { image = originalImage } else { image = nil } picker.mediaPickerHandler?(image, nil) } // 取消选择 func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true) picker.mediaPickerHandler?(nil, nil) } }