I have a piece of code that runs perfectly until iOS 7, but now, with iOS 8 it does not work any more.
I have a UILabel and I have a class that is set to observe any changes in its text. The setup is as below..
[lcdLabel addObserver:self
forKeyPath:#"text"
options:NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld
context:NULL];
And this is how I am registering the changes.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
//other code related to observation goes here
}
Any help as to why this stopped working (the observeValueForKeyPath method is not getting called any more) in iOS8?
First of all, I have implemented a very basic key value observing (KVO) example on a UILabel's text property and it works consistently on both iOS 7 & iOS 8. So you may just have something wrong your code that you are not showing.
That being said, I do not think that KVO is the best approach to responding to changes of the lcdLabel.text property.
Apple's KVO document specifically states:
Note: Although the classes of the UIKit framework generally do not support KVO, you can still implement it in the custom objects of your application, including custom views.
Also, the documentation for UILabel mentions nothing of KVO compliance for the text property. Therefore, I do not think that you should rely on this approach.
Unlike a UITextField, the text property of a UILabel, will never change without you specifically setting it, you can simply subclass UILabel and override the setText method.
- (void)setText:(NSString *)text {
_text = text;
// other code related to observation goes here
// or call back to the UIViewController via a delegate through a custom protocol
}
You may also want to override setAttributedText as well.
Same problem had here. I cannot change UILabel.text from delegate method. The method fires, but UILabel is not updated or screen refreshed...
I am using SWIFT... tried to implement NSNotification centre- the same problem...
Found out that I was calling delegate method from completion handler block and it was not able to update UIlabel as it has to be done on main thread.
Added:
dispatch_async(dispatch_get_main_queue(),{
... update label...
});
and worked like a charm
Related
I need to get notified when an UITableView's drag has come to an end.
But I'm working on an UITableView's category, so I can't use scrollViewDidEndDragging:willDecelerate: to archive this.
I tried use KVO to observe on dragging Key Path:
[self addObserver:self forKeyPath:#"dragging" options:NSKeyValueObservingOptionNew context:nil];
But observeValueForKeyPath:ofObject:change:context: didn't get called, since UITableView.dragging doesn't have and setter and this property is not compliant with KVO.
Is there any other method to archive this expect for using scrollViewDidEndDragging:willDecelerate:?
Any help is grateful! Thanks!
Edit: My solution below was the first thing to come in mind and turned out to be rather hacky and may be unsafe to use in case Apple decides to change the internals of the UIScrollView class. See the answer suggested by Mazyod which should be safer and more straightforward.
This is implementation-dependent and may be changed by Apple in future iOS updates, but currently UIScrollView class seems to rely on gesture recognizers for managing user interaction and UITableView being a subclass of the scroll view class does the same.
If you go to UIScrollView.h of the UIKit framework, you can notice a suspicious _pan ivar which has an id type, but seems to actually be a UIPanGestureRecognizer.
So I've tried this, and it seems to work.
[_tableView addObserver: self
forKeyPath: #"pan.state"
options: NSKeyValueObservingOptionNew
context: nil];
When dragging the table view, state of the gesture recognizer changes several times, and when you stop dragging, state receives its last change to the value of UIGestureRecognizerStateEnded.
Please note that although this seems to do the trick, some other problem may stand in your way. It is generally not a good idea to override existing class methods in a category since the original implementation becomes inaccessible after that. Documentation on the NSKeyValueObserving informal protocol states that
NSObject provides an implementation of the NSKeyValueObserving protocol that provides an automatic observing capability for all objects.
So if you override observeValueForKeyPath:ofObject:change:context: in a category, the default implementation will be inaccessible (and we cannot be sure that UITableView or UIScrollView do not user KVO for something). That may cause some unexpected errors.
Egor Chiglintsev's answer reminded me I can observe the panGestureRecognizer property already exposed in UIScrollView. It should be much safer than pan. But then.. I found out I can just add myself as a target!
[_scrollView.panGestureRecognizer addTarget:self action:#selector(gestureRecognizerUpdate:)];
This works great for me!
I'm just wondering how exactly does a delegate method know when to be called? For example in the UITextFieldDelegate protocol the textFieldDidBeginEditing: method is called when editing begins in the textfield (provided I implemented this method).
So how exactly does the code to detect when to call textFieldDidBeginEditing:? Does the system just check if textFieldDidBeginEditing: is already implemented and if it is it runs that method? Is there something under the hood that I'm not seeing?
Exactly.
I can't vouch for how Apple's framework code is implemented under the hood, but an exceedingly common refrain is:
if ([[self delegate] respondsToSelector:#selector(someInstance:didDoSomethingWith:)]) {
[[self delegate] someInstance:self didDoSomethingWith:foo];
}
This allows you to have optional delegate methods, which appears to be your question.
The code doesn't 'detect when to call' a delegate method. The textField receives an event, and calls the method on it's delegate (which has the textFieldDidBeginEditing: method implemented).
In short, when you tap the textfield to start editing, the textField says 'oh, I'm editing now!' and internally calls [self.delegate textFieldDidBeginEditing:self], where the delegate is the instance in which you've set to be the delegate (usually a UIViewController subclass)
To allow for flexible layouts, I wanted to create a subclass UIView that overrides layoutSubviews: to layout all of its subviews under each other automagically and would continue to do this every time one of its subviews got resized.
However, the only way that I can think of to let the superview know that it should call layoutSubviews: is by overriding that method in each of its subviews, something that I would like to try and avoid (I want people to be able to add arbitrary UIViews to the superview and have this taken care of).
Is there a way for the superview to call layoutSubviews: whenever a subview changes its size, without adding any code to the subview in question?
You could use KVO to observe the frame property of each of your subviews. You would need to add yourself as an observer each time a subview is added and remove the observation when a subview is removed – you can override didAddSubview: and willRemoveSubview: in your superview to do that.
- (void)didAddSubview:(UIView *)subview {
[subview addObserver:self forKeyPath:#"frame" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)willRemoveSubview:(UIView *)subview {
[subview removeObserver:self forKeyPath:#"frame"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"frame"]) {
// Do your layout here...
}
}
- (void)dealloc {
// You might need to remove yourself as an observer here, in case
// your subviews are still used by others
}
You can add a category to the class and try overriding layoutSubviews: from within the category. (This technique has been suggested for customizing navigation bars, and it may well work here too.)
Here's how you'd make a category, taken from my answer here. In your case, remember to substitute UIView for UINavigationController.
Hit Command+N or open the "New File" dialog. Next, choose "Objective-C category" from the Cocoa Touch menu:
Click Next and you will be prompted to enter the name of the class that you would like to add methods to as a category. It should look something like this:
Then, you should end up with a save file dialog. A quick note about convention here. Convention is to name a category after the original class, the plus sign, and then a description of what you're adding. Here's what yours might look like:
Once you save your file, you will need get something like this:
Edit:
If you want to go ahead and do this without a category, then your best bet is to make a subclass of UIView and then subclass that class wherever you want your custom behavior. Another advantage over a category is that your method will only work where you explicitly use the custom class. In categories, the method gets added everywhere.
Good luck!
Is there a way to know when the index on uitableview is actually used? I can’t find any methods in the Apple documentation, but I was wondering if anyone else knows anything. I would basically like to make an animation in my table view that changes depending on what section is selected in the index, and I have no idea how to do this without accessing the table view index.
Any help is greatly appreciated.
Thanks.
The UITableViewDataSource delegate method -tableView:sectionForSectionIndexTitle:atIndex: returns an NSInteger representing the section selected from the section index. Override this method in whichever class is your data source delegate (probably your table view controller).
Set up a property in the view controller, an NSInteger called selectedSectionIndex. Its value is set within the aforementioned delegate method.
Finally, set up an observer in the view controller, which waits for changes to this property and triggers your desired code when a change does get observed.
In your -viewWillAppear: method, for example:
[self addObserver:self forKeyPath:#"selectedSectionIndex" options:NSKeyValueObservingOptionNew context:nil];
In your -viewWillDisappear: method, unregister the observer:
[self removeObserver:self forKeyPath:#"selectedSectionIndex"];
It's important to do this so that the -dealloc method doesn't throw an exception.
Finally, set up the observer method to do something when there is a change to selectedSectionIndex:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqual:#"selectedSectionIndex"]) {
// The section index changed, so trigger some cool animation
}
}
The Key-Value Observing pattern is a good, general way to trigger something when an object's value changes somewhere. Apple has written a good "quick-start" document that introduces this topic.
Not a very elegant solution, but you may be able to subclass UITableView and watch for it in - (void)didAddSubview:(UIView *)subview; when it first shows up. This assumes that it is added directly to the UITableView and not one of its subviews or superview, and that you can recognize the added view as the index. It is also possible that after the first time it is added it will be hidden and shown instead of being removed and added again.
I would like to have a pair of scrollviews - call them scrollA and scrollB - on the screen that work in parallel. When the user scroll/zooms scrollA scrollB mimics that behavior, zooming and panning identically. And vice versa.
Is this possible? Thanks in advance.
Cheers,
Doug
As per JoePasq's answer, I would use KVO for this and register observers for whichever key/value pairs you'd like to mimic in each UIScrollView. It would look something like this (untested):
// Do this during initialisation of scrollView2
[scrollView1 addObserver:self
forKeyPath:#"contentOffset"
options:NSKeyValueObservingOptionNew
context:NULL];
[scrollView1 addObserver:self
forKeyPath:#"zoomScale"
options:NSKeyValueObservingOptionNew
context:NULL];
// Implement this method on scrollView2
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
[self setValue:[change valueForKey:NSKeyValueChangeNewKey] forKey:keyPath];
}
I think the best way would be to set the delegate of each scrollview to your controller then implement the '- (void)scrollViewDidScroll:(UIScrollView *)scrollView' in your controller, inside the method you'll want to call 'setContentOffset:animated:' on the scrollview that didn't scroll, to get the correct contentOffset you can use the UIScrollView.contentOffset property of the UIScrollView that was scrolled which will be passed to your implementation of - (void)scrollViewDidScroll:(UIScrollView *)scrollView in your controller
Key value coding, if you don't know that then pick up Cocoa programming for Mac OS X, and learning objective-C by stephen kochan (spelling & punctuation?)
Check the docs for a way to get the position of the scroll view (like the index of a tableview) then set up KVC. Then you'll understand how the iPod keeps your scroll position after quits.
Does this work for the flick gesture as well?
From my experiences, the contentOffset is updated after the flick gesture deceleration is done.. so even with KVO it will still be observing old contentOffsets
am i right?
Perhaps I am missing something obvious to others, but how can this work both ways? It works in the case of a "master and slave" scenario, but if both scrollviews are mimicking one another, it ends up in an infinite loop. Help? – Sedate Alien May 4 '11 at 1:08
Keep track of the last view being dragged, and in "scrollViewWillBeginDragging:" you can remove the previous observers (if any) and add new ones for the view being dragged.
I was able to do it successfully.