MediaView
屏幕截图
描述
MediaView 可以显示图片、视频,现在还可以显示 GIF 和音频!它继承自 UIImageView,并具有从网络上懒加载图片的功能。此外,它还可以显示通过 URL 从磁盘或网络下载的视频。视频包含具有时间线和快进功能的播放器。GIF 可以通过从网络上懒加载或通过 NSData 设置在 MediaView 中显示。下载的 GIF 保存为 UIImage 对象以方便存储。音频也可以通过简单地提供网络或磁盘上的 URL 在播放器中显示。主要新增功能是此 MediaView 有一个队列,并且可以以全屏模式显示 MediaView。有多种功能可以开启或关闭,以自定义视图。
废弃的 Objective-C 版本: ABMediaView
目录
示例
要运行示例项目,请首先克隆仓库,然后从示例目录运行pod install
。
要求
- 需要iOS 9.0或更高版本
- 需要自动引用计数(ARC)
功能
- 显示图像、视频、GIF和音频
- 图像、视频和GIF的轻松懒加载
- 可最小化和关闭的全屏幕显示
- 用于全屏幕播放媒体视图的队列
- 跟踪缓冲、进度和拖动
- 自动缓存
未来功能
- 进度和加载视图
- 缩放
- 点击显示详细信息选项(而不是暂停)
如果您有更多功能建议,请通过Twitter向我发消息 @TrepIsLife!
安装
MediaView可以通过CocoaPods获得。要安装它,只需将以下行添加到您的Podfile中:
pod "MediaView"
您可以使用以下行将import MediaView添加到您的类中:
import MediaView
用法
调用管理器
可以使用共享的MediaQueue实例来呈现和消失MediaViews。
MediaQueue.shared
通过MediaQueue共享实例,有几个功能可以使用来排队、呈现和消失MediaViews。这些功能中的第一个用于将新的MediaView添加到队列中。如果没有MediaViews在队列中,则新排队的视图将被呈现。此外,可以使用dequeue方法从队列中删除一个MediaView。
// Add mediaView to the queue, and present if queue was previously empty
MediaQueue.shared.queue(mediaView: mediaView)
// Check if mediaView is in queue, and if so, remove it
MediaQueue.shared.dequeue(mediaView: mediaView)
其次,如果想要呈现一个MediaView并跳过队列,可以通过使用'present'函数来完成。调用此函数将消失当前正在呈现的MediaView,将提供的MediaView移到队列的前面,并呈现它。
// Present the mediaView with animation
MediaQueue.shared.present(mediaView: mediaView)
// Present the mediaView with the option to animate
MediaQueue.shared.present(mediaView: mediaView, animated: false)
另一方面,如果想要消失当前显示的MediaView,则可以调用'dismissCurrent'函数。如果视图被最小化,这将使视图移出屏幕。如果不是,则视图将消失。它附带一个完成闭包,以便在消失后执行操作。
MediaQueue.shared.dismissCurrent(animated: true) {
// Perform action after dismissal
}
以下函数结合了前面两个函数的功能。如果队列中有多个MediaViews,则可以通过在共享Manager上调用'presentNext'函数来显示下一个视图。此函数将消失当前MediaView,并呈现队列中的下一个视图。如果没有其他MediaViews在队列中,则在消失后不会采取任何进一步的操作。
MediaQueue.shared.presentNext()
初始化
MediaView可以通过编程方式初始化,或者通过在界面构建器中从UIImageView派生子类来实现。
// MediaView initiliazed using frame
let mediaView = MediaView(frame: view.frame)
MediaView懒惰地加载其媒体,所需要提供的只是源URL字符串。还有一个完成块,其中返回下载的媒体以供缓存。
// Set the image to be displayed in the mediaView, which will be downloaded and available for caching
mediaView.setImage(url: "http://yoursite.com/yourimage.jpg")
// Similar to the preview method, with a completion handler for when the image has completed downloading
mediaView.setImage(url: "http://yoursite.com/yourimage.jpg") { (image) in
// Take action after image has been downloaded and set to the mediaView
}
// Set the video to be displayed in the mediaView, which will be downloaded and available for caching
mediaView.setVideo(url: "http://yoursite/yourvideo.mp4")
// Set both the video url, and the thumbnail image for the mediaView, downloading both and making both available for caching
mediaView.setVideo(url: "http://yoursite/yourvideo.mp4", thumbnailUrl: "http://yoursite.com/yourimage.jpg")
// Set the video url for the mediaView, downloading it and making it available for caching, as well as the thumbnail image
mediaView.setVideo(url: "http://yoursite/yourvideo.mp4", thumbnail: UIImage(named: "image.png"))
如果正在加载数据文件夹中的文件,(比如说您从网络下载了一个视频,现在想要显示它),可以从目录中指定内容的URL,通过在MediaView上设置'fileFromDirectory'变量来实现。
// Designates that the file is sourced from the Documents Directory of the user's device
mediaView.isFileFromDirectory = true
MediaView也已支持GIF。要将GIF设置到MediaView中,只需通过URL字符串或Data设置它,它将被下载并设置到视图中。GIF作为UIImages提供,以便于存储。
// GIFs can be displayed in MediaView, where the GIF is sourced from the internet
mediaView.setGIF(url: "http://yoursite/yourgif.gif")
// GIFs can also be displayed via Data
mediaView.setGIF(data: data)
此外,MediaView 也支持音频功能。要将音频设置到 MediaView 中,只需通过 URL 字符串设置即可,它将被下载并设置为查看。
// Set the audio to be displayed in the mediaView
mediaView.setAudio(url: "http://yoursite/youraudio.mp4")
// Set both the audio and thumbnail url for the mediaView
mediaView.setAudio(url: "http://yoursite/youraudio.mp4", thumbnailUrl: "http://yoursite.com/yourimage.jpg")
// Set the audio url for the mediaView, as well as the thumbnail image
mediaView.setAudio(url: "http://yoursite/youraudio.mp4", thumbnail: UIImage(named: "thumbnail.png"))
关于整个应用中的播放功能,已增加新的功能,确保即使在设备处于振动模式下,用户仍然能够播放音频。这些变量设置使得在 MediaView 中播放媒体开始和结束时,音频将相应地启用或禁用,并且可以使用 MediaView 类的这些方法进行设置。
// Toggle this functionality to enable/disable sound to play when an MediaView begins playing, and the user's app is on silent
MediaView.audioTypeWhenPlay = .playWhenSilent
// In addition, toggle this functionality to enable/disable sound to play when an MediaView ends playing, and the user's app is on silent
MediaView.audioTypeWhenStop = .standard
加分功能: GIF 动画也可以用作视频和音频的缩略图。
// Set video for mediaView by URL, and set GIF as thumbnail by URL
mediaView.setVideo(url: "www.video.com/urlHere", thumbnailGIFUrl: "http://yoursite/yourgif.gif")
// Set video for mediaView by URL, and set GIF as thumbnail using Data
mediaView.setVideo(url: "www.video.com/urlHere", thumbnailGIFData: gifData)
// Set audio for mediaView by URL, and set GIF as thumbnail by URL
mediaView.setAudio(url: "www.video.com/urlHere", thumbnailGIFUrl: "http://yoursite/yourgif.gif")
// Set audio for mediaView by URL, and set GIF as thumbnail using Data
mediaView.setAudio(url: "www.video.com/urlHere", thumbnailGIFData: gifData)
新增了一个加分功能,即用户按下并长按 MediaView 时,会显示 GIF 预览。此功能目前适用于视频,并且可以通过以下方法实现。
let thumbnailImage: UIImage = ...
let gifData: Data = ...
// Set video for the MediaView, then the thumbnail UIImage, and the url for the preview GIF
mediaView.setVideo(url: "www.video.com/urlHere", thumbnail: thumbnailImage, previewGIFUrl: "http://yoursite/yourgif.gif")
// Set video for the MediaView, then the thumbnail UIImage, and the Data for the preview GIF
mediaView.setVideo(url: "www.video.com/urlHere", thumbnail: thumbnailImage, previewGIFData: gifData)
// Set video for the MediaView, then the url for the thumbnail image, and the url for the preview GIF
mediaView.setVideo(url: "www.video.com/urlHere", thumbnailUrl: "http://yoursite.com/yourimage.jpg", previewGIFUrl: "http://yoursite/yourgif.gif")
// Set video for the MediaView, then the url for the thumbnail image, and the Data for the preview GIF
mediaView.setVideo(url: "www.video.com/urlHere", thumbnailUrl: "http://yoursite.com/yourimage.jpg", previewGIFData: gifData)
非常重要 如果您的应用程序支持设备旋转,您的应用内所有的 MediaView 都需要接收旋转通知。因此,您可能需要实现类似以下内容的功能:这里。以下是我找到的一些最佳实现示例
方法 1:将以下代码块放在应用程序的根视图控制器中,或在初始化 MediaView 的视图控制器中。这将使 MediaView 能够知道用户的设备何时旋转,并相应地旋转。
// If 'viewWillTransitionToSize' is already implemented in your code, add the two MediaViewNotifications to your 'animate:alongsideTransition' block
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
Notification.post(.mediaViewWillRotateNotification)
}) { _ in
Notification.post(.mediaViewDidRotateNotification)
}
}
方法 2:在 AppDelegate 的 didFinishLaunchingWithOptions 方法中添加一个通知来捕获旋转。我不太喜欢这种实现方式,因为它无法捕获将要旋转的通知,因此会延迟 MediaView 的旋转。
// Notification which should be added to AppDelegate
NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
// Function called from notification
func rotated() {
Notification.post(.mediaViewWillRotateNotification)
Notification.post(.mediaViewDidRotateNotification)
}
关于屏幕旋转,如果您的应用程序的 UI 需要纵向,但您希望 MediaView 在横向上可见,则示例项目中包含处理这种情况的方法。这是一个流行的功能,因此包含在内以简化开发。
最后,当一个 MediaView 完成时并且想要清除它以显示新内容(例如在使用可复用单元格时),有一些方法可以轻松处理此任务。
// Removes image, video, audio and GIF data from the MediaView
- (void) resetMediaInView;
// Calls resetMediaInView and also resets the configurations in the MediaView
- (void) resetVariables;
自定义
MediaView 还提供了一种切换功能,允许用户将 MediaView 滑动以关闭或最小化到右下角。最小化允许用户在不离开 MediaView 的情况下与底层界面交互。如果已经在播放,视频和音频将继续播放,用户可以滑动右边关闭缩小的视图。可以通过设置 MediaView 上的 'swipeMode' 值来设置这些设置。
// User can swipe down to dismiss the MediaView
mediaView.swipeMode = .dismiss
// User can swipe down to minimize the MediaView
mediaView.swipeMode = .minimize
// MediaView should only be dismissed if the 'X' close button is pressed, and not swipable.
mediaView.swipeMode = .none
MediaView 还为视频和音频播放提供了一些选项。其中一项选项是 'allowLooping',该选项用于切换媒体在到达末尾后是否应该重新播放。另一个选项是 'autoPlayAfterPresentation',它用于切换媒体在展示后是否应该自动播放。默认情况下,MediaView 将 'shouldAutoPlayAfterPresentation' 设置为 true。
// Toggle looping functionality
mediaView.allowLooping = true
// Toggle functionality to automatically play videos after presenting
mediaView.shouldAutoPlayAfterPresentation = true
如果您希望全屏 MediaView 在视频播放完毕后关闭,可以在 MediaView 上将值 'shouldDismissAfterFinish' 设置为 true。对于全屏 MediaView,此功能将优先于 'allowLooping'。
mediaView.shouldDismissAfterFinishedPlaying = true
MediaView 在显示视频和音频时显示进度条也有几种启用的选项和编辑选项。
// Enable progress track to show at the bottom of the MediaView
mediaView.shouldShowTrack = true
// Toggles the funtionality which would show remaining time instead of total time on the right label on the track
mediaView.shouldDisplayRemainingTime = true
// Change the font for the labels on the track
let font: UIFont = ...
mediaView.trackFont = font
此外,在全屏显示时,可以将标题和详情设置为显示在MediaView的顶部。
// Set just the title to show
mediaView.setTitle("Fight Club")
// Set a title and details label to show
mediaView.setTitle("What's My Age Again", details: "By: Blink-182")
MediaView具有主题颜色,它将改变轨道以及播放按钮和失败指示器的颜色。
// Changing the theme color changes the color of the play and failed indicators as well as the progress track
mediaView.themeColor = .red
MediaView将根据在视图中设置的contentMode显示图片、视频和GIF。然而,还可以设置contentMode为aspectFill,同时videoGravity设置为aspectFit。
// Setting the contentMode to aspectFit will set the videoGravity to aspectFit as well
mediaView.contentMode = .scaleAspectFit
// If you desire to have the image to fill the view, however you would like the videoGravity to be aspectFit, then you can implement this functionality
mediaView.contentMode = .scaleAspectFill
mediaView.videoAspectFit = true
要实现MediaView全屏功能,需要在相应的MediaView上设置'shouldDisplayFullscreen'值为true。默认情况下,此值为false。
mediaView.shouldDisplayFullscreen = true
如果您想为MediaView使用自定义的播放按钮或失败指示器,应在MediaView上设置'customPlayButton'、'customFailedButton'和'customMusicButton'变量。
// Set a custom image for the play button visible on MediaView's with video or audio
mediaView.customPlayButton = UIImage(named: "play.png")
// Set a custom image for when the mediaView fails to play media
mediaView.customFailButton = UIImage(named: "failed.png")
// Set a custom image for the play button visible for MediaView's specifically with audio, supercedes the customPlayButton
mediaView.customMusicButton = UIImage(named: "playMusic.png")
还有隐藏关闭按钮的功能,这样在全屏弹出的mediaView中就不会显示。此功能仅在'swipeMode'设置为'.minimize'或'dismiss'时允许,否则将没有其他关闭弹出窗口的方法。另外,由于横屏模式下禁用了滑动,关闭按钮仍然在横屏视图中可见。
mediaView.shouldHideCloseButton = true
类似地,还有隐藏可播放媒体(视频/音频)的播放按钮的功能。如果您想将MediaView用作背景视频播放器,这个功能很有用。
mediaView.shouldHidePlayButton = true
在屏幕上存在你不希望隐藏的UIStatusBar,或者需要在屏幕顶部为其他视图保留空间时,MediaView具有将屏幕顶部的子视图偏移以避免隐藏这些视图的能力。设置MediaView的'topOffset'属性将向下移动'closeButton'和其他顶部锚点视图。再次强调,最大的使用案例是设置'topOffset'属性为20px,以避免覆盖UIStatusBar。
mediaView.topBuffer = 20
默认情况下,最小化MediaView和屏幕底部之间有12px的缓冲区。可以通过调整MediaView的'bottomBuffer'值来增加更多空间。这样做可以将mediaView显示在UITabBars和UIToolbars等视图之上,从而避免覆盖需要底边空间保留的视图。
mediaView.bottomBuffer = 0
为了使这些缓冲区更容易使用,我已扩展CGFloat以包括以下值。
// .statusBarBuffer = 20px OR 44px if iPhone X
// .navigationBarBuffer = 44px
// .statusAndNavigationBuffer = 64px OR 104px if iPhone X
// .tabBarBuffer = 49px
mediaView.topBuffer = .statusBarBuffer
mediaView.bottomBuffer = .tabBarBuffer
MediaView具有设置全屏弹出窗口起始框架的功能。此功能与'shouldDisplayFullscreen'结合使用很有用,因为它将允许弹出窗口从具有'shouldDisplayFullscreen'启用的MediaView框架中启动。
// Rect that specifies where the mediaView's frame will originate from when presenting, and will be converted into its position in the mainWindow
mediaView.originRect = view.frame
// Rect that specifies where the mediaView's frame will originate from when presenting, and is already converted into its position in the mainWindow
mediaView.originRectConverted = view.frame
但是,如果你正在使用动态UI,因此无法确定MediaView的originRect,可以将属性'shouldPresentFromOriginRect'设置为true。启用此功能后,全屏MediaView将从展示它的MediaView框架中弹出。如果'shouldPresentFromOriginRect'被启用,那么就没有必要设置'originRect'或'originRectConverted',因为这个属性覆盖了这两个属性。
mediaView.shouldPresentFromOriginRect = true
可以指定MediaView是否将显示在可重用的视图中,这将允许MediaView在不重用的情况下获得更好的UI转换性能。默认情况下假设MediaView将被重用,所以可以设置值为'imageViewNotReused'为true(如果未重用)。
mediaView.imageViewNotReused = true
当MediaView的'isMinimizable'值启用时,可以自定义最小化视图的大小比例。此比例的默认值为预设的ABMediaViewRatioPresetLandscape,它是一个宽屏16:9的纵横比。还有预设的方形(ABMediaViewRatioPresetSquare)和横屏9:16(ABMediaViewRatioPresetPortrait)选项。
// Aspect ratio of the minimized view
mediaView.minimizedAspectRatio = .landscapeRatio
mediaView.minimizedAspectRatio = .square
mediaView.minimizedAspectRatio = .portrait
mediaView.minimizedAspectRatio = .landscapeRatio
mediaView.minimizedAspectRatio = (6.0 / 5.0) // Height/Width
与上述选项配合使用,还可以指定最小化视图拉伸覆盖屏幕宽度的比例。默认情况下,最小化视图拉伸覆盖半个屏幕宽度(0.5比例)。此功能在调整最小化视图大小,且MediaView的'minimizedAspectRatio'大于横向时非常有用。
// Ratio of the screen's width that the minimized view will stretch across
mediaView.minimizedWidthRatio = 0.5
缓存
如果你的项目没有缓存系统,并且需要自动化的缓存系统,MediaView就具有这个功能!使用MediaView,图像和GIF保存在内存中的NSCache中,而视频和音频文件则保存到磁盘中。有几种选项可以用于管理缓存,但让我们先从如何启用自动缓存开始。这可以通过在CacheManager共享实例中设置'cacheMediaWhenDownloaded'的值来完成。此外,通过在MediaView上设置'shouldCacheStreamedMedia',视频将被缓存,因为它们在流式传输时会被缓存。目前,视频是在缓冲区从流中完全加载时进行缓存的。音频仍在开发中。
// Saves media to cache
CacheManager.cacheMediaWhenDownloaded = true
// Caches videos when streaming
mediaView.shouldCacheStreamedMedia = true
如果你想要将视频和音频预加载到缓存中,你可以通过在ABMediaView的共享Manager上指定'shouldPreloadPlayableMedia',使MediaView始终在下设音频或视频URL到MediaView时下载视频和音频。但是,如果你只想在单个实例的基础上预加载视频或音频,可以使用'preloadVideo'和'preloadAudio'来完成。如果你不希望预加载视频或音频,只需将'shouldCacheStreamedMedia'设置为true即可,那么视频和音频将进行流式传输。
// Ensure that all video and audio is preloaded before playing, instead of just streaming (works best if your app plays videos/audio that is short in length)
mediaView.shouldPreloadPlayableMedia = true
// Preload the video for this specific mediaView
mediaView.preloadVideo()
// Preload the audio for this specific mediaView
mediaView.preloadAudio()
如果想要清除图像和GIF的内存缓存,只需在MediaView共享Manager上将'shouldCacheMedia'设置为false即可。但是,为了清除用于Documents目录和tmp目录的磁盘缓存,CacheManager提供了一个简单的类函数来清除这些缓存。
// Clear all of the documents directory of cached items in the ABMedia folder
CacheManager.clear(directory: .all)
// Clear the video directory of cached items in the ABMedia folder
CacheManager.clear(directory: .video)
// Clear the audio directory of cached items in the ABMedia folder
CacheManager.clear(directory: .audio)
// Clear all of the temp directory of cached items
CacheManager.clear(directory: .temp)
委托
有一个委托,其中有可选的方法来确定MediaView何时播放或暂停了AVPlayer中的视频,以及视图最小化的程度。
/// A listener to know what percentage that the view has minimized, at a value from 0 to 1
func mediaView(_ mediaView: MediaView, didChangeOffset offsetPercentage: CGFloat)
/// When the mediaView begins playing a video
func didPlayMedia(for mediaView: MediaView)
/// When the mediaView fails to play a video
func didFailToPlayMedia(for mediaView: MediaView)
/// When the mediaView pauses a video
func didPauseMedia(for mediaView: MediaView)
此外,还有一些委托方法可以帮助判断MediaView即将显示、已被显示、即将消失和已消失的情况。
/// Called when the mediaView has begun the presentation process
func willPresent(mediaView: MediaView)
/// Called when the mediaView has been presented
func didPresent(mediaView: MediaView)
/// Called when the mediaView has begun the dismissal process
func willDismiss(mediaView: MediaView)
/// Called when the mediaView has completed the dismissal process. Useful if not looking to utilize the dismissal completion block
func didDismiss(mediaView: MediaView)
如果想要确定媒体视图是否已播放完其视频,可以利用'didFinishPlayableMedia:withLoop:'方法。这还指定了媒体视图在播放完成后是否设置为循环。
/// When the mediaView finishes playing a video, and whether it looped
func didFinishPlayableMedia(for mediaView: MediaView, withLoop didLoop: Bool)
以下委托方法在判断媒体视图是否已经开始、进行中或已完成最小化时很有用。此用法的一个流行场景是根据MediaView是否可见来调整UIStatusBarStyle。
/// Called when the mediaView is in the process of minimizing, and is about to make a change in frame
func willChangeMinimization(for mediaView: MediaView)
/// Called when the mediaView is in the process of minimizing, and has made a change in frame
func didChangeMinimization(for mediaView: MediaView)
/// Called before the mediaView ends minimizing, and informs whether the minimized view will snap to minimized or fullscreen mode
func willEndMinimizing(for mediaView: MediaView, atMinimizedState isMinimized: Bool)
/// Called when the mediaView ends minimizing, and informs whether the minimized view has snapped to minimized or fullscreen mode
func didEndMinimizing(for mediaView: MediaView, atMinimizedState isMinimized: Bool)
另一方面,如果一个MediaView上的'swipeMode'值设置为'.dismiss',会提供委托方法来监听MediaView开始和结束消失过程的时间。
/// Called when the mediaView is in the process of minimizing, and is about to make a change in frame
func willChangeDismissing(for mediaView: MediaView)
/// Called when the mediaView is in the process of minimizing, and has made a change in frame
func didChangeDismissing(for mediaView: MediaView)
/// Called before the mediaView ends minimizing, and informs whether the minimized view will snap to minimized or fullscreen mode
func willEndDismissing(for mediaView: MediaView, withDismissal didDismiss: Bool)
/// Called when the mediaView ends minimizing, and informs whether the minimized view has snapped to minimized or fullscreen mode
func didEndDismissing(for mediaView: MediaView, withDismissal didDismiss: Bool)
如果想要检测MediaView中包含的图像是否已设置或更改,可以监听以下委托方法。
/// Called when the 'image' value of the UIImageView has been set
func mediaView(_ mediaView: MediaView, didSetImage image: UIImage)
如果您想要缓存通过 MediaView 下载的图片、视频或 GIF,已经通过代理来处理这些对象。
/// Called when the mediaView has completed downloading the image from the web
func mediaView(_ mediaView: MediaView, didDownloadImage image: UIImage)
/// Called when the mediaView has completed downloading the video from the web
func mediaView(_ mediaView: MediaView, didDownloadVideo video: URL)
/// Called when the mediaView has completed downloading the audio from the web
func mediaView(_ mediaView: MediaView, didDownloadAudio audio: URL)
/// Called when the mediaView has completed downloading the gif from the web
func mediaView(_ mediaView: MediaView, didDownloadGif gif: UIImage)
最后,如果您在 MediaView 上设置了标题或详情值,您可以在以下代理方法中接收这些标签的触摸。
/// Called when the user taps the title label
func handleTitleSelection(in mediaView: MediaView)
/// Called when the user taps the details label
func handleDetailsSelection(in mediaView: MediaView)
补充库
- ABVolumeControl:通过不同的样式覆盖 MPVolumeView,并提供一个代理以实现自定义体积视图。
- ABUtils:一组有用的方法,可以集成到任何项目中。
- ABKeyboardAccessory:UIView 的子类,可以用作“输入辅助”,并提供代理方法来了解键盘框架何时改变,例如出现和消失。
作者
Andrew Boryk,[email protected]
在 Twitter 上联系我:@TrepIsLife
许可证
MediaView 使用 MIT 许可证提供。有关更多信息,请参阅 LICENSE 文件。