Memory Leak in UIButton extension for fadeOut animation in swift4 - swift

I built an extension to the UIButton class to do fadeOut. When I use this I get memory leak warning in the profiler. I am using Swift 4 and Xcode 9.3.
Thanks in advance for any help.
extension UIButton {
func fadeOut() {
let fadeOut = CABasicAnimation(keyPath: "opacity")
fadeOut.duration = 0.35
fadeOut.fromValue = 1
fadeOut.toValue = 0.0
fadeOut.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
fadeOut.autoreverses = false
fadeOut.repeatCount = 0
fadeOut.isRemovedOnCompletion = true
self.layer.add(fadeOut, forKey: nil)
}
}
The calling function is given below. Also please note: new, level and card are UIButtons. When I comment out button.fadeout() in the function below the memory leak goes away as per the Xcode profiler. Hope this gives more context. If any other information is required to help analyze, I happy to provide the info.
private func menu_fadeout(){
func menu_fadeout_helper(_ button:UIButton){
button.fadeOut()
button.isHidden = true
button.isEnabled = false
}
menu_fadeout_helper(hint)
menu_fadeout_helper(new)
menu_fadeout_helper(level)
menu_fadeout_helper(card)
}

After staring at the code for a couple minutes, I see the issue. In your function. . .
private func menu_fadeout(){
func menu_fadeout_helper(_ button:UIButton){
button.fadeOut()
button.isHidden = true
button.isEnabled = false
}
menu_fadeout_helper(hint)
menu_fadeout_helper(new)
menu_fadeout_helper(level)
menu_fadeout_helper(card)
}
. . .you never directly reference the UIButtons hint, new, level, and card. Eventually, after pressing the buttons a ton of times, the memory will fill up with nothing and your app will crash. (or worse)
Change the function to this to (supposedly) remove the memory leak.
private func menu_fadeout(){
func menu_fadeout_helper(_ button: UIButton) -> UIButton {
button.fadeOut()
button.isHidden = true
button.isEnabled = false
return button
}
menu_fadeout_helper(self.hint)
menu_fadeout_helper(self.new)
menu_fadeout_helper(self.level)
menu_fadeout_helper(self.card)
}

After lots of poking around it turns out the animation layers cause leaks for various reasons - most have guesses but no precise answers.
To solve my problem I reimplemented the fadeOut function without using the CABasicAnimation and using UIView.animate and made NO other change to the code. The profiler has no issues now - all is good. Thanks!
fyi there seems to inadvertent leaks whenever using stings in the context of buttons etc. If anyone has any pointers or suggestions on that topic would appreciate it.

look for reference cycle (use weak/unowned self in capture lists)
remove all animations from layers
invalidate timer if there is one

Related

How do I update a UITableView on a timer while scrolling

My problem is very complicated to explain so I will do my best for explain it.
I'm doing a swift application with TableView. In this TableView, I have some data which are store in local (Dictionnary, Arrays, var, ...).
So, in my TableView I'm refreshing this datas every 0.01 second. Then, when I scroll my TableView this refresh is stopped and I don't want it. I Want a "continue refresh".
Someone can explain how I can do it? I search on StackOverflow and the most answer is : the Thread.
I understood Thread in C but it's very vague for me in Swift.
If you have an exercise for train multithreading in Swift you can post it !
Thanks for your time.
P.S: I can post some code but I don't think it's really necessary for my question.
EDIT:
There is the code for the timer and update
override func viewDidLoad() {
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self,
selector:#selector(ViewController.updateTimer), userInfo: nil,
repeats: true)
}
func updateTimer () {
var j = 0
for _ in rows {
if (rows[j]["Activ"] as! Bool == true ) {
rows[j]["timer"] = (rows[j]["timer"] as! Double + 0.01) as AnyObject
}
j += 1
}
myTableView.reloadData()
}
So, it happens because the Timer works in this same DispatchQueue as scrolling in the UITableView. You can solve this problem by using, for example, AsyncTimer.

skLabelNodes only working sometimes

