my ViewController is still sending an array update, even if I'm in another View, what can I do? Here is my code:
import UIKit
import CoreBluetooth
class ViewController: UIViewController {
var audioVibe : AudioVibes!
var superpowered:Superpowered!
var displayLink:CADisplayLink!
var layers:[CALayer]!
var magnitudeArray : [UInt16] = [0, 0, 0, 0, 0, 0, 0]
override func viewDidLoad() {
super.viewDidLoad()
// Setup 8 layers for frequency bars.
let color:CGColorRef = UIColor(red: 0, green: 0.6, blue: 0.8, alpha: 1).CGColor
layers = [CALayer(), CALayer(), CALayer(), CALayer(), CALayer(), CALayer(), CALayer(), CALayer()]
for n in 0...7 {
layers[n].backgroundColor = color
layers[n].frame = CGRectZero
self.view.layer.addSublayer(layers[n])
}
superpowered = Superpowered()
// A display link calls us on every frame (60 fps).
displayLink = CADisplayLink(target: self, selector: #selector(ViewController.onDisplayLink))
displayLink.frameInterval = 1
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
}
// Gets triggered when you leave the ViewController.
override func viewWillDisappear(animated: Bool) {
superpowered.togglePlayback();
superpowered.moritz();
//delete(superpowered);
}
func onDisplayLink() {
// Get the frequency values.
let magnitudes = UnsafeMutablePointer<Float>.alloc(8)
superpowered.getMagnitudes(magnitudes)
// Wrapping the UI changes in a CATransaction block like this prevents animation/smoothing.
CATransaction.begin()
CATransaction.setAnimationDuration(0)
CATransaction.setDisableActions(true)
// Set the dimension of every frequency bar.
let originY:CGFloat = self.view.frame.size.height - 20
let width:CGFloat = (self.view.frame.size.width - 47) / 5
var frame:CGRect = CGRectMake(20, 0, width, 0)
for n in 0...4 {
frame.size.height = CGFloat(magnitudes[n]) * 2000
frame.origin.y = originY - frame.size.height
layers[n].frame = frame
frame.origin.x += width + 1
}
// Set the magnitudes in the array.
for n in 0...6 {
magnitudeArray[n] = UInt16(magnitudes[n] * 32768)
}
// Update the array in the audioVibe class to trigger the sending command.
audioVibe.magnitudeArray = magnitudeArray
CATransaction.commit()
// Dealloc the magnitudes.
magnitudes.dealloc(8)
}
}
I want him to stop doing things like audioVibe.magnitudeArray = magnitudeArray while I'm not in his view, what can I do?
Thanks!
Looks like you display link isn't getting paused when you transition to different views.
You could pause your display link when transitioning away from this view and resume it when coming back to this view using the paused property on CADisplayLink. You would pause it in viewWillDisappear and resume in viewWillAppear.
In your viewDidLoad method you're creating a CADisplayLink and adding it to the run loop.
If you don't do anything, that display link will stay active when you push another view controller on top of it.
You should move the code that creates the display link and adds it to the run loop to your viewWillAppear method.
Then you need to add code in viewWillDisappear that removes the display link from the run loop, invalidates it, and nils it out.
That way, you'll start your display link code when the view appears, and stop it when the view disappears.
Related
I've read many Stack Overflow questions in search of a solution for this, but I can't find one that addresses this particular issue in a way that works in iOS 13.
I have a label that I want to pulse repeatedly (like a slow, fading blink) once my view controller loads.
When it's time to transition away from the view, at the push of a button, I want to stop the pulsing at whatever alpha the pulsing label currently has, and then fade it out from there along with other UI elements.
In the past, I would have used a UIViewPropertyAnimator for this repeating animation, and then paused the animation once the button is tapped, but as of iOS 13 UIView.setAnimationRepeatCount(.greatestFiniteMagnitude) and UIView.setAnimationRepeatAutoreverses(true) are deprecated, which (I think) makes it impossible to build my pulse animation with a property animator.
Here is my example code:
import UIKit
class ViewController: UIViewController {
private lazy var pulsingLabel: UILabel = {
let label = UILabel()
label.text = "I am pulsing"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private lazy var stopButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Stop Pulsing",
for: .normal)
button.addTarget(self,
action: #selector(fadeEverythingOut),
for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(pulsingLabel)
view.addSubview(stopButton)
pulsingLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
pulsingLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stopButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stopButton.firstBaselineAnchor.constraint(equalToSystemSpacingBelow: pulsingLabel.lastBaselineAnchor,
multiplier: 4).isActive = true
startRepeatingAnimation()
}
private func startRepeatingAnimation() {
UIView.animate(withDuration: 1,
delay: 0,
options: [.repeat, .autoreverse, .curveEaseInOut],
animations: {
self.pulsingLabel.alpha = 0
})
}
#objc
private func fadeEverythingOut() {
pulsingLabel.layer.removeAllAnimations()
UIView.animate(withDuration: 2.0,
animations: {
self.pulsingLabel.alpha = 0
})
}
}
In this example, I'm calling pulsingLabel.layer.removeAllAnimations() to stop the animation, but this is not an acceptable solution because the label just immediately disappears when it's called.
I've also tried some weird stuff like sandwiching that line between UIView.setAnimationsEnabled(false) and UIView.setAnimationsEnabled(true), but that didn't work.
I also tried saving the alpha of pulsingLabel right before calling pulsingLabel.layer.removeAllAnimations() and then setting it again right after, but I always get a value of 0 for pulsingLabel.alpha.
Am I missing something here by abandoning UIViewPropertyAnimator, where I would still be able to get it to repeat, even with the deprecated functions on UIView? Or maybe I need to be calling .layer.removeAllAnimations() on a different layer or differently somehow?
Any ideas would be much appreciated, as I care quite a bit about getting these fine details right in my app!
I didn't see that you were looking for swift answer, but I only know ObjC right now. So here is the solution by timer (though in ObjC sorry):
In viewdidload:
self.angle = 0;
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(update) userInfo:nil repeats:YES];
angle is CGFloat to store current value to calculate the alpha by cosine function in update method:
-(void)update {
self.angle += M_PI/36.0;
if (self.angle > M_PI) self.angle = 0.0;
CGFloat val = (1 + cos(self.angle)) / 2.0;
dispatch_async(dispatch_get_main_queue(), ^{
self.myLabel.alpha = val;
});
}
You can adjust the value 36.0 to make the fade in/out slower or faster. val will give a swing value between 0 and 1 back and forth, as angle increases. Then using GCD we set the label alpha as needed in real time. When a button is pressed to move to another viewcontroller (as per your requirement), you only need to stop the timer by invalidating it, and the label will remain at whatever alpha is was set.
[self.myTimer invalidate];
self.myTimer = nil;
// CGFloat currentAlpha = self.myLabel.alpha;
From here, you can do other thing with label alpha.
I'm trying to make an animation where the color red pulses on the screen and when the screen is tapped the speed of the pulse increases. Right now I have two issues. One, is that the animation does not occur, the second is that the animation must speed up without restarting the animation. I want the animation to speed up from whatever point it is at when the screen is tapped.
class ViewController: UIViewController {
var tapCount: Int = 0
var pulseSpeed: Double = 3
let pulseAnimLayer = CALayer()
let pulseAnim = CABasicAnimation(keyPath: "Opacity")
override func viewDidLoad() {
super.viewDidLoad()
counter.center = CGPoint(x: 185, y: 118)
pulseAnim.fromValue = 0.5
pulseAnim.toValue = 1.0
pulseAnim.duration = 3.0
pulseAnim.autoreverses = true
pulseAnim.repeatCount = .greatestFiniteMagnitude
pulseAnimLayer.add(pulseAnim, forKey: "Opacity")
}
func pulseAnimation(pulseSpeed: Double) {
UIView.animate(withDuration: pulseSpeed, delay: 0,
options: [UIViewAnimationOptions.repeat, UIViewAnimationOptions.autoreverse],
animations: {
self.red.alpha = 0.5
self.red.alpha = 1.0
}
)
}
#IBOutlet weak var red: UIImageView!
#IBOutlet weak var counter: UILabel!
#IBAction func screenTapButton(_ sender: UIButton) {
tapCount += 1
counter.text = "\(tapCount)"
pulseSpeed = Double(3) / Double(tapCount)
pulseAnim.duration = pulseSpeed
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I didn't read this carefully enough the first time.
You've got an odd mix here of core animation and UIView animation. The two exist at different levels. Core Animation will allow you to animate a property on a view (say it's background color).
UIView animation is built on top of Core Animation and makes it easier to animate view changes automatically by building the underlying Core Animations.
You start with Core Animation when you create a CABasicAnimation but then you have a function, pulseAnimation which looks to be doing something using UIVIew animations. I think you can get rid of that function entirely.
When you add your CABasicAnimation you tell it to change the value of the layer's "Opacity". What you probably want there is "opacity" (lower case).
Your animation is being applied to a new CALayer that you've created... but you don't seem to ever put that layer into the view (so it is never drawn). More likely than not what you should probably do is attach your animation to the view's layer and just allow that layer to have it's color animated.
To add one layer to another use addSublayer (https://developer.apple.com/reference/quartzcore/calayer/1410833-addsublayer). The view's layer from your code should be available as self.view.layer though you may have to use it in viewWillAppear instead of in viewDidLoad
You need to set the alpha of the red imageview to 0.0 before animating
func pulseAnimation(pulseSpeed: Double) {
UIView.animate(withDuration: pulseSpeed, delay: 1.0,
options: [UIViewAnimationOptions.repeat, UIViewAnimationOptions.autoreverse],
animations: {
self.red.alpha = 0.5
self.red.alpha = 1.0
}
)
}
override func viewDidAppear(_ animated: Bool) {
self.red.alpha = 0.0
counter.center = CGPoint(x: 185, y: 118)
}
I am a beginner in swift. I am building a single view tip calculator app and i only want some animation to happen when the app loads for the first time. And not happen every time i edit something.
Here is my code:
I am hiding the container outside the view when the app loads
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
billView.center.y -= (billView.frame.height)/2
}
And animating it to move down
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
UIView.animateWithDuration(0.8, delay: 0.0,
options: [.CurveEaseOut], animations: {
self.billView.center.y = 210 }, completion: nil)
print(billView.center.y)
}
This works fine when the app loads. Unfortunately this animation keeps happening again and again anytime I interact with the app.
Like this animation happens when this function is called (which is when any information is entered on the text field or the slider is moved)
let billAmount = NSString(string: billField.text!).doubleValue
var tipPercentages = [0.15, 0.18, 0.22]
let tipPercentage = tipPercentages[segTipControl.selectedSegmentIndex]
let percentLabel = round(tipPercentage * 100)
selectedbillPercentageLabel.text = "+\(percentLabel)%"
let tipAmount = billAmount * tipPercentage
let totalAmount = billAmount + tipAmount
let roundedTotalAmount = round(totalAmount * 100)/100
totalLabel.text = "$\(roundedTotalAmount)"
let splitSelected = Double(splitValueSlider.value)
let intsplitSelected = Int(splitSelected)
splitLabel.text = "\(intsplitSelected)"
let splitDollarAmount = roundedTotalAmount/splitSelected
let roundedSplitDollarAmount = round(splitDollarAmount * 100)/100
splitAmount.text = "$\(roundedSplitDollarAmount)"
Is there a way to avoid calling the method viewDidLayoutSubviews() when any data is entered in the app.
animated gif
Rather than avoiding calling viewDidLayourSubviews, you could add a simple boolean flip to show your animation only once. Add a boolean property that you initialize to false; show animation on condition of this property being false, and flip the boolean after the animation has been shown.
var animationHasBeenShown = false // class property
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !animationHasBeenShown {
UIView.animateWithDuration(0.8, delay: 0.0,
options: [.CurveEaseOut], animations: {
self.billView.center.y = 210 }, completion: nil)
print(billView.center.y)
animationHasBeenShown = true
}
}
Alternatively, consider placing your animation in another context; does it really need to be in viewDidLayoutSubviews() (depends on the context of your app, but possibly rather in viewDidAppear()?)
Im trying to make a small game. And there is some problem with the animation. Im new to Swift. So lets take a look. I create a UIImageView picture and want to do animation of this picture appear in a different places on a screen. I believe that the algorithm will look like this:
Infinite loop{
1-GetRandomPlace
2-change opacity from 0 to 1 and back(with smooth transition)
}
Looks simple, but I can't understand how to do it correctly in Xcode.
Here is my test code but it looks useless
Thank you for help and sorry if there was already this question, I can't find it.
class ViewController: UIViewController {
#IBOutlet weak var BackgroundMainMenu:UIImageView!
#IBOutlet weak var AnimationinMenu: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// MoveBackgroundObject(AnimationinMenu)
// AnimationBackgroundDots(AnimationinMenu, delay: 0.0)
// self.AnimationinMenu.alpha = 0
//
// UIImageView.animateWithDuration(3.0,
// delay: 0.0,
// options: UIViewAnimationOptions([.Repeat, .CurveEaseInOut]),
// animations: {
// self.MoveBackgroundObject(self.AnimationinMenu)
// self.AnimationinMenu.alpha = 1
// self.AnimationinMenu.alpha = 0
// },
// completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
AnimationBackgroundDots(AnimationinMenu, delay: 0.0)
}
func ChangeOpacityto1(element: UIImageView){
element.alpha = 0
UIView.animateWithDuration(3.0) {
element.alpha = 1
}
}
func ChangeOpacityto0(element: UIImageView){
element.alpha = 1
UIView.animateWithDuration(3.0){
element.alpha = 0
}
}
func AnimationBackgroundDots(element: UIImageView, delay: Double){
element.alpha = 0
var z = 0
while (z<4){
MoveBackgroundObject(AnimationinMenu)
UIImageView.animateWithDuration(3.0,
animations: {
element.alpha = 0
element.alpha = 1
element.alpha = 0
},
completion: nil)
z++
}
}
func MoveBackgroundObject(element: UIImageView) {
// Find the button's width and height
let elementWidth = element.frame.width
let elementHeight = element.frame.height
// Find the width and height of the enclosing view
let viewWidth = BackgroundMainMenu.superview!.bounds.width
let viewHeight = BackgroundMainMenu.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - elementWidth
let yheight = viewHeight - elementHeight
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
element.center.x = xoffset + elementWidth / 2
element.center.y = yoffset + elementHeight / 2
}
}
The problem is in AnimationBackgroundDots.
You are immediately creating 4 animations on the same view but only one can run at a time. What you need to do is wait until one animation is finished (fade in or fade out) before starting a new one.
Also, the animations closure is for setting the state you want your view to animate to. It looks at how your view is at the start, runs animations, then looks at the view again and figures out how to animate between the two. In your case, the alpha of the UIImageView starts at 0, then when animations runs, the alpha ends up being 0 so nothing would animate. You can't create all the steps an animation should take that way.
Want you need to do it move your view and start the fade in animation. The completion closure of fading in should start the fade out animation. The completion closure of the fading out should then start the process all over again. It could look something like this.
func AnimationBackgroundDots(element: UIImageView, times: Int) {
guard times > 0 else {
return
}
MoveBackgroundObject(element)
element.alpha = 1
// Fade in
UIView.animateWithDuration(3.0, animations: {
element.alpha = 1
}, completion: { finished in
// Fade Out
UIView.animateWithDuration(3.0, animations: {
element.alpha = 0
}, completion: { finished in
// Start over again
self.AnimationBackgroundDots(element, times: times-1)
})
})
}
You called also look at using keyframe animations but this case is simple enough that theres no benefit using them.
Also as a side note. The function naming convention in swift is to start with a lowercase letter, so AnimationBackgroundDots should be animationBackgroundDots.
I'm trying to get a twinkling star background effect in all my app screens. I put this code in the initial view controller file:
for index in 1...50 {
var x = CGFloat(arc4random_uniform(400))
var y = CGFloat(arc4random_uniform(700))
// Set the Center of the Circle
// 1
var circleCenter = CGPointMake(x,y)
// Set a random Circle Radius
// 2
var circleWidth = CGFloat(arc4random_uniform(7) + 2)
var circleHeight = circleWidth
// Create a new CircleView
// 3
var circleView = circleview(frame: CGRectMake(circleCenter.x, circleCenter.y, circleWidth, circleHeight))
view.addSubview(circleView)
UIView.animateWithDuration(NSTimeInterval(1000 + arc4random_uniform(100)) / 1000, delay: NSTimeInterval(arc4random_uniform(100)) / 1000, options: .CurveEaseIn | .Repeat | .Autoreverse, animations: {circleView.alpha = 0
}, completion: { finished in
println("thing!") })
}
And I put the same code in each additional view controller file but the circles don't appear. If I click to go back to the initial screen, the circles again don't appear (even though they do the first time the app is loaded).
If something runs the first time a view appears, but doesn't when you navigate back, you probably set it up in viewdidload. Try viewwillappear. This gets fired each time the view is shown. Not only when the view is first loaded.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// your code that needs to run each time a view appears
}