I have a custom UIView fooView, where I've overridden the drawRect method, with an initWithFrame method.
I also have a custom UIView barView which holds and contains fooView.
barView is supposed to get it's height from fooView since fooView is drawing custom stuff.
My problem is everytime I check for fooView's frame, or bounds property, it remains the same. Even though I can clearly see it outgrowing the initial height dictated by the initWithFrame.
Which leads me to believe that maybe since I've overridden the drawRect method, now it's my responsibility to update fooView's frame.
Should I do this?
How would I do this?
What is the best practice?
EDIT: Added Code
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) [self setupWithMessage:nil];
return self;
}
setupWithMessage just calculates the dimensions (mainly height) required for text to fit in a constrained width.
- (void)drawRect:(CGRect)rect{
[super drawRect:rect];
// a whole bunch of drawing logic, but basically draws a message bubble
// based on the size of the text (The text dynamically changes), and then
// draws the text so if there is a lot of text, the calculated height of
// what it takes to draw the text can easily be larger than the size
// originally passed in during initialization
}
The method drawInRect:(CGRect)rect gives you a rect that is not greater than your's view frame.
So if you draw in rect that is higher that given rect - your drawings will be clipped.
So steps that you should do:
Calculate all dimensions and set your's fooView frame.
After that you should call [fooView setNeedsDisplay].
It will invoke your drawInRect: method with a new frame, so you can focus there on drawing.
If your fooView main task is to draw given text from barView I suggest you to create #property (nonatomic) NSString *textToDraw; in fooView class and override setter in which you will follow steps above.
EDIT:
As I see you created a setupWithText: method for this purposes. So you need to calculate all dimensions, set frame self.frame = (CGRect){previousX, previousY, newWidth, newHeight} and call [self setNeedsDisplay];
Related
The problem: Let's suppose I have an image and an element which constantly triggers viewDidLayoutSubviews (a scrollview.. so that at every scroll viewDidLayout would be called... or whatever element that triggers viewDidLayout quite often).
This image as well as the "viewDidLayout_element" are all set up well with autolayout in the storyboard.
Now:
I need to make the image to be a rounded one.
with the typical: layer.cornerRadius.. and layer.masksToBounds. This requires a calculated imageView frame.
In what view controller cycle do I make it programatically? Taking into consideration that:
*except for the viewDidLayoutSubviews I don't get the right frame
*the viewDidLayoutSubviews can be called even thousands of times depending on the "viewDidLayout_element" that triggers it.
*If I call layoutIfNeeded on the imageView in viewDidAppear (because it's the only case when frames are already available so we can force their calculation) the user will already catch a glimpse of the transformation from a square image to a circular image. In other words in viewDidAppear the frame becomes available for our manipulation, but is also available for milliseconds to the user's eyes.
*it does not make sense to fill the viewDidLayoutSubviews with flags (especially if there will be something more performance intensive than a circular imageView transformation) like below:
if !iDidChangedTheImage
{
imageView.applyCornerRadius()
}
The question:
Where do I have the correct frame size inside a viewController cycle without the problems from above? Or how do you usually solve this problem?
Subclass UIImageView to your custom class, and override layoutSubviews. After that you don't need to care about image view size change and updating corner radius, just use that class for your custom image view
#implementation CustomImageView
- (void)awakeFromNib {
[super awakeFromNib];
self.layer.masksToBounds = YES;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.layer.cornerRadius = self.frame.size.height / 2.0;
}
#end
I'm trying to create a view controller to simulate a classic weighing scale. I have a UIView subclass (DragView) to represent the weights, and a another UIView subclass (ContainerView) to simulate the plates os the scale.
When a DragView is drag over the ContainerView, I trigger an animation to place the DragView inside the ContainerView (changing the size if is necessary). But, if the user releases the DragView outside the ContainerView, then the DragView is animated to its original position and size.
Here you can see the DragView (in green) and two ContainerView (in clear Color above the "plates")
The original frame of the DragView is set with constraints (proportional width, top and leading). Everything looks fine but when I animate the DragView back to his original position, then I've got this.
See the difference in the DragView's frame?. Why is this happening?
Here are the relevant parts of my code.
DragView.m
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
_originalFrame = self.frame;
}
return self;
}
- (void)animateBackToOrigin
{
[UIView animateWithDuration:0.1 animations:^{
self.frame = _originalFrame;
}];
}
I've checked the _originalFrame values in both methods and it returned the same values.
ANSWER:
My mistake was setting the _originalFrame within initWithCoder, layoutSubViews is the right place. Because layoutSubViews is called every time the view is set, I added a check (with CGRectIsEmpty) in order to set the frame only if there is no value.
- (void)layoutSubviews
{
if (CGRectIsEmpty(_originalFrame)) {
_originalFrame = self.frame;
}
}
It is to early in initWithCoder: to take resulting frame. The view is just instantiated and not processed through layout process. I think, the best place is layoutSubviews method.
When autolayout is present bad things will happen if you mess with frame.
Try instead of changing the full frame, change the .origin of the object
I'm developing iOS UI with auto layout.
Because I need to set corner radius of view's layer for make view looks like circle, get actual view's size is necessary. (I couldn't change layer by constraints)
Finally, I realize I can get view's real size in viewDidLayoutSubviews and I looks well. But when I called it's super method, size becomes wrong value.
- (void)viewDidLayoutSubviews {
// [super viewDidLayoutSubviews];
if (_viewProfileCoverForReward) {
float circleSize = imageViewOppositeProfile.frame.size.width;
_viewProfileCoverForReward.layer.cornerRadius = circleSize / 2;
NSLog([NSString stringWithFormat:#"(%ld)circle size : %f", super.missionData.missionID, circleSize]);
}
}
Here is my code and it works well. But in my opinion, don't call super's method is not good and why I must not call super's method if I want to get view's real size. It doesn't make sense!
I'm initting a UIView with a xib, programatically:
- (id)initWithFrame:(CGRect)frame
{
self = [[[NSBundle mainBundle] loadNibNamed:#"MyViewNib" owner:self options:nil] objectAtIndex:0];
if (self)
{
//perform setup of various components
return self;
}
}
This view uses size classes for Any Width, Any Height, and Any Width, Compact Height.
I have a UIButton in a xib that I need to put a circle in the background of, like so:
self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width / 2.0f;
self.closeButton.layer.backgroundColor = [UIColor colorWithWhite:164.0f / 255.0f alpha:1.0f].CGColor;
If I try setting the button's corner radius in the init function when I'm on a device using Any Width, Compact Height, the label's frame is still set to the Any Width, Any Height value. I've also tried overriding layoutSubviews and setting the value there, with no luck. It appears that the size class constraints are applied after layoutSubviews without another call to layoutSubviews, since the other components appear on-screen correctly.
So, I'm wondering if there's a good entry point for me to catch where the size classes are applied, so that I can set the background corner radius of the button correctly. I could just set the button up programmatically, but I'd like to figure out how to do this since it will probably come up again in the process of converting to size classes.
In your xib class -(void)awakeFromNib;
This works if I put it at the end of the init function:
[self performSelector:#selector(updateButtonBackground) withObject:nil afterDelay:.001];
Apparently by this point the constraints have been set up with the size classes. It's certainly non-ideal though, so if anyone has an answer that's event-driven or involves view "lifecycle" (as far as that applies to UIView) I'll accept that...
How about doing this?
-(void)layoutSubviews
{
[super layoutSubviews];
self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width / 2.0f;
self.closeButton.layer.backgroundColor = [UIColor colorWithWhite:164.0f / 255.0f alpha:1.0f].CGColor;
//more of your changes
[self layoutIfNeeded]; // You can remove this line if it works without it for you..
}
I know this thread is old, but I see no good answer here.
Size classes are defined in traitCollectionDidChange: method. You can't ensure that traitCollection is set in initWithFrame: or awakeFromNib:.
So set your corner radius in traitCollectionDidChange: method based on the view size classes.
You need to apply the corner radius in viewDidLayoutSubviews. It is at this point that you know the bounds of the button have been established.
You can see an example on my blog.
In reading the View Programming Guide from Apple it is apparent they have not updated it for constraints. So a couple of questions:
Many posts here talk about NOT using -initWithFrame when initializing a new UIView or setting it to CGRectZero or some equivalent. How would you handle this then?:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self){
//Set up rounded rectangle ivar
CGRect frame = [self bounds];
CGFloat radius = frame.size.height / 2;
_rectPath = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:radius];
}
}
and then putting the drawing code in the drawRect:. I have found that it won't init properly without a frame. (Obviously, I'm initing this UIView and immediately setting constraints but no frame immediately). This code set's frame = (0,0,0,0) because constraints haven't been computed yet.
If we don't put this code in init where would we put it? Also, I thought it was good practice to init all our stuff we needed in the view in init.
So this leads to question...
When is -updateConstraints called in the runtime cycle and when does the view compute it's bounds using those constraints? I have noticed that the view really doesn't know what it's bounds are until after -layoutSubviews. Is there another place before this that the UIView knows it's bounds if it has only been set with constraints?
Sorry about all the questions, but they all kinda seem connected. Thanks for the looks.
Your problem with your frames is not related to auto layout. View size changes all the time. It is almost never the same as during object instantiation. You need to create/calculate the size of paths to fit the current size during drawRect, not when you first create the view.
The only objects you should set during init are properties and/or ivars that the rest of your class relies on existing in a certain state.