I'm currently using the owncloud iOS SDK to upload a file to my private cloud. I'm trying to bring the Key-Value observing mechanism provided in this example to Swift.
The library forces me to pass a pointer to a NSProgress object in the upload method. I would then like to do a Key Value Observing and update my UI accordingly.
class OwnCloudManager : NSObject {
//Key Value observation requires the dynamic keyword
//NSProgress must also extend from NSObject
dynamic var progress: NSProgress = NSProgress()
let apiURL : NSString = "https://cloud.example.com/remote.php/webdav/"
let ocCommunication = OCCommunication()
override init() {
//configure ocCommunication here, set user name, password
//and adjust ocCommunication.securityPolicy
//...
}
deinit {
//remove observer when object is destroyed
progress.removeObserver(self, forKeyPath: "fractionCompleted")
}
func uploadFile(folderURL : NSURL, filename: String) -> NSURLSessionUploadTask?{
let fileURL = apiURL.stringByAppendingPathComponent(filename)
let progressPointer = AutoreleasingUnsafeMutablePointer<NSProgress?>.init(&progress)
let uploadTask = ocCommunication.uploadFileSession(folderURL.path, toDestiny: fileURL, onCommunication: ocCommunication,
withProgress: progressPointer,
successRequest: ({(response, redirectedServer) in
//...
}),
failureRequest: ({ (response, redirectedServer, error) in
//request failed while execution
}),
failureBeforeRequest: ({ error in
//failure before request is executed
})
)
progress.addObserver(self, forKeyPath: "fractionCompleted", options: .New, context: nil)
uploadTask.resume()
return uploadTask
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if let observedProgress = object as? NSProgress where keyPath == "fractionCompleted" {
//call my delegate here that updates the UI
}
else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
}
I've set a breakpoint in the observation method but unfortunately it is never called. Can someone tell me how to fix this?
See this code for Progressing..!!
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "fractionCompleted"{
let progress : NSProgress = object as! NSProgress
print(progress)
}
}
Related
The project I am working on has an extension that writes data to UserDefaults. Then in the containing app should the UI should get updated according to the changes. The problem is that UserDefaults.didChangeNotification does not get fired unless the screen comes from background. What could be the reason and is there a way to be fixed or another way to get the needed update?
Writing the data in the extension:
let sharedUserDefaults = UserDefaults(suiteName: Common.UserDefaultsSuite)
var receivedNotifications = sharedUserDefaults?.array(forKey: Common.ReceivedNotifications)
if receivedNotifications != nil {
receivedNotifications?.append(aData)
} else {
receivedNotifications = [aData]
}
sharedUserDefaults?.set(receivedNotifications, forKey: Common.ReceivedNotifications)
Registering for the notification in the view controller:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil)
}
And working with changed user defaults (that actually does not get called):
#objc func userDefaultsDidChange(_ notification: Notification) {
print("User defaults did change")
gatherReceivedNotifications()
}
Still no idea why the other way doesn't work but the following works so it's a solution. As per suggested here I did the following:
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults(suiteName: Common.UserDefaultsSuite)?.addObserver(self, forKeyPath: Common.ReceivedNotifications, options: .new, context: nil)
}
Then implemented observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?):
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == Common.ReceivedNotifications {
gatherReceivedNotifications()
}
}
It is fired immediately and only when a change to UserDefaults for the key Common.ReceivedNotifications is made.
The code #selector(userDefaultsDidChange) is means func userDefaultsDidChange() without parameter.
But you defined func userDefaultsDidChange(_ notification: Notification), it's have one parameter.
Next step:
Change #selector(userDefaultsDidChange) to #selector(userDefaultsDidChange(_:)) can fixed it.
I need to observe AVPlayer.status change.
I have an AVPlayer instance and a context variable
private var lastPlayer : AVPlayer?
private var playerStatusContext = 0
After I've set up the AVPlayer instance I add an observer like so:
// KVO status property
self.lastPlayer!.addObserver(self, forKeyPath: "status", options: [.new, .initial], context: &playerStatusContext)
Then I've overridden observeValue function like so:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
let status : AVPlayerStatus? = change?[.newKey] as? AVPlayerStatus
if(status != nil && context == &playerStatusContext)
{
// DO MY STUFF
}
}
The problem is that change is either 0 key/value dictionary or some (whatever that means) and my local status constant is always nil, hense I can't do my stuff.
Maybe I am converting change to AVPlayerStatus wrongly? Please help. Thanks.
Well, it seems like cast like this
let status : AVPlayerStatus? = change?[.newKey] as? AVPlayerStatus
does not work. The app crashed when tried checking change for nil and then force unwrap it. Using raw values helped:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
// Check status
if keyPath == "status" && context == &playerStatusContext && change != nil
{
let status = change![.newKey] as! Int
// Status is not unknown
if(status != AVPlayerStatus.unknown.rawValue)
{
// DO STUFF!!!
}
}
}
I am not sure this is the best way however.
I am trying to track changes to the map orientation using the camera heading property.
// Register for notifications of changes to camera
if let camera = self.mapView?.camera {
self.camera = camera
camera.addObserver(self, forKeyPath: "heading", options: NSKeyValueObservingOptions.new, context: &MapViewController.myContext)
}
...
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &MapViewController.myContext {
if keyPath == "heading" {
if let heading = change?[NSKeyValueChangeKey.newKey] as? CLLocationDirection {
self.heading = heading
}
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
The camera property mode is copy so you are not observing the current camera instance just one that was copied when you called the getter. You need to observe key path of #"camera.heading" or #"camera" on the map view and hope that a new camera object is set internally when the heading changes.
I want to deallocate NSOperationQueue when all the operations in progress are finished executing.
So far, I have coded below but as far as I know waitUntilAllOperationsAreFinished is async call and could not hold from my operationQueue getting nil.
- (void)deallocOperationQueue
{
[operationQueue waitUntilAllOperationsAreFinished];
operationQueue = nil;
}
Quoting Avi
You don't need to wait for all operations to finish. Just set
operationQueue to nil when you're done with it. If the queue still has
operations, nothing happens to them; they will still complete.
- (void)deallocOperationQueue
{
operationQueue = nil;
}
I have tested the code and confirm that stated behaviour does happen.
You can subclass an NSOperationQueue and observe the operations key path until it reaches zero:
class DownloadOperationQueue: NSOperationQueue {
private static var operationsKeyPath: String {
return "operations"
}
deinit {
self.removeObserver(self, forKeyPath: "operations")
}
var completionBlock: (() -> Void)? {
didSet {
self.addObserver(self, forKeyPath: DownloadOperationQueue.operationsKeyPath, options: .New, context: nil)
}
}
override init() {
super.init()
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if let operationPath = keyPath where operationPath == DownloadOperationQueue.operationsKeyPath {
if self.operations.count == 0 {
NSOperationQueue.mainQueue().addOperationWithBlock({
self.completionBlock?()
})
}
}
}
}
When completed:
var operationQueue: DownloadOperationQueue? = DownloadOperationQueue()
// Add your operations ...
operationQueue?.completionBlock = {
operationQueue = nil
}
I'm building a camera app, and I'm trying to expose the current exposure duration to the user. Since this value constantly changes until manually set, I need to use kvo to stream the values to the user. I've successfully done this with the ISO, and can observe changes to the exposureDuration, but cannot coerce the new value to a CMTime object (which is what exposureDuration is). Below is the code I'm using to try and accomplish this:
override init() {
super.init()
captureDevice = self.selectCamera()
captureDevice?.addObserver(self, forKeyPath: "ISO", options: .New, context: &isoContext)
captureDevice?.addObserver(self, forKeyPath: "exposureDuration", options: .New, context: &shutterContext)
}
deinit {
captureDevice?.removeObserver(self, forKeyPath: "ISO")
captureDevice?.removeObserver(self, forKeyPath: "exposureDuration")
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
let newValue = change?[NSKeyValueChangeNewKey]
if context == &isoContext {
store.iso.value = newValue as! Float
} else if context == &shutterContext {
// The app crashes at this line.
// Thread 1: EXC_BREAKPOINT (code=1, subcode=0x100091670)
// newValue is "AnyObject" in the debug area
store.shutterSpeed.value = newValue as! CMTime
}
}
Am I doing something wrong, or is this a legitimate bug that I need to file with apple?
exposureDuration's newValue is not CMTime, but NSValue.
This is fixed code(swift3).
store.shutterSpeed.value = (newValue as! NSValue).timeValue