Observing UIViewPropertyAnimator is running issue - ios

From Apple Documents pausesoncompletion
Because the completion handler is not called when this property is true, you cannot use the animator's completion handler to determine when the animations have finished running. Instead, you determine when the animation has ended by observing the isRunning property.
But i found observe isRunning not work. util i wathched WWDC 2017 - Session 230-Advanced Animations with UIKit ,i konwed i should observe running
//not work
animator.addObserver(self, forKeyPath: "isRunning", options: [.new], context: nil)
//this work
animator.addObserver(self, forKeyPath: "running", options: [.new], context: nil)
My Question is: where can i find the excatly keypath, not only this case. Thx~

In Swift it's recommended to use the block-based KVO API (available as of Swift 4) which allows you to observe a property in a type safe and compile-time checked manner:
// deinit or invalidate the returned observation token to stop observing
let observationToken = animator.observe(\.isRunning) { animator, change in
// Check `change.newValue` for the new (optional) value
}
Note that the key path is \.isRunning because the property on UIViewPropertyAnimator in Swift is called isRunning.
This both has the benefit that you don't have to know how the string for a given property is spelled and that the changed value has the same type as the observed property.
Note that in Objective-C this API is not available, so the corresponding Objective-C documentation ask you to observe the "running" property. This is because in Objective-C the property is called "running" (but has a getter called "isRunning").

A little late to the party, but the currently accepted answer doesn't exactly answer the question, so let me go ahead.
The discrepancy comes from using different names for ObjC and Swift. Use #keyPath(Type.propertyName)
So for your specific case it would be #keyPath(UIViewPropertyAnimator.isRunning)
Try printing the result. It will correctly show "running"

Related

Swift NotificationCenter remove observer quickest way

I am adding a number of observers in my viewController -- applicationWillResignActive, applicationDidEnterBackground, and many others. I want to remove self as observer to all registered notifications in one line. My question is whether the following line is enough to do that, or are there issues with this code?
deinit {
NotificationCenter.default.removeObserver(self)
}
#Sh_Khan is right:
NotificationCenter.default.removeObserver(self)
You can get even further, as mentioned in the Apple Documentation:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method.
So I'm working with this in an app right now and the answer might not be as straightforward.
In the documentation, it does state that for iOS 9 and above you are no longer required to explicitly remove the observer in the deinit/dealloc methods for objects. https://developer.apple.com/documentation/foundation/notificationcenter/1413994-removeobserver
However, it appears that this is only true for selector based notification observers. I'll be referencing this blog post: https://oleb.net/blog/2018/01/notificationcenter-removeobserver/.
If you are using block based observers you must still manually remove the observers.
addObserver(forName:object:queue:using:)
The best general way to do this is to capture the tokens in an array, append them when you add the observer and use them for removal when you deinit/dealloc or otherwise need to remove observer behavior for your object.
in your VC/object properties create an array to store observer 'tokens'
var notifObservers = [NSObjectProtocol]()
Register for a block based notification by capturing the function return object and storing it as a token
let observer = NotificationCenter.default.addObserver(forName: , object: , queue:) { [weak self] notification in
// do a thing here
}
notifObservers.append(observer)
Removal
for observer in notifObservers {
NotificationCenter.default.removeObserver(observer)
}
notifObservers.removeAll()
Yes
NotificationCenter.default.removeObserver(self)
this line is sufficient to remove the vc observation as long as all are added with
NotificationCenter.default.addObserver

Stuck in KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED

I'm using the new Swift 4 API and I'm getting the KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED in a non-deterministic manner. Was anyone able to fix it? The code is pretty simple creating a KVO and storing it in a local variable.
private var rateObservation: NSKeyValueObservation?
rateObservation = player.observe(\AVQueuePlayer.rate, options: [.initial, .new]) { (_, change) in
observer(change.newValue)
}
The answer doesn't make sense to me. But it is the only way that made it work for me.
deinit {
rateObservation?.invalidate()
}
Doesn't make sense because the documentation of the new observation clearly says that we don't need to unregister the notification and that works for almost most of the time but sometimes I got the mentioned exception and when I explicitly invalidated the observer it never throw the exception.

Swift 4 KVO block crash: observed object deallocated while observer was still registered

