diff --git a/ABVideoRangeSlider.podspec b/ABVideoRangeSlider.podspec index 6b6b74a..2439c9d 100644 --- a/ABVideoRangeSlider.podspec +++ b/ABVideoRangeSlider.podspec @@ -8,8 +8,8 @@ Pod::Spec.new do |s| s.name = 'ABVideoRangeSlider' - s.version = '0.1.5' - s.summary = 'A simple range slider for trimming videos written in Swift 3. Includes a Progress Indicator.' + s.version = '0.1.6' + s.summary = 'A simple range slider for trimming videos written in Swift 4. Includes a Progress Indicator.' # This description is used to generate tags and improve search results. # * Think: What does it do? Why did you write it? What is the focus? @@ -20,13 +20,12 @@ Pod::Spec.new do |s| s.description = 'Display thumbnails and a slider for trimming videos in iOS. Includes a Progress Indicator' s.homepage = 'https://github.com/AppsBoulevard/ABVideoRangeSlider' - # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'Oscar J. Irun' => 'oscarjiv91@gmail.com' } s.source = { :git => 'https://github.com/AppsBoulevard/ABVideoRangeSlider.git', :tag => s.version.to_s } s.social_media_url = 'https://twitter.com/AppsBoulevard' - s.ios.deployment_target = '8.0' + s.ios.deployment_target = '12.0' s.source_files = 'ABVideoRangeSlider/Classes/**/*', 'ABVideoRangeSlider/Assets/*' diff --git a/ABVideoRangeSlider/Classes/ABBorder.swift b/ABVideoRangeSlider/Classes/ABBorder.swift index fae2746..97208a8 100644 --- a/ABVideoRangeSlider/Classes/ABBorder.swift +++ b/ABVideoRangeSlider/Classes/ABBorder.swift @@ -11,23 +11,23 @@ import UIKit class ABBorder: UIView { var imageView = UIImageView() - + override init(frame: CGRect) { super.init(frame: frame) - + let bundle = Bundle(for: ABStartIndicator.self) let image = UIImage(named: "BorderLine", in: bundle, compatibleWith: nil) - + imageView.frame = self.bounds imageView.image = image - imageView.contentMode = UIViewContentMode.scaleToFill + imageView.contentMode = .scaleToFill self.addSubview(imageView) } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func layoutSubviews() { super.layoutSubviews() imageView.frame = self.bounds diff --git a/ABVideoRangeSlider/Classes/ABEndIndicator.swift b/ABVideoRangeSlider/Classes/ABEndIndicator.swift index 2286644..c10ebed 100644 --- a/ABVideoRangeSlider/Classes/ABEndIndicator.swift +++ b/ABVideoRangeSlider/Classes/ABEndIndicator.swift @@ -9,24 +9,24 @@ import UIKit class ABEndIndicator: UIView { - + public var imageView = UIImageView() - + override init(frame: CGRect) { super.init(frame: frame) self.isUserInteractionEnabled = true - + let bundle = Bundle(for: ABStartIndicator.self) let image = UIImage(named: "EndIndicator", in: bundle, compatibleWith: nil) - + imageView.frame = self.bounds imageView.image = image - imageView.contentMode = UIViewContentMode.scaleToFill + imageView.contentMode = .scaleToFill self.addSubview(imageView) } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + } diff --git a/ABVideoRangeSlider/Classes/ABProgressIndicator.swift b/ABVideoRangeSlider/Classes/ABProgressIndicator.swift index 5eef82a..6b1caaf 100644 --- a/ABVideoRangeSlider/Classes/ABProgressIndicator.swift +++ b/ABVideoRangeSlider/Classes/ABProgressIndicator.swift @@ -9,24 +9,24 @@ import UIKit class ABProgressIndicator: UIView { - + var imageView = UIImageView() - + override init(frame: CGRect) { super.init(frame: frame) - + let bundle = Bundle(for: ABStartIndicator.self) let image = UIImage(named: "ProgressIndicator", in: bundle, compatibleWith: nil) imageView.frame = self.bounds imageView.image = image - imageView.contentMode = UIViewContentMode.scaleToFill + imageView.contentMode = .scaleToFill self.addSubview(imageView) } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func layoutSubviews() { super.layoutSubviews() imageView.frame = self.bounds @@ -37,9 +37,9 @@ class ABProgressIndicator: UIView { y: 0, width: self.frame.size.width * 2, height: self.frame.size.height) - if frame.contains(point){ + if frame.contains(point) { return self - }else{ + } else { return nil } } diff --git a/ABVideoRangeSlider/Classes/ABStartIndicator.swift b/ABVideoRangeSlider/Classes/ABStartIndicator.swift index b85c29b..648b439 100644 --- a/ABVideoRangeSlider/Classes/ABStartIndicator.swift +++ b/ABVideoRangeSlider/Classes/ABStartIndicator.swift @@ -9,19 +9,19 @@ import UIKit class ABStartIndicator: UIView { - + var imageView = UIImageView() - + override init(frame: CGRect) { super.init(frame: frame) self.isUserInteractionEnabled = true - + let bundle = Bundle(for: ABStartIndicator.self) let image = UIImage(named: "StartIndicator", in: bundle, compatibleWith: nil) - + imageView.frame = self.bounds imageView.image = image - imageView.contentMode = UIViewContentMode.scaleToFill + imageView.contentMode = .scaleToFill self.addSubview(imageView) } diff --git a/ABVideoRangeSlider/Classes/ABThumbnailsManager.swift b/ABVideoRangeSlider/Classes/ABThumbnailsManager.swift index b2cdda9..409149f 100644 --- a/ABVideoRangeSlider/Classes/ABThumbnailsManager.swift +++ b/ABVideoRangeSlider/Classes/ABThumbnailsManager.swift @@ -10,72 +10,69 @@ import UIKit import AVFoundation class ABThumbnailsManager: NSObject { - + var thumbnailViews = [UIImageView]() - private func addImagesToView(images: [UIImage], view: UIView){ - + private func addImagesToView(images: [UIImage], view: UIView) { + self.thumbnailViews.removeAll() var xPos: CGFloat = 0.0 var width: CGFloat = 0.0 - for image in images{ + for image in images { DispatchQueue.main.async { - if xPos + view.frame.size.height < view.frame.width{ + if xPos + view.frame.size.height < view.frame.width { width = view.frame.size.height - }else{ + } else { width = view.frame.size.width - xPos } - + let imageView = UIImageView(image: image) imageView.alpha = 0 - imageView.contentMode = UIViewContentMode.scaleAspectFill + imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true imageView.frame = CGRect(x: xPos, y: 0.0, width: width, height: view.frame.size.height) self.thumbnailViews.append(imageView) - - + view.addSubview(imageView) UIView.animate(withDuration: 0.2, animations: {() -> Void in imageView.alpha = 1.0 }) - view.sendSubview(toBack: imageView) + view.sendSubviewToBack(imageView) xPos = xPos + view.frame.size.height } } } - + private func thumbnailCount(inView: UIView) -> Int { - - var num : Double = 0; - + + var num: Double = 0 + DispatchQueue.main.sync { num = Double(inView.frame.size.width) / Double(inView.frame.size.height) } return Int(ceil(num)) } - - func updateThumbnails(view: UIView, videoURL: URL, duration: Float64) -> [UIImageView]{ + + func updateThumbnails(view: UIView, videoURL: URL, duration: Float64) -> [UIImageView] { var thumbnails = [UIImage]() var offset: Float64 = 0 - - for view in self.thumbnailViews{ - DispatchQueue.main.sync - { + for view in self.thumbnailViews { + DispatchQueue.main.sync { view.removeFromSuperview() } } - + let imagesCount = self.thumbnailCount(inView: view) - - for i in 0.. UIImage{ + static func thumbnailFromVideo(videoUrl: URL, time: CMTime) -> UIImage { let asset: AVAsset = AVAsset(url: videoUrl) as AVAsset let imgGenerator = AVAssetImageGenerator(asset: asset) imgGenerator.appliesPreferredTrackTransform = true - do{ + do { let cgImage = try imgGenerator.copyCGImage(at: time, actualTime: nil) let uiImage = UIImage(cgImage: cgImage) return uiImage - }catch{ - + } catch { + } return UIImage() } - - static func videoDuration(videoURL: URL) -> Float64{ + + static func videoDuration(videoURL: URL) -> Float64 { let source = AVURLAsset(url: videoURL) return CMTimeGetSeconds(source.duration) } - + } diff --git a/ABVideoRangeSlider/Classes/ABVideoRangeSlider.swift b/ABVideoRangeSlider/Classes/ABVideoRangeSlider.swift index abeefc9..afbee91 100644 --- a/ABVideoRangeSlider/Classes/ABVideoRangeSlider.swift +++ b/ABVideoRangeSlider/Classes/ABVideoRangeSlider.swift @@ -11,19 +11,19 @@ import UIKit @objc public protocol ABVideoRangeSliderDelegate: class { func didChangeValue(videoRangeSlider: ABVideoRangeSlider, startTime: Float64, endTime: Float64) func indicatorDidChangePosition(videoRangeSlider: ABVideoRangeSlider, position: Float64) - + @objc optional func sliderGesturesBegan() @objc optional func sliderGesturesEnded() } public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { - private enum DragHandleChoice { + private enum DragHandle { case start case end } - - public weak var delegate: ABVideoRangeSliderDelegate? = nil + + public weak var delegate: ABVideoRangeSliderDelegate? var startIndicator = ABStartIndicator() var endIndicator = ABEndIndicator() @@ -34,76 +34,79 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { public var startTimeView = ABTimeView() public var endTimeView = ABTimeView() + + let thumbnailsManager = ABThumbnailsManager() var duration: Float64 = 0.0 var videoURL = URL(fileURLWithPath: "") - var progressPercentage: CGFloat = 0 // Represented in percentage - var startPercentage: CGFloat = 0 // Represented in percentage - var endPercentage: CGFloat = 100 // Represented in percentage + var progressPercentage: CGFloat = 0 /// Represented in percentage (0-100) + var startPercentage: CGFloat = 0 /// Represented in percentage (0-100) + var endPercentage: CGFloat = 100 /// Represented in percentage (0-100) let topBorderHeight: CGFloat = 5 let bottomBorderHeight: CGFloat = 5 + let timeviewHeight:CGFloat = 25 let indicatorWidth: CGFloat = 20.0 public var minSpace: Float = 1 // In Seconds public var maxSpace: Float = 0 // In Seconds - - public var isProgressIndicatorSticky: Bool = false - public var isProgressIndicatorDraggable: Bool = true - + + public var isProgressIndicatorDraggable = false + public var rangeHandleChangeResetsProgress = false /// range handle change will effect progress indicator + public var rangeHandlesConstrainProgress = false /// progress indicator is bounded by start/end range + var isUpdatingThumbnails = false var isReceivingGesture: Bool = false - - public enum ABTimeViewPosition{ + + public enum ABTimeViewPosition { case top case bottom } override public func awakeFromNib() { super.awakeFromNib() - self.setup() + setup() } - override init(frame: CGRect) { + override public init(frame: CGRect) { super.init(frame: frame) - self.setup() + setup() } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } - private func setup(){ - self.isUserInteractionEnabled = true + private func setup() { + isUserInteractionEnabled = true // Setup Start Indicator - let startDrag = UIPanGestureRecognizer(target:self, + let startDrag = UIPanGestureRecognizer(target: self, action: #selector(startDragged(recognizer:))) startIndicator = ABStartIndicator(frame: CGRect(x: 0, y: -topBorderHeight, width: 20, - height: self.frame.size.height + bottomBorderHeight + topBorderHeight)) - startIndicator.layer.anchorPoint = CGPoint(x: 1, y: 0.5) + height: frame.size.height + bottomBorderHeight + topBorderHeight)) + startIndicator.layer.anchorPoint = CGPoint(x: 0, y: 0.5) startIndicator.addGestureRecognizer(startDrag) - self.addSubview(startIndicator) + addSubview(startIndicator) // Setup End Indicator - let endDrag = UIPanGestureRecognizer(target:self, + let endDrag = UIPanGestureRecognizer(target: self, action: #selector(endDragged(recognizer:))) endIndicator = ABEndIndicator(frame: CGRect(x: 0, y: -topBorderHeight, width: indicatorWidth, - height: self.frame.size.height + bottomBorderHeight + topBorderHeight)) - endIndicator.layer.anchorPoint = CGPoint(x: 0, y: 0.5) + height: frame.size.height + bottomBorderHeight + topBorderHeight)) + endIndicator.layer.anchorPoint = CGPoint(x: 1, y: 0.5) endIndicator.addGestureRecognizer(endDrag) - self.addSubview(endIndicator) - + addSubview(endIndicator) // Setup Top and bottom line @@ -111,105 +114,117 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { y: -topBorderHeight, width: indicatorWidth, height: topBorderHeight)) - self.addSubview(topLine) + addSubview(topLine) bottomLine = ABBorder(frame: CGRect(x: 0, - y: self.frame.size.height, + y: frame.size.height, width: indicatorWidth, height: bottomBorderHeight)) - self.addSubview(bottomLine) + addSubview(bottomLine) - self.addObserver(self, + addObserver(self, forKeyPath: "bounds", options: NSKeyValueObservingOptions(rawValue: 0), context: nil) // Setup Progress Indicator - let progressDrag = UIPanGestureRecognizer(target:self, + let progressDrag = UIPanGestureRecognizer(target: self, action: #selector(progressDragged(recognizer:))) progressIndicator = ABProgressIndicator(frame: CGRect(x: 0, y: -topBorderHeight, width: 10, - height: self.frame.size.height + bottomBorderHeight + topBorderHeight)) + height: frame.size.height + bottomBorderHeight + topBorderHeight)) progressIndicator.addGestureRecognizer(progressDrag) - self.addSubview(progressIndicator) + addSubview(progressIndicator) // Setup Draggable View - let viewDrag = UIPanGestureRecognizer(target:self, + let viewDrag = UIPanGestureRecognizer(target: self, action: #selector(viewDragged(recognizer:))) draggableView.addGestureRecognizer(viewDrag) - self.draggableView.backgroundColor = .clear - self.addSubview(draggableView) - self.sendSubview(toBack: draggableView) + draggableView.backgroundColor = .clear + addSubview(draggableView) + sendSubviewToBack(draggableView) // Setup time labels - startTimeView = ABTimeView(size: CGSize(width: 60, height: 30), position: 1) + startTimeView = ABTimeView(size: CGSize(width: 60, height: 25), position: 1) startTimeView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) - self.addSubview(startTimeView) + addSubview(startTimeView) - endTimeView = ABTimeView(size: CGSize(width: 60, height: 30), position: 1) + endTimeView = ABTimeView(size: CGSize(width: 60, height: 25), position: 1) endTimeView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) - self.addSubview(endTimeView) + addSubview(endTimeView) } - public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "bounds"{ - self.updateThumbnails() + updateThumbnails() + updateHandles() } } + + private func updateHandles() { + var newStart = startIndicator.frame + newStart.size.height = bounds.height + startIndicator.frame = newStart + + var newEnd = endIndicator.frame + newEnd.size.height = bounds.height + endIndicator.frame = newEnd + } // MARK: Public functions - public func setProgressIndicatorImage(image: UIImage){ - self.progressIndicator.imageView.image = image + public func setProgressIndicatorImage(image: UIImage) { + progressIndicator.imageView.image = image } - public func hideProgressIndicator(){ - self.progressIndicator.isHidden = true + public func hideProgressIndicator() { + progressIndicator.isHidden = true } - public func showProgressIndicator(){ - self.progressIndicator.isHidden = false + public func showProgressIndicator() { + progressIndicator.isHidden = false } - public func updateProgressIndicator(seconds: Float64){ + public func updateProgressIndicator(seconds: Float64) { if !isReceivingGesture { - let endSeconds = secondsFromValue(value: self.endPercentage) - if seconds >= endSeconds { - self.resetProgressPosition() + let endSeconds = secondsFromValue(value: endPercentage) + let shouldReset = seconds >= endSeconds && rangeHandlesConstrainProgress + if shouldReset { + resetProgressPosition() } else { - self.progressPercentage = self.valueFromSeconds(seconds: Float(seconds)) + progressPercentage = valueFromSeconds(seconds: Float(seconds)) } layoutSubviews() } } - public func setStartIndicatorImage(image: UIImage){ - self.startIndicator.imageView.image = image + public func setStartIndicatorImage(image: UIImage) { + startIndicator.imageView.image = image } - public func setEndIndicatorImage(image: UIImage){ - self.endIndicator.imageView.image = image + public func setEndIndicatorImage(image: UIImage) { + endIndicator.imageView.image = image } - public func setBorderImage(image: UIImage){ - self.topLine.imageView.image = image - self.bottomLine.imageView.image = image + public func setBorderImage(image: UIImage) { + topLine.imageView.image = image + bottomLine.imageView.image = image } - public func setTimeView(view: ABTimeView){ - self.startTimeView = view - self.endTimeView = view + public func setTimeView(view: ABTimeView) { + startTimeView = view + endTimeView = view } - public func setTimeViewPosition(position: ABTimeViewPosition){ + public func setTimeViewPosition(position: ABTimeViewPosition) { switch position { case .top: @@ -220,16 +235,16 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { } } - public func setVideoURL(videoURL: URL){ - self.duration = ABVideoHelper.videoDuration(videoURL: videoURL) + public func setVideoURL(videoURL: URL) { + duration = ABVideoHelper.videoDuration(videoURL: videoURL) self.videoURL = videoURL - self.superview?.layoutSubviews() - self.updateThumbnails() + superview?.layoutSubviews() + updateThumbnails() } - public func updateThumbnails(){ - if !isUpdatingThumbnails{ - self.isUpdatingThumbnails = true + public func updateThumbnails() { + if !isUpdatingThumbnails { + isUpdatingThumbnails = true let backgroundQueue = DispatchQueue(label: "com.app.queue", qos: .background, target: nil) backgroundQueue.async { _ = self.thumbnailsManager.updateThumbnails(view: self, videoURL: self.videoURL, duration: self.duration) @@ -238,63 +253,66 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { } } - public func setStartPosition(seconds: Float){ - self.startPercentage = self.valueFromSeconds(seconds: seconds) + public func setStartPosition(seconds: Float) { + startPercentage = valueFromSeconds(seconds: seconds) layoutSubviews() } - public func setEndPosition(seconds: Float){ - self.endPercentage = self.valueFromSeconds(seconds: seconds) + public func setEndPosition(seconds: Float) { + endPercentage = valueFromSeconds(seconds: seconds) + layoutSubviews() + } + + public func resetHandles() { + startPercentage = 0 + endPercentage = 100 layoutSubviews() } // MARK: - Private functions // MARK: - Crop Handle Drag Functions - @objc private func startDragged(recognizer: UIPanGestureRecognizer){ - self.processHandleDrag( + @objc private func startDragged(recognizer: UIPanGestureRecognizer) { + processHandleDrag( recognizer: recognizer, - drag: .start, - currentPositionPercentage: self.startPercentage, - currentIndicator: self.startIndicator + handle: .start, + currentPositionPercentage: startPercentage, + currentIndicator: startIndicator ) } - - @objc private func endDragged(recognizer: UIPanGestureRecognizer){ - self.processHandleDrag( + + @objc private func endDragged(recognizer: UIPanGestureRecognizer) { + processHandleDrag( recognizer: recognizer, - drag: .end, - currentPositionPercentage: self.endPercentage, - currentIndicator: self.endIndicator + handle: .end, + currentPositionPercentage: endPercentage, + currentIndicator: endIndicator ) } private func processHandleDrag( recognizer: UIPanGestureRecognizer, - drag: DragHandleChoice, + handle: DragHandle, currentPositionPercentage: CGFloat, currentIndicator: UIView ) { - - self.updateGestureStatus(recognizer: recognizer) - + + updateGestureStatus(recognizer: recognizer) let translation = recognizer.translation(in: self) - var position: CGFloat = positionFromValue(value: currentPositionPercentage) // self.startPercentage or self.endPercentage - + var position: CGFloat = positionFromValue(value: currentPositionPercentage) // startPercentage or endPercentage position = position + translation.x - if position < 0 { position = 0 } - - if position > self.frame.size.width { - position = self.frame.size.width + + if position > frame.size.width { + position = frame.size.width } - let positionLimits = getPositionLimits(with: drag) - position = checkEdgeCasesForPosition(with: position, and: positionLimits.min, and: drag) + let positionLimits = getPositionLimits(with: handle) + position = checkEdgeCasesForPosition(with: position, and: positionLimits.min, and: handle) - if Float(self.duration) > self.maxSpace && self.maxSpace > 0 { - if drag == .start { + if Float(duration) > maxSpace && maxSpace > 0 { + if handle == .start { if position < positionLimits.max { position = positionLimits.max } @@ -304,63 +322,67 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { } } } - + recognizer.setTranslation(CGPoint.zero, in: self) - - currentIndicator.center = CGPoint(x: position , y: currentIndicator.center.y) - - let percentage = currentIndicator.center.x * 100 / self.frame.width - - let startSeconds = secondsFromValue(value: self.startPercentage) - let endSeconds = secondsFromValue(value: self.endPercentage) - - self.delegate?.didChangeValue(videoRangeSlider: self, startTime: startSeconds, endTime: endSeconds) - - var progressPosition: CGFloat = 0.0 - - if drag == .start { - self.startPercentage = percentage + currentIndicator.center = CGPoint(x: position, y: currentIndicator.center.y) + let percentage = currentIndicator.center.x * 100 / frame.width + let startSeconds = secondsFromValue(value: startPercentage) + let endSeconds = secondsFromValue(value: endPercentage) + + delegate?.didChangeValue(videoRangeSlider: self, startTime: startSeconds, endTime: endSeconds) + + if handle == .start { + startPercentage = percentage } else { - self.endPercentage = percentage + endPercentage = percentage } + + if rangeHandleChangeResetsProgress { + resetProgressIndicatorForHandle(handle, recognizer: recognizer) + } + + layoutSubviews() + } + + private func resetProgressIndicatorForHandle(_ drag: DragHandle, recognizer: UIGestureRecognizer) { + + var progressPosition: CGFloat = 0.0 if drag == .start { - progressPosition = positionFromValue(value: self.startPercentage) - + progressPosition = positionFromValue(value: startPercentage) } else { if recognizer.state != .ended { - progressPosition = positionFromValue(value: self.endPercentage) + progressPosition = positionFromValue(value: endPercentage) } else { - progressPosition = positionFromValue(value: self.startPercentage) + progressPosition = positionFromValue(value: startPercentage) } } - progressIndicator.center = CGPoint(x: progressPosition , y: progressIndicator.center.y) - let progressPercentage = progressIndicator.center.x * 100 / self.frame.width + progressIndicator.center = CGPoint(x: progressPosition, y: progressIndicator.center.y) + let progressPercentage = progressIndicator.center.x * 100 / frame.width - if self.progressPercentage != progressPercentage { + if progressPercentage != progressPercentage { let progressSeconds = secondsFromValue(value: progressPercentage) - self.delegate?.indicatorDidChangePosition(videoRangeSlider: self, position: progressSeconds) + delegate?.indicatorDidChangePosition(videoRangeSlider: self, position: progressSeconds) } self.progressPercentage = progressPercentage - - layoutSubviews() } - @objc func progressDragged(recognizer: UIPanGestureRecognizer){ + + @objc func progressDragged(recognizer: UIPanGestureRecognizer) { if !isProgressIndicatorDraggable { return } - + updateGestureStatus(recognizer: recognizer) - + let translation = recognizer.translation(in: self) - let positionLimitStart = positionFromValue(value: self.startPercentage) - let positionLimitEnd = positionFromValue(value: self.endPercentage) + let positionLimitStart = positionFromValue(value: startPercentage) + let positionLimitEnd = positionFromValue(value: endPercentage) - var position = positionFromValue(value: self.progressPercentage) + var position = positionFromValue(value: progressPercentage) position = position + translation.x if position < positionLimitStart { @@ -372,28 +394,22 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { } recognizer.setTranslation(CGPoint.zero, in: self) - - progressIndicator.center = CGPoint(x: position , y: progressIndicator.center.y) - - let percentage = progressIndicator.center.x * 100 / self.frame.width - + progressIndicator.center = CGPoint(x: position, y: progressIndicator.center.y) + let percentage = progressIndicator.center.x * 100 / frame.width let progressSeconds = secondsFromValue(value: progressPercentage) - - self.delegate?.indicatorDidChangePosition(videoRangeSlider: self, position: progressSeconds) - - self.progressPercentage = percentage - + delegate?.indicatorDidChangePosition(videoRangeSlider: self, position: progressSeconds) + progressPercentage = percentage layoutSubviews() } - @objc func viewDragged(recognizer: UIPanGestureRecognizer){ + @objc func viewDragged(recognizer: UIPanGestureRecognizer) { updateGestureStatus(recognizer: recognizer) - + let translation = recognizer.translation(in: self) - var progressPosition = positionFromValue(value: self.progressPercentage) - var startPosition = positionFromValue(value: self.startPercentage) - var endPosition = positionFromValue(value: self.endPercentage) + var progressPosition = positionFromValue(value: progressPercentage) + var startPosition = positionFromValue(value: startPercentage) + var endPosition = positionFromValue(value: endPercentage) startPosition = startPosition + translation.x endPosition = endPosition + translation.x @@ -405,62 +421,64 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { progressPosition = progressPosition - translation.x } - if endPosition > self.frame.size.width{ - endPosition = self.frame.size.width + if endPosition > frame.size.width { + endPosition = frame.size.width startPosition = startPosition - translation.x progressPosition = progressPosition - translation.x } recognizer.setTranslation(CGPoint.zero, in: self) - progressIndicator.center = CGPoint(x: progressPosition , y: progressIndicator.center.y) - startIndicator.center = CGPoint(x: startPosition , y: startIndicator.center.y) - endIndicator.center = CGPoint(x: endPosition , y: endIndicator.center.y) - - let startPercentage = startIndicator.center.x * 100 / self.frame.width - let endPercentage = endIndicator.center.x * 100 / self.frame.width - let progressPercentage = progressIndicator.center.x * 100 / self.frame.width + startIndicator.center = CGPoint(x: startPosition, y: startIndicator.center.y) + endIndicator.center = CGPoint(x: endPosition, y: endIndicator.center.y) + let startPercentage = startIndicator.center.x * 100 / frame.width + let endPercentage = endIndicator.center.x * 100 / frame.width + let startSeconds = secondsFromValue(value: startPercentage) let endSeconds = secondsFromValue(value: endPercentage) - - self.delegate?.didChangeValue(videoRangeSlider: self, startTime: startSeconds, endTime: endSeconds) - - if self.progressPercentage != progressPercentage{ - let progressSeconds = secondsFromValue(value: progressPercentage) - self.delegate?.indicatorDidChangePosition(videoRangeSlider: self, position: progressSeconds) - } - + + delegate?.didChangeValue(videoRangeSlider: self, startTime: startSeconds, endTime: endSeconds) + self.startPercentage = startPercentage self.endPercentage = endPercentage - self.progressPercentage = progressPercentage - + + if rangeHandleChangeResetsProgress { + progressIndicator.center = CGPoint(x: progressPosition, y: progressIndicator.center.y) + let progressPercentage = progressIndicator.center.x * 100 / frame.width + if progressPercentage != progressPercentage { + let progressSeconds = secondsFromValue(value: progressPercentage) + delegate?.indicatorDidChangePosition(videoRangeSlider: self, position: progressSeconds) + } + self.progressPercentage = progressPercentage + } + layoutSubviews() } - + // MARK: - Drag Functions Helpers - private func positionFromValue(value: CGFloat) -> CGFloat{ - let position = value * self.frame.size.width / 100 + private func positionFromValue(value: CGFloat) -> CGFloat { + let position = value * frame.size.width / 100 return position } - - private func getPositionLimits(with drag: DragHandleChoice) -> (min: CGFloat, max: CGFloat) { + + private func getPositionLimits(with drag: DragHandle) -> (min: CGFloat, max: CGFloat) { if drag == .start { return ( - positionFromValue(value: self.endPercentage - valueFromSeconds(seconds: self.minSpace)), - positionFromValue(value: self.endPercentage - valueFromSeconds(seconds: self.maxSpace)) + positionFromValue(value: endPercentage - valueFromSeconds(seconds: minSpace)), + positionFromValue(value: endPercentage - valueFromSeconds(seconds: maxSpace)) ) } else { return ( - positionFromValue(value: self.startPercentage + valueFromSeconds(seconds: self.minSpace)), - positionFromValue(value: self.startPercentage + valueFromSeconds(seconds: self.maxSpace)) + positionFromValue(value: startPercentage + valueFromSeconds(seconds: minSpace)), + positionFromValue(value: startPercentage + valueFromSeconds(seconds: maxSpace)) ) } } - - private func checkEdgeCasesForPosition(with position: CGFloat, and positionLimit: CGFloat, and drag: DragHandleChoice) -> CGFloat { + + private func checkEdgeCasesForPosition(with position: CGFloat, and positionLimit: CGFloat, and drag: DragHandle) -> CGFloat { if drag == .start { - if Float(self.duration) < self.minSpace { + if Float(duration) < minSpace { return 0 } else { if position > positionLimit { @@ -468,46 +486,46 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { } } } else { - if Float(self.duration) < self.minSpace { - return self.frame.size.width + if Float(duration) < minSpace { + return frame.size.width } else { if position < positionLimit { return positionLimit } } } - + return position } - - private func secondsFromValue(value: CGFloat) -> Float64{ + + private func secondsFromValue(value: CGFloat) -> Float64 { return duration * Float64((value / 100)) } - private func valueFromSeconds(seconds: Float) -> CGFloat{ - return CGFloat(seconds * 100) / CGFloat(duration) + private func valueFromSeconds(seconds: Float) -> CGFloat { + return duration > 0 ? CGFloat(seconds * 100) / CGFloat(duration):0 } - + private func updateGestureStatus(recognizer: UIGestureRecognizer) { if recognizer.state == .began { - - self.isReceivingGesture = true - self.delegate?.sliderGesturesBegan?() - + + isReceivingGesture = true + delegate?.sliderGesturesBegan?() + } else if recognizer.state == .ended { - - self.isReceivingGesture = false - self.delegate?.sliderGesturesEnded?() + + isReceivingGesture = false + delegate?.sliderGesturesEnded?() } } - + private func resetProgressPosition() { - self.progressPercentage = self.startPercentage - let progressPosition = positionFromValue(value: self.progressPercentage) - progressIndicator.center = CGPoint(x: progressPosition , y: progressIndicator.center.y) - - let startSeconds = secondsFromValue(value: self.progressPercentage) - self.delegate?.indicatorDidChangePosition(videoRangeSlider: self, position: startSeconds) + progressPercentage = startPercentage + let progressPosition = positionFromValue(value: progressPercentage) + progressIndicator.center = CGPoint(x: progressPosition, y: progressIndicator.center.y) + + let startSeconds = secondsFromValue(value: progressPercentage) + delegate?.indicatorDidChangePosition(videoRangeSlider: self, position: startSeconds) } // MARK: - @@ -515,12 +533,12 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { override public func layoutSubviews() { super.layoutSubviews() - startTimeView.timeLabel.text = self.secondsToFormattedString(totalSeconds: secondsFromValue(value: self.startPercentage)) - endTimeView.timeLabel.text = self.secondsToFormattedString(totalSeconds: secondsFromValue(value: self.endPercentage)) + startTimeView.timeLabel.text = secondsToFormattedString(totalSeconds: secondsFromValue(value: startPercentage)) + endTimeView.timeLabel.text = secondsToFormattedString(totalSeconds: secondsFromValue(value: endPercentage)) - let startPosition = positionFromValue(value: self.startPercentage) - let endPosition = positionFromValue(value: self.endPercentage) - let progressPosition = positionFromValue(value: self.progressPercentage) + let startPosition = positionFromValue(value: startPercentage) + let endPosition = positionFromValue(value: endPercentage) + let progressPosition = positionFromValue(value: progressPercentage) startIndicator.center = CGPoint(x: startPosition, y: startIndicator.center.y) endIndicator.center = CGPoint(x: endPosition, y: endIndicator.center.y) @@ -528,8 +546,7 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { draggableView.frame = CGRect(x: startIndicator.frame.origin.x + startIndicator.frame.size.width, y: 0, width: endIndicator.frame.origin.x - startIndicator.frame.origin.x - endIndicator.frame.size.width, - height: self.frame.height) - + height: frame.height) topLine.frame = CGRect(x: startIndicator.frame.origin.x + startIndicator.frame.width, y: -topBorderHeight, @@ -537,7 +554,7 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { height: topBorderHeight) bottomLine.frame = CGRect(x: startIndicator.frame.origin.x + startIndicator.frame.width, - y: self.frame.size.height, + y: frame.size.height, width: endIndicator.frame.origin.x - startIndicator.frame.origin.x - endIndicator.frame.size.width, height: bottomBorderHeight) @@ -546,25 +563,23 @@ public class ABVideoRangeSlider: UIView, UIGestureRecognizerDelegate { endTimeView.center = CGPoint(x: endIndicator.center.x, y: endTimeView.center.y) } - override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool { let extendedBounds = CGRect(x: -startIndicator.frame.size.width, y: -topLine.frame.size.height, - width: self.frame.size.width + startIndicator.frame.size.width + endIndicator.frame.size.width, - height: self.frame.size.height + topLine.frame.size.height + bottomLine.frame.size.height) + width: frame.size.width + startIndicator.frame.size.width + endIndicator.frame.size.width, + height: frame.size.height + topLine.frame.size.height + bottomLine.frame.size.height) return extendedBounds.contains(point) } + private func secondsToFormattedString(totalSeconds: Float64) -> String { + let minutes: Int = Int(totalSeconds.truncatingRemainder(dividingBy: 3600) / 60) + let seconds: Int = Int(totalSeconds.truncatingRemainder(dividingBy: 60)) + let subseconds: Int = Int(totalSeconds.truncatingRemainder(dividingBy: 1) * 10) - private func secondsToFormattedString(totalSeconds: Float64) -> String{ - let hours:Int = Int(totalSeconds.truncatingRemainder(dividingBy: 86400) / 3600) - let minutes:Int = Int(totalSeconds.truncatingRemainder(dividingBy: 3600) / 60) - let seconds:Int = Int(totalSeconds.truncatingRemainder(dividingBy: 60)) - - if hours > 0 { - return String(format: "%i:%02i:%02i", hours, minutes, seconds) + if minutes > 0 { + return String(format: "%i:%02i.%i", minutes, seconds, subseconds) } else { - return String(format: "%02i:%02i", minutes, seconds) + return String(format: "%i.%i", seconds, subseconds) } } diff --git a/Example/ABVideoRangeSlider/AppDelegate.swift b/Example/ABVideoRangeSlider/AppDelegate.swift index 719e288..15b0aaf 100644 --- a/Example/ABVideoRangeSlider/AppDelegate.swift +++ b/Example/ABVideoRangeSlider/AppDelegate.swift @@ -13,7 +13,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true @@ -41,6 +40,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - } - diff --git a/Example/ABVideoRangeSlider/ViewController.swift b/Example/ABVideoRangeSlider/ViewController.swift index bbd4c5a..a4ed4af 100644 --- a/Example/ABVideoRangeSlider/ViewController.swift +++ b/Example/ABVideoRangeSlider/ViewController.swift @@ -18,9 +18,9 @@ class ViewController: UIViewController, ABVideoRangeSliderDelegate { @IBOutlet var lblStart: UILabel! @IBOutlet var lblEnd: UILabel! @IBOutlet var lblMinSpace: UILabel! - - let path = Bundle.main.path(forResource: "test", ofType:"mp4") - + + let path = Bundle.main.path(forResource: "test", ofType: "mp4") + override func viewDidLoad() { super.viewDidLoad() } @@ -33,7 +33,7 @@ class ViewController: UIViewController, ABVideoRangeSliderDelegate { playerViewController.player!.play() } } - + override func viewWillAppear(_ animated: Bool) { videoRangeSlider.setVideoURL(videoURL: URL(fileURLWithPath: path!)) videoRangeSlider.delegate = self @@ -41,13 +41,13 @@ class ViewController: UIViewController, ABVideoRangeSliderDelegate { // videoRangeSlider.maxSpace = 180.0 lblMinSpace.text = "\(videoRangeSlider.minSpace)" - + // Set initial position of Start Indicator videoRangeSlider.setStartPosition(seconds: 50.0) - + // Set initial position of End Indicator videoRangeSlider.setEndPosition(seconds: 150.0) - + /* Uncomment to customize the Video Range Slider */ /* let customStartIndicator = UIImage(named: "CustomStartIndicator") @@ -63,8 +63,6 @@ class ViewController: UIViewController, ABVideoRangeSliderDelegate { videoRangeSlider.setProgressIndicatorImage(image: customProgressIndicator!) */ - - // Customize starTimeView let customView = UIView(frame: CGRect(x: 0, y: 0, @@ -80,14 +78,14 @@ class ViewController: UIViewController, ABVideoRangeSliderDelegate { videoRangeSlider.startTimeView.marginRight = 2.0 videoRangeSlider.startTimeView.timeLabel.textColor = .white } - + // MARK: ABVideoRangeSlider Delegate - Returns time in seconds - + func didChangeValue(videoRangeSlider: ABVideoRangeSlider, startTime: Float64, endTime: Float64) { lblStart.text = "\(startTime)" lblEnd.text = "\(endTime)" } - + func indicatorDidChangePosition(videoRangeSlider: ABVideoRangeSlider, position: Float64) { print("position of indicator: \(position)") } diff --git a/Example/Tests/Tests.swift b/Example/Tests/Tests.swift index 5c87877..bb2bd8f 100644 --- a/Example/Tests/Tests.swift +++ b/Example/Tests/Tests.swift @@ -3,27 +3,27 @@ import XCTest import ABVideoRangeSlider class Tests: XCTestCase { - + override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } - + func testExample() { // This is an example of a functional test case. XCTAssert(true, "Pass") } - + func testPerformanceExample() { // This is an example of a performance test case. - self.measure() { + self.measure { // Put the code you want to measure the time of here. } } - + }