How to get Swift KVO working for static member? - swift

I have a UIViewController with the following code. I want to know when the value of portrait effect is changed (in control center). I have tried AVCaptureDevice.isPortraitEffectEnabled and .portraitEffectEnabled, both have the same result: observeValue() is never called. I have verified that the value itself does actually change, and the docs state that KVO is supported for this member.
What am I missing?
To test this I am toggling the value of portaitEffectEnabled by calling AVCaptureDevice.showSystemUserInterface(.videoEffects) and turning it on/off, and expecting the KVO to fire.
#objc class EventSettingsCaptureViewController : UIViewController, ... {
required init(...) {
super.init(nibName: nil, bundle: nil)
if #available(iOS 15.0, *) {
AVCaptureDevice.self.addObserver(self, forKeyPath: "portraitEffectEnabled", options: [.new], context: nil)
}
}
deinit {
if #available(iOS 15.0, *) {
AVCaptureDevice.self.removeObserver(self, forKeyPath: "portraitEffectEnabled", context: nil)
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// Breakpoint set here: never hits
if keyPath == "portraitEffectEnabled" {
guard let object = object as? AVCaptureDevice.Type else { return }
if #available(iOS 15.0, *) {
WLog("isPortraitEffectEnabled changed: \(object.isPortraitEffectEnabled)")
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}

That won’t work because the AVCaptureDevice class itself doesn’t have a portraitEffectSupported property.
The issue is that the portraitEffectSupported property is an instance property.
you can always use class_copyPropertyList to double check that the property you’re trying to observe actually exists on that object. Here's an example:
import AVFoundation
func getPropertyNames(of target: AnyObject) -> [String] {
let itsClass: AnyClass = object_getClass(target)!
var count = UInt32()
guard let p = class_copyPropertyList(itsClass, &count) else {
return []
}
defer { p.deallocate() }
let properties = UnsafeBufferPointer(start: p, count: Int(count))
return properties.map { String(cString: property_getName($0)) }
}
// `AVCaptureDevice` has no class properties.
let propertiesOfTheClassItself = getPropertyNames(of: AVCaptureDevice.self)
print(propertiesOfTheClassItself) // => []
// Instances of `AVCaptureDevice` have some instance properties.
let propertiesOfASampleInstance = getPropertyNames(of: AVCaptureDevice.default(for: .video)!)
print(propertiesOfASampleInstance) // => ["transportControlsSupported", "transportControlsPlaybackMode", "transportControlsSpeed", "adjustingFocus", "adjustingExposure", "adjustingWhiteBalance"]

Related

delegate method does not get called second time

I am building a simple currency converter app. When ViewController gets opened it calls a function from CoinManager.swift:
class ViewController: UIViewController {
var coinManager = CoinManager()
override func viewDidLoad() {
super.viewDidLoad()
coinManager.delegate = self
coinManager.getCoinPrice(for: "AUD", "AZN", firstCall: true)
}
...
}
CoinManager.swift:
protocol CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String)
func tellTableView(descriptions: [String], symbols: [String])
func didFailWithError(error: Error)
}
struct CoinManager {
var delegate: CoinManagerDelegate?
let baseURL = "https://www.cbr-xml-daily.ru/daily_json.js"
func getCoinPrice (for currency1: String,_ currency2: String, firstCall: Bool) {
if let url = URL(string: baseURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
if let coinData = self.parseJSON(safeData) {
if firstCall {
var descriptions = [""]
let listOfCoins = Array(coinData.keys)
for key in listOfCoins {
descriptions.append(coinData[key]!.Name)
}
descriptions.removeFirst()
self.delegate?.tellTableView(descriptions: descriptions, symbols: listOfCoins)
}
if let coinInfo1 = coinData[currency1] {
let value1 = coinInfo1.Value
if let coinInfo2 = coinData[currency2] {
let value2 = coinInfo2.Value
//this line does not do anything the second time I call getCoinPrice:
self.delegate?.didUpdatePrice(price1: value1, currency1: currency1, price2: value2, currency2: currency2)
//And this one does work
print("delegate:\(currency1)")
} else {
print("no name matches currency2")
}
} else {
print("no name matches currency1")
}
}
}
}
task.resume()
}
}
func ParseJSON....
}
The method it calls (ViewController.swift):
extension ViewController: CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String) {
print("didUpdatePrice called")
DispatchQueue.main.async {
let price1AsString = String(price1)
let price2AsString = String(price2)
self.leftTextField.text = price1AsString
self.rightTextField.text = price2AsString
self.leftLabel.text = currency1
self.rightLabel.text = currency2
}
}
...
}
and finally, CurrencyViewController.swift:
var coinManager = CoinManager()
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
So when I launch the app i get following in my debug console:
didUpdatePrice called
delegate:AUD
And when I call getCoinPrice() from CurrencyViewController the delegate method does not get called. I know that my code goes through the delegate function line as I get this in debug console:
delegate:USD
I just can't wrap my head around it. The delegate method does not work when gets called second time. Even though it is called by the same algorithm
It's because you're creating a new object of CoinManager in CurrencyViewController where the delegate is not set. So you've to set the delegate every time you create a new instance of CoinManager.
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.delegate = self
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
Update: So, the above solution would require for you to make the delegate conformance in CurrencyViewController. If you're looking for an alternate solution you should probably pass the instance of coinManager in ViewController to CurrencyViewController. For that here are the things you need to update.
In CurrencyViewController:
class CurrencyViewController: UIViewController {
var coinManager: CoinManager! // you can optional unwrap if you intent to use CurrencyViewController without coinManager
//...
And in ViewController:
currencyViewController.coinManager = coinManager // passing the instance of coinManager
Can you share the full code of CoinManager? I see this part
if firstCall {
...
}
Maybe some block logic here or unhandled cases? And can you share the full code of protocol?
Also try to print something before this code:
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}

