On iOS, a custom view won't animate? - iphone

In a new Single View App, the following code:
- (void)viewDidAppear:(BOOL)animated {
self.fooView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
self.fooView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:self.fooView];
[UIView animateWithDuration:2 animations:^{
self.fooView.backgroundColor = [UIColor blueColor];
}];
}
will animate the yellow background to blue. The fooView is just:
#property (strong, nonatomic) UIView *fooView;
But if a custom view class FooView is defined, and I change the above code above by replacing UIView with FooView, and also define an empty -drawRect for FooView, then there will be no animation -- the background is just changed to blue immediately.
It made no difference if using [UIView animateWithDuration: ...] or [FooView animateWithDuration: ...] -- it is no animation.
If inside -drawRect, I call [super drawRect:rect];, then it is the same thing: no animation.
If I change drawRect to drawRect2, or if I remove the whole method, then the animation will work.
(and the -drawRect might be needed, for example, what if we need to draw a circle in the custom view FooView?)
Why is that? Besides giving a solution of how to make it work even with a -drawRect, please explain what was going on that made it not animate?

The presence of drawRect: changes the way UIView operates. You should only use drawRect: when absolutely necessary and you should never have an empty drawRect: implementation.
If the -drawRect: method is present in your subclass then UIView must do a number of things before it can call this method. Namely, it must allocate a potentially expensive backing store into which your custom drawing will occur, and it must fill this backing store with the background color.
UIView will then call your drawRect: implementation to do any custom drawing on to the backing store, before handing the backing store to the GPU. Only after all that does your animation block begin to run.
So at the start of your animation the backing store that represents your custom UIView has already been painted a new background color.
If your UIView subclass has no drawRect: method then the view has no backing store. The background color is handled directly by the GPU, which can very efficiently animate transitions between colors.
Empty UIView objects (without a drawRect: implementation) are rather lightweight. Your custom view should be composed of multiple subviews depending on what animation effects you want to achieve.
For example, the UITableViewCell class is composed of many subviews. UITableViewCell itself is a UIView. It contains a background view and a selected background view. On top of that is the content view, and inside the content view are various text labels and image views. All of these views combine to produce the selection and resize animations you see in a regular UITableView.

Using 'CATransition' class .You will have to be do animation.
Using this method
+(CAAnimation *)setAnimation{}

Use following line in animation Block
[self.view addSubview:self.fooView];

Instead of this we have to create a new class and using the CATransition and using this class methods '+(CAAnimation *)setAnimation'.We have to create an object for this and call when will transition happen

Related

Best practices for framing a hierarchy of views

