Say you have an everyday CADisplayLink
class Test: UIViewController {
private var _ca : CADisplayLink?
#IBAction func frames() {
_ca?.invalidate()
_ca = nil
_ca = CADisplayLink(
target: self,
selector: #selector(_step))
_ca?.add(to: .main, forMode: .commonModes)
}
#objc func _step() {
let s = Date().timeIntervalSince1970
someAnime.seconds = CGFloat(s)
}
Eventually the view controller is dismissed.
Does anyone really definitively know,
do you have to explicitly call .invalidate() (and indeed nil _ca) when the view controller is dismissed?
(So perhaps in deinit, or viewWillDisappear, or whatever you prefer.)
The documentation is worthless, and I'm not smart enough to be able to look in to the source. I've never found anyone who truly, definitively, knows the answer to this question.
Do you have to explicitly invalidate, will it be retained and keep running if the VC goes away?
A run loop keeps strong references to any display links that are added to it. See add(to:forMode:) documentation:
The run loop retains the display link. To remove the display link from all run loops, send an invalidate() message to the display link.
And a display link keeps strong reference to its target. See invalidate() documentation:
Removing the display link from all run loop modes causes it to be released by the run loop. The display link also releases the target.
So, you definitely have to invalidate(). And if you're using self as the target of the display link, you cannot do this in deinit (because the CADisplayLink keeps a strong reference to its target).
A common pattern if doing this within a view controller is to set up the display link in viewDidAppear and remove it in viewDidDisappear.
For example:
private weak var displayLink: CADisplayLink?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startDisplayLink()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopDisplayLink()
}
private func startDisplayLink() {
stopDisplayLink() // stop previous display link if one happens to be running
let link = CADisplayLink(target: self, selector: #selector(handle(displayLink:)))
link.add(to: .main, forMode: .commonModes)
displayLink = link
}
private func stopDisplayLink() {
displayLink?.invalidate()
}
#objc func handle(displayLink: CADisplayLink) {
// do something
}
Method definition of invalidate():
Removing the display link from all run loop modes causes it to be
released by the run loop. The display link also releases the target.
For me this means that displaylink holds the target and run loop holds DispayLink.
Also, according to this link I found, it looks like its rather important to call invalidate() for cleanup of CADisplayLink.
We can actually validate using XCode's wonderful Memory graph debugger:
I have created a test project where a DetailViewController is being pushed on a navigation stack.
class DetailViewController: UIViewController {
private var displayLink : CADisplayLink?
override func viewDidAppear() {
super.viewDidAppear()
startDisplayLink()
}
func startDisplayLink() {
startTime = CACurrentMediaTime()
displayLink = CADisplayLink(target: self,
selector: #selector(displayLinkDidFire))
displayLink?.add(to: .main, forMode: .commonModes)
}
}
This initiates CADispalyLink when view gets appeared.
If we check the memory graph, we can see that DetailViewController is still in the memory and CADisplayLink holds its reference. Also, the DetailViewController holds the reference to CADisplayLink.
If we now call invalidate() on viewDidDisappear() and check the memory graph again, we can see that the DetailViewController has been deallocated successfully.
This to me suggests that invalidate is a very important method in CADisplayLink and should be called to dealloc CADisplayLink to prevent retain cycles and memory leaks.
Related
I want to implement an action that when I press and hold begins to repeatedly do an action (similar to a scroll button on a Desktop UI). Is there first class support for this in the UIGestureRecognizer/events framework, or do I just roll my own?
E.g.
var timer:Timer?
func killDing() {
self.timer?.invalidate()
self.timer = nil
}
func startDing() {
self.killTimer()
self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) {
self.ding() // this is where the repeated code happens
}
}
override func beginTracking(_ touch:UITouch, with event:UIEvent?) -> Bool {
self.startDing()
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
self.killDing()
}
I can of course do this with a LongPressGestureRecognizer as well. My question is whether I need to roll my own ding loop as shown above, or if there's something more first class in UIKit that I'm currently not aware of and should be taking advantage of.
I think you are on the right way. You can use use timers to repeat some actions, but you should add the created timer into a run loop with a common mode, without this mode, the run loop will not call the timer's action while a user is touching the screen
let timer = Timer(timerInterval: interval, repeats: true, block: block)
RunLoop.current.add(timer, forMode: . common)
Also you can use CADisplayLink, to call your action. You can find example of using CADisplayLink in my library, witch can help to you implement animation based on CADisplayLink:
let link = CADisplayLink(target: self, selector: #selector(updateAction(sender:)));
link.add(to: RunLoop.main, forMode: .commonModes);
In swift, how can I run some code 1 second after a user stops typing into a textfield? There is textFieldDidChange but that will run code immediately after a new character is typed.
Try with this custom UITextField, you need setup a timer to 1 second every time a user put a character in your UITextField, and invalidate the timer before re-schedule again, I added the action as closure to allow any kind of action, giving full freedom in you implementation
Added Inspectable property for delay customization
import UIKit
#IBDesignable
class AfterOneSecondTextField: UITextField {
#IBInspectable var delayValue : Double = 1.0
var timer:Timer?
var actionClosure : (()->Void)?
override func awakeFromNib() {
super.awakeFromNib()
self.addTarget(self, action: #selector(changedTextFieldValue), for: .editingChanged)
}
func changedTextFieldValue(){
timer?.invalidate()
//setup timer
timer = Timer.scheduledTimer(timeInterval: delayValue, target: self, selector: #selector(self.executeAction), userInfo: nil, repeats: false)
}
func executeAction(){
actionClosure?()
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
Use it
You only need to set the class, and pass a closure to your desired action in my case is debugPrint but you can do whatever you need
class ViewController: UIViewController {
#IBOutlet weak var tfText: AfterOneSecondTextField!
override func viewDidLoad() {
super.viewDidLoad()
tfText.actionClosure = {
debugPrint("Test")
}
}
This works!, was tested
Hope this helps
What I have done is use a cancelable closure to run code after some delay; that way as the user types the closure keeps getting cancelled, but when they pause long enough or finish the code to search or fetch is actually run after a brief delay.
I used the cancelable closure code from here:
Cancel a timed event in Swift?
Then the code that goes into textFieldDidChange looks like:
fileprivate var delayedSearchClosure : dispatch_cancelable_closure?
open func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if let searchClosure = delayedSearchClosure {
searchClosure(true)
delayedSearchClosure = .none
}
delayedSearchClosure = delayClosure(0.8) { [weak self] in
self?.doSearch(searchText)
self?.delayedSearchClosure = .none
}
}
To explain what is happening here - in the callback for the textfield as the user types, I first terminate the existing closure created to run at a specific time. Then in the next block of code, it makes a new delayedSearchClosure that will execute 0.8 seconds from now and call "doSearch()", then toss away the closure.
Thus as the user types, closures are being thrown away that no longer matter. Eventually the user will pause or stop typing, at that point the code you want run is actually executed (in this case a search is performed based on the text). This gives you a search form that feels responsive but does not waste time with an added search launched for every character a user types (which can add overhead and start to impact the UI performance).
I have a UIView that plays audio, has a NSTimer and progress circular bar animation.
To keep things simple, if the user switches apps or takes a call, I would like all processes to stop and reset themselves.
I propose to use:
Call the Observer - perhaps in viewWillAppear:
override func viewWillAppear(animated: Bool) {
// set observer for WillEnterForeground
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(WorkoutFullFace1Exercise7TVC.willEnterBackground), name: UIApplicationDidEnterBackgroundNotification, object: nil)
...}
and
Stop relevant tasks:
// Stop all audio, timer and animation if app enters background.
func willEnterBackground() {
myAudioPlayer.stop()
myAudioPlayer.currentTime = 0
swiftTimer.invalidate()
swiftCounter = 60
timerLabel.text = String(swiftCounter)
pauseBtn.alpha = 1.0
playBtn.alpha = 1.0
stopBtn.alpha = 1.0
currentCount = 0
circularProgressView.animateFromAngle(circularProgressView.angle, toAngle: 0, duration: 0.5, completion: nil)
}
Dismiss the observer:
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidEnterBackgroundNotification, object: nil)
.... }
Where is the correct place to load and dismiss the observer? I have read threads that state to use viewDidLoad/ deinit, viewWillAppear / Disappear, etc..
Can anyone please shed some light on what is recommended for my scenario and also what is current and likely to remain 'usable' in the future (language and practise seems to change rapidly in the programming World).
And am I using the correct syntax above?
Thanks!
Everything seems correct to me
Just make sure to call super implementation in each method. That can
lead to some issues when you are subclassing things later
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
// other code
...}
// same for viewWillDisappear
So I'm doing little timer app in swift and I just have 2 buttons. One to start timer, and one to stop it and reset value to 0. I've figured out everything, and I have this function called timer which increases value for one each second for variable "Time". Problem is that when I click the STOP button, it resets the value to 0 but it keeps counting again.
Question is how do I stop that function from running.
Here is some code
var time = 0
func result() {
time++
print(time)
}
#IBAction func clickToStart(sender: AnyObject) {
result()
}
#IBAction func clickToStop(sender: AnyObject) {
time = 0
print(time)
}
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer()
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("result"), userInfo: nil, repeats: true)
}
Make your timer a member variable and call timer.invalidate() on it
Change your variable timer to be an instance variable. Make it weak, since the system owns it, and when you stop the timer it will be deallocated automatically.
In your clickToStop method, call timer.invalidate().
As others have pointed out using the timer.invalidate() functions, but you can also use flag variables in case you are doing something else not related to time or want another actions to stop.
Basically, you can just create a bool variable and when the user stops the actions, make the bool true. In the other function, make it that if bool is true then don't do the actions unless it's false. This works well for a mute button.
Like in my game, when it's a game over, I have a bool variable touchesInvalid that is at the very top of the touchesBegan function. If the touchesInvalid bool is true, then the user can't do any more actions that involved touch.
I'm trying to animate some bezier paths in swift, and I need to get some of them to launch after a set delay. To do this, I have to write both of these sets of code in the viewDidLoad function.
Here is some sample code with the same idea:
override func viewDidLoad() {
super.viewDidLoad
func testFunc() {
println("Hello")
}
var frontOfBaseTimer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("testFunc"), userInfo: nil, repeats: false)
}
This would crash the app 3 seconds after the view loaded. The error message leads me to believe that the only problem in this case is the target property.
What should I change here to get this to work?
If you're bound and determined to use your own delay loop, consider using GCD and the dispatch_after method. That method takes a closure and invokes the closure after a specified delay, which is pretty much exactly what you want. You would pass nil for the queue parameter so your closure would be run on the main queue.
I created a global function delay that lets me invoke dispatch_async painlessly without having to figure out it's confusing parameters:
func delay(delay: Double, block:()->())
{
let nSecDispatchTime = dispatch_time(DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC)));
let queue = dispatch_get_main_queue()
dispatch_after(nSecDispatchTime, queue, block)
}
You call it like this:
delay(2.0)
{
//code to fire after a delay
}
try this:-
override func viewDidLoad() {
super.viewDidLoad()
var frontOfBaseTimer =
NSTimer.scheduledTimerWithTimeInterval(3,
target: self,
selector:"testFunc",
userInfo: nil,
repeats: false)
}
func testFunc(){
println("hello its me")
}
Do like this way, Your app get crash because, timer call the function which has been added in viewdidload method.
override func viewDidLoad() {
super.viewDidLoad()
var frontOfBaseTimer =
NSTimer.scheduledTimerWithTimeInterval(3,
target: self,
selector: Selector("testFunc"),
userInfo: nil,
repeats: false)
}
func testFunc() {
println("Hello")
}
I just tested it and confirmed that you can't use a nested function as the selector for an NSTimer. The method the timer calls needs to be defined at the global scope of the target.
If you're trying to do animation, why not use UIView animation? That family of methods includes a delay parameter.
You could also use Core Animation. You can animate a path very easily by installing the CGPath from a bezierPath into a shape layer, then changing the shape layer's path as part of a CABasicAnimation.
Using Core Animation makes for very smooth, clean animations.
I have a project called RandomBlobs on Github that shows how to animate Bezier paths using CAShapeLayers and CABasicAnimations. It's written in Objective-C, but the techniques are directly applicable in Swift.
You can see a short video of the bezier path animation on Youtube.