How to know when AVPlayer is ready to play and sent info to the controller

I'm bulding a radio streaming app in swift. Currently is all working but i want to improve a little the user experience.
I have a RadioPlayer.swift class that handles my radio actions.
import Foundation
import AVFoundation
class RadioPlayer {
static let sharedInstance = RadioPlayer()
private var player = AVPlayer(URL: NSURL(string:"http://rfcmedia.streamguys1.com/classicrock.mp3")!)
private var isPlaying = false
func play() {
player = AVPlayer(URL: NSURL(string: "http://rfcmedia.streamguys1.com/classicrock.mp3")!)
player.play()
isPlaying = true
player.currentItem?.status
}
func pause() {
player.pause()
isPlaying = false
player.replaceCurrentItemWithPlayerItem(nil)
}
func toggle() {
if isPlaying == true {
pause()
} else {
play()
}
}
func currentlyPlaying() -> Bool {
return isPlaying
}
Then i have a View Controller that implement that class. My objective is that when the player is loading, send a message saying that the streaming is being prepared, so the user know that have to wait (also disable the play button).
So my question is how can achieve that, in android i used broadcasts in order to send messages but i didn't found an equivalent in swift.
You can add observers for the AVPlayer properties, e.g. in Swift 3:
player.addObserver(self, forKeyPath: "reasonForWaitingToPlay", options: .new, context: &observerContext)
Or in Swift 2, use .New:
player.addObserver(self, forKeyPath: "reasonForWaitingToPlay", options: .New, context: &observerContext)
Note, that's using a private property to identify the context:
private var observerContext = 0
And then you can add the observer method. In Swift 3:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &observerContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
// look at `change![.newKey]` to see what the status is, e.g.
if keyPath == "reasonForWaitingToPlay" {
NSLog("\(keyPath): \(change![.newKey])")
}
}
Or in Swift 2:
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
guard context == &observerContext else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
return
}
// look at `change![NSKeyValueChangeNewKey]` to see what the status is, e.g.
if keyPath == "reasonForWaitingToPlay" {
NSLog("\(keyPath): \(change![NSKeyValueChangeNewKey])")
}
}

KVO and Swift 3.0: How to evaluate the change dictionary?

