123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- import AVKit
- import AVFoundation
- import SDWebImage
- import DcCore
- protocol ContextMenuItem {
- var msg: DcMsg { get set }
- var thumbnailImage: UIImage? { get set }
- }
- // MARK: - ContextMenuController
- class ContextMenuController: UIViewController {
- let item: ContextMenuItem
- var msg: DcMsg {
- return item.msg
- }
- init(item: ContextMenuItem) {
- self.item = item
- super.init(nibName: nil, bundle: nil)
- }
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- // MARK: - lifecycle
- override func viewDidLoad() {
- super.viewDidLoad()
- let viewType = msg.viewtype
- var thumbnailView: UIView?
- switch viewType {
- case .image:
- thumbnailView = makeImageView(image: msg.image)
- case .video:
- thumbnailView = makeVideoView(videoUrl: msg.fileURL)
- case .gif:
- thumbnailView = makeGifView(gifImage: item.thumbnailImage)
- default:
- return
- }
- guard let contentView = thumbnailView else {
- return
- }
- view.addSubview(contentView)
- contentView.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- contentView.leftAnchor.constraint(equalTo: view.leftAnchor),
- contentView.rightAnchor.constraint(equalTo: view.rightAnchor),
- contentView.topAnchor.constraint(equalTo: view.topAnchor),
- contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- ])
- }
- // MARK: - thumbnailView creation
- private func makeGifView(gifImage: UIImage?) -> UIView? {
- let view = SDAnimatedImageView()
- view.contentMode = .scaleAspectFill
- view.clipsToBounds = true
- view.backgroundColor = DcColors.defaultBackgroundColor
- if let image = gifImage {
- setPreferredContentSize(for: image)
- }
- view.image = gifImage
- return view
- }
- private func makeImageView(image: UIImage?) -> UIView? {
- guard let image = image else {
- safe_fatalError("unexpected nil value")
- return nil
- }
- let imageView = UIImageView()
- imageView.clipsToBounds = true
- imageView.contentMode = .scaleAspectFill
- imageView.image = image
- setPreferredContentSize(for: image)
- return imageView
- }
- private func makeVideoView(videoUrl: URL?) -> UIView? {
- guard let videoUrl = videoUrl, let videoSize = item.thumbnailImage?.size else { return nil }
- let player = AVPlayer(url: videoUrl)
- let playerController = AVPlayerViewController()
- addChild(playerController)
- view.addSubview(playerController.view)
- playerController.didMove(toParent: self)
- playerController.view.backgroundColor = .darkGray
- playerController.view.clipsToBounds = true
- playerController.player = player
- playerController.showsPlaybackControls = false
- player.play()
- // truncate edges on top/bottom or sides
- let resizedHeightFactor = view.frame.height / videoSize.height
- let resizedWidthFactor = view.frame.width / videoSize.width
- let effectiveResizeFactor = min(resizedWidthFactor, resizedHeightFactor)
- let maxHeight = videoSize.height * effectiveResizeFactor
- let maxWidth = videoSize.width * effectiveResizeFactor
- let size = CGSize(width: maxWidth, height: maxHeight)
- preferredContentSize = size
- return playerController.view
- }
- private func setPreferredContentSize(for image: UIImage) {
- let width = view.bounds.width
- let height = image.size.height * (width / image.size.width)
- self.preferredContentSize = CGSize(width: width, height: height)
- }
- }
- class ContextMenuProvider {
- var menu: [ContextMenuItem] = []
- init(menu: [ContextMenuItem] = []) {
- self.menu = menu
- }
- func setMenu(_ menu: [ContextMenuItem]) {
- self.menu = menu
- }
- // iOS 12- action menu
- var menuItems: [UIMenuItem] {
- return menu
- .filter({ $0.title != nil && $0.action != nil })
- .map({ return UIMenuItem(title: $0.title!, action: $0.action!) })
- }
- // iOS13+ action menu
- @available(iOS 13, *)
- func actionProvider(title: String = "", image: UIImage? = nil, identifier: UIMenu.Identifier? = nil, indexPath: IndexPath) -> UIMenu {
- var children: [UIMenuElement] = []
- for item in menu {
- //we only support 1 submenu layer for now
- if let subMenus = item.children {
- var submenuChildren: [UIMenuElement] = []
- for submenuItem in subMenus {
- submenuChildren.append(generateUIAction(item: submenuItem, indexPath: indexPath))
- }
- let submenu = UIMenu(title: "", options: .displayInline, children: submenuChildren)
- children.append(submenu)
- } else {
- children.append(generateUIAction(item: item, indexPath: indexPath))
- }
- }
- return UIMenu(
- title: title,
- image: image,
- identifier: identifier,
- children: children
- )
- }
- @available(iOS 13, *)
- private func generateUIAction(item: ContextMenuItem, indexPath: IndexPath) -> UIAction {
- let image = UIImage(systemName: item.imageName ?? "") ??
- UIImage(named: item.imageName ?? "")
- let action = UIAction(
- title: item.title ?? "",
- image: image,
- handler: { _ in item.onPerform?(indexPath) }
- )
- if item.isDestructive ?? false {
- action.attributes = [.destructive]
- }
- return action
- }
- func canPerformAction(action: Selector) -> Bool {
- return !menu.filter {
- $0.action == action
- }.isEmpty
- }
- func performAction(action: Selector, indexPath: IndexPath) {
- menu.filter {
- $0.action == action
- }.first?.onPerform?(indexPath)
- }
- }
- extension ContextMenuProvider {
- struct ContextMenuItem {
- var title: String?
- var imageName: String?
- let isDestructive: Bool?
- var action: Selector?
- var onPerform: ((IndexPath) -> Void)?
- var children: [ContextMenuItem]?
- init(title: String, imageName: String, isDestructive: Bool = false, action: Selector, onPerform: ((IndexPath) -> Void)?) {
- self.title = title
- self.imageName = imageName
- self.isDestructive = isDestructive
- self.action = action
- self.onPerform = onPerform
- }
- init(submenuitems: [ContextMenuItem]) {
- title = nil
- imageName = nil
- isDestructive = nil
- action = nil
- onPerform = nil
- children = submenuitems
- }
- }
- }
|