Possible to make superView intercept layoutSubviews: from subviews? - iphone

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!

Related

iOS 8 - UILabel observer on change of text not being called

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

Handling UIDynamicBehaviors when removing the respective UIView

Periodically, I add a UIView to the UIDynamicAnimator, which has some behaviours of its own. But when I remove the UIView from it's superview (when it falls offscreen) the UIDynamicAnimator still keeps the UIView's behaviours in its 'behaviors' property.
My question is, exactly what is the best approach to handling the behaviours in a UIDynamicAnimator?
Do I have to manually keep track of all the behaviours pertaining to that UIView and manually remove them before removing the UIView from the view hierarchy?
[myBehavior removeItem:item] does not throw an exception if item is not part of myBehavior, so what you could do is have a generic removeView method that removes a view from all behaviors that could possibly pertain to it, something like:
- (void) removeView: (UIView *) view{
[_gravity removeItem: view];
[_collisions removeItem:view];
[_otherBehavior removeItem:view];
//and et cetra for all of your behaviors
[self.view removeItem: view];
}
Which could be called whenever you need to remove a view. Even if, say, the view is not part of _otherBehavior this method would still properly remove the view.

iOS SDK Check if view already exists

I have a UIWebView that is drawn programmatically and gets allocated and displayed through multiple subviews (the webview gets added to the superview).
This all works, however I have one little problem:
If 2 different subviews display this webview then I get 2 webviews, so when 1 view dismisses the webview the other remains. I don't want this.
Originally I was thinking just implement the webview in the superview class, however it didn't work.
How can I have the web view check to see if there are more then one of itself?
A webview instance can only be added to the view hierarchy once. If you have two webviews visible on screen at once, they are two different instances. You should keep track of these instances that you add to the hierarchy and when one dismisses, remove all the instances you are tracking from their superview.
You can also crawl a view hierarchy and look for instances of UIWebView.
for (UIView *subView in [myView subviews]) {
if ([subView isKindOfClass:[UIWebView class]]) {
[subView removeFromSuperview];
}
}
mhm, no, wait, it's not so clear what you are meaning...
You say: "...there are more then one of itself" and "...gets allocated and displayed through multiple subviews"
You probably mean that you have 2 instances of "the same" UIWebView class, but then you should not consider them as "the same object" which lives in 2 different superviews... they are different objects, everyone has its own properties...
Or did i misunderstood?
So, if you meant as i said and you just want to control from a subView (mhm... or we should say from its UIViewController) if there are other views which use a UIWebView.
I'd probably use one UIViewController "parent" where to load my subViews (eventually they could also have their own UIViewController, then every time i Alloc and addSubview a UIWebView in my subView i just add a tag to it:
myWebView.tag = 11;
it could change if needed for next one...
the purpose is to be able to control if in my UIView there are allocated some UIWebView,
now we can do that with this in my main parent UIViewController:
for (UIView *view_level_1 in [self.view subviews]) {
for (UIView *view_level_2 in [view_level_1 subviews]) {
if (view_level_1 >= 10) {
// do something: dismiss this UIWebView too...
}
}
}
it just control in all the subViews of the view of my mainViewControl if there is a subView "tagged" before (a tag is a sort of "name/id")
I'm not sure the structure of your subView could be like that, it was not so clear in your question, but you can change the code adopting it...
luca
Use UIView's isDescendantOfView method to know if any subView is currently present on parentView.
if([addedSubView isDescendantOfView:parentView])
{
//addedSubView is subview of parentView
//Take necessary action.
}
else
{
//addedSubView is not subview of parentView
//Take necessary action.
}

Is there a way to know when the index on uitableview is actually used?

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.

What is the correct way to "gang" together and keep in sync a pair os UIScrollViews?

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.