Play rhythmic sound when view is spinning - swift

I want to play a rhythmic sound when the view is rotating, dependent on speed. Here I have the spin animation for UIview.
var spinAnimation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation")
spinAnimation.fromValue = 0
spinAnimation.toValue = NSNumber(double: 10.0+Double(arc4random_uniform(360))*M_PI * 3)
spinAnimation.duration = 5
spinAnimation.delegate = self
spinAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
layer.addAnimation(spinAnimation, forKey: "SpinAnimation")
How do I feedback the user about the rotation speed via sound?

In the apple developer document they have given an option to change the volume of the music, pl. refer the the below url
https://developer.apple.com/library/prerelease/ios/documentation/AVFoundation/Reference/AVAudioPlayerClassReference/index.html
and you can use the below code snippet for your reference
var spinnigSpeedMusic= AVAudioPlayer()
var spinningMusicUrl:NSURL = NSBundle.mainBundle().URLForResource("spinningSpeed", withExtension: ".mp33")
spinnigSpeedMusic = AVAudioPlayer(contentsOfURL:spinningMusicUrl, error: nil)
spinnigSpeedMusic.prepareToPlay()
spinnigSpeedMusic.play()
spinnigSpeedMusic.volume = 0.1 // this value can be changed from 0.0 to 1, based on the rotation speed.

Related

Swift5: Stop CABasicAnimation animation, just when the animation is finished

I have 4 edges, one for each corner, with animation. The only thing that the animation does is to vary the alpha of that border. It goes from 0.05 to 1 that alpha.
I am doing this way to the animation:
private func startAnimation(duration: CFTimeInterval) {
let cornerAnimate = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
cornerAnimate.fromValue = 1
cornerAnimate.toValue = 0.05
cornerAnimate.duration = duration
cornerAnimate.repeatCount = .infinity
cornerAnimate.autoreverses = true
cornerAnimate.isRemovedOnCompletion = false
cornerAnimate.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
corners.forEach { corner in
corner.add(cornerAnimate, forKey: "opacity")
}
}
I have it in .infinity because that's what I want to do. I want the animation to be displayed infinitely, and when I tell it to, at any time, stop it.
But I don't want it to stop abruptly, I want it to stop when the alpha is at 1.0. I mean, when I call the function stopAnimation(), it follows a little bit the animation until it 'finishes that cycle' and when the alpha is at 1.0 then it stops it.
This is what I tried to do, but the animation is still abrupt:
func stopAnimation() {
let endAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
var actualOpacity: Double = 0.05
corners.forEach { corner in
actualOpacity = corner.presentation()?.value(forKeyPath: "opacity") as! Double
}
endAnimation.fromValue = actualOpacity
endAnimation.toValue = 1.0
endAnimation.duration = 1.0
corners.forEach { corner in
corner.add(endAnimation, forKey: "end")
corner.removeAnimation(forKey: "opacity")
}
}
It looks like you're building the app in the simulator (since I can see the mouse movement), this appears to be a bug that effects simulators only. I was able to reproduce it in the simulator but not on an actual device.
Run it on a device and you should not be seeing that glitch.

Animating CALayer [filters, compositionFilter, backFilters]

I need to animate one of [filters, compositionFilter, backFilters] on a CALayer in video composition I tried the following code on NSView layer to test if the code works in the first place
view.wantsLayer = true
view.layerUsesCoreImageFilters = true
if let filter = CIFilter.init(name: "CIGaussianBlur", parameters: ["inputRadius": 50]) {
let animation = CABasicAnimation.init(keyPath: "filters")
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
animation.duration = 2 //for testing
animation.autoreverses = false
animation.fromValue = NSArray.init(array: [])
animation.toValue = NSArray.init(array: [filter])
view.layer.add(animation, forKey: "CIGaussianBlurAnimation")
}
but nothing happens, so I need to know what is the proper way to animate one/all of [filters, compositionFilter, backFilters] properties of CALayer, and whether or not it is supported on AVVideoComposition(on iOS 'I know it is not supported on iOS UIView CALayers')
I found 1/2 the solution
BUT the main question remains: "is there any way to enable CIFilter on CALayer in video composition on iOS"
it is indicated in the documentation, to update any attribute of the filter after is applied to the layer, you should use
layer.setValue(1, forKeyPath: "backgroundFilters.myFilter.inputRadius")
So it means to animate any filter, we should update the filter properties directly in the animation, so the following should be done
for compositingFilter property:
let animation = CABasicAnimation(keyPath: "compositingFilter.inputRadius")
and for a filter in filters property:
filter.name = "filterName"
layer.filters = [someFilters, filter, otherFilters]
let animation = CABasicAnimation(keyPath: "filters.filterName.inputRadius")
and for a filter in backgroundFilters property:
filter.name = "filterName"
layer.backgroundFilters = [someFilters, filter, otherFilters]
let animation = CABasicAnimation(keyPath: "backgroundFilters.filterName.inputRadius")
and set the animation properties, then add it to the layer layer.add(animation, forKey: "animationKey")
NOTE: animationKey could be any value, even a nil value, but reusing the value will remove the previous animation; even when is running

How to smoothly finish infinity animation

