When I add a subview to a UIView, or when I resize an existing subview, I would expect [view sizeToFit] and [view sizeThatFits] to reflect that change. However, my experience is that sizeToFit does nothing, and sizeThatFits returns the same value before and after the change.
My test project has a single view that contains a single button. Clicking the button adds another button to the view and then calls sizeToFit on the containing view. The bounds of the view are dumped to the console before and after adding the subview.
- (void) logSizes {
NSLog(#"theView.bounds: %#", NSStringFromCGRect(theView.bounds));
NSLog(#"theView.sizeThatFits: %#", NSStringFromCGSize([theView sizeThatFits:CGSizeZero]));
}
- (void) buttonTouched {
[self logSizes];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.frame = CGRectMake(10.0f, 100.0f, 400.0f, 600.0f);
[theView addSubview:btn];
[theView sizeToFit];
[self performSelector:#selector(logSizes) withObject:nil afterDelay:1.0];
}
And the output is:
2010-10-15 15:40:42.359 SizeToFit[14953:207] theView.bounds: {{0, 0}, {322, 240}}
2010-10-15 15:40:42.387 SizeToFit[14953:207] theView.sizeThatFits: {322, 240}
2010-10-15 15:40:43.389 SizeToFit[14953:207] theView.bounds: {{0, 0}, {322, 240}}
2010-10-15 15:40:43.391 SizeToFit[14953:207] theView.sizeThatFits: {322, 240}
I must be missing something here.
Thanks.
The documentation is pretty clear on this. -sizeToFit pretty much calls -sizeThatFits: (probably with the view's current size as the argument), and the default implementation of -sizeThatFits: does almost nothing (it just returns its argument).
Some UIView subclasses override -sizeThatFits: to do something more useful (e.g. UILabel). If you want any other functionality (such as resizing a view to fit its subviews), you should subclass UIView and override -sizeThatFits:.
If you won't override UIView, u can just use extension.
Swift:
extension UIView {
func sizeToFitCustom () {
var size = CGSize(width: 0, height: 0)
for view in self.subviews {
let frame = view.frame
let newW = frame.origin.x + frame.width
let newH = frame.origin.y + frame.height
if newW > size.width {
size.width = newW
}
if newH > size.height {
size.height = newH
}
}
self.frame.size = size
}
}
The same code but 3 times faster:
extension UIView {
final func sizeToFitCustom() {
var w: CGFloat = 0,
h: CGFloat = 0
for view in subviews {
if view.frame.origin.x + view.frame.width > w { w = view.frame.origin.x + view.frame.width }
if view.frame.origin.y + view.frame.height > h { h = view.frame.origin.y + view.frame.height }
}
frame.size = CGSize(width: w, height: h)
}
}
You can do some like that using IB alone (xcode 4.5):
Click on the UIView
in the Size inspector drag content hugging to 1 (both horizontal and vertical)
drag compression resistance to 1000 (for both)
under the UIView's constraints click on Width and change priority to 250
Do the same for Height
You can use the UIView's inset to control padding for left/right/top/bottom
self.errorMessageLabel.text = someNewMessage;
// We don't know how long the given error message might be, so let's resize the label + containing view accordingly
CGFloat heightBeforeResize = self.errorMessageLabel.frame.size.height;
[self.errorMessageLabel sizeToFit];
CGFloat differenceInHeightAfterResize = self.errorMessageLabel.frame.size.height - heightBeforeResize;
self.errorViewHeightContstraint.constant = kErrorViewHeightConstraintConstant + differenceInHeightAfterResize;
This worked for me.
Related
I want to create an animation similar to app opens in iPhone in iOS7. In this animation it just shows that app is opening from which point and closing at same point.
Can anyone please help me?
#property (weak, nonatomic) IBOutlet UIImageView *bg;
#property (weak, nonatomic) IBOutlet UIImageView *cal;
…
bool nowZoomed = NO;
CGRect iconPosition = {16,113,60,60}; // customize icon position
- (CGRect)zoomedRect // just a helper function, to get the new background screen size
{
float screenWidth = UIScreen.mainScreen.bounds.size.width;
float screenHeight = UIScreen.mainScreen.bounds.size.height;
float size = screenWidth / iconPosition.size.width;
float x = screenWidth/2 - (CGRectGetMidX(iconPosition) * size);
float y = screenHeight/2 - (CGRectGetMidY(iconPosition) * size);
return CGRectMake(x, y, screenWidth * size, screenHeight * size);
}
- (IBAction)test
{
float animationDuration = 0.3f; //default
if (nowZoomed) // zoom OUT
{
[UIView animateWithDuration:animationDuration animations:^{ // animate to original frame
_cal.frame = iconPosition;
_bg.frame = UIScreen.mainScreen.bounds;
} completion:^(BOOL finished) {
[UIView animateWithDuration:animationDuration/2.0f animations:^{ // then fade out
_cal.alpha = 0.0f;
} completion:^(BOOL finished) {
_cal.hidden = YES;
}];
}];
}
else // zoom IN
{
_cal.alpha = 0.0f;
_cal.hidden = NO;
[UIView animateWithDuration:animationDuration/2.0f animations:^{ // fade in faster
_cal.alpha = 1.0f;
}];
[UIView animateWithDuration:animationDuration animations:^{ // while expanding view
_cal.frame = UIScreen.mainScreen.bounds;
_bg.frame = [self zoomedRect];
}];
}
nowZoomed = !nowZoomed;
}
you can test it, by creating a sample project like this:
make two screenshots from simulator like I did (homescreen and calendar view) or grab these two: homescreen / calendar
add 2 image views and 1 button into storyboard
make the background image view as big as the whole screen
and the other image view with this dimensions: {16,113,60,60}
create an IBOutlet for both (the very first two lines of code)
set the button action target to -(void)test
the storyboard picture (left) and animation transition (right)
I personally prefer to use CGAffineTransformMakeScale() and setting -[CALayer affineTransform] in this case.
affineTransform is super easy to use and comes with a few nice, implicit benefits from Core Animation. Examples being that does things like handling of changing the frame's origin for you implicitly and making it really easy to reset back to the initial size if needed -- you never lost it in the first place!
[UIView animateWithDuration:0.3 animations:^{
view.layer.affineTransform = CGAffineTransformMakeScale(10.0, 10.0); // To make a view larger:
otherView.layer.affineTransform = CGAffineTransformMakeScale(0.0, 0.0); // to make a view smaller
}];
and
// To reset views back to their initial size after changing their sizes:
[UIView animateWithDuration:0.3 animations:^{
view.layer.affineTransform = CGAffineTransformIdentity;
otherView.layer.affineTransform = CGAffineTransformIdentity;
}];
As far as I know, that animation is made using screenshots. It updates the frame of the view and simultaneously makes a smooth transition from the app logo to the screenshot from the app. I have imitated the opening of the iPod (music) application from the bottom right corner of the device to the screen size:
UIView * v = [[UIView alloc]init];
CGSize size = self.view.bounds.size;
CGRect frameInitial = CGRectMake(size.width - 30, size.height - 30, 20, 20);
CGRect frameFinal = CGRectMake(0,0, size.width, size.height);
[v setFrame:frameInitial];
Then use the lines below when you want to animate the frame size:
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
[v setFrame:frameFinal];
} completion:nil];
Edit: Did not realize that the zooming also included the background. The code below is not tested (I am not at work) so expect some defects and typos.
Imagine you have two layers on the view controller's view. Directly on the vc there is the app you want to be opened, lets call it finalView. And on the top layer there is the window with all apps, which will zoom and fade into your app, which is a view behind it. Lets call it firstView.
Initial cond: firstView has a frame of 320 x 480 (It is a window with all the app icons). It has an alpha of 1. finalView has the same frame and alpha, but it is behind firstView.
Final cond: finalView will still have the same frame and alpha. But firstView will zoom into bottom right corner (will have a huge frame) and fade out (alpha -> 0).
//Initial cond: (Or better yet use IB)
CGRect frameInitial = CGRectMake(0,0, self.view.size.width, self.view.size;
CGRect frameFinal = CGRectMake(self.view.size.width * -4 ,self.view.size.height * -5, self.view.size.width * -5,self.view.size.width * -6);
[v setFrame:frameInitial];
Then use the lines below when you want to animate the frame size:
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
[v setFrame:frameFinal];
} completion:nil];
I have small repo that uses a UICollectionViewFloatLayout to create the zoom effect, https://github.com/MichaelQuan/ios7ZoomEffect. It is still a work in progress but the basic idea is there
The layout code is:
#interface ExpandingCollectionViewLayout ()
#property (nonatomic, assign) CGRect selectedCellFrame;
#property (nonatomic, strong) NSIndexPath *selectedIndexPath;
#end
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *layoutAttributes = [super layoutAttributesForElementsInRect:rect];
[layoutAttributes enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *obj, NSUInteger idx, BOOL *stop) {
[self _transformLayoutAttributes:obj];
}];
return layoutAttributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *layoutAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
[self _transformLayoutAttributes:layoutAttributes];
return layoutAttributes;
}
- (void)_transformLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
if (self.selectedIndexPath != nil)
{
if ([layoutAttributes.indexPath isEqual:self.selectedIndexPath]) {
// set the frame to be the bounds of the collectionView to expand to the entire collectionView
layoutAttributes.frame = self.collectionView.bounds;
} else {
//scale = collectionView.size / cell_selected.size
//translate = (scale - 1)(cell_i.center - cell_selected.center) + (collectionView.center - cell_selected.center)
CGRect collectionViewBounds = self.collectionView.bounds;
CGRect selectedFrame = self.selectedCellFrame;
CGRect notSelectedFrame = layoutAttributes.frame;
// Calculate the scale transform based on the ratio between the selected cell's frame and the collection views bound
// Scale on that because we want everything to look scaled by the same amount, and the scale is dependent on how much the selected cell has to expand
CGFloat x_scale = collectionViewBounds.size.width / selectedFrame.size.width;
CGFloat y_scale = collectionViewBounds.size.height / selectedFrame.size.height;
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(x_scale, y_scale);
// Translation based on how much the selected cell has been scaled
// translate based on the (scale - 1) and delta between the centers
CGFloat x_zoomTranslate = (x_scale - 1) * (CGRectGetMidX(notSelectedFrame) - CGRectGetMidX(selectedFrame));
CGFloat y_zoomTranslate = (y_scale - 1) * (CGRectGetMidY(notSelectedFrame) - CGRectGetMidY(selectedFrame));
CGAffineTransform zoomTranslate = CGAffineTransformMakeTranslation(x_zoomTranslate, y_zoomTranslate); //Translation based on how much the cells are scaled
// Translation based on where the selected cells center is
// since we move the center of the selected cell when expanded to full screen, all other cells must move by that amount as well
CGFloat x_offsetTranslate = CGRectGetMidX(collectionViewBounds) - CGRectGetMidX(selectedFrame);
CGFloat y_offsetTranslate = CGRectGetMidY(collectionViewBounds) - CGRectGetMidY(selectedFrame);
CGAffineTransform offsetTranslate = CGAffineTransformMakeTranslation(x_offsetTranslate, y_offsetTranslate);
// multiply translations first
CGAffineTransform transform = CGAffineTransformConcat(zoomTranslate, offsetTranslate);
transform = CGAffineTransformConcat(scaleTransform, transform);
layoutAttributes.transform = transform;
}
}
}
To expand a cell using this layout code, set both the selectedCellFrame and selectedIndexPath to the cell you want expanded and call performBatchUpdates:completion: on the collection view. To collapse set selectedCellFrame = CGRectNull and selectedIndexPath = nil and call performBatchUpdates:completion:
I have a UIScrollView which scrolls horizontally from left to right.
I want to be able to vertically position my UIScrollView on the Y axis according to the screen size of the iPhone. E.g iPhone 4 and iPhone 5.
CGFloat startX = (70.0f * ((float)[_attachments count] - 1.0f) + padding);
CGFloat startY = 295;
CGFloat width = 64;
CGFloat height = 64;
Right now I start my Y position at 295 which works okay with the iPhone 4 but not on iPhone 5.
How would I change the startY to accommodate the same position for different screen size?
CGRect screenBounds = [[UIScreen mainScreen] bounds];
if (screenBounds.size.height == 568)
{
//iphone5
}
else
{
//iphone4
}
You can set your y value accordingly
Do use layoutSubviews in your view ...
- (void)layoutSubviews {
CGRect frame = self.bounds;
frame.origin.y += frame.size.height - _scrollView.frame.size.height;
if ( ! CGRectEqualToRect( _scrollView.frame, frame ) {
_scrollView.frame = frame;
}
}
... I assume that your horizontal scroll view is in _scrollView ivar and also that the _scrollView.frame.size.height contains correct height of your _scrollView. If not, replace _scrollView.frame.size.height with some constant which does contain your UIScrollView height.
Why the condition with CGRectEqualToRect? Because layoutSubviews can be called very often and when you set new frame (even if it equals) to it, UIScrollView can stop scrolling.
you can do it like this-
first get the width and the height of the screen
CGRect screenBounds = [UIScreen mainScreen].bounds ;
CGFloat width = CGRectGetWidth(screenBounds);
CGFloat height = CGRectGetHeight(screenBounds) ;
now create a method which will do some calculations and return an int
-(NSInteger) getwidth:-(NSInteger *) value
{
int temp = (value * 100) / 480;
return int((height * temp) / 100);
}
-(NSInteger) getheight:-(NSInteger *) value
{
var temp = (value * 100) / 320;
return int((width * temp) / 100);
}
now set the bounds of view according to these function
actind.frame=CGRectMake(getWidth(20),getheight(20),16,16);
this will work on all the devices and the views will arrange themselves according to the width and height of the screen.
hope this will help for you. :)
I have a UIScrollView. I want to implement an infinite left side-scrolling effect. How can this be possible?
- (void) scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.frame.size.width/7;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
Lbl_Rate.text=[NSString stringWithFormat:#" pagenumber==>%d",page ];
int p=page%10;
if(p==0){
page=1;
SCrl_Wheel.contentOffset = CGPointMake(0, 0);
}
}
I had trid to set contentoffset by SCrl_Wheel.contentOffset = CGPointMake(0, 0);
But it is not actually setting contentoffset.x=0;
And this is making :-[SCrl_Wheel setContentOffset:CGPointMake(0,0) animated:YES]; my loop as infinite loop.
Add this setContentSize: to your code.
[SCrl_Wheel setContentSize:CGSizeMake(-100000,50)];
Now it would scroll like the way you want to and why hav u added your y = 0 ?? then the height of the UIScrollView would be 0....
EDIT:
[SCrl_Wheel setContentOffset:CGPointMake(1000, 0.0)];
Add this code ull b getting both sides scrolling ur way :) I tested it ...
To add Left Side scrolling effect, you can create a scrollview with very large width and then set its initial contentOffset to midpoint of contentwidth of scrollview :
scrollView.contentSize = CGSizeMake(100000, 60);
scrollView.contentOffset = CGPointMake(scrollView.contentSize.width/2, 0);
This is the code I used for scrolling left:
HorizontalScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0,50,yourView.frame.size.width,tbv.frame.size.height)];
[HorizontalScroll setContentSize:CGSizeMake(930,340)];
[HorizontalScroll setBackgroundColor:[UIColor redColor]]; // color is set just to know where the scroll view is , remove this line afterwards.
[self.view addSubview:HorizontalScroll];
[HorizontalScroll addSubview:yourView];
I want to reset a UIView's frame size so I wrote:
view.frame.size.width = x;
but it doesn't work, can anyone tell me why?
you can't set width directly, do this
CGRect frm = view.frame;
frm.size.width = x;
view.frame = frm;
When you call view.frame you get a copy of the frame rect property so setting it is with frame.size.width changes the width of the copy and not the view's frame size
Here's one pass at why. Your code translates into
[view frame] // <- This hands you a CGRect struct, which is not an object.
// At this point, view is out of the game.
.size // <- On the struct you were handed
.width // "
= x; // <- the struct you were handed changed, but view was untouched
One other way of thinking of it is that there is an invisible variable there, that you can't access:
CGRect _ = [view frame]; // hands you a struct
_.size.width = x;
-(void)changeWidth:(UIView*)view wid:(int)newWid{
CGRect rc=view.frame;
view.frame=CGRectMake(rc.origin.x, rc.origin.y, newWid, rc.size.height);
}
- (void) adjustViewtForNewOrientation: (UIInterfaceOrientation) orientation {
//if (UIInterfaceOrientationIsLandscape(orientation)) {
loadMoreView.frame=CGRectMake(0, 0, WIDTH, 50);
headerView.frame=CGRectMake(0, [MLTool getPaddingHeight:self], WIDTH, HEI_SEGMENT);
[self changeWidth:webview wid:WIDTH];
[self changeWidth:tableView wid:WIDTH];
[self changeWidth: segmentedControl wid:WIDTH-SEG_LEFT*2];
}
I have a UIView inside a UIScrollView. Whenever the UIScrollView zoom changes, I want to redraw the entire UIView at the new zoom level.
In iOS < 3.2, I was doing this by resizing the UIView within the UIScrollView to make it the new size, and then setting the transform back to Identity, so that it wouldn't try to resize it further. However, with iOS >= 3.2, changing the identity also changes the UIScrollView's zoomScale property.
The result is that whenever I zoom (say 2x), I adjust the embedded UIView to be the appropriate size, and redraw it. However now (since I reset the transform to Identity), the UIScrollView thinks its once again at zoomScale 1, rather than zoomScale 2. So if I have my maxZoomScale set at 2, it will still try zooming further, which is wrong.
I thought about using the CATiledLayer, but I don't think this is sufficient for me, since I want to redraw after every zoom, not just at certain zoom thresholds like it tries to do.
Does anyone know how to do the proper redrawing of the UIView on a zoom?
Tom,
Your question is a bit old, but I came up with a solution for this, so I figured I'd put in an answer in case it helps you or anyone else. The basic trick is to reset the scroll view's zoomScale to 1, and then adjust the minimumZoomScale and maximumZoomScale so that the user can still zoom in and out as expected.
In my implementation, I've subclassed UIScrollView and set it to be its own delegate. In my subclass, I implement the two delegate methods you need for zooming (shown below). contentView is a property I added to my UIScrollView subclass that in order to give it the view that actually displays the content.
So, my init method looks something like this (kMinimumZoomScale and kMaximumZoomScale are #define's at the top of the class):
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.autoresizesSubviews = YES;
self.showsVerticalScrollIndicator = YES;
self.showsHorizontalScrollIndicator = NO;
self.bouncesZoom = YES;
self.alwaysBounceVertical = YES;
self.delegate = self;
self.minimumZoomScale = kMinimumZoomScale;
self.maximumZoomScale = kMaximumZoomScale;
}
return self;
}
Then I implement the standard UIScrollView delegate methods for zooming. My ContentView class has a property called zoomScale that tells it what scale to use for displaying its content. It uses that in its drawRect method to resize the content as appropriate.
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)aScrollView {
return contentView;
}
- (void)scrollViewDidEndZooming:(UIScrollView *)aScrollView withView:(UIView *)view atScale:(float)scale {
CGFloat oldZoomScale = contentView.zoomScale;
CGSize size = self.bounds.size;
// Figure out where the scroll view was centered so that we can
// fix up its offset after adjusting the scaling
CGPoint contentCenter = self.contentOffset;
contentCenter.x += size.width / (oldZoomScale * scale) / 2;
contentCenter.y += size.height / (oldZoomScale * scale) / 2;
CGFloat newZoomScale = scale * oldZoomScale;
newZoomScale = MAX(newZoomScale, kMinimumZoomscale);
newZoomScale = MIN(newZoomScale, kMaximumZoomscale);
// Set the scroll view's zoom scale back to 1 and adjust its minimum and maximum
// to allow the expected amount of zooming.
self.zoomScale = 1.0;
self.minimumZoomScale = kMinimumZoomScale / newZoomScale;
self.maximumZoomScale = kMaximumZoomScale / newZoomScale;
// Tell the contentView about its new scale. My contentView.zoomScale setter method
// calls setNeedsDisplay, but you could also call it here
contentView.zoomScale = newZoomScale;
// My ContentView class overrides sizeThatFits to give its expected size with
// zoomScale taken into account
CGRect newContentSize = [contentView sizeThatFits];
// update the content view's frame and the scroll view's contentSize with the new size
contentView.frame = CGRectMake(0, 0, newContentSize.width, newContentSize.height);
self.contentSize = newContentSize;
// Figure out the new contentOffset so that the contentView doesn't appear to move
CGPoint newContentOffset = CGPointMake(contentCenter.x - size.width / newZoomScale / 2,
contentCenter.y - size.height / newZoomScale / 2);
newContentOffset.x = MIN(newContentOffset.x, newContentSize.width - size.width);
newContentOffset.x = MAX(0, newContentOffset.x);
newContentOffset.y = MIN(newContentOffset.y, newContentSize.height - .size.height);
newContentOffset.y = MAX(0, newContentOffset.y);
[self setContentOffset:newContentOffset animated:NO];
}
In my case, I have an image view and on top of the image view I have several other imageviews. This is the implementation I came up with and works fine:
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(double)scale{
NSLog(#"finished zooming");
NSLog(#"position x :%f",pin1.frame.origin.x);
NSLog(#"position y :%f",pin1.frame.origin.y);
CGRect frame = pin1.frame;
frame.origin.x = pin1.frame.origin.x * scale;
frame.origin.y = pin1.frame.origin.y * scale;
pin1.frame = frame;
NSLog(#"position x new :%f",pin1.frame.origin.x);
NSLog(#"position y new:%f",pin1.frame.origin.y);
}