diff --git a/Example/iRecordView/ViewController.swift b/Example/iRecordView/ViewController.swift index 80bc382..c847946 100644 --- a/Example/iRecordView/ViewController.swift +++ b/Example/iRecordView/ViewController.swift @@ -59,7 +59,7 @@ class ViewController: UIViewController,RecordViewDelegate { recordView.trailingAnchor.constraint(equalTo: recordButton.leadingAnchor, constant: -20).isActive = true recordView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true - recordView.bottomAnchor.constraint(equalTo: recordButton.bottomAnchor).isActive = true + recordView.centerYAnchor.constraint(equalTo: recordButton.centerYAnchor).isActive = true recordButton.recordView = recordView recordView.delegate = self diff --git a/Source/AudioPlayer.swift b/Source/AudioPlayer.swift index c732e72..8cb7c76 100644 --- a/Source/AudioPlayer.swift +++ b/Source/AudioPlayer.swift @@ -13,16 +13,18 @@ enum AudioPlayerSounds { case start, end, error } - class AudioPlayer { +class AudioPlayer: NSObject { private var player: AVAudioPlayer! + + var didFinishPlaying: ((Bool) -> Void)? - init() { + override init() { player = AVAudioPlayer() } public func playAudioFile(soundType: AudioPlayerSounds) { - + didFinishPlaying = nil let bundle = Bundle(identifier: "org.cocoapods.iRecordView") @@ -32,9 +34,14 @@ enum AudioPlayerSounds { } do { - player = try AVAudioPlayer(contentsOf: url) + try AVAudioSession.sharedInstance().setCategory(.playAndRecord) + try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker) + try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation) + + player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.wav.rawValue) + player.delegate = self + player.prepareToPlay() player.play() - } catch { print("could not play audio file!") @@ -57,3 +64,10 @@ enum AudioPlayerSounds { } } + +extension AudioPlayer: AVAudioPlayerDelegate { + func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { + didFinishPlaying?(flag) + didFinishPlaying = nil + } +} diff --git a/Source/RecordButton.swift b/Source/RecordButton.swift index cad571a..da3895f 100644 --- a/Source/RecordButton.swift +++ b/Source/RecordButton.swift @@ -41,7 +41,7 @@ open class RecordButton: UIButton, UIGestureRecognizerDelegate { setTitle("", for: .normal) if image(for: .normal) == nil { - let image = UIImage.fromPod("mic_blue") + let image = UIImage.fromPod("mic_blue").withRenderingMode(.alwaysTemplate) setImage(image, for: .normal) tintColor = .blue @@ -54,7 +54,7 @@ open class RecordButton: UIButton, UIGestureRecognizerDelegate { touchDownAndUpGesture = iGesutreRecognizer(target: self, action: #selector(handleUpAndDown(_:))) - touchDownAndUpGesture.gestureDelegate = self + touchDownAndUpGesture.delegate = self addGestureRecognizer(moveGesture) @@ -89,7 +89,6 @@ open class RecordButton: UIButton, UIGestureRecognizerDelegate { recordView.onTouchUp(recordButton: self) } - @objc private func touchMoved(_ sender: UIPanGestureRecognizer) { recordView.touchMoved(recordButton: self, sender: sender) } @@ -101,24 +100,23 @@ open class RecordButton: UIButton, UIGestureRecognizerDelegate { case .ended: recordView.onTouchUp(recordButton: self) + + case .cancelled: + recordView.onTouchCancelled(recordButton: self) default: break } } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return (gestureRecognizer == touchDownAndUpGesture && otherGestureRecognizer == moveGesture) || (gestureRecognizer == moveGesture && otherGestureRecognizer == touchDownAndUpGesture) + } } -extension RecordButton: GesutreDelegate { - func onStart() { - recordView.onTouchDown(recordButton: self) - } - - func onEnd() { - recordView.onTouchUp(recordButton: self) - } - +extension RecordButton { open override func layoutSubviews() { super.layoutSubviews() superview?.bringSubviewToFront(self) diff --git a/Source/RecordView.swift b/Source/RecordView.swift index 653f5be..4d14b84 100644 --- a/Source/RecordView.swift +++ b/Source/RecordView.swift @@ -24,6 +24,7 @@ public class RecordView: UIView, CAAnimationDelegate { public weak var delegate: RecordViewDelegate? public var offset: CGFloat = 20 public var isSoundEnabled = true + public var buttonTransformScale: CGFloat = 2 public var slideToCancelText: String! { didSet { @@ -80,10 +81,6 @@ public class RecordView: UIView, CAAnimationDelegate { return label }() - - - - private func setup() { bucketImageView = BucketImageView(frame: frame) bucketImageView.animationDelegate = self @@ -111,14 +108,14 @@ public class RecordView: UIView, CAAnimationDelegate { arrow.heightAnchor.constraint(equalToConstant: 15).isActive = true slideToCancelStackVIew.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true - slideToCancelStackVIew.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true + slideToCancelStackVIew.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true timerStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true - timerStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true + timerStackView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true - mTransform = CGAffineTransform(scaleX: 2.0, y: 2.0) + mTransform = CGAffineTransform(scaleX: buttonTransformScale, y: buttonTransformScale) audioPlayer = AudioPlayer() } @@ -134,8 +131,15 @@ public class RecordView: UIView, CAAnimationDelegate { } func onTouchUp(recordButton: RecordButton) { + guard !isSwiped else { + return + } onFinish(recordButton: recordButton) } + + func onTouchCancelled(recordButton: RecordButton) { + onTouchCancel(recordButton: recordButton) + } required public init?(coder aDecoder: NSCoder) { @@ -151,9 +155,23 @@ public class RecordView: UIView, CAAnimationDelegate { //this will be called when user starts tapping the button private func onStart(recordButton: RecordButton) { + isSwiped = false + + self.prepareToStartRecording(recordButton: recordButton) + + if isSoundEnabled { + audioPlayer.playAudioFile(soundType: .start) + audioPlayer.didFinishPlaying = { [weak self] _ in + self?.delegate?.onStart() + } + } else { + delegate?.onStart() + } + } + + private func prepareToStartRecording(recordButton: RecordButton) { resetTimer() - isSwiped = false //start timer timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateDuration), userInfo: nil, repeats: true) @@ -174,26 +192,27 @@ public class RecordView: UIView, CAAnimationDelegate { bucketImageView.isHidden = false bucketImageView.resetAnimations() bucketImageView.animateAlpha() - - if isSoundEnabled { - audioPlayer.playAudioFile(soundType: .start) - } - - delegate?.onStart() - } - //this will be called when user swipes to the left and cancel the record - private func onSwipe(recordButton: RecordButton) { - isSwiped = true - + fileprivate func animateRecordButtonToIdentity(_ recordButton: RecordButton) { UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: { recordButton.transform = .identity }) - - + } + + //this will be called when user swipes to the left and cancel the record + fileprivate func hideCancelStackViewAndTimeLabel() { slideToCancelStackVIew.isHidden = true timerLabel.isHidden = true + } + + private func onSwipe(recordButton: RecordButton) { + isSwiped = true + audioPlayer.didFinishPlaying = nil + + animateRecordButtonToIdentity(recordButton) + + hideCancelStackViewAndTimeLabel() if !isLessThanOneSecond() { bucketImageView.animateBucketAndMic() @@ -207,6 +226,23 @@ public class RecordView: UIView, CAAnimationDelegate { delegate?.onCancel() } + + private func onTouchCancel(recordButton: RecordButton) { + isSwiped = false + + audioPlayer.didFinishPlaying = nil + + animateRecordButtonToIdentity(recordButton) + + hideCancelStackViewAndTimeLabel() + + bucketImageView.isHidden = true + delegate?.onAnimationEnd?() + + resetTimer() + + delegate?.onCancel() + } private func resetTimer() { timer?.invalidate() @@ -217,7 +253,7 @@ public class RecordView: UIView, CAAnimationDelegate { //this will be called when user lift his finger private func onFinish(recordButton: RecordButton) { isSwiped = false - + audioPlayer.didFinishPlaying = nil UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: { recordButton.transform = .identity }) @@ -235,7 +271,6 @@ public class RecordView: UIView, CAAnimationDelegate { } } else { if isSoundEnabled { - audioPlayer.playAudioFile(soundType: .end) } } @@ -246,11 +281,10 @@ public class RecordView: UIView, CAAnimationDelegate { } - //this will be called when user starts to move his finger func touchMoved(recordButton: RecordButton, sender: UIPanGestureRecognizer) { - if isSwiped { + guard !isSwiped else { return } @@ -273,18 +307,12 @@ public class RecordView: UIView, CAAnimationDelegate { } } - - case .ended: - onFinish(recordButton: recordButton) - - default: break } } - } diff --git a/Source/iGesutreRecognizer.swift b/Source/iGesutreRecognizer.swift index fc4ec5b..40dbfd2 100644 --- a/Source/iGesutreRecognizer.swift +++ b/Source/iGesutreRecognizer.swift @@ -8,22 +8,21 @@ import UIKit -protocol GesutreDelegate { - func onStart() - func onEnd() -} - - class iGesutreRecognizer: UIGestureRecognizer { - var gestureDelegate: GesutreDelegate? - override func touchesBegan(_ touches: Set, with event: UIEvent) { - gestureDelegate?.onStart() + guard state != .began else { + return + } + state = .began } override func touchesEnded(_ touches: Set, with event: UIEvent) { - gestureDelegate?.onEnd() + state = .ended + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent) { + state = .cancelled } }