I started developing my app recently with iOS 11 as target version, because that was the default value. I have now lowered the version to 9.3, because of reasons.
The app is pure swift 4, using the new KVO block thing.
I fixed the few compile-time errors I had with safeAreaInsets and whatnot, and the app built successfully. A quick job. Nice.
I tried running it on an iPhone 7 iOS 10.3.1 Simulator, and lord - it was a train wreck. I guess UITableViewAutomaticDimension wasn't really a thing back in the days.
Anyway, I have fixed most of the layout-issues, but now I'm stuck with a few hard crashes. Everywhere I've used this new KVO it crashes when I navigate back.
My navigation-pushed ViewController is KVO-listening to a field inside an object it holds. When I pop the navigation, the viewController and the object is deallocated, in that order, and the app crashes, giving me this error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7fdf2e724250 of class MyProject.MyObject was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x60800003fd80> (
<NSKeyValueObservance 0x610000050020: Observer: 0x61000006f140, Key path: isSelected, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x6180000595f0>
)'
As far as I can tell, it says that the observed object MyObject was deallocated while there was someone observing the variable isSelected.
This is the oberservation-code in MyViewController that observes MyObject.
var observations:[NSKeyValueObservation] = []
func someFunction() {
observations.removeAll()
let myObject = MyObject(/*init*/)
self.myObject = myObject
observations.append(myObject.observe(\.isSelected, changeHandler: { [weak self] (object, value) in
//Do stuff
}))
}
I was under the impression that this new magic KVO-block-style would solve world peace, but apparently that only applies to iOS 11.
Now, I have tried a few things, but I can't get it to not crash. It happens every single time, and I don't understand why.
Since the crash logs tells me that the observed object is being deallocated while an object is observing it, but I also know that the observing object is deallocated before the observed object, I have tried doing this in the observer:
//In MyViewController
deinit {
observations.forEach({$0.invalidate()})
observations.removeAll()
print("Observers removed")
}
But this doesn't help. I have also done this:
//In MyObject
deinit{
print("MyObject deinit")
}
And when I do the thing - I get the following output:
>Observers removed
>MyObject deinit
>WORLD WAR 5 STACK TRACE
I have also tried
//In MyViewController
deinit{
self.myObject.removeObserver(self, forKeyPath: "isSelected")
}
But I get the output saying Can't remove because it is not registered as an observer. So I guess MyViewController isn't actually the observer that gets attached when using this new KVO.
Why didn't any of this work? Where and when do I have to remove the observers in < iOS11?
It looks like you're running into a bug that I reported about a year ago, but which unfortunately has received very little attention:
https://bugs.swift.org/browse/SR-5752
Since this bug hasn't bitten me in a while, I had hoped that it'd been fixed in the Swift overlay, but I just tried copying my code from the bug report into an iOS project and running it in the 10.3.1 simulator, and sure enough, the crash came back.
You can work around it like this:
deinit {
for eachObservation in observations {
if #available(/*whichever version of iOS fixes this*/) { /* do nothing */ } else {
self.removeObserver(eachObservation, forKeyPath: #keyPath(/*the key path*/))
}
eachObservation.invalidate()
}
observations.removeAll()
}
Make sure you only do this on iOS versions affected by the bug, because otherwise you'll remove an observation that's already been removed, and then that will probably crash. Isn't this fun?

AVPlayer removing observer crash in Swift 2.2

I have a video app that I built a while back in Swift 1 and I've been trying to migrate to Swift 2.2. It all (finally) works apart from a weird crash to do with observers.
func removeObservers()
{
print("REMOVING OBSERVERS")
if ( !self.is_image && self.player != nil ) {
if (self.player?.observationInfo != nil) {
self.player?.removeObserver(self, forKeyPath: "currentItem.status")
self.player?.removeObserver(self, forKeyPath: "readyForDisplay")
}
}
NSNotificationCenter.defaultCenter().removeObserver(self)
}
This worked previously using SwiftTryCatch but with the lines in place crashes with "'Cannot remove an observer for the key path "readyForDisplay" from because it is not registered as an observer.'" OR with that an observer is registered on a deallocated object if I comment it out.
If I add a do { } catch {} to it I get an error that "this does not throw" and it just crashes the same. How do I go about putting this in some form of try-catch format?
In Swift 2, the libs got annoyingly strict about errors that are truly unexpected (which throw) versus errors that the programmer could have prevented (which do not throw, but just crash your app).
(I’m not a fan of this distinction, or at least not of all the specific decisions Apple made about which errors fall in which category. The JSON API verges on the nonsensical in this department. But…we work with the API we’ve got.)
The NSKeyValueObserving docs say:
It is an error to call removeObserver:forKeyPath: if the object has not been registered as an observer.
“It is an error” is Apple code for “you are responsible for never doing this, and if you do, your app will crash in an uncatchable way.”
In these situations, there is usually an API call you can make to check the validity of the thing you’re about to do. However, AFAIK, there’s no KVO API call you can make to ask, “Is X observing key path Y of object Z?” That means you have three options:
Figure out why you’re trying to remove an observer from something you’re not observing, and prevent that using your program’s own internal logic.
Keep a weak instance var for “player I’m observing,” and check that for a match before attempting to remove the observer.
Add self as an observer before removing it. (I’m pretty sure that a redundant add is OK.)
Since you are making a call removeObserver(self) at the end of the method, why cant you uncomment above code? Because removeObserver(self) removes all the observers if registered any. I hope this solves your issue.
NSNotificationCenter.defaultCenter().removeObserver(self)
status is a property of either AVPlayer or AVPlayerItem.
readyForDisplay is a property of AVPlayerLayer

Multi-threaded progress bar concurrency issue outside the main view controller

