Best way to redraw a custom view when orientation changes - ios

I have a custom view:
-(id)initWithFrame:(CGRect)frame
{
CGRect frameRect = CGRectMake(0, NAVIGATION_BAR_HEIGHT , frame.size.width, 4 * ROW_HEIGHT + NAVIGATION_BAR_HEIGHT + MESSAGE_BODY_PADDING);
self = [super initWithFrame:frameRect];
if (self) {
_selectionViewWidth = &frame.size.width;
[self initView];
}
return self;
}
-(void)initView
{
CGRect sectionSize = CGRectMake(0, 0 , *(_selectionViewWidth), ROW_HEIGHT * 4);
_selectionView = [[UIView alloc] initWithFrame:sectionSize];
[_selectionView setBackgroundColor:[UIColor clearColor]];
That I use in a View Controller the next way:
_mailAttributesView = [[MailAttributesView alloc]initWithFrame:self.view.frame];
_mailAttributesView.delegate = self;
[self.view addSubview:_mailAttributesView];
So when orientation changes from P to L I have the next problem:
What's the best way to get orientation change callback and redraw my custom view?

You likely need to override your UIView layoutSubviews method and proceed to manually layout your subviews (looks like to/from/cc/subject controls) there.
Or, you could better configure your subview spring/struts (or autolayout constraints) for automatic layout. You could do this in code or via a nib or storyboard.
EDIT: additional info since you seem not to be getting layoutSubviews on orientation change.
My guess is that the viewcontroller-view isn't resizing/repositioning your MailAttributes view either.
It's also not clear when/where you add your MailAttributesView to the veiwcontroller view. If you're doing it in viewDidLoad your viewcontroller view may or may not have a valid frame size (depending if it was loaded from a nib or not). It's best not to depend on the viewcontroller-view frame for layout purposes in viewDidLoad.
Rather, layout any viewcontroller-view subviews in viewWillLayoutSubviews. There your viewcontroller-view frame will be set.
Others may point out that you can set your autoresizingFlags in viewDidLoad for any subviews, but there are gotcha's with this. Primarily if the parent view has zero size, and your subviews are to be inset but have springs/struts defined to glue them to the parent view edges.
The best solution overall IMO is to setup autolayout constraints for everything contained in your viewcontroller view, on down.

Related

Strange behaviour with autolayout and frame changing with iOS 9

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

When are UIView size classes applied, for a UIView loaded directly from a Xib?

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.

Objective-C - When I add a subview, my subview is going out of the screen at the bottom. Misunderstanding of frames?

Im practicing objective-C, and I try to do everything programmatically.
I'm making a simple view that I add on my view of the ViewController, but this subview is going out of the screen.
When I set my frame, the position for the X and Y are respected, but the rest, no...
Here is the screenshot of my result :
As you can see... The red subview is going out of the screen.
Here is my loadView where I add that subview :
HomeViewController.m - loadView
-(void)loadView
{
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIView *subview = [[UIView alloc] initWithFrame:CGRectMake(15, 15, self.view.frame.size.width - 30, self.view.frame.size.height - 30)];
[subview setBackgroundColor:[UIColor redColor]];
[self.view addSubview:subview];
}
For the padding, I did put 15 for the position x and y... And for the frame, I did calculate with the size of the self.view by removing paddings... As you see, it works well for the width, but for the height, it is a big fail. It goes outside the screen.
In my AppDelegate.h, I set the navigationController.navigationBar.translucent = NO;, in order to that when I set position for x, and y, it starts well after the navigationBar .
I don't understand this weird behavior for the height... If someone has a good explanation for this please.
Thanks
First, you shouldn't rely on the value of self.view in viewDidLoad. It is set to a correct value later, in viewWillAppear:. You can keep your code, if you make your subview resize automatically when self.view is displayed. For that, set autoresizingMask on the subview:
subview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
(or add an equivalent set of constraints if you use Auto Layout.)
Also, I recommend to use bounds instead of frame:
UIView *subview = [[UIView alloc] initWithFrame:CGRectMake(15, 15, self.view.bounds.size.width - 30, self.view.bounds.size.height - 30)];
It doesn't make a difference here, but it often does, e.g. if you calculate the frame's x and y based on the parent frame.
loadView method just creates the view. At the point when loadView gets called there is no information about final view frame hence its children views cannot be placed properly.
The right place to update your children views' frames is viewDidLayoutSubviews:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// update child view frame here
}
Remarks: you can define auto-layout constraints of your child view in code and they will be automatically applied to child views when view controller's view gets resized.

