From 6a2402005d401868795d983ad8a8640721ac301a Mon Sep 17 00:00:00 2001 From: William Entriken Date: Fri, 27 Oct 2017 15:09:26 -0400 Subject: [PATCH] Switch to Swift4.0 --- .swift-version | 2 +- CHANGELOG.md | 10 +- FDWaveformView.podspec | 2 +- Source/FDWaveformView.swift | 218 +++++++++++++++--------------------- 4 files changed, 103 insertions(+), 129 deletions(-) diff --git a/.swift-version b/.swift-version index 9f55b2c..5186d07 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.0 +4.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index bdd6060..3d0c01a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,15 @@ All notable changes to this project will be documented in this file. --- -## [Master](https://github.com/fulldecent/FDWaveformView/compare/2.2.1...master) +## [Master](https://github.com/fulldecent/FDWaveformView/compare/3.0.0...master) + +--- + +## [3.0.0](https://github.com/fulldecent/FDWaveformView/releases/tag/3.0.0) +Released on 2017-10-27. + +#### Changed +- Now supporting Swift 4.0 --- diff --git a/FDWaveformView.podspec b/FDWaveformView.podspec index 7411d18..aa02250 100644 --- a/FDWaveformView.podspec +++ b/FDWaveformView.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FDWaveformView' - s.version = '2.2.1' + s.version = '3.0.0' s.license = { :type => 'MIT', :file => 'LICENSE' } s.summary = 'Reads an audio file and displays the waveform' s.description = <<-DESC diff --git a/Source/FDWaveformView.swift b/Source/FDWaveformView.swift index 93441bb..f89ffe6 100644 --- a/Source/FDWaveformView.swift +++ b/Source/FDWaveformView.swift @@ -15,7 +15,7 @@ import Accelerate open class FDWaveformView: UIView { /// A delegate to accept progress reporting /*@IBInspectable*/ open weak var delegate: FDWaveformViewDelegate? - + /// The audio file to render /*@IBInspectable*/ open var audioURL: URL? { didSet { @@ -24,32 +24,32 @@ open class FDWaveformView: UIView { audioContext = nil return } - + loadingInProgress = true delegate?.waveformViewWillLoad?(self) - + FDAudioContext.load(fromAudioURL: audioURL) { audioContext in DispatchQueue.main.async { guard self.audioURL == audioContext?.audioURL else { return } - + if audioContext == nil { NSLog("FDWaveformView failed to load URL: \(audioURL)") } - + self.audioContext = audioContext // This will reset the view and kick off a layout - + self.loadingInProgress = false self.delegate?.waveformViewDidLoad?(self) } } } } - + /// The total number of audio samples in the file open var totalSamples: Int { return audioContext?.totalSamples ?? 0 } - + /// The samples to be highlighted in a different color /*@IBInspectable*/ open var highlightedSamples: CountableRange? = nil { didSet { @@ -63,18 +63,7 @@ open class FDWaveformView: UIView { setNeedsLayout() } } - - /// A portion of the waveform rendering to be highlighted - @available(*, deprecated, message: "Use `zoomSamples` to set range") - open var progressSamples: Int { - get { - return highlightedSamples?.upperBound ?? 0 - } - set { - highlightedSamples = 0 ..< newValue - } - } - + /// The samples to be displayed /*@IBInspectable*/ open var zoomSamples: CountableRange = 0 ..< 0 { didSet { @@ -85,45 +74,23 @@ open class FDWaveformView: UIView { setNeedsLayout() } } - - /// The first sample to render - @available(*, deprecated, message: "Use `zoomSamples` to set range") - open var zoomStartSamples: Int { - get { - return zoomSamples.startIndex - } - set(newStart) { - zoomSamples = newStart ..< zoomSamples.endIndex - } - } - - /// One plus the last sample to render - @available(*, deprecated, message: "Use `zoomSamples` to set range") - open var zoomEndSamples: Int { - get { - return zoomSamples.endIndex - } - set(newEnd) { - zoomSamples = zoomSamples.startIndex ..< newEnd - } - } - + /// Whether to allow tap and pan gestures to change highlighted range /// Pan gives priority to `doesAllowScroll` if this and that are both `true` /*@IBInspectable*/ open var doesAllowScrubbing = true - + /// Whether to allow pinch gesture to change zoom /*@IBInspectable*/ open var doesAllowStretch = true - + /// Whether to allow pan gesture to change zoom /*@IBInspectable*/ open var doesAllowScroll = true - + /// Supported waveform types //TODO: make this public after reconciling FDWaveformView.WaveformType and FDWaveformType enum WaveformType { case linear, logarithmic } - + // Type of waveform to display var waveformType: WaveformType = .logarithmic { didSet { @@ -131,55 +98,55 @@ open class FDWaveformView: UIView { setNeedsLayout() } } - + /// The color of the waveform @IBInspectable open var wavesColor = UIColor.black { didSet { imageView.tintColor = wavesColor } } - + /// The color of the highlighted waveform (see `progressSamples` @IBInspectable open var progressColor = UIColor.blue { didSet { highlightedImage.tintColor = progressColor } } - - + + //TODO: MAKE PUBLIC - + /// The portion of extra pixels to render left and right of the viewable region private var horizontalBleedTarget = 0.5 - + /// The required portion of extra pixels to render left and right of the viewable region /// If this portion is not available then a re-render will be performed private var horizontalBleedAllowed = 0.1 ... 3.0 - + /// The number of horizontal pixels to render per visible pixel on the screen (for antialiasing) private var horizontalOverdrawTarget = 3.0 - + /// The required number of horizontal pixels to render per visible pixel on the screen (for antialiasing) /// If this number is not available then a re-render will be performed private var horizontalOverdrawAllowed = 1.5 ... 5.0 - + /// The number of vertical pixels to render per visible pixel on the screen (for antialiasing) private var verticalOverdrawTarget = 2.0 - + /// The required number of vertical pixels to render per visible pixel on the screen (for antialiasing) /// If this number is not available then a re-render will be performed private var verticalOverdrawAllowed = 1.0 ... 3.0 - + /// The "zero" level (in dB) fileprivate let noiseFloor: CGFloat = -50.0 - - - + + + // Mark - Private vars - + /// Whether rendering for the current asset failed private var renderForCurrentAssetFailed = false - + /// Current audio context to be used for rendering private var audioContext: FDAudioContext? { didSet { @@ -189,12 +156,12 @@ open class FDWaveformView: UIView { inProgressWaveformRenderOperation = nil cachedWaveformRenderOperation = nil renderForCurrentAssetFailed = false - + setNeedsDisplay() setNeedsLayout() } } - + /// Currently running renderer private var inProgressWaveformRenderOperation: FDWaveformRenderOperation? { willSet { @@ -203,10 +170,10 @@ open class FDWaveformView: UIView { } } } - + /// The render operation used to render the current waveform image private var cachedWaveformRenderOperation: FDWaveformRenderOperation? - + /// Image of waveform private var waveformImage: UIImage? { get { return imageView.image } @@ -216,12 +183,12 @@ open class FDWaveformView: UIView { highlightedImage.image = imageView.image } } - + /// Desired scale of image based on window's screen scale private var desiredImageScale: CGFloat { return window?.screen.scale ?? UIScreen.main.scale } - + /// Waveform type for rending waveforms //TODO: make this public after reconciling FDWaveformView.WaveformType and FDWaveformType var waveformRenderType: FDWaveformType { @@ -232,17 +199,17 @@ open class FDWaveformView: UIView { } } } - + /// Represents the status of the waveform renderings fileprivate enum CacheStatus { case dirty case notDirty(cancelInProgressRenderOperation: Bool) } - + fileprivate func decibel(_ amplitude: CGFloat) -> CGFloat { return 20.0 * log10(abs(amplitude)) } - + /// View for rendered waveform lazy fileprivate var imageView: UIImageView = { let retval = UIImageView(frame: CGRect.zero) @@ -250,7 +217,7 @@ open class FDWaveformView: UIView { retval.tintColor = self.wavesColor return retval }() - + /// View for rendered waveform showing progress lazy fileprivate var highlightedImage: UIImageView = { let retval = UIImageView(frame: CGRect.zero) @@ -258,35 +225,35 @@ open class FDWaveformView: UIView { retval.tintColor = self.progressColor return retval }() - + /// A view which hides part of the highlighted image fileprivate let clipping: UIView = { let retval = UIView(frame: CGRect.zero) retval.clipsToBounds = true return retval }() - + /// Gesture recognizer fileprivate var pinchRecognizer = UIPinchGestureRecognizer() - + /// Gesture recognizer fileprivate var panRecognizer = UIPanGestureRecognizer() - + /// Gesture recognizer fileprivate var tapRecognizer = UITapGestureRecognizer() - + /// Whether rendering is happening asynchronously fileprivate var renderingInProgress = false - + /// Whether loading is happening asynchronously open var loadingInProgress = false - + func setup() { addSubview(imageView) clipping.addSubview(highlightedImage) addSubview(clipping) clipsToBounds = true - + pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture)) pinchRecognizer.delegate = self addGestureRecognizer(pinchRecognizer) @@ -297,28 +264,28 @@ open class FDWaveformView: UIView { // tapRecognizer.delegate = self addGestureRecognizer(tapRecognizer) } - + required public init?(coder aCoder: NSCoder) { super.init(coder: aCoder) setup() } - + override init(frame rect: CGRect) { super.init(frame: rect) setup() } - + deinit { inProgressWaveformRenderOperation?.cancel() } - + /// If the cached waveform or in progress waveform is insufficient for the current frame fileprivate func cacheStatus() -> CacheStatus { guard !renderForCurrentAssetFailed else { return .notDirty(cancelInProgressRenderOperation: true) } - + let isInProgressRenderOperationDirty = isWaveformRenderOperationDirty(inProgressWaveformRenderOperation) let isCachedRenderOperationDirty = isWaveformRenderOperationDirty(cachedWaveformRenderOperation) - + if let isInProgressRenderOperationDirty = isInProgressRenderOperationDirty { if let isCachedRenderOperationDirty = isCachedRenderOperationDirty { if isInProgressRenderOperationDirty { @@ -340,30 +307,30 @@ open class FDWaveformView: UIView { } else { return .dirty } - + return .notDirty(cancelInProgressRenderOperation: false) } - + func isWaveformRenderOperationDirty(_ renderOperation: FDWaveformRenderOperation?) -> Bool? { guard let renderOperation = renderOperation else { return nil } - + if renderOperation.format.type != waveformRenderType { return true } if renderOperation.format.scale != desiredImageScale { return true } - + let requiredSamples = zoomSamples.extended(byFactor: horizontalBleedAllowed.lowerBound).clamped(to: 0 ..< totalSamples) if requiredSamples.clamped(to: renderOperation.sampleRange) != requiredSamples { return true } - + let allowedSamples = zoomSamples.extended(byFactor: horizontalBleedAllowed.upperBound).clamped(to: 0 ..< totalSamples) if renderOperation.sampleRange.clamped(to: allowedSamples) != renderOperation.sampleRange { return true } - + let verticalOverdrawRequested = Double(renderOperation.imageSize.height / frame.height) if !verticalOverdrawAllowed.contains(verticalOverdrawRequested) { return true @@ -372,16 +339,16 @@ open class FDWaveformView: UIView { if !horizontalOverdrawAllowed.contains(horizontalOverdrawRequested) { return true } - + return false } - + override open func layoutSubviews() { super.layoutSubviews() guard audioContext != nil && !zoomSamples.isEmpty else { return } - + switch cacheStatus() { case .dirty: renderWaveform() @@ -391,7 +358,7 @@ open class FDWaveformView: UIView { inProgressWaveformRenderOperation = nil } } - + // We need to place the images which have samples in `cachedSampleRange` // inside our frame which represents `startSamples.. Bool { switch lhs { @@ -468,24 +435,24 @@ enum FDWaveformType: Equatable { } return false } - + public var floorValue: CGFloat { switch self { case .linear: return 0 case .logarithmic(let noiseFloor): return noiseFloor } } - + func process(normalizedSamples: inout [Float]) { switch self { case .linear: return - + case .logarithmic(let noiseFloor): // Convert samples to a log scale var zero: Float = 32768.0 vDSP_vdbcon(normalizedSamples, 1, &zero, &normalizedSamples, 1, vDSP_Length(normalizedSamples.count), 1) - + //Clip to [noiseFloor, 0] var ceil: Float = 0.0 var noiseFloorFloat = Float(noiseFloor) @@ -498,7 +465,7 @@ extension FDWaveformView: UIGestureRecognizerDelegate { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } - + @objc func handlePinchGesture(_ recognizer: UIPinchGestureRecognizer) { if !doesAllowStretch { return @@ -506,22 +473,22 @@ extension FDWaveformView: UIGestureRecognizerDelegate { if recognizer.scale == 1 { return } - + let zoomRangeSamples = CGFloat(zoomSamples.count) let pinchCenterSample = zoomSamples.lowerBound + Int(zoomRangeSamples * recognizer.location(in: self).x / bounds.width) let newZoomRangeSamples = Int(zoomRangeSamples * 1.0 / recognizer.scale) let newZoomStart = pinchCenterSample - Int(CGFloat(pinchCenterSample - zoomSamples.lowerBound) * 1.0 / recognizer.scale) let newZoomEnd = newZoomStart + newZoomRangeSamples - + zoomSamples = (newZoomStart ..< newZoomEnd).clamped(to: 0 ..< totalSamples) recognizer.scale = 1 } - + @objc func handlePanGesture(_ recognizer: UIPanGestureRecognizer) { guard !zoomSamples.isEmpty else { return } - + let point = recognizer.translation(in: self) if doesAllowScroll { if recognizer.state == .began { @@ -529,7 +496,7 @@ extension FDWaveformView: UIGestureRecognizerDelegate { } let translationSamples = Int(CGFloat(zoomSamples.count) * point.x / bounds.width) recognizer.setTranslation(CGPoint.zero, in: self) - + switch translationSamples { case let x where x > zoomSamples.startIndex: zoomSamples = 0 ..< zoomSamples.count @@ -549,7 +516,7 @@ extension FDWaveformView: UIGestureRecognizerDelegate { delegate?.waveformDidEndScrubbing?(self) } } - + @objc func handleTapGesture(_ recognizer: UITapGestureRecognizer) { if doesAllowScrubbing { let rangeSamples = CGFloat(zoomSamples.count) @@ -563,22 +530,22 @@ extension FDWaveformView: UIGestureRecognizerDelegate { @objc public protocol FDWaveformViewDelegate: NSObjectProtocol { /// Rendering will begin @objc optional func waveformViewWillRender(_ waveformView: FDWaveformView) - + /// Rendering did complete @objc optional func waveformViewDidRender(_ waveformView: FDWaveformView) - + /// An audio file will be loaded @objc optional func waveformViewWillLoad(_ waveformView: FDWaveformView) - + /// An audio file was loaded @objc optional func waveformViewDidLoad(_ waveformView: FDWaveformView) - + /// The panning gesture did begin @objc optional func waveformDidBeginPanning(_ waveformView: FDWaveformView) - + /// The panning gesture did end @objc optional func waveformDidEndPanning(_ waveformView: FDWaveformView) - + /// The scrubbing gesture did end @objc optional func waveformDidEndScrubbing(_ waveformView: FDWaveformView) } @@ -586,11 +553,11 @@ extension FDWaveformView: UIGestureRecognizerDelegate { //MARK - extension Comparable { - + func clamped(from lowerBound: Self, to upperBound: Self) -> Self { return min(max(self, lowerBound), upperBound) } - + func clamped(to range: ClosedRange) -> Self { return min(max(self, range.lowerBound), range.upperBound) } @@ -604,7 +571,7 @@ extension Strideable where Self.Stride: SignedInteger } extension CountableRange where Bound: Strideable { - + // Extend each bound away from midpoint by `factor`, a portion of the distance from begin to end func extended(byFactor factor: Double) -> CountableRange { let theCount: Int = numericCast(count) @@ -612,4 +579,3 @@ extension CountableRange where Bound: Strideable { return lowerBound.advanced(by: -amountToMove) ..< upperBound.advanced(by: amountToMove) } } -