Observe a property from a viewController - ios

I try to observe a property in a view controller, for example, isBeingDimissed property.. But It doesnt seem to work... Only when I set the options to .initial do I see that it works.
But I wanna observe new values.. Am I missing anything?
class ViewController: UIViewController {
var firstViewController = FirstViewController.init()
override func viewDidLoad() {
super.viewDidLoad()
firstViewController.addObserver(self, forKeyPath: #keyPath(UIViewController.isBeingPresented), options: .new, context: &myContext)
}
#IBAction func tapButton(_ sender: Any) {
present(firstViewController, animated: true, completion: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("let me see the change:", keyPath)
}
}

Related

Identify when AVPlayerViewController is closed by tapping X button

Is there any method to identify when we close the AVPlayerViewController when the video is still playing?
Attached the image as to which 'Close' button I am referring to.
try this :-
import UIKit
import AVKit
import AVFoundation
class VC: UIViewController {
var video_Url:String = String()
let playerController = AVPlayerViewController()
//MARK:- ViewDidload
override func viewDidLoad() {
super.viewDidLoad()
let player = AVPlayer(url: URL(string: video_Url)!)
playerController.player = player
self.present(playerController, animated: false) {
player.play()
self.playerController.addObserver(self, forKeyPath: #keyPath(UIViewController.view.frame), options: [.old, .new], context: nil)
}
}
override func viewDidDisappear(_ animated: Bool) {
NotificationCenter.default.removeObserver(NSNotification.Name.AVPlayerItemDidPlayToEndTime)
}
//MARK:- All Method
func playerDidFinishPlaying(note: NSNotification) {
self.navigationController?.popViewController(animated: false)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
self.playerController.removeObserver(self, forKeyPath: #keyPath(UIViewController.view.frame))
self.navigationController?.popViewController(animated: false)
}
}

object communication when object is not kvc compliant in Swift

I want to create a custom view that display it when an action occurs (like changed property) on another object (subclass of UIControl)
My approach was it
create a protocol whose UIControl objects can conform
create my custom view in which I can observe my delegate
Unfortunately, it's not work, worse, it's crash because compiler say "its not kvc-compliant"
Bellow, my code :
protocol CustomDelegate: class where Self : UIControl {
func respondeToControl() -> Bool
}
class CustomView: UIView {
weak var delegate: CustomDelegate? {
didSet{
observeIfControlIsPressed()
}
}
func observeIfControlIsPressed(){
if delegate != nil {
let keypath = delegate! as! UIControl
keypath.addObserver(keypath,
forKeyPath: "backgroundColor",
options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old],
context: nil)
}
}
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("background changed")
}
}
My question is, how I can re-design my solution to make it
work ?
You addObserver is wrong, the "observer" should be self no "keypath"
Try this:
keypath.addObserver(self, forKeyPath: "backgroundColor", options: [.new, .old], context: nil)
You did not read your error correctly.. For example:
protocol CustomDelegate : class where Self : UIControl {
func respondeToControl() -> Bool
}
class CustomView: UIView {
weak var delegate: CustomDelegate? {
didSet{
observeIfControlIsPressed()
}
}
func observeIfControlIsPressed(){
if delegate != nil {
let keypath = delegate! as! UIControl
keypath.addObserver(keypath,
forKeyPath: "backgroundColor",
options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old],
context: nil)
}
}
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("background changed")
}
}
class Foo : UIControl, CustomDelegate {
func respondeToControl() -> Bool {
return true
}
}
class ViewController: UIViewController {
let testBlock = UIView()
override func viewDidLoad() {
super.viewDidLoad()
let view = CustomView()
let button = Foo()
view.delegate = button
button.backgroundColor = UIColor.red
}
}
Throws an exception:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<Meh.Foo: 0x7fc977c1a0c0; baseClass = UIControl; frame = (0 0; 0 0); layer = <CALayer: 0x608000226480>>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: backgroundColor
It means Foo is observing foo but did not implement the observeValueForKeyPath function..
Now why is that? Well.. it's because you did:
keypath.addObserver(keypath, //keypath is observing itself..
forKeyPath: "backgroundColor",
options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old],
context: nil)
where keypath is your delegate.. and is adding observer on itself..
It should be:
keypath.addObserver(self, //self is observing keypath..
forKeyPath: "backgroundColor",
options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old],
context: nil)
The first parameter of addObserver needs to be the class that wants to do the observing.
That change will cause it to print: background changed.. Don't forget to remove the observer later.

How to observe Image rotation?