I've found so many solutions for progress bar update within the same thread and view controller, however they seemed to be not similar cases as mine.
In my application, the main view controller calls loadIntoCoreData()(implemented in class MyLoadingService) which asynchronously loads data into core data by another thread. This function has to continuously update the loading percentage (which is written in NSUserDefaults.standardUserDefaults()) to the main thread so that it could be shown on the progress bar in main view controller. I had ever used a while loop in MainViewController to continuously fetch the current percentage value, like below:
class MainViewController {
override func viewDidLoad() {
MyLoadingService.loadIntoCoreData() { result in
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "isLoading")
// do something to update the view
}
self.performSelectorInBackground("updateLoadingProgress", withObject: nil)
}
func updatingLoadingProgress() {
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setBool(true, forKey: "isLoading")
// here I use a while loop to listen to the progress value
while(prefs.boolForKey("isLoading")) {
// update progress bar on main thread
self.performSelectorOnMainThread("showLoadingProcess", withObject: nil, waitUntilDone: true)
}
prefs.setValue(Float(0), forKey: "loadingProcess")
}
func showLoadingProcess() {
let prefs = NSUserDefaults.standardUserDefaults()
if let percentage = prefs.valueForKey("loadingProcess") {
self.progressView.setProgress(percentage.floatValue, animated: true)
}
}
}
And in the class of function loadIntoCoreData:
class MyLoadingService {
let context = (UIApplication.sharedApplication()delegate as! AppDelegate).managedObjectContext!
func loadIntoCoreData(source: [MyModel]) {
var counter = 0
for s in source {
//load into core data using the class context
NSOperationQueue.mainQueue.addOperationWithBlock({
// updating the value of "loadingProcess" in NSUserDefaults.standardUserDefaults()
// and synchronize it on main queue
})
counter++
}
}
}
The above code can successfully run the progress bar, however it often encounter BAD_ACCESS or some other exceptions(like "Cannot update object that was never inserted") due to the conflicts on core data context (thought it seems that managedObjectContext isn't touched by the main thread). Therefore, instead of using a while loop listening on the main thread, I consider using NSOperationQueue.performSelectorOnMainThread to acknowledge the main thread after each entry. Therefore I put my view controller as an argument sender into loadCoreData and call performSelectorOnMainThread("updateProgressBar", withObject: sender, waitUntilDone: true) but failed with error "unrecognized selector sent to class 'XXXXXXXX'". So I would like to ask if is it possible to update an UI object between threads? Or, how to modify my previous solution so that the core data context conflicts could be solved? Any solutions are appreciated.
class MyLoadingService {
func loadIntoCoreData(sender: MainViewController, source: [MyModel]) {
var counter = 0
for s in source {
//load into core data using the class context
NSOperationQueue.mainQueue.addOperationWithBlock({
// updating the value of "loadingProcess" in NSUserDefaults.standardUserDefaults()
// and synchronize it on main queue
})
NSOperationQueue.performSelectorOnMainThread("updateProgressBar", withObject: sender, waitUntilDone: true)
counter++
}
}
func updateProgressBar(sender: MainViewController) {
sender.progressView.setProgress(percentage, animated: true)
}
}
class MainViewController {
override func viewDidLoad() {
MyLoadingService.loadIntoCoreData(self) { result in
// do something to update the view
}
}
}
First, you are abusing NSUserDefaults in horrible ways. The documentation describes it as this...
The NSUserDefaults class provides a programmatic interface for
interacting with the defaults system. The defaults system allows an
application to customize its behavior to match a user’s preferences.
For example, you can allow users to determine what units of
measurement your application displays or how often documents are
automatically saved. Applications record such preferences by assigning
values to a set of parameters in a user’s defaults database. The
parameters are referred to as defaults since they’re commonly used to
determine an application’s default state at startup or the way it acts
by default.
You are using it to store a global variable.
Furthermore, you are completely abusing the user's CPU in your loop where you continuously are checking the value in the user defaults, and clipping off a selector to the main thread. "Abuse of the CPU" doesn't even come close to describing what this code is doing.
You should use NSProgress for reporting progress. There is a WWDC 2015 presentation dedicated exclusively to using NSProgress.
On to your core data usage.
Unfortunately, since you intentionally redacted all of the core data code, it's impossible to say what is going wrong.
However, based on what I see, you are probably trying to use that managed object context from your app delegate (which is probably still created with the deprecated confinement policy) from a background thread, which is a cardinal sin of the highest order as far as core data is concerned.
If you want to import data as a long running operation, use a private context, and execute the operations in the background. Use NSProgress to communicate progress to anyone wanting to listen.
EDIT
Thanks for the advice on my core data context usage. I digged into all
the contexts in my code and re-organized the contexts inside, the
conflict problem does not happen anymore. As for NSProgress , it's a
pity that the WWDC presentation focus on the feature on iOS 9 (while
my app must compact on iOS 8 devices). However, even though I use
NSProgress, I should still tell the main thread how many data the core
data (on another thread) already has, right? How does the thread on
NSProgress know the loading progress on my core data thread? –
whitney13625
You can still use NSProgress for iOS8, then only real difference is that you can't explicitly add children, but the implicit way still works, and that video explains it as well.
You really should watch the whole video and forget about the iOS9 part, except to know that you must add children implicitly instead of explicitly.
Also, this pre-iOS9 blog post should clear up any questions you have about it.

Resources