what's wrong with this assign?

I am using GMGridView in my project. And in - (GMGridViewCell *)GMGridView:(GMGridView *)gridView cellForItemAtIndex:(NSInteger)index method, i write a snippet which is out of my expectation. The snippet is as follows:
if (!cell)
{
cell = [[GMGridViewCell alloc] init];
ThumbImageView *view = [[ThumbImageView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
[view setBackgroundColor:DEFAULT_BACKGROUND_COLOR];
[view setImage:[UIImage imageWithContentsOfFile:path]];
view.userInteractionEnabled = YES;
view.layer.shadowOffset = CGSizeMake(8, 8);
view.layer.shadowOpacity = 0.5;
NSLog(#"view frame width:%f,height:%f",view.frame.size.width,view.frame.size.height);
cell.contentView = view;
NSLog(#"cell.contentView frame width:%f,height:%f",cell.contentView.frame.size.width,cell.contentView.frame.size.height);
}
when it runs the output is as follows:
2013-06-03 11:02:05.508 XXX[71692:707] view frame width:115.000000,height:180.000000
2013-06-03 11:02:05.511 XXX[71692:707] cell.contentView frame width:0.000000,height:0.000000
why assign view to cell.contentView, cell.contentView.frame.size still be zero? and also cell.contentView can display the image properly. what's the reason? I am totally confused:(.
Check the docs:
contentView
Returns the content view of the cell object. (read-only)
#property(nonatomic, readonly, retain) UIView *contentView
contentView is a readonly property. You can't assign it.
The problem is probably that you've created new views, and you're looking at the frame of one of those views before the run loop has reached its layout phase.
When you create the GMGridViewCell, it starts with a default frame and bounds of CGRectZero. When you set its content view, it sets the content view's frame to its (the cell's) bounds. So even though you set the content view's frame directly using initWithFrame:, the frame gets changed to CGRectZero when you set the view as the cell's content view.
Since the cell isn't a subview of the grid view yet, there's no way to ask the grid view to lay out the cell by the time you're trying to log the content view's frame. If the grid view is using a constant size for all cells, and you know what that size is, you could manually set the cell to that size before setting the cell's content view. Otherwise, you need to wait until after the grid view's layoutSubviews method has run to check the content view's frame.

Where does UICollectionView etc calculate content size so that I can permanently increase it?

I want to add a view to the bottom of the content view of both a collection view and table view (and hence is applicable to any kind of scroll view) and I also want to be able to scroll down to see this view e.g.:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Observe change in content size so can move my view when
// content size changes (keep it at the bottom)
[self addObserver:self forKeyPath:#"contentSize"
options:(NSKeyValueObservingOptionPrior)
context:nil];
CGRect frame = CGRectMake(0, 0, 200, 30);
self.loadingView = [[UIView alloc] initWithFrame:frame];
[self.loadingView setBackgroundColor:[UIColor blackColor]];
[self addSubview:self.loadingView];
// Increase height of content view so that can scroll to my view.
self.contentSize = CGSizeMake(self.contentSize.width, self.contentSize.height+30);
}
return self;
}
However when, for example, a cell is inserted the contentSize is recalculated and whilst my view is still visible at the bottom of the content size (due to being able to bounce the scroll view) I can no longer scroll to it.
How do I ensure that the content size stays, as in my code, 30 points taller?
An additional question is:
is there any other way to track content size other than observing it?
Thanks in advance.
I have tried:
- (void)layoutSubviews
{
[super layoutSubviews];
self.contentSize = CGSizeMake(self.contentSize.width, self.contentSize.height+30);
}
However this causes all sorts of display issues.
If i understand correctly, you want to show a loading view in the tableView (f.e.) at the bottom. You could add an extra UITableViewCell containing this LoaderView to the tableView.
(Must change the numberOfRowsInTableView)
In another perspective for scrollViews: Use smaller bounds then the content itself, to make it scrollable. For example frame = fullscreen. At every cell adding or modification in subviews (adding) contentSize = content size + 30 px.
Try making a subclass of the scroll view and override the contentSize getter to return always 30 px more.
- (CGSize)contentSize {
CGSize customContentSize = super.contentSize;
customContentSize.height += 30;
return customContentSize;
}
(I'm writing the code by memory, there may be errors)

Resources