Adding observer for KVO without pointers using Swift

后端 未结 5 885
灰色年华
灰色年华 2020-12-05 00:28

In Objective-C, I would normally use something like this:

static NSString *kViewTransformChanged = @\"view transform changed\";
// or
static const void *kVie         


        
相关标签:
5条回答
  • 2020-12-05 01:14

    Update for Swift 4

    Context is not required for block-based observer function and existing #keyPath() syntax is replaced with smart keypath to achieve swift type safety.

    class EventOvserverDemo {
    var statusObserver:NSKeyValueObservation?
    var objectToObserve:UIView?
    
    func registerAddObserver() -> Void {
        statusObserver = objectToObserve?.observe(\UIView.tag, options: [.new, .old], changeHandler: {[weak self] (player, change) in
            if let tag = change.newValue {
                // observed changed value and do the task here on change.
            }
        })
    }
    
    func unregisterObserver() -> Void {
        if let sObserver = statusObserver {
            sObserver.invalidate()
            statusObserver = nil
        }
      }
    }
    
    0 讨论(0)
  • 2020-12-05 01:17

    Swift 4 - observing contentSize change on UITableViewController popover to fix incorrect size

    I had been searching for an answer to change to a block based KVO because I was getting a swiftlint warning and it took me piecing quite a few different answers together to get to the right solution. Swiftlint warning:

    Block Based KVO Violation: Prefer the new block based KVO API with keypaths when using Swift 3.2 or later. (block_based_kvo).

    My use case was to present a popover controller attached to a button in a Nav bar in a view controller and then resize the popover once it's showing - otherwise it would be too big and not fitting the contents of the popover. The popover itself was a UITableViewController that contained static cells, and it was displayed via a Storyboard segue with style popover.

    To setup the block based observer, you need the following code inside your popover UITableViewController:

    // class level variable to store the statusObserver
    private var statusObserver: NSKeyValueObservation?
    
    // Create the observer inside viewWillAppear
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        statusObserver = tableView.observe(\UITableView.contentSize,
            changeHandler: { [ weak self ] (theTableView, _) in self?.popoverPresentationController?.presentedViewController.preferredContentSize = theTableView.contentSize
            })
    }
    
    // Don't forget to remove the observer when the popover is dismissed.
    override func viewDidDisappear(_ animated: Bool) {
        if let observer = statusObserver {
            observer.invalidate()
            statusObserver = nil
        }
    
        super.viewDidDisappear(animated)
    }
    

    I didn't need the previous value when the observer was triggered, so left out the options: [.new, .old] when creating the observer.

    0 讨论(0)
  • 2020-12-05 01:18

    Complete example using Swift:

    //
    //  AppDelegate.swift
    //  Photos-MediaFramework-swift
    //
    //  Created by Phurg on 11/11/16.
    //
    //  Displays URLs for all photos in Photos Library
    //
    //  @see http://stackoverflow.com/questions/30144547/programmatic-access-to-the-photos-library-on-mac-os-x-photokit-photos-framewo
    //
    
    import Cocoa
    import MediaLibrary
    
    // For KVO: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID12
    private var mediaLibraryLoaded = 1
    private var rootMediaGroupLoaded = 2
    private var mediaObjectsLoaded = 3
    
    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate {
    
        @IBOutlet weak var window: NSWindow!
        var mediaLibrary : MLMediaLibrary!
        var allPhotosAlbum : MLMediaGroup!
    
    
        func applicationDidFinishLaunching(_ aNotification: Notification) {
    
            NSLog("applicationDidFinishLaunching:");
    
            let options:[String:Any] = [
                MLMediaLoadSourceTypesKey: MLMediaSourceType.image.rawValue, // Can't be Swift enum
                MLMediaLoadIncludeSourcesKey: [MLMediaSourcePhotosIdentifier], // Array
            ]
    
            self.mediaLibrary = MLMediaLibrary(options:options)
            NSLog("applicationDidFinishLaunching: mediaLibrary=%@", self.mediaLibrary);
    
            self.mediaLibrary.addObserver(self, forKeyPath:"mediaSources", options:[], context:&mediaLibraryLoaded)
            NSLog("applicationDidFinishLaunching: added mediaSources observer");
    
            // Force load
            self.mediaLibrary.mediaSources?[MLMediaSourcePhotosIdentifier]
    
            NSLog("applicationDidFinishLaunching: done");
    
        }
    
        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            NSLog("observeValue: keyPath=%@", keyPath!)
            let mediaSource:MLMediaSource = self.mediaLibrary.mediaSources![MLMediaSourcePhotosIdentifier]!
    
            if (context == &mediaLibraryLoaded) {
                NSLog("observeValue: mediaLibraryLoaded")
                mediaSource.addObserver(self, forKeyPath:"rootMediaGroup", options:[], context:&rootMediaGroupLoaded)
                // Force load
                mediaSource.rootMediaGroup
    
            } else if (context == &rootMediaGroupLoaded) {
                NSLog("observeValue: rootMediaGroupLoaded")
                let albums:MLMediaGroup = mediaSource.mediaGroup(forIdentifier:"TopLevelAlbums")!
                for album in albums.childGroups! {
                    let albumIdentifier:String = album.attributes["identifier"] as! String
                    if (albumIdentifier == "allPhotosAlbum") {
                        self.allPhotosAlbum = album
                        album.addObserver(self, forKeyPath:"mediaObjects", options:[], context:&mediaObjectsLoaded)
                        // Force load
                        album.mediaObjects
                    }
                }
    
            } else if (context == &mediaObjectsLoaded) {
                NSLog("observeValue: mediaObjectsLoaded")
                let mediaObjects:[MLMediaObject] = self.allPhotosAlbum.mediaObjects!
                for mediaObject in mediaObjects {
                    let url:URL? = mediaObject.url
                    // URL does not extend NSObject, so can't be passed to NSLog; use string interpolation
                    NSLog("%@", "\(url)")
                }
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-12-05 01:26

    There is now a technique officially recommended in the documentation, which is to create a private mutable variable and use its address as the context.

    (Updated for Swift 3 on 2017-01-09)

    // Set up non-zero-sized storage. We don't intend to mutate this variable,
    // but it needs to be `var` so we can pass its address in as UnsafeMutablePointer.
    private static var myContext = 0
    // NOTE: `static` is not necessary if you want it to be a global variable
    
    observee.addObserver(self, forKeyPath: …, options: [], context: &MyClass.myContext)
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        if context == &myContext {
            …
        }
        else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
    
    0 讨论(0)
  • 2020-12-05 01:27

    Now that KVOContext is gone in Xcode 6 beta 3, you can do the following. Define a global (i.e. not a class property) like so:

    let myContext = UnsafePointer<()>()
    

    Add an observer:

    observee.addObserver(observer, forKeyPath: …, options: nil, context: myContext)
    

    In the observer:

    override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafePointer<()>) {
        if context == myContext {
            …
        } else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
        }
    }
    
    0 讨论(0)
提交回复
热议问题