From other stack overflow questions, I understand that in your view controller, you can use the viewDidLoad method to initialize subviews and the viewWillLayoutSubviews method to set the frames. This implies that we need to keep a reference of all the views within the view controller instance (as below). This is all fine and great and handles device rotations, etc.
UILabel* myLabel;
- (void)viewDidLoad
{
myLabel = [[UILabel alloc] init];
[myLabel setText:#"Hello"];
}
- (void)viewWillLayoutSubviews
{
[myLabel setFrame:CGRectMake(0, 0, self.view.frame.size.width, 21)];
}
My question is what if you have a complex hierarchy of subviews. For example, what if you have a subview which is a UIView and you add subviews to that UIView (and potentially it even goes deeper). Do you have to keep a reference to every view in this whole view hierarchy so that on viewWillLayoutSubviews, you can modify the frame of each view? This seems like the only way. Are constraints the best way so that you don't have to modify frames as often? Is the best answer to build tons of custom UIViews to make managing this hierarchy more manageable and self-contained within each custom view?
Hopefully this is a clear enough question..thanks in advance.
Anytime you manipulate a view or subview, in a way that could change the layout constraints, you have to call
[view layoutSubviews];
this will remake the constraints with the new property settings. When the viewController is loaded, you would set all properties not dealing with the frame or bounds and then with in the viewWillLayoutSubviews you set the frames of the views this is so all the constraints are made and ensured that they are met.
Any object you put in the storyboard that you want to use, should have a property this will hold a reference to that object. This is a good coding practice, especially if you follow the rules of test driven development.
Now to answer your questions, if you create the complex hierarchy in the storyboard then everything should be fine as long as your constraints are fine, if you make it in code just be sure to call layoutSubviews when ever you call addSubview
Example:
UIView* view1 = [[UIView alloc] initWithFrame:CGRectZero];
UIView* view2 = [[UIView alloc] initWithFrame:CGRectZero];
[view1 addSubview:view2];
[view1 layoutSubviews];
More views does not make the structure better, but if you have 100 subviews... lets be real.. something needs to be categorized in a separate view..
Constraints let you define the frames of views perfectly. You might not notice the differences at first, but if you compare it to Autoresizing Mask then you will see the differences. Also, here is a guide that blatantly shows you the differences.
LayoutConstraints and Autolayout
Also worth considering is using child view controllers if you wish to compartmentalize some of the logic and reuse components.

iOS: Adding a fixed image just below the navigation bar

It feels like this should be fairly simple but nothing i've tried so far has worked. In a nutshell, I want to add a fixed image just below the navigation bar in a UITableViewController that i create programmatically. In other words, I want the image to stay just below the navigation bar even as the user scrolls up and down the table view (it's basically a custom drop-shadow for the navigation bar).
The closest I've gotten is the code below (in the UITableViewController's init method), which adds the image but doesn't keep it from moving when the user scrolls.
// Add nav bar drop shadow
UIImage *dropShadowImage = [UIImage imageNamed:#"NavBarDropShadow.png"];
UIImageView *dropShadowView = [[UIImageView alloc] initWithImage:dropShadowImage];
[self.view addSubview:dropShadowView];
Is there an easy way to add an add an image to the screen programmatically, position it wherever you like, and have it stay there even as the user scrolls? Thanks for any and all input!
EDIT: IOS5 has a better way to do this. Please check out the new UIAppearance protocol.
Adding this block of code to your code will allow you to draw your shadow on all UINavigationBars in the app. This is a better solution than adding the shadow as a UIImageView:
#implementation UINavigationBar (ShadowBar)
- (void)drawRect:(CGRect)rect {
//draw the shadow ui nav bar
UIImage *image = [UIImage imageNamed: #"UINavBarWithShadow.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
- (void)layoutSubviews {
self.frame = CGRectMake(0, 0, self.frame.size.width, 300);
}
#end
To make the UINavigationBar higher and thus not clipping your content, override the layoutSubviews and set the frame you need (the code above assumes your header is 300 points high). layoutSubviews does nothing by default, but is "lazy" called before lay-outing the view.
For more info about this custom size/look overrides that apply to UIView (and any other subclass) have a look here
You can make a subclass or a category on UINavigationBar, and have it add the image in the init or drawRect methods. If you think about it, you're trying to add a shadow to the navigation bar, not to the UITableView, so it makes sense to modify the navbar, not the table.
You are adding your dropShadowView to self.view that in your case is the view of an UITableViewController. It means that your self.view is an UITableView so when you scroll up and down you scroll the dropShadowView as well because is inside the tableView.
Try to write a custom UIViewController and add two subviews: one is the dropShadowView and the other one is your table.
Have a look at this similar question I answered a while back. It should do exactly what you want with little customization.
Transparent View at the Top of a UITableView
Dont make a UITableView the main view ie. the view outlet, set that to a UIView that contains a UITableView, then make your controller a subclass of UIViewController, and make it conform to UITableViewDataSource and UITableViewDelegate. in your nib, set up your view so that you have a UIImageView at the top and a UITableView below. and set the delegate and datasource to your file's owner.

UITextField not showing when added as subview

I'm making an OpenGL ES application and I am trying to work out how to incorporate parts of the UIKit GUI over the view with the OpenGL ES working on it.
In the init method of the EAGLView class I have this to setup the UITextField:
add_name_field = [[UITextField alloc] initWithFrame: CGRectMake(10, 10, [UIScreen mainScreen].bounds.size.width, 50)];
Somewhere in the drawRect method where everything is rendered and processed for the game I have:
[add_name_field becomeFirstResponder];
[self insertSubview: add_name_field atIndex:0];
I can confirm this code is called because I used NSLog to test that but when it is run the text field does not show over the game's OpenGL view. If I put the code in the init method, the text field shows properly with the keyboard at te beginning of the game.
So how do I get it to work in the drawRect method?
Thank you for any answer. I will write some code for the delegate in the meantime.
THank you for all the other answers but I found the solution.
It was a simple solution. I simply moved the addSubView method to the initialisation of the EAGLView class. The keyboard wont show until I make the text field the first responder. When I'm finished with it, I can simply clear the text and make the EAGLView class the first responder again and that hides it all again.
This way I can simply use the keyboard to get text input for my game, displaying text when typing and hiding it afterwards.
I assume you're in a UIViewController subclass, with the code above? If so, then instead of
[self insertSubview: add_name_field atIndex:0];
do:
[self.view addSubview:add_name_field];
The important part is, you're calling this method on self.view rather than self. Adding subviews is a VIEW function, not a View CONTROLLER function.
insertSubview:atIndex: ought to work too (if called on the right object), but addSubview: will stick it on top of the view no matter what else is there, so it's a bit cleaner.
Add your text subview outside of drawRect. drawRect is for rendering that one view, and may or may not ignore drawing any other views (such as your text view), depending on how the view stack is rendered.
Is there any reason to have it done in the drawRect method? You said that it works fine in the init method, so why not have it there?
As for why it doesn't work, I don't exactly know OpenGL views but, assuming they work just like regular UIViews, you probably shouldn't be adding other UIViews in the drawRect function. UIViews don't get shown & drawn immediately; they have to wait till the runloop tells them to draw. Likely what happens is that when your superview's drawRect finishes, it assumes all its subviews have finished drawing and doesn't realized that your newly added textfield didn't get a chance to. What you could try is calling setNeedsDisplay on your textfield.

Border image on UIView

I want to have a UIView subclass that has a border image, but I don't want or care about this 'new' frame/bounds around the border image itself.
What I wanted to do was just use drawRect and draw outside of the rect but all drawing is clipped and I don't see a way to not clip drawing outside of this context rect.
So now I have added a sublayer to the views layer, set [self clipsToBounds] on the view and override setFrame to control my sublayers frame and always keep it at the proper size (spilling over the views frame by 40px).
The problem with this is that setFrame on a uiview by default has no animation but seTFrame on a calayer does.
I cant just disable the animations on the calayers setFrame because if I were to call setFrame on the uiview inside a uiview animation block the calayer would still have its animation disabled.
The obvious solution is to look up the current animationDuration on the uiview animation and set a matching animation on the sublayer, but I don't know if this value is available. And even if it is, I'm afraid that calling an animation from within another animation is wrong.
Unfortunately the best solution is to not use a calayer at all and just add a uiview as a subview and draw into that just like I am drawing into my layer, and hope that with autoresizingMask set to height and width that everything will 'just work'. Just seems like unnecessary overhead for such a simple task.
My solution would be to override the initWithFrame: to add the surrounding border pixels and contain the content in a subview. It probably is unneccesary overhead but definietly the "cocoa" route. It's probably going to be easier in the end too since a subview structure will allow you to edit the content seperatly from the border so you dont have to redraw the border when you redraw the content. And keeping them seperate simply makes sense from a OOP perspective.
The clipsToBounds route is probably the easiest route besides the subview structure but managing the border and content in one drawing cycle and in one object will probably be a lot more work so it'll be worth the overhead.
Excuse any typos, typed this from my iPhone.

How do I draw to the contentView of a UITableViewCell?

I'm trying to draw a line on a custom UITableViewCell. When I draw inside the overridden drawRect and use the current graphics context I don't see anything.
I know I can't see anything because the draw rect is drawing to the UIView and not the contentView. So my question is, how do I draw to the content view using CG? Say, grabbing the graphics context to the contentView?
I have played with this in the past and what i have ended up doing is creating my own subclass of UIView and adding iot to a custom subclass of UITableViewCell and drawing to it that way. I like the control that this gives me.
For some reason (I don't know why, but I did notice this) if you "custom-draw" (ie, redefine drawRect) on what's in the 'contentView' of your cell, it does not show up on the table cell. However, the view that you assign to cell.backgroundView and cell.selectedBackgroundView show up perfectly well...
You could put your custom view in cell.backgroundView, put nothing in cell.contentView and it will show up fine (except for when you select the row, because then your view disappears and is replaced by cell.selectedBackgroundView).
So, what you can do is:
Use cell.contentView to show a custom view without any background
Use cell.backgroundView and cell.selectedBackgroundView to show a fancy background for your cell...
Or:
Make your custom view flexible enough so that it can show both the selected and non-selected state, and use 2 instances of your custom view: one in cell.backgroundView and one in cell.selectedBackgroundView
I found my answer here. Basically the super class ABTabeViewCell sets up the context so you can easily draw in the drawContentView function.