So I added two label nodes to my code, and want one to be hidden while the other is shown, and then vice versa once the game begins.
class GameScene: SKScene {
// Declarations
let startLabel:SKLabelNode = SKLabelNode()
let scoreLabel:SKLabelNode = SKLabelNode()
var score = 0
override func didMoveToView(view: SKView) {
// Properties
startLabel.fontSize = 20
startLabel.fontColor = UIColor.blackColor()
startLabel.position = CGPointMake(self.frame.width*0.5, self.frame.height*0.5)
startLabel.text = "Touch Paddle To Begin"
startLabel.hidden = false
self.addChild(startLabel)
scoreLabel.fontSize = 20
scoreLabel.fontColor = UIColor.blackColor()
scoreLabel.position = CGPointMake(self.frame.width*0.5, self.frame.height*0.75)
scoreLabel.text = "\(score)"
scoreLabel.hidden = true
self.addChild(scoreLabel)
Then, when the Paddle is touched, a moveBall() function is initiated and the game begins. This is where I swapped the labels.
func moveBall() {
// Setting Labels
self.startLabel.hidden = true
self.scoreLabel.hidden = false
// Starting game
self.ball.physicsBody?.dynamic = true
However, it only works sometimes. It will work perfectly, and then without changing anything I'll run it again and only one label will show up. Then again and the other label will show up, or the startLabel will show for only a frame then disappear.
Disclaimer: I do not really know how to code, just got an idea for a game and am trying to make it a reality. Apologies if solution is something simple. Also any advice for my code would be appreciated. Thanks!
From the information given it should work correctly. However you should call the moveBall() in the touchesBegan method. Not the update method.
Also if you want it to be called if the user touches the labelNode use the location of touches began. (But I would rather use a sprite instead of a label coz labels are a bit unresponsive coz sometimes finger doesn't touch the label.
So call the moveBall() in the correct method. Give some more info to give a more accurate answer.

Swift UIViewPropertyAnimator automatically plays when leaving app

Good day everyone!
I've a problem with my app. I'm using an UIViewPropertyAnimator to animate my blur. I let the blur effect run from nil (0) to .light (1) but because the light effect is just too much I set the UIViewPropertyAnimator to 0.1 (.fractioncomplete = 0.1).
This works greath! But the problem is that if you leave the app (not kill, just leaving by pressing home button or going to another app), it automatically plays and goes from 0.1 to 1.
I think this is a bug? I hope someone has a solution for this.
Here some pictures: (not enough reputation to post them directly)
picture with blur on 0.1
picture with blur on 1
Code:
let effectView = UIVisualEffectView(effect: nil)
var animator: UIViewPropertyAnimator?
viewdidload:
effectView.frame = view.bounds
effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mainView.addSubview(effectView)
animator = UIViewPropertyAnimator(duration: 1, curve: .linear) {
self.effectView.effect = UIBlurEffect(style: .light)
}
blur action (need to start/pause first because of a known bug from apple to wake the animator up):
self.animator?.startAnimation()
self.animator?.pauseAnimation()
self.animator?.fractionComplete = CGFloat(0.1)
Thanks in advance !
I solved this issue by setting the pausesOnCompletion property of UIViewPropertyAnimator to true, immediately after setting the fractionComplete. No need to restart the animator.
I reproduced your issue, and confirm this behavior, but I doubt it's a bug. Property animator just returns from paused state to active and runs animation. To overcome it you have to stop it and then finish in current fraction of the state (respective states are described in docs). One more issue here - somehow stopping it right after setting fractionComplete stops it at 0 so you have to wait a while (very short). Try this code under your "blurAction":
self.animator?.startAnimation()
self.animator?.pauseAnimation()
self.animator?.fractionComplete = CGFloat(0.1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animator?.pauseAnimation()
self.animator?.stopAnimation(true)
self.animator?.finishAnimation(at: .current)
}
I verified it works for background/foreground app lifecycle change.

NSWindow transition animation for View Controller Segues

I've been trying to figure out how to get a NSWindow to perform a transition animation with Swift 3. I found a few examples in Objective-C, but I haven't been able to tease out the relevant details and translate into the target language / newer SDK and get it applied to the right object. This one is pretty flipping cool, but it's ~8yrs old: http://www.theregister.co.uk/2009/08/21/cocoa_graphics_framework/ -- I would imagine there's a better way to do the CGSCube effect now in macOS Sierra with Swift.
Here's what I have so far:
class ViewController: NSViewController {
func doAnimation() {
if let layer = view .layer {
layer.backgroundColor = CGColor.black
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(CGFloat.pi * 2.0)
rotateAnimation.duration = 10.0
layer.add(rotateAnimation, forKey: nil)
}
}
override func viewWillAppear() {
if let window = self.view.window {
window.styleMask = NSWindowStyleMask.borderless
window.backingType = NSBackingStoreType.buffered
window.backgroundColor = NSColor.clear
window.isOpaque = false
window.hasShadow = false
}
}
override func viewDidLoad() {
super.viewDidLoad()
doAnimation()
}
}
This doesn't really do the trick at all. I get a white background on my window instead of a transparent background, and my view rolls across the frame instead of the window frame itself being animated.
Ideally, I would like to do something more advanced like these 3d transitions -- https://cocoapods.org/pods/MKCubeController & https://www.appcoda.com/custom-view-controller-transitions-tutorial/ & https://github.com/andresbrun/ABCustomUINavigationController#cube but I'm not quite sure how to translate the examples from the iOS SDK over to the macOS SDK without UIKit. (Annoyingly, I remember pulling this off a few years back in ObjC, but the project was lost somewhere between formats / new computers.)
How can I apply a transform to the NSWindow itself while segueing between View Controllers? Any tips toward adding some 3d to this effect would be appreciated. I hoping there's maybe a CocoaPod that gets me halfway there.

UISlider glitch shows two thumbs?

A UISlider in my app sometimes shows a second thumb after it is dragged. This is completely unintentional and unwanted. I've done some searching and can't find any other references to this phenomenon. Can anyone figure out why this is happening? Is this a bug?
Two thumb slider:
This code is executed when a "show options" button is pressed:
readAndApplySettings() //This sets GameSpeed from a file
var SpeedSlider = UISlider()
SpeedSlider.minimumValue = 1
SpeedSlider.maximumValue = 20
SpeedSlider.setValue(Float(21 - GameState.GameSpeed), animated: true)
SpeedSlider.continuous = true
SpeedSlider.addTarget(self, action: "SpeedSliderValueDidChange:", forControlEvents: .ValueChanged)
// Code setting frame and location omitted
OptionsLabelButton.addSubview(SpeedSlider)
This is the ValueDidChange code:
func SpeedSliderValueDidChange(sender:UISlider) {
GameState.GameSpeed = Int(21 - sender.value)
TimerInterval = Double(GameState.GameSpeed) * 0.0167
tempSpeedLabel.text = "Speed: \(21 - GameState.GameSpeed)"
}
Looks like you have created multiple UISliders, double check you are not calling your function twice. As well, if you are using storyboard to create the UISliders. Make sure you do not have two sliders on top of each other.