diff --git a/VideoThumbnailSelectionView.xcodeproj/project.pbxproj b/VideoThumbnailSelectionView.xcodeproj/project.pbxproj index d72c175..70f0422 100644 --- a/VideoThumbnailSelectionView.xcodeproj/project.pbxproj +++ b/VideoThumbnailSelectionView.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -111,10 +111,11 @@ }; }; buildConfigurationList = E91E336E1D017AB2004EF96C /* Build configuration list for PBXProject "VideoThumbnailSelectionView" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 11.0"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = E91E336A1D017AB2004EF96C; @@ -195,6 +196,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -236,6 +238,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -254,7 +257,11 @@ INFOPLIST_FILE = VideoThumbnailSelectionView/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.sarpsolakoglu.VideoThumbnailSelectionView; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -273,7 +280,11 @@ INFOPLIST_FILE = VideoThumbnailSelectionView/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.sarpsolakoglu.VideoThumbnailSelectionView; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -299,6 +310,7 @@ E91E337E1D017AB2004EF96C /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/VideoThumbnailSelectionView/SelectionThumb.swift b/VideoThumbnailSelectionView/SelectionThumb.swift index 640483f..94d2a45 100644 --- a/VideoThumbnailSelectionView/SelectionThumb.swift +++ b/VideoThumbnailSelectionView/SelectionThumb.swift @@ -8,13 +8,14 @@ import UIKit -class SelectionThumb: UIView { +final class SelectionThumb: UIView { var previewImageView: UIImageView! override init(frame: CGRect) { - previewImageView = UIImageView(frame: CGRectMake(2, 2, frame.size.width - 4, frame.size.height - 4)) + previewImageView = UIImageView(frame: CGRect(x: 2, y: 2, width: frame.size.width - 4, height: frame.size.height - 4)) super.init(frame: frame) - backgroundColor = .whiteColor() + + backgroundColor = .white addSubview(previewImageView) } diff --git a/VideoThumbnailSelectionView/VideoThumbnailSelectionView.swift b/VideoThumbnailSelectionView/VideoThumbnailSelectionView.swift index 7684317..6a244f2 100644 --- a/VideoThumbnailSelectionView/VideoThumbnailSelectionView.swift +++ b/VideoThumbnailSelectionView/VideoThumbnailSelectionView.swift @@ -9,7 +9,7 @@ import UIKit import AVFoundation -@objc public class VideoThumbnailSelectionView: UIView { +@objc public final class VideoThumbnailSelectionView: UIView { //MARK: - Private params @@ -33,8 +33,8 @@ import AVFoundation didSet { if asset != nil { generator = AVAssetImageGenerator(asset: asset!) - generator!.requestedTimeToleranceBefore = kCMTimeZero - generator!.requestedTimeToleranceAfter = kCMTimeZero + generator!.requestedTimeToleranceBefore = CMTime.zero + generator!.requestedTimeToleranceAfter = CMTime.zero } else { generator = nil } @@ -47,7 +47,9 @@ import AVFoundation View calls this block when selection changes. Should be set before loading video. */ public var onUpdatedImage: (UIImage)->() = { _ in } - + + /// This one tells the time at which the thumbnail is for + public var imageTime: Float64 = 0 /** The opacity of the black shade color on the thumbnails. Defaults to 0.5. @@ -75,7 +77,7 @@ import AVFoundation /** The color of the corner of the view. */ - public var cornerColor: UIColor = .whiteColor() { + public var cornerColor: UIColor = .white { didSet { view.backgroundColor = cornerColor } @@ -84,7 +86,7 @@ import AVFoundation /** The corner thickness. */ - public var cornerInsets: UIEdgeInsets = UIEdgeInsetsMake(8.0, 8.0, 8.0, 8.0) { + public var cornerInsets: UIEdgeInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0) { didSet { topMargin.constant = cornerInsets.top leftMargin.constant = cornerInsets.left @@ -112,23 +114,20 @@ import AVFoundation - completion: UIImage is returned in this block if succesful. - failure: This block is called in case of failure. */ - @objc public func snapshot(forSecond second: Float64, completion:(UIImage)->(), failure:(()->())?) { + @objc public func snapshot(forSecond second: Float64, completion: @escaping (UIImage)->(), failure:(()->())?) { guard let generator = generator else { - if failure != nil { - failure!() - } + failure?() return } - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { - if let image = self.generateThumbnail(generator: generator, second: second) { - dispatch_async(dispatch_get_main_queue(), { + + DispatchQueue.global().async { + if let image = self.generateThumbnail(generator: generator, second: second) { + DispatchQueue.main.async { completion(image) - }) + } return } else { - if failure != nil { - failure!() - } + failure?() return } } @@ -147,27 +146,26 @@ import AVFoundation if videoLoaded { return } if videoLoading { return } - guard let assetVideoTrack : AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeVideo)[0] else { return } + guard let assetVideoTrack : AVAssetTrack = asset.tracks(withMediaType: AVMediaType.video)[0] else { return } self.asset = asset activityIndicator.startAnimating() - - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { let size = assetVideoTrack.naturalSize let thumbnailHeight = self.thumbnailView.bounds.size.height let videoAspect = size.width / size.height let thumbnailWidth = thumbnailHeight * videoAspect let generator = AVAssetImageGenerator(asset: asset) - generator.requestedTimeToleranceBefore = kCMTimeZero - generator.requestedTimeToleranceAfter = kCMTimeZero + generator.requestedTimeToleranceBefore = .zero + generator.requestedTimeToleranceAfter = .zero let thumbnailCount = Int(ceil(self.thumbnailView.bounds.size.width / thumbnailWidth)) let videoDuration = CMTimeGetSeconds(asset.duration) let sampleInterval = videoDuration / Float64(thumbnailCount) - self.selectionThumb = SelectionThumb(frame: CGRectMake(self.thumbnailView.frame.origin.x, self.thumbnailView.frame.origin.y, thumbnailWidth, thumbnailHeight)) + self.selectionThumb = SelectionThumb(frame: CGRect(x: self.thumbnailView.frame.origin.x, y: self.thumbnailView.frame.origin.y, width: thumbnailWidth, height: thumbnailHeight)) self.scrollOptions = ScrollOptions(startPoint: self.leftMargin.constant + thumbnailWidth / 2, endPoint: self.view.bounds.size.width - thumbnailWidth / 2 - self.rightMargin.constant) @@ -180,8 +178,8 @@ import AVFoundation self.selectionThumb!.previewImageView.image = image self.view.addSubview(self.selectionThumb!) } - let imageView = UIImageView(frame: CGRectMake(currentX, 0.0, thumbnailWidth, thumbnailHeight)) - imageView.contentMode = .ScaleAspectFill + let imageView = UIImageView(frame: CGRect(x: currentX, y: 0.0, width: thumbnailWidth, height: thumbnailHeight)) + imageView.contentMode = .scaleAspectFill imageView.image = image self.thumbnailView.addSubview(imageView) self.thumbnails.append(imageView) @@ -191,7 +189,8 @@ import AVFoundation self.activityIndicator.stopAnimating() self.videoLoaded = true self.videoLoading = false - self.didScrollToPercent(0, override: true) + self.didScrollToPercent(percent: 0, override: true) + } } @@ -228,27 +227,28 @@ import AVFoundation snapshot(forSecond: videoDuration * Float64(percent), completion: {[weak self] image in self?.selectionThumb?.previewImageView.image = image self?.onUpdatedImage(image) + self?.imageTime = videoDuration * Float64(percent) self?.shouldUpdateFrame = true }, failure: {[weak self] in self?.shouldUpdateFrame = true }) } - private func generateThumbnail(generator generator: AVAssetImageGenerator, second: Float64) -> UIImage? { - let time = CMTimeMake(Int64(second * 60), 60) + func generateThumbnail(generator: AVAssetImageGenerator, second: Float64) -> UIImage? { + let time = CMTimeMake(value: Int64(second * 60), timescale: 60) do { - let imgRef = try generator.copyCGImageAtTime(time, actualTime: nil) - return UIImage(CGImage: imgRef) + let imgRef = try generator.copyCGImage(at: time, actualTime: nil) + return UIImage(cgImage: imgRef) } catch { return nil } } - //MARK: - Private loader functions - private func setup(frame frame: CGRect?) { + //MARK: - Private lounctions + private func setup(frame: CGRect?) { loadNib() if frame != nil { - view.frame = CGRectMake(0, 0, CGRectGetWidth(frame!), CGRectGetHeight(frame!)) + view.frame = CGRect(x: 0, y: 0, width: frame!.width, height: frame!.height) } addSubview(view) pinView() @@ -256,33 +256,33 @@ import AVFoundation } private func loadNib() { - NSBundle(forClass: VideoThumbnailSelectionView.self).loadNibNamed("VideoThumbnailSelectionView", owner: self, options: nil) + Bundle(for: VideoThumbnailSelectionView.self).loadNibNamed("VideoThumbnailSelectionView", owner: self, options: nil) } private func pinView() { view.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint(item: view, attribute: .Leading, relatedBy: .Equal, toItem: self , attribute: .Leading, multiplier: 1, constant: 0).active = true - NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: 0).active = true - NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0).active = true - NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0).active = true + NSLayoutConstraint(item: view, attribute: .leading, relatedBy: .equal, toItem: self , attribute: .leading, multiplier: 1, constant: 0).isActive = true + NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0).isActive = true + NSLayoutConstraint(item: view!, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0).isActive = true + NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0).isActive = true setNeedsLayout() layoutIfNeeded() } //MARK: - Touches - override public func touchesBegan(touches: Set, withEvent event: UIEvent?) { + public override func touchesBegan(_ touches: Set, with event: UIEvent?) { if scrollOptions == nil { return } guard let selectionThumb = selectionThumb else { return } if let touch = touches.first { - let loc = touch.locationInView(thumbnailView) - if CGRectContainsPoint(selectionThumb.frame, loc) { + let loc = touch.location(in: thumbnailView) + if selectionThumb.frame.contains(loc) { scrollOptions!.thumbStartLocation = selectionThumb.center.x scrollOptions!.scrollStartLocation = loc.x scrollOptions!.currentlyScrolling = true if zoomAnimationScale > 1.0 { selectionThumb.layer.removeAllAnimations() - UIView.animateWithDuration(0.2, animations: { + UIView.animate(withDuration: 0.2, animations: { selectionThumb.layer.transform = CATransform3DScale(CATransform3DIdentity, self.zoomAnimationScale, self.zoomAnimationScale, 1.0) }) } @@ -291,27 +291,27 @@ import AVFoundation } - override public func touchesMoved(touches: Set, withEvent event: UIEvent?) { + public override func touchesMoved(_ touches: Set, with event: UIEvent?) { if scrollOptions == nil { return } if !(scrollOptions!.currentlyScrolling) { return } guard let selectionThumb = selectionThumb else { return } if let touch = touches.first { - let loc = touch.locationInView(thumbnailView) - let newX = scrollOptions!.getNewLocationAccordingToPoint(loc.x) + let loc = touch.location(in: thumbnailView) + let newX = scrollOptions!.getNewLocationAccordingToPoint(x: loc.x) selectionThumb.center.x = newX //call view - didScrollToPercent(scrollOptions!.scrollPercent, override: false) + didScrollToPercent(percent: scrollOptions!.scrollPercent, override: false) } } - override public func touchesEnded(touches: Set, withEvent event: UIEvent?) { + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { if scrollOptions == nil { return } if !(scrollOptions!.currentlyScrolling) { return } guard let selectionThumb = selectionThumb else { return } - didScrollToPercent(scrollOptions!.scrollPercent, override: true) + didScrollToPercent(percent: scrollOptions!.scrollPercent, override: true) if zoomAnimationScale > 1.0 { selectionThumb.layer.removeAllAnimations() - UIView.animateWithDuration(0.2, animations: { + UIView.animate(withDuration: 0.2, animations: { selectionThumb.layer.transform = CATransform3DIdentity }) }