Correct way to create CADisplayLink: UIScreen vs. init() - ios

There are 2 ways to get CADisplayLink in iOS. The direct one is to use initializer:
let displaylink = CADisplayLink(target: self,
selector: #selector(step))
Returns a new display link.
This way is used in Apple's example: Listing 1.
But there are other way to get it from UIScreen:
let displayLink = UIScreen.main.displayLink(withTarget: self,
selector: #selector(step))
Returns a display link object for the current screen.
You use display link objects to synchronize your drawing code to the screen’s refresh rate. The newly constructed display link retains the target.
Documentation is very poor for details, but the second way looks a little more optimised. May be someone who has experience with CADisplayLink can tell which way to create it is preferred.

Related

Is it bad practice to modify view after viewDidDisappear called?

I have UITabBarController and in one of the UIViewController there I scroll UICollectionView each 5 second using Timer. Here is short code how I do it:
override func viewDidLoad() {
super.viewDidLoad()
configureTimer()
}
private func configureTimer() {
slideTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(scrollCollectionView), userInfo: nil, repeats: true)
}
#objc func scrollCollectionView() {
collectionView.scrollToItem(at: someIndexPath, at: .centeredHorizontally, animated: true)
}
It perfectly works. But I think it has a big issue. Of course, I can open another screen from this UIViewController (for example, I can tap to another tab or push another UIViewController). It means, my UIViewController's view, containing UICollectionView, disappears. In another words, viewDidDissapear will be called. But my timer still exists and I am having strong reference to it, possibly, there is retain cycle. It keeps working and each 5 second scrollCollectionView method is called even my view dissapeared. I don't know how, but iOS somehow handles it. In other words, it can modify view even it is not visible. How is that possible and is it good practice? Of course, I can invalidate my timer in viewDidDissapear and start it in viewDidAppear. But I don't want to loose my timer value and don't want to start it from zero again. Or may be it is ok to invalidate my timer in deinit?
My question covers pretty common situation. For instance, if I make network request and open another UIViewController. After that request finished, I should modify UI, but now am on another screen. Is it ok to allow iOS to modify UI even it is not visible?
A couple of thoughts:
If the timer is updating the UI at some interval, you definitely should start it in viewDidAppear and stop it in viewDidDisappear. There’s no point in wasting resources updating a view that is not visible.
By doing this, you can solve your strong reference cycle, too.
In terms of “losing” your timer value and starting at zero, we generally would just save the time you’re counting from or to and calculate the necessary value when restarting the timer later.
We do this, anyway, because you really shouldn’t be using timers to increment values because you’re technically not assured that they’ll be called with the frequency you expect.
All of that said, I don’t know what timer “value” you’re worried about losing in this example.
But definitely don’t waste time updating a UI that is no longer visible. It’s not scalable and blurs the distinction between the model (what you’re counting to or from) from the UI (the update that happens every five seconds).

Update UITableViewCell using Timer without blocking UI

I'm trying to create a UITableView that contains a bunch of cells that count down from a value. Initially I was just running an NSTimer in the ViewController on repeat to run a function that would update the labels and then run a reloadData on the table, and that does technically work, but with some issues.
To fill things in a bit more, I'm well aware NSTimer isn't accurate as a timer, so I use a date calculation for the time on the labels. I also considered using CADisplayLink so the refresh would sync with the screen, but this ran into problems with scrolling the table, making it very jumpy. Another issue is when issuing the Edit command on the table, the UI is unable to delete rows because of the refresh.
So, I've since considered moving my Timer into each UITableViewCell, and I can tell that it is running via the console, but the table doesn't update. This makes sense, given that it can't run the reloadData on the table, but I'm a bit stuck as to where to go next.
I found a similar thread here and even tried the AsyncTimer suggested here but either I'm doing something wrong, or these aren't quite the same issues? Any help is appreciated, thank you!
var timer = Timer()
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.checkTimers), userInfo: nil, repeats: true)
#objc func checkTimers() {
for counter in counters {
if counter.isActive {
counter.updateSeconds()
}
}
tableView.reloadData()
}

UILongPressGestureRecognizer in Spritekit and Swift 4

I am a complete newbie to programming and I am trying to learn how to make a simple iOS game using Spritekit and Swift 4.
So far, I have achieved some mild success, but I would like to add some further details to the game to make it a little more playable.
I have added some actions to my GameScene so that when the user taps the screen, a Sprite execute an action. It works fine, but now I want to keep repeating that action if the user holds its finger on the screen.
I have read some posts about it, but they all seem to point to Objective-C or earlier versions of Swift that just pop a bunch of errors when testing and I am unable to get them working for me.
I know I am supposed to be using some instance of UILongPressGestureRecognizer but Apple's documentation seems rather confusing about how to initialise it or what to declare on action: Selector?
As I understand it, in my viewDidLoad I must include something like:
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPress:")
self.addGestureRecognizer(longTapRecognizer)
And then write a function (I am not sure if inside viewDidLoad as well) that handles the action:
func handleLongPress(recognizer: UIGestureRecognizer) {
if recognizer.state == .began {
print("Long press")
}
}
As easy as this may sound, I simply cannot seem to understand how the action: is supposed to be declared or how to solve this.
Any guidance will be greatly appreciated!
The syntax for the action in swift is #selector(methodName(params:))
(See https://developer.apple.com/documentation/swift/using_objective_c_runtime_features_in_swift)
Your gesture recognizer would be written as such:
let longTapRecognizer = UILongPressGestureRecognizer(
target: self,
action: #selector(handleLongPress(recognizer:)))

Showing scroll indicator all the time UITextView

I would like to show my scrollview all the time. I have looked around for some answers and i found this which i implemented:
override func viewDidLoad() {
super.viewDidLoad()
_ = NSTimer.scheduledTimerWithTimeInterval(0.001, target: self, selector: #selector(flashIndicator), userInfo: nil, repeats: true)
and the function:
func flashIndicator(){
informationView.flashScrollIndicators()
}
To make sure it is getting called i put a simple print in the function and its getting called many times...
But the scroll is not being shown? Just when im actually scrolling. Anybody knows why?
To the best of my knowledge, Apple frowns upon scroll indicators been constantly shown. Thus, I believe that have deliberately made it a private API. So in short, it may be possible, but I don't think the app would be allowed into the app store.

CAAnimation get live updates of animated properties

I have added a CAAnimation to some view's layer, and it animates the view's position. While the animation runs, I want to animate an object in a OpenGL window. In order to do so, I have to somehow get live updates from the property I animate.
I tried
someView.layer.addObserver(self, forKeyPath: "position", options: nil, context: nil)
But I only get updates once the animatin completes. I also tried to add the observer to the presentation layer, without success.
Any ideas?
I was trying to observe (KVO) the transform of a CALayer during an CABasicAnimation but never worked for me, even trying on the PresentationLayer... The way I solved it in my situation was to use a CADisplayLink.
That will allow you to specify the frameTimeInterval you want the link to run with and each time it fires you can directly check on the transform of the layer you have been trying to get KVO updates from.
Works great for me.
So, something like this:
displayLink = CADisplayLink(target: self, selector: "foo")
displayLink?.frameInterval = 1
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
Inside foo() I would then check for the transform on the layer's presentationLayer() and then I would use the information as needed...

Resources