I am trying to figure out how to evaluate the [NSKeyValueChangeKey : AnyObject] change dictionary parameter in func observeValue(forKeyPath.... I have the following code in a playground and the way I'm evaluating the change dictionary I always end up thinking the change is a NSKeyValueChange.setting (which is definitely wrong).
What is the right way to evaluate the change dictionary?
import Foundation
class KVOTester: NSObject {
dynamic var items = [Int]() // Observe via KVO
override init() {
super.init()
self.addObserver(self, forKeyPath: #keyPath(KVOTester.items), options: [], context: nil)
}
deinit {
self.removeObserver(self, forKeyPath: #keyPath(KVOTester.items))
}
func exerciseKVO() {
self.items = [Int]() // NSKeyValueChange.setting
self.items.append(1) // NSKeyValueChange.insertion
self.items[0] = 2 // NSKeyValueChange.replacement
self.items.remove(at: 0) // NSKeyValueChange.removal
}
override func observeValue(forKeyPath keyPath: String?, of object: AnyObject?, change: [NSKeyValueChangeKey : AnyObject]?, context: UnsafeMutablePointer<Void>?) {
// We are only interested in changes to our items array
guard keyPath == "items" else { return }
// #1: object is the KVOTester instance - why isn't it the items array?
// #2 I don't understand how to use the change dictionary to determine what type of change occurred. The following
// is wrong - it *always* prints "Setting".
if let changeKindValue = change?[.kindKey] as? UInt, changeType = NSKeyValueChange(rawValue: changeKindValue) {
switch changeType {
case .setting:
print("Setting")
break
case .insertion:
print("Insertion")
break
case .removal:
print("Removal")
break
case .replacement:
print("Replacement")
break
}
}
}
}
let kvoTester = KVOTester()
kvoTester.exerciseKVO()
As the original questioner pointed out in his comment, the following code will give the expected result:
import Foundation
class KVOTester: NSObject {
dynamic var items = [Int]() // Observe via KVO
override init() {
super.init()
self.addObserver(self, forKeyPath: #keyPath(KVOTester.items), options: [], context: nil)
}
deinit {
self.removeObserver(self, forKeyPath: #keyPath(KVOTester.items))
}
func exerciseKVO() {
let kvoArray = self.mutableArrayValue(forKey: #keyPath(KVOTester.items))
items = [Int]() // NSKeyValueChange.setting
kvoArray.add(1) // NSKeyValueChange.insertion
kvoArray.replaceObject(at: 0, with: 2) // NSKeyValueChange.replacement
kvoArray.removeObject(at: 0) // NSKeyValueChange.removal
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// We are only interested in changes to our items array
guard keyPath == "items" else { return }
if let changeKindValue = change?[.kindKey] as? UInt,
let changeType = NSKeyValueChange(rawValue: changeKindValue) {
switch changeType {
case .setting:
print("Setting")
break
case .insertion:
print("Insertion")
break
case .removal:
print("Removal")
break
case .replacement:
print("Replacement")
break
}
}
}
}
let kvoTester = KVOTester()
kvoTester.exerciseKVO()

Using switch in observeValueForKeyPath

I'm attempting to increase legibility of my KVO observeValueForKeyPath implementation by replacing the typical long string of nested if/else statements with a single switch statement.
So far, the only thing that's actually worked is:
private let application = UIApplication.sharedApplication()
switch (object!, keyPath!) {
case let (object, "delegate") where object as? UIApplication === application:
appDelegate = application.delegate
break
...
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
Which, if anything, is even harder to read than:
if object as? UIApplication === application && keyPath! == "delegate" {
}
else {
}
Does anybody have a good model for using switch in observeValueForKeyPath (and similar methods)
EDIT: Relevant to #critik's question below, here's more of the code to demonstrate the problems with just using switch (object as! NSObject, keyPath!) {:
private let application = UIApplication.sharedApplication()
private var appDelegate : UIApplicationDelegate?
private var rootWindow : UIWindow?
public override func observeValueForKeyPath(
keyPath: String?,
ofObject object: AnyObject?,
change: [String : AnyObject]?,
context: UnsafeMutablePointer<Void>) {
switch (object as! NSObject, keyPath!) {
case (application, "delegate"):
appDelegate = application.delegate
(appDelegate as? NSObject)?.addObserver(self, forKeyPath: "window", options: [.Initial], context: nil)
break
case (appDelegate, "window"):
rootWindow = appDelegate?.window?.flatMap { $0 }
break
case (rootWindow, "rootViewController"):
rebuildViewControllerList(rootWindow?.rootViewController)
break
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
How about a switch on tuples:
switch (object as! NSObject, keyPath!) {
case (application, "delegate"):
appDelegate = application.delegate
...
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
Note 1. Although I'm agains forced stuff in Swift (downcasts, unwraps, etc), you can safely force downcast to NSObject without getting a crash, as per this SO question, KVO is only available for NSObject subclasses.
Note 2. You also don't need a break in Swift, this will also shorten your code by at least one line :)
This doesn't really solve the problem of using switch in observeValueForKey but it does demonstrate how I simplified the problem space to eliminate the verbose code.
I created a utility class, KVOValueWatcher which allows me to add (and remove) KVO observation on a single property of an object:
public class KVOValueWatcher<ObjectType:NSObject, ValueType:NSObject> : NSObject {
public typealias OnValueChanged = (ValueType?, [String:AnyObject]?) -> ()
let object : ObjectType
let keyPath : String
let options : NSKeyValueObservingOptions
let onValueChanged : OnValueChanged
var engaged = false
public init(object:ObjectType, keyPath:String, options : NSKeyValueObservingOptions = [], onValueChanged: OnValueChanged) {
self.object = object
self.keyPath = keyPath
self.onValueChanged = onValueChanged
self.options = options
super.init()
engage()
}
deinit {
if(engaged) {
print("KVOValueWatcher deleted without being disengaged")
print(" object: \(object)")
print(" keyPath: \(keyPath)")
}
disengage()
}
public func engage() {
if !engaged {
self.object.addObserver(self, forKeyPath: keyPath, options: options, context: nil)
engaged = true
}
}
public func disengage() {
if engaged {
self.object.removeObserver(self, forKeyPath: keyPath)
engaged = false
}
}
override public func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
self.onValueChanged(((object as? NSObject)?.valueForKeyPath(keyPath!) as? ValueType), change)
}
}
My problem code then becomes:
rootWindowWatcher = KVOValueWatcher(object: applicationDelegate as! NSObject, keyPath: "window", options: [.Initial]) { window, changes in
self.rootViewWatcher?.disengage()
self.rootViewWatcher = nil
if let window = window {
self.rootViewWatcher = KVOValueWatcher(object: window, keyPath: "rootViewController", options: [.Initial]) {
[unowned self] rootViewController, changes in
self.rootViewController = rootViewController
self.rebuildActiveChildWatchers()
}
}
}
The primary reason for the change was because it was becoming a nightmare to maintain all the different observations and get them properly added and removed. Adding the wrapper class eliminates the problem and groups watching a property and the action to be taken when the property changes in the same location.

KVO: How to get old / new values in observeValue(forKeyPath:...) in Swift?

I have created an observer with .Old | .New options. In the handler method I try to fetch before after values, but compiler complains: 'NSString' is not convertible to 'NSDictionaryIndex: NSObject, AnyObject
override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafeMutablePointer<Void>) {
let approvedOld = change[NSKeyValueChangeOldKey] as Bool
let approvedNew = change[NSKeyValueChangeNewKey] as Bool
iOS 11 and Swift >4.1
iOS 11 and Swift 4 brings significant changes to KVO.
The classes should adopt #objcMembers annotation in order to enable the KVO or KVO fails silently.
The variable to be observed must be declared dynamic.
Here is newer implementation,
#objcMembers
class Approval: NSObject {
dynamic var approved: Bool = false
let ApprovalObservingContext = UnsafeMutableRawPointer(bitPattern: 1)
override init() {
super.init()
addObserver(self,
forKeyPath: #keyPath(approved),
options: [.new, .old],
context: ApprovalObservingContext)
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
guard let observingContext = context,
observingContext == ApprovalObservingContext else {
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
return
}
guard let change = change else {
return
}
if let oldValue = change[.oldKey] {
print("Old value \(oldValue)")
}
if let newValue = change[.newKey] {
print("New value \(newValue)")
}
}
deinit {
removeObserver(self, forKeyPath: #keyPath(approved))
}
}
There is also new bock based api for KVO, which works like this,
#objcMembers
class Approval: NSObject {
dynamic var approved: Bool = false
var approvalObserver: NSKeyValueObservation!
override init() {
super.init()
approvalObserver = observe(\.approved, options: [.new, .old]) { _, change in
if let newValue = change.newValue {
print("New value is \(newValue)")
}
if let oldValue = change.oldValue {
print("Old value is \(oldValue)")
}
}
}
}
Block based api look super good and easy to use. Also, KeyValueObservation is invalidated when deinited, so there is no hard requirement for removing observer.
Swift 2.0 and iOS < 10
With Swift 2.0, here is a complete implementation for a class that uses KVO,
class Approval: NSObject {
dynamic var approved: Bool = false
let ApprovalObservingContext = UnsafeMutablePointer<Int>(bitPattern: 1)
override init() {
super.init()
addObserver(self, forKeyPath: "approved", options: [.Old, .New], context: ApprovalObservingContext)
}
override func observeValueForKeyPath(keyPath: String?,
ofObject object: AnyObject?,
change: [String : AnyObject]?,
context: UnsafeMutablePointer<Void>) {
if let theChange = change as? [String: Bool] {
if let approvedOld = theChange[NSKeyValueChangeOldKey] {
print("Old value \(approvedOld)")
}
if let approvedNew = theChange[NSKeyValueChangeNewKey]{
print("New value \(approvedNew)")
}
return
}
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
deinit {
removeObserver(self, forKeyPath: "approved")
}
}
let a = Approval()
a.approved = true