How to get if an observer is registered in Swift - ios

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;
}
}

Related

Observe a property from a viewController

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)
}
}

NSBundleResourceRequest No Progress Update

When I try to observe the progress of a NSBundleResourceRequest, observeValue(forKeyPath: object: change: context:) is not called for the .new observing option. Therefore, the NSProgressIndicator isn't updated. Here is the setup and code:
Setup:
Xcode 8.3.1
Deployment Target iOS 10.3
Device: iPad 4
Resource tags (368 KB) are located located in Download Only On Demand and consist of thumbnails displayed in a UICollectionView. Thumbnail images are located in MyCollection.xcassets. All IBOutlets are connected.
Images are correctly displayed in the collection view, but progress bar remains at zero.
Code:
final class MyCollectionVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var myCollectionVCResourceRequest: NSBundleResourceRequest!
var myCollectionVCResourceRequestLoaded = false
static var myCollectionProgressObservingContext = UUID().uuidString
private let designThumbnailTags: Set<String> = ["Resource1", "Resource2", "Resource3"]
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadOnDemandResources()
}
func loadOnDemandResources() {
myCollectionVCResourceRequest = NSBundleResourceRequest(tags: designThumbnailTags)
myCollectionVCResourceRequest.progress.addObserver(self, forKeyPath: "fractionCompleted", options: [.new, .initial], context: &MyCollectionVC.myCollectionProgressObservingContext)
myCollectionVCResourceRequest.beginAccessingResources(completionHandler: { (error) in
print("Complete: \(self.myCollectionVCResourceRequest.progress.fractionCompleted)") // Prints Complete: 0.0
self.myCollectionVCResourceRequest.progress.removeObserver(self, forKeyPath: "fractionCompleted", context: &MyCollectionVC.myCollectionProgressObservingContext)
OperationQueue.main.addOperation({
guard error == nil else { self.handleOnDemandResourceError(error! as NSError); return }
self.myCollectionVCResourceRequestLoaded = true
self.updateViewsForOnDemandResourceAvailability()
self.fetchDesignThumbnailsWithOnDemandResourceTags(self.myCollectionVCResourceRequest) // Correctly creates Core Data Instances
})
})
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &MyCollectionVC.myCollectionProgressObservingContext {
OperationQueue.main.addOperation({
let progressObject = object as! Progress
self.progressView.progress = Float(progressObject.fractionCompleted)
print(Float(progressObject.fractionCompleted)) // Prints 0.0 as a result of including the .initial option
self.progressDetailLabel.text = progressObject.localizedDescription
})
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}

Swift: Using Volume Buttons does not work after returning from Background

I needed to write an iOS App which counts down by pressing the Up Volume Key on an iPhone or iPad. Therefore I used some advice from here to use AVAudioSession and observe the "outputVolume" key.
Here you can find the code of my ViewController:
import UIKit
import MediaPlayer
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBOutlet weak var chickenLabel: UILabel!
let audioSession = AVAudioSession.sharedInstance()
var maxHendl:Int = 100
var istHendl:Int = 100
var isVolumeChanged = false
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
listenVolumeButton()
removeVolumeView()
}
func listenVolumeButton() {
do {
try audioSession.setActive(true)
}
catch {
print(error.localizedDescription)
}
audioSession.addObserver(self, forKeyPath: "outputVolume", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "outputVolume" {
if isVolumeChanged == false {
// Set Volume to 50%
MPVolumeView().subviews.filter{NSStringFromClass($0.classForCoder) == "MPVolumeSlider"}.first as? UISlider)?.setValue(0.5, animated: false)
//do some Counting
}
}
}
func removeVolumeView() {
let volumeView: MPVolumeView = MPVolumeView(frame: CGRect.zero)
view.addSubview(volumeView)
}
}
Everything works as it should after starting the App for the first time. When I now press the Home Button and return back to the App the Events of pressing the volume buttons are not captured and the MPVolumeWindow is displayed again.
Can somebody help me to solve this issue?
Regards
Armin
When you come back from being in the background, your audio session is no longer active. You need to activate it again.

