I'm relatively new to objective-c...I'm using the iphone 3.0 SDK
I have a UIView, which is a subview, that I want to resize under certain circumstances.
The way I do it is within a controller class.
for example,
CGSize el = CGSizeMake(30, 40);
[self.subview setSize:el];
the above code does work, but the compiler gives a warning: 'UIView' may not respond to 'setSize:'
At some level, "if it ain't broke, I don't want to fix it", but I'm a little worried that I'm doing something wrong.
Any ideas as to why I'm getting the warning and how I can fix it?
TIA
That probably means that setSize for UIView is implmented but it is not shown in the header file that is imported into the project. That makes it an undocumented API, ie one day it could change and break your code :)
And sure enough if you go to the documentation of UIView you will find no refrence to the size property. So I would avoid it.
What you should use instead is the frame property
CGSize el = CGSizeMake(30, 40);
CGRect bounds = CGself.subview.bounds;
bounds.size = el;
CGself.subview.bounds = bounds;
Give that a shot.
The right thing to do here is use something else instead of the non-public size property. But for the sake of discussion: If you wanted to get rid of the warning, you can declare that you know about the size property somewhere at the top of your implementation file:
#import "MyClass.h"
#interface UIView (private)
- (void) setSize: (CGSize) newSize;
#end
#implementation MyClass
…
#end
The compiler will stop complaining.
Here is a closer explanation using the "frame" property for "myView":
[self.myView.frame = CGRectMake(x, y, width, height)];
Where:
x, coordinate FOR the top left corner of your view having as
reference the top left corner of its parents "x" coordinate.
y, same
as x but y axis
width, horizontal size of the frame
height, vertical size of the frame
i.E. You have a view which fits to the screen bounds, so its coordinate (0,0) will be the same as your device screen top left corner.
if you want to add a subview barely smaller that the screen size and center it horizontally and vertically, here is the set up:
[self.myView.frame = CGRMake ( 20 , 20 , self.view.frame.size.width-40, self.view.frame.size.height-40);
This example sets the frame inside the view and centered. Note that we subtract 40 to the width corresponding to: 20 left side, 20 right side, and so the same for vertical adjustments.
This code will also work in portrait and landscape mode.
Related
I have two questions regarding the placement and handling UILabel's and UIImageView's:
a. I'd like to know if its possible to detect the location of the "edge" of the text in a UILabel, to put a UIImageView a certain distance to the left or right of the UILabel (with the same height), for example: (UILabel -certain distance in width- UIImageView). (Preferably a method that could be used to detect the edges of a UIImageView as well)
b. I'd also like to know how the creation of UILabel's and UIImageView's programmatically works, I understand that to create for example a UIImageView it's as simple as:
UIImageView *myImgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
but I'd like to know: If I have a method to create a UIImageView programmatically, and it sets the CGRect based on a factor that changes, would I need a new name for every UIImageView I create in code, or could I use the same name and add them every time the method runs by calling addSubView? If I do need to individually name them all, and I'm unsure how many I will need to create (because it depends on the user), what is the best method to go about this? If I can create them all with the same name, will they all respond to the same name? Example, if I set myImgView to hidden, will this effect all the UIImageView's named myImgView?
You seem to be new to object oriented programming (as evidenced by the last part), and I would highly recommend you read this: Object-Oriented Programming with Objective-C
But I will answer your question about the UIViews. UIImageView and UILabel are both subclasses of the UIView class. UIView has a special kind of variable called a property. That means that the variable can be set on an instance of that class by another class and can then be accessed from other classes as well. UIView has the frame property that contains the outline of the view in a CGRect. When you have a bit of code like
ImageView *myImgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
You are creating an instance of the UIImageView class, allocating memory for it, and initializing it with a frame (in this case you are using rect with zero height and width, which is a bit useless). In order to set the frame to something useful, you use the format CGRectMake(originX, originY, width, height). Keep in mind that on iOS, the origin is on the upper left hand corner of the view, and a higher y value will be farther down the screen. If you need to change the frame later, rather than creating a whole new instance, you can simply alter the property on the existing instance:
myImgView.frame = CGRectMake(10, 20, 50, 100);
//this will make the frame of myImgView to a rectangel 10 units off of the right side of the view, 20 units down from the top of the view, and have a width of 50 units and a height of 100 units
However, you have declared myImgView as a local variable, meaning that you can only access it in the function that you have declared it in, at the time that you have declared it. In order to access it anywhere and anytime, you will need to declare it as an Instance Variable, meaning that you declare it in the .h file.
#interface MyView : UIView {
UIImageView *myImgView;
}
#end
Then the variable will be accessible anywhere in that class. Altering its frame as I demonstrated above will alter the frame in the view.
Finally, you can get the frame of an view if you need to do calculations with it. For example, you can get the frame of myImgView like so:
CGRect viewFrame = myImgView.frame;
CGPoint origin = myImgView.frame.origin;
CGSize width = myImgView.frame.size;
float x = myImgView.frame.origin.x;
float height = myImgView.frame.size.height;
//I think you can get it from here
Now with UILabels, it can be a bit trickier. The frame for a UILabel does not necessarily match the frame that the text takes up. Usually the actual frame is a bit smaller. In order to get the frame for the text of a UILabel, use something like this:
//UILabel *myLabel has been declared either in the .h or earlier in the function
CGRect textFrame = CGRectMake(myLabel.x, myLabel.y, [myLabel.text sizeWithFont:myLabel.font].width, [myLabel.text sizeWithFont:myLabel.font].height);
Now, finally, to put it all together, this would be how to calculate a new frame for your UIImageView:
myImgView.frame = CGRectMake(myLabel.x + [myLabel.text sizeWithFont:myLabel.font].width, yOrigin, width , height);
//where myImgView is your UIImageView myLabel is your UILabel, yOrigin is the y value of the origin of the imageView, and width and height are the width and height of the image.
Well I hope that helps and I highly suggest you read the object oriented programming guide.
I have a bunch of questions:
How do I position a UIView so that it is on the bottom of the view I am adding it to?
How can I add a subview to a view so that it is positioned in the corner of the superview with a small gap (Like if I want a 'x' cross sign for closing something)
Is their a utility class for easy UIView positioning (and rotation)?
Any references, open source tutorials etc. will be more then welcome!
(a) How do I position a UIView so that it is on the bottom of the view I am adding it to?
OK, let's say you want to position button as a subview at the bottom of view form, you calculate the origin.y of the subview button by subtracting button's height from the height of the form
CGRect buttonFrame = button.frame;
buttonFrame.origin.y = form.bounds.size.height - buttonFrame.size.height;
button.frame = buttonFrame;
[form addSubview:button];
You can change origin horizontal position as well. You want it on the bottom left of form?
buttonFrame.origin.x = 0;
Or on the right edge of form?
buttonFrame.origin.x = form.bounds.size.width - buttonFrame.size.width;
Or in the middle (horizontally) of form?
buttonFrame.origin.x = (form.bounds.size.width - buttonFrame.size.width) / 2;
or another way using CGRectGetMidX (found in CGGeometry utility methods):
buttonFrame.origin.x = CGRectGetMidX(form.bounds) - buttonFrame.size.width/2;
Autoresizing handles adjusting the frame when the parent view's size changes. But you still have to position it first.
int xOffset = 20;
int yOffset = 20;
CGRect BottomRight_NewFrame = CGRectMake((superview.frame.size.width - subview.frame.size.width-xOffset), (superview.frame.size.height - subview.frame.size.height-yOffset), subview.frame.size.width, subview.frame.size.height);
subview.frame = BottomFrame;
You can use the new Autolayout feature of iOS 6 or the old Struts & Springs in the Interface Builder to achieve this.
This tutorial explains both:
http://msmvps.com/blogs/kevinmcneish/archive/2012/12/10/tutorial-ios-6-auto-layout-versus-springs-and-struts.aspx
Or you can set the autoresizing mask programatically. It is explained pretty well here:
UIView autoresizingMask - Interface Builder to Code - Programmatically create struts and springs - Swift or Objective-C
It's easy enough to just set the frame, e.g. (untested code )
subview.frame = CGRectMake((superview.frame.origin.x - subview.frame.origin.size.width/2)-20, (superview.view.frame.origin.y - subview.frame.origin.size.height/2)-20, subview.view.frame.size.width, subview.view.frame.size.height);
if you'll be doing a lot of this then create a utility class or method.
Autolayout will help you position the views and maintain those positions if the size of the superview changes. If the superview isn't going to change, you don't really need to mess with constraints -- you can just set the position of the subview appropra
If you're adding view viewB to view viewA:
a) To position viewB so that it's bottom edge corresponds to the bottom edge of viewA:
viewB.frame.origin.y = viewA.bounds.size.height - viewB.bounds.size.height;
b) You don't say which corner, but it's just a matter of doing the math. For example, the upper right corner of viewA is at {viewA.bounds.size.x, 0} in viewA's coordinate system. If you want to put viewB there, set it's origin to:
{viewA.bounds.size.x-viewB.bounds.size.x, 0}
If you want to add a margin, you can add that to the computation:
int margin = 10;
{viewA.bounds.size.x-viewB.bounds.size.x-margin, margin}
d) Use NSLayoutConstraint to access the autolayout system's constraints programmatically. There's a nice visual format language, so that for your question (a) you could set the constraint for viewA to:
V:|-[viewB]-0-|
The V means that you're working in the vertical direction, |'s represent the edges (top and bottom, thanks to the V) of the superview (that's viewA), and the 0 means that the distance between viewB and the bottom of its superview should be 0.
You can setup constraints in iOS6 but if you want to work on older os's you need to position them manually. Math.
I'm having some trouble catering for the new iPhone 5 screen height, I need to resize my table view already to show an advert.
Up till iOS6 I didn't have a problem, I used the following function, but it doesn't use scale. To be honest I'm surprised it works.
+ (CGRect)setTableBoundsByHeight:(int)lHeight:(UITableView*)tbl {
CGRect tableFrame = tbl.frame;
return CGRectMake(tableFrame.origin.x,
tableFrame.origin.y,
tableFrame.size.width,
lHeight);
}
Here's the code, where I have hard coded the height of my table view at 367, that's minus the height of a navigation controller and a tab bar. 50 is the height of the advert.
if (!productPurchased) {
#ifdef VER_FREE
[[LARSAdController sharedManager]
addAdContainerToView:self.view withParentViewController:self];
[[LARSAdController sharedManager]
setGoogleAdPublisherId:#"number"];
[reportTableView setFrame:[General
setTableBoundsByHeight:(367-50):reportTableView]];
#endif
} else {
[reportTableView setFrame:[General
setTableBoundsByHeight:367:reportTableView]];
}
I've found some code which scales but I'm not sure how to implement this.
CGFloat scale = [UIScreen mainScreen].scale;
result = CGSizeMake(result.width * scale, result.height * scale);
If this code is inside your view controller, just use self.view.bounds.height rather than 367.
By the way: You should really rename
+ (CGRect)setTableBoundsByHeight:(int)lHeight:(UITableView*)tbl
to something like
+ (CGRect)setTableBoundsByHeight:(int)lHeight tableView:(UITableView *)tbl
Ignore the scale, it scales automatically. Just check if iPhone 5 and set different height, but you use iphone5 pixel count/2 as it'll scale it to 2x itself.
Using hard-coded values (a.k.a. "magic numbers") is a wrong habit, you see why now. Always prefer using constants or runtime-computed values. Besides, it makes code easier to read because by using constants you will know what the numbers correspond to, instead of being "magic numbers" coming from nowhere.
So for your problem, compute the height value at runtime using this kind of code below.
// simply use the height of the current viewController's `view`
// which is probably the view of the `navigationController`'s `topViewController`
// and is already at the correct size, namely 367 in iPhone 3.5" and 455 in iPhone 4".
CGFloat screenHeight = self.view.height;
if (!productPurchased)
{
static CGFloat advertHeight = 50;
#ifdef VER_FREE
[[LARSAdController sharedManager]
addAdContainerToView:self.view withParentViewController:self];
[[LARSAdController sharedManager]
setGoogleAdPublisherId:#"number"];
[reportTableView setFrame:[General
setTableBoundsByHeight:(screenHeight-advertHeight):reportTableView]];
#endif
} else {
[reportTableView setFrame:[General
setTableBoundsByHeight:screenHeight:reportTableView]];
}
Note that you don't need to do any substraction yourself, as the UIViewControllers resize their view according to the space available, so that if you have for example a UITabBarController that contains a UINavigationController that itself shows a UIViewController on top of its stack, the height of this last viewController's view will be the height of the screen minus the tabBar, statusBar and navBar heights.
So instead of fetching the [UIScreen mainScreen].applicationFrame for example, and subtract the tabBar (if any) and navBar heights to have your value of 367pt, simply use the height of the viewController's view directly and you should have the right value directly.
Additional note: you should give a prefix to your second argument, thus name your method setTableBoundsByHeight:tableView: instead of setTableBoundsByHeight:: where the second argument does not have any prefix. (See #MrMage answer that suggest this too).
A better naming for your method would even be setHeight:forTableView: for example, to beter fit the Apple naming conventions.
Consider the screen shot above. The UIView is positioned too much to the top causing the upper test string to be partially off and there is a gap shown at the bottom screen. The screen shot also shows its corresponding positioning values in IB : x = 0 and y = 20. So there is actually an offset (y = 20) default to clear the top status bar. But still the status bar shown covers part of the UIView.
The x, y entry is greyed out, so it seems not possible to change its values. Have spent quite some time but still not able to solve this seemingly easy problem. Hope that somebody who is familiar with this could give some hints on how this should be done.
Update :
Have done it in code as suggested. It seems to work ok. If you find any errors please let me know ...
- (void)viewDidLoad
{
CGRect position_and_size;
CGPoint cg_point;
CGSize cg_size;
[super viewDidLoad];
// setup after loading the view from its nib.
NSLog(#" --- Screen loaded --- ");
cg_size.width = 320;
cg_size.height = 460;
cg_point.x = 0;
cg_point.y = 20;
position_and_size.origin = cg_point;
position_and_size.size = cg_size;
[[self view] setFrame : position_and_size];
}
.
The following is the latest screen shot :
The problem is that you're placing a view that's 460 pixels high at {0,0} in a window that's 480 pixels high. The first 20 pixels of your view therefore ends up under the status bar, and the view fails to cover the bottom 20 pixels of the window. You can fix it in any of the following ways:
Change the autoresize options so that the view's size will be automatically adjusted to fill the window.
Resize the view to match the window's bound, either in code or in IB.
Position the view at {0,20}, either in code or in IB.
You can fix it programmatically:
[yourSubView setFrame:self.view.bounds];
Assuming self.view - parent view, where it should fit.
I'm new to iOS development and I'm not sure if what I want to do is possible.
Basically I'm retrieving some products via a webservice and I want to display each one across the screen, 4 squares per row. Each one of these squares (with image and name of product) is a UIView which has a .h,.m and xib file. I create each view like this:
CategoryItemView *catItem = [[[NSBundle mainBundle] loadNibNamed:#"CategoryItemView" owner:self options:nil] objectAtIndex:0];
[categoryItems addObject:catItem];
and I position it like:
while (colCount < cols && items < [categoryItems count]) {
CGRect viewRect = CGRectMake( x * colCount + pad , (row * (x-pad)) + pad*row + pad, x - pad, x - pad );
CategoryItemView* myView = [categoryItems objectAtIndex:items];
myView.frame = viewRect;
[self.view addSubview:myView];
colCount++;
items++;
}
I want to use a xib so I can layout all the elements. However I cannot find a way to lay out the xib so that when positioning the UIView like this all the elements are scaled to fit and keep their relative positions in the UIView. Is this possible?
Update: You can view a 2 class example here thats not working http://home.glasscubes.com/share/s/d57sb19
thanks
autoresizingMask is the property you are looking for on those views.
From the docs:
autoresizingMask
An integer bit mask that determines how the receiver resizes itself
when its superview’s bounds change.
#property(nonatomic) UIViewAutoresizing autoresizingMask
Discussion
When a view’s bounds change, that view automatically resizes its
subviews according to each subview’s autoresizing mask. You specify
the value of this mask by combining the constants described in
UIViewAutoresizing using the C bitwise OR operator. Combining these
constants lets you specify which dimensions of the view should grow or
shrink relative to the superview. The default value of this property
is UIViewAutoresizingNone, which indicates that the view should not be
resized at all.
When more than one option along the same axis is set, the default
behavior is to distribute the size difference proportionally among the
flexible portions. The larger the flexible portion, relative to the
other flexible portions, the more it is likely to grow. For example,
suppose this property includes the UIViewAutoresizingFlexibleWidth and
UIViewAutoresizingFlexibleRightMargin constants but does not include
the UIViewAutoresizingFlexibleLeftMargin constant, thus indicating
that the width of the view’s left margin is fixed but that the view’s
width and right margin may change. Thus, the view appears anchored to
the left side of its superview while both the view width and the gap
to the right of the view increase.
If the autoresizing behaviors do not offer the precise layout that you
need for your views, you can use a custom container view and override
its layoutSubviews method to position your subviews more precisely.