I have an ImageView called - (woowwww): ImageView
#IBOutlet weak var ImageView: UIImageView!
... and I'd like to fire an event every time the transform-property of the ImageView changed.
An event should get called every time the ImageView get's rotated by maybe something like this:
self.ImageView.transform = CGAffineTransform(rotationAngle: CGFloat(10))
To achieve this I tried to use some kind of KVO but obviously it is not working as expected...
override func viewDidLoad() {
super.viewDidLoad()
self.ImageView.addObserver(self.ImageView.transform, forKeyPath: "test", options: .new, context: nil)
}
func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutableRawPointer) {
print("changed")
}
Any help would be very appreciated! Thanks.
Set "transform" in keyPath like below:
imgView.addObserver(self, forKeyPath: "transform", options: .new, context: nil)
then add below code:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let newValue = change?[.newKey] as? NSObject {
print("Changed \(newValue)")
}
Hope it will help you.
keyPath should be nameOfObject.property so if imageView name is dd to listen to transform it's should dd.transform
Try this
class ViewController: UIViewController {
#IBOutlet weak var dd: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.addObserver(self, forKeyPath: "dd.transform", options: .new, context: nil)
self.dd.transform = CGAffineTransform(rotationAngle: CGFloat(10))
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let newValue = change?[.newKey] as? NSObject {
print("Changed \(newValue)")
}
}}

Using KVC with Singleton pattern

My questionwas about if it was possible to use KVC on a Singleton property on Swift. I was testing KVC on a class was able to get it working but decided to see if it work on a Singleton class.
I'm running into an error stating that the "shared" property of my Singleton isn't KVC-compliant.
class KVOObject: NSObject {
#objc static let shared = KVOObject()
private override init(){}
#objc dynamic var fontSize = 18
}
override func viewDidLoad() {
super.viewDidLoad()
addObserver(self, forKeyPath: #keyPath(KVOObject.shared.fontSize), options: [.old, .new], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(KVOObject.shared.fontSize) {
// do something
}
}
I am currently getting the error below:
NetworkCollectionTest[9714:452848] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ addObserver: forKeyPath:#"shared.fontSize" options:3 context:0x0] was sent to an object that is not KVC-compliant for the "shared" property.'
The key path is not correct. It’s KVOObject.fontSize. And you need to add the observer to that singleton:
KVOObject.shared.addObserver(self, forKeyPath: #keyPath(KVOObject.fontSize), options: [.old, .new], context: nil)
As an aside, (a) you should probably use a context to identify whether you're handling this or whether it might be used by the superclass; (b) you should call the super implementation if it's not yours; and (c) make sure to remove the observer on deinit:
class ViewController: UICollectionViewController {
private var observerContext = 0
override func viewDidLoad() {
super.viewDidLoad()
KVOObject.shared.addObserver(self, forKeyPath: #keyPath(KVOObject.fontSize), options: [.new, .old], context: &observerContext)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &observerContext {
// do something
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
deinit {
KVOObject.shared.removeObserver(self, forKeyPath: #keyPath(KVOObject.fontSize))
}
...
}
Or, if in Swift 4, it's now much easier as it's closure-based (avoiding need for context) and is automatically removed when the NSKeyValueObservation falls out of scope:
class ViewController: UICollectionViewController {
private var token: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
token = KVOObject.shared.observe(\.fontSize, options: [.new, .old]) { [weak self] object, change in
// do something
}
}
...
}
By the way, a few observations on the singleton:
The shared property does not require #objc qualifier; only the property being observed needs that; and
The init method really should be calling super; and
I'd probably also declare it to be final to avoid confusion that can result in subclassing singletons.
Thus:
final class KVOObject: NSObject {
static let shared = KVOObject()
override private init() { super.init() }
#objc dynamic var fontSize: Int = 18
}

How to get if an observer is registered in Swift

I want to remove an observer after it run or when the view dissapeared. Here is the code but sometimes the observer was already removed when i want to remove it again. How to check if it is still registered?
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if(!didOnce){
if(keyPath == "myLocation"){
location = mapView.myLocation.coordinate;
self.mapView.animateToLocation(self.location!);
self.mapView.animateToZoom(15);
didOnce = true;
self.mapView.removeObserver(self, forKeyPath: "myLocation");
}
}
}
override func viewDidAppear(animated: Bool) {
didOnce = false;
}
override func viewWillDisappear(animated: Bool) {
if(!didOnce){
self.mapView.removeObserver(self, forKeyPath: "myLocation");
didOnce = true;
}
}
You're on the right track. Add an isObserving property to your class. Set it to true when you start observing, and set it to false when you stop observing. In all cases check the flag before starting/stopping observing to make sure you are not already in that state.
You can also add a willSet method to the property and have that code start/stop observing when the property changes states.
Main reason: addObserver() is called 1 time (at viewDidLoad or init), but removeObserver() can be called 2 times or more (base on the times viewWillDisappear() was called).
To resolve: move addObserver() to viewWillAppear():
private var didOnce = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.mapView.addObserver(self, forKeyPath: "myLocation", options: .new, context: nil)
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if(!didOnce){
if(keyPath == "myLocation"){
location = mapView.myLocation.coordinate;
self.mapView.animateToLocation(self.location!);
self.mapView.animateToZoom(15);
didOnce = true;
self.mapView.removeObserver(self, forKeyPath: "myLocation");
}
}
}
override func viewWillDisappear(animated: Bool) {
if(!didOnce){
self.mapView.removeObserver(self, forKeyPath: "myLocation");
didOnce = true;
}
}

Resources