rectForRowAtIndexPath crashing with defined indexPath in UITableView

The following code is crashing at
currentCellRect = tableView.rectForRowAtIndexPath(indexPaths[0])
But only sometimes.
public func showCellScrollCount(animated:Bool) {
self.tableView.addObserver(self, forKeyPath: "contentOffset", options: NSKeyValueObservingOptions.New, context: nil)
self.tableView.addObserver(self, forKeyPath: "dragging", options: NSKeyValueObservingOptions.New, context: nil)
}
override public func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
switch (keyPath, object) {
case (.Some("contentOffset"), _):
self.updateScrollPosition()
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
func updateScrollPosition() {
let indexPaths = tableView.indexPathsForVisibleRows
var currentCellRect:CGRect?
if let indexPaths = indexPaths {
if indexPaths.count > 0 {
currentCellRect = tableView.rectForRowAtIndexPath(indexPaths[0])
scrollCountView.currentScrollCountNum = indexPaths[0].row
}
}
}
It crashes with "BAD_ACCESS". Does anyone have any idea why?
...
EDIT: Is it possible it's happening because I'm calling tableView.reloadData() right before I add observers, and the call isn't finished yet?
Updated
Your code works fine since I added it to my tableView controller and called showCellScrollCount func once. Where you call showCellScrollCount, do you call it multiple times? It can be problem if so.
Update 3
here is the problem:
public var totalScrollCountNum = 0 {
didSet {
scrollCountView.totalScrollCountNum = totalScrollCountNum
showCellScrollCount(false)
}
}
move showCellScrollCount(false) to initializers

KVO for scroll start & end in UIScrollView

Is there a way to use KVO (key value observing) to detect scrollViewWillBeginDragging and scrollViewDidEndDecelerating in swift?
Edit: I tried
scrollView.addObserver(self, forKeyPath: "dragging", options: NSKeyValueObservingOptions.New, context: nil)
But it's never called. If I observe for instance "contentOffset" in the same way, it's called. Is it KVC compliant?
I'm not sure whether it is sufficient for your question, but while you may not be able to observe isDragging or isDecelerating directly you can track them as your observing contentOffset. To find the start and end of dragging you can simply track when isDragging changes from false to true and vice versa by creating a global Boolean (here draggingInitiated) and including something like this in your observeValue:
if !draggingInitiated && scrollView.isDragging {
draggingInitiated = true
print("Started Dragging")
} else if draggingInitiated && !scrollView.isDragging {
draggingInitiated = false
print("Ended Dragging")
}
Answer based on #Philip De Vries
Swift 5
add observer
scrollView.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil)
observe
// MARK: - Observation
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if let obj = object as? UIScrollView {
if obj == self.scrollView && keyPath == "contentOffset" {
scrollViewDidScroll(scrollView)
if !draggingInitiated && scrollView.isDragging {
draggingInitiated = true
onStartDraggin()
} else if draggingInitiated && !scrollView.isDragging {
draggingInitiated = false
onReleaseDragging()
}
}
}
}
cleanUp
private func removeObsrvers() {
scrollView.removeObserver(self, forKeyPath: "contentOffset")
}
You can add observers using NSNotification center.
In the viewDidLoad of your VC,
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.informBeginDragging), name: "begin", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.informEndDragging), name: "end", object: nil)
}
You can do anything in the following two methods
func informBeginDragging() {
//do something
}
func informEndDragging() {
//do something
}
And these two methods will be triggered when scroll view begin dragging or end dragging
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
NSNotificationCenter.defaultCenter().postNotificationName("end", object: nil)
}
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
NSNotificationCenter.defaultCenter().postNotificationName("begin", object: nil)
}

Resources