I have infinity CABasicAnimation which actually simulate pulsating by increasing and decreasing scale:
scaleAnimation.fromValue = 0.5
scaleAnimation.toValue = 1.0
scaleAnimation.duration = 0.8
scaleAnimation.autoreverses = true
scaleAnimation.repeatCount = .greatestFiniteMagnitude
scaleAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
I want to smoothly stop this animation in toValue. In other words, I want to allow current animation cycle finish, but stop repeating. Is there a nice and clean way to do this? I had a few ideas about freezing current animation, removing it and creating a new one with time offset, but maybe there is a better way?
There is a standard way to do this cleanly — though it's actually quite tricky if you don't know about it:
The first thing you do is set the layer's scale to the scale of its presentationLayer.
Then call removeAllAnimations on the layer.
Now do a fast animation where you set the layer's scale to 1.
Here's a possible implementation (for extra credit, I suppose we could adjust the duration of the fast animation to match what the current scale is, but I didn't bother to do that here):
#IBAction func doStop(_ sender: Any) {
let lay = v.layer
lay.transform = lay.presentation()!.transform
lay.removeAllAnimations()
CATransaction.flush()
lay.transform = CATransform3DIdentity
let scaleAnimation = CABasicAnimation(keyPath: "transform")
scaleAnimation.duration = 0.4
scaleAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
lay.add(scaleAnimation, forKey: nil)
}
Result:

How to trigger an animation by PERCENTAGE in swift?

Alright, I am trying to trigger an animation incrementally as a scroll view is scrolled. To do this, I have taken this link here and converted it to swift - http://www.oliverfoggin.com/controlling-animations-with-a-uiscrollview/
Giving me the percentage offset x for my scrollview. This is all great.
Problem is I'm fairly new to swift and don't know how to tie this back into my existing animation which is a transform/move instead of changing color.
Here's my animation here-
self.borderlines.transform = CATransform3DMakeScale(CGFloat(0.01), CGFloat(1.0), 1)
self.activeBorder.layer.opacity = 1
CATransaction.begin()
self.activeBorder.layer.transform = CATransform3DMakeScale(CGFloat(0.03), CGFloat(1.0), 1)
let anim2 = CABasicAnimation(keyPath: "transform")
let fromTransform = CATransform3DMakeScale(CGFloat(0.01), CGFloat(1.0), 1)
let toTransform = CATransform3DMakeScale(CGFloat(1.0), CGFloat(1.0), 1)
anim2.fromValue = NSValue(CATransform3D: fromTransform)
anim2.toValue = NSValue(CATransform3D: toTransform)
anim2.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
anim2.fillMode = kCAFillModeForwards
anim2.removedOnCompletion = false
self.activeBorder.layer.addAnimation(anim2, forKey: "_activeBorder")
CATransaction.commit()
And then I also have another move animation. As is these are called on touch, so 1 static event.
The guy in the tutorial from what I can see recalculated/triggers his animation EVERY scroll then alters bg color by percentage. I don't know how to apply this to another type of animation- he mentions key frames and I have no idea what those are.
How can I achieve this effect? What do I need to change here?
Look into "freezing" your animation by setting its layer's speed to zero and manipulating its timeOffset as your response to scrolling.

Disable Audio (and interruption) with MPMoviePlayerController using Swift

At the moment, this is how I'm playing a video on the subview of my UIViewController:
override func viewDidAppear(animated: Bool) {
let filePath = NSBundle.mainBundle().pathForResource("musicvideo", ofType: "mp4")
self.moviePlayerController.contentURL = NSURL.fileURLWithPath(filePath)
self.moviePlayerController.play()
self.moviePlayerController.repeatMode = .One
self.moviePlayerController.view.frame = self.view.bounds
self.moviePlayerController.scalingMode = .AspectFill
self.moviePlayerController.controlStyle = .None
self.moviePlayerController.allowsAirPlay = false
self.view.addSubview(self.moviePlayerController.view)
}
I've read on ways to disable the audio by doing the following below (none of which work, at all). Keep in mind I'm trying to disable it to the point of not interrupting the current music playing via the Music app, Spotify, etc.
// Playing media items with the applicationMusicPlayer will restore the user's Music state after the application quits.
// The current volume of playing music, in the range of 0.0 to 1.0.
// This property is deprecated -- use MPVolumeView for volume control instead.
1) MPMusicPlayerController.applicationMusicPlayer().volume = 0
2) MPVolumeView doesn't even have a setting for setting the actual volume? It's a control.
3) self.moviePlayerController.useApplicationAudioSession = false
So I found this answer.
This is my Swift code that I ended up going with. I then used an AVPlayerLayer to add to the view as a sublayer, which works perfectly.
Thanks to the OP who managed to get a hold of an Apple technician and provided the original Objective-C code.
The only problems I'm facing now is that it:
1) Interrupts current music playback, whether it's from Music, Spotify, etc.
2) Video stops playing if I close the app and open it up again.
override func viewDidAppear(animated: Bool) {
let filePath = NSBundle.mainBundle().pathForResource("musicvideo", ofType: "mp4")
var asset: AVURLAsset?
asset = AVURLAsset.URLAssetWithURL(NSURL.fileURLWithPath(filePath), options: nil)
var audioTracks = NSArray()
audioTracks = asset!.tracksWithMediaType(AVMediaTypeAudio)
// Mute all the audio tracks
let allAudioParams = NSMutableArray()
for track: AnyObject in audioTracks {
// AVAssetTrack
let audioInputParams = AVMutableAudioMixInputParameters()
audioInputParams.setVolume(0.0, atTime: kCMTimeZero)
audioInputParams.trackID = track.trackID
allAudioParams.addObject(audioInputParams)
}
let audioZeroMix = AVMutableAudioMix()
audioZeroMix.inputParameters = allAudioParams
// Create a player item
let playerItem = AVPlayerItem(asset: asset)
playerItem.audioMix = audioZeroMix
// Create a new Player, and set the player to use the player item
// with the muted audio mix
let player = AVPlayer.playerWithPlayerItem(playerItem) as AVPlayer
player.play()
let layer = AVPlayerLayer(player: player)
player.actionAtItemEnd = .None
layer.frame = self.view.bounds
layer.videoGravity = AVLayerVideoGravityResizeAspectFill
self.view.layer.addSublayer(layer)
}