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.
Related
The row height beyond the last row in TableView (the empty placeholder rows that are just there for visual detail if the number of items is less than the number of rows a TableView can display at once) is always the same as the last row height.
Is it possible to change this without resorting to adding a dummy last row?
The private UITableView instance method _spacingForExtraSeparators returns the height of the placeholder rows. If you're not writing an app for the App Store, just override that. It returns a CGFloat.
Here's an different approach that is App Store-compliant (as far as I know), and might be easier than creating a dummy row.
UITableView sends itself the layoutSubviews message a lot. It does this whenever it adds, removes, or rearranges cells, and whenever it scrolls (and probably other times too). So let's override layoutSubviews to draw a view that starts at the bottom edge of your table view's last section. The view will be filled with a repeating pattern that looks like placeholder cells, with a height you define.
Create a subclass of UITableView. Give your subclass a UIView *_bottomView instance variable. We need to override layoutSubviews, so we'll initialize _bottomView lazily in that method.
#implementation MyTableView
{
UIView *_bottomView;
}
Override layoutSubviews. The first thing you do in your layoutSubviews is call [super layoutSubviews] so you'll continue to act like a proper UITableView.
- (void)layoutSubviews
{
[super layoutSubviews];
The next thing you do is lazily initialize _bottomView.
if (!_bottomView) {
You need a pattern to fill _bottomView, and it needs to look like placeholder cells. Make an image context that's 1 pixel wide, and as tall as you want a placeholder cell to be. Fill the context with white, and paint a separator line (pixel, really) at the bottom. Get the image from the context.
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, placeholderHeight), YES, 0);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectMake(0, 0, 1, placeholderHeight));
[self.separatorColor setFill];
UIRectFill(CGRectMake(0, placeholderHeight - 1, 1, 1));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Next, create _bottomView, set its background "color" to that image as a repeating pattern, and add it to yourself as a subview.
_bottomView = [[UIView alloc] initWithFrame:CGRectZero];
_bottomView.opaque = YES;
_bottomView.backgroundColor = [UIColor colorWithPatternImage:image];
[self addSubview:_bottomView];
} // end of if-block
Finally, find the rect of your last section. Use that rect to compute a frame for _bottomView that starts at the bottom of the last section, is plenty tall (twice your own height is more than enough), and is as wide as the last section's rect.
int lastSectionIndex = [self.dataSource numberOfSectionsInTableView:self] - 1;
if (lastSectionIndex < 0)
lastSectionIndex = 0;
CGRect lastSectionRect = [self rectForSection:lastSectionIndex];
CGRect bottomViewFrame = CGRectMake(lastSectionRect.origin.x, CGRectGetMaxY(lastSectionRect), lastSectionRect.size.width, self.bounds.size.height * 2);
_bottomView.frame = bottomViewFrame;
Finally, make sure _bottomView is your frontmost subview so it will overdraw UITableView's placeholder cells.
[self bringSubviewToFront:_bottomView];
} // end of layoutSubviews
The end.
#end
I don't think there is a public API to do so. It is reasonable to use the last row height as the placeholder rows height.
If you insist on changing the height of placeholder rows, as I know, add an empty dummy cell at the end of your cells is the only way to do so.
Just as you've done.
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.
I am adding a UILabel instance as a subview of my custom UITableViewCell instance's contentView.
When I select the cell, the row is highlighted blue, except for the background of the label. The label text is sharp.
When I set the label and content view backgroundColor property to [UIColor clearColor], the label text becomes blurry.
How do I set the label background color to be clear, to allow the row highlight to come through, while still keeping the label text sharp?
One suggestion I read elsewhere was to round the label's frame values, but this did not have any effect.
CODE
Here is a snippet of my custom UITableViewCell subview's -setNeedsLayout method:
UILabel *_objectTitleLabel = [[UILabel alloc] initWithFrame:CGRectNull];
_objectTitleLabel.text = [self.awsObject cleanedKey];
_objectTitleLabel.font = [UIAppDelegate defaultObjectLabelFont];
_objectTitleLabel.highlightedTextColor = [UIColor clearColor]; //[UIAppDelegate defaultLabelShadowTint];
_objectTitleLabel.backgroundColor = [UIColor clearColor]; //[UIAppDelegate defaultWidgetBackgroundTint];
_objectTitleLabel.frame = CGRectMake(
kCellImageViewWidth + 2.0 * self.indentationWidth,
0.5 * (self.tableView.rowHeight - 1.5 * kCellLabelHeight) + kCellTitleYPositionNudge,
contentViewWidth,
kCellLabelHeight
);
_objectTitleLabel.frame = CGRectIntegral(_objectTitleLabel.frame);
_objectTitleLabel.tag = kObjectTableViewCellTitleSubviewType;
//NSLog(#"_objectTitleLabel: %#", NSStringFromCGRect(_objectTitleLabel.frame));
[self.contentView addSubview:_objectTitleLabel];
[_objectTitleLabel release], _objectTitleLabel = nil;
...
self.contentView.backgroundColor = [UIAppDelegate defaultWidgetBackgroundTint];
self.contentView.clearsContextBeforeDrawing = YES;
self.contentView.autoresizesSubviews = YES;
self.contentView.clipsToBounds = YES;
self.contentView.contentMode = UIViewContentModeRedraw;
The issue is sub-pixel rendering, which occurs when your origin (which is a float value) has a non-zero fractional component. Round to the nearest whole number and you should be fine.
In my case, having set shouldRasterize = YES on the CGLayer of the view containing the UILabel was the culprit. Removing that line made the text nice and crisp.
Ok found the problem, Make sure your parent view's coordinates are rounded as well.
I ran into this problem myself today, and read somewhere that non-integer values for the origin and size of the UILabel's frame can cause this (I know they're floats, but you know what I mean). There has got to be a more elegant solution, but this quick hack appears to have solved the problem for me:
self.valueLabel.frame = CGRectMake((int) frame.origin.x, (int) frame.origin.y, (int) frame.size.width, (int) frame.size.height);
If you find a better solution, please let me know, I'd love to replace this hack with something a bit more tasteful.
Another cause of garbled/blurry text is cell reuse. If you are de-queuing a reusable cell then it may redraw with different dimensions somewhere else and again be re-used when it gets to your cell with the garbled text.
To ensure the cells are unique be sure to allocate a new cell for the indicies where the text is garbled, and mark that UITableViewCell instance with a different reuse identifier. This is only practical of course if you're dealing with a very small number of cells and if you know exactly which cells are causing problems.
Setting shouldRasterize to YES may introduce blurriness. Set the rasterization scale and that should eliminate the blurriness. [self.layer setRasterizationScale:[[UIScreen mainScreen] scale]];
Sometimes the reason for the blurriness you have mentioned can be that labels's frame is beyond the cell frame.
Even if you see all of your text you have put inside the label on your cell, the actual label size can be bigger than the cell frame.
To check if that is the reason for the effect you see I would suggest to check/print all the data you have about labels size/location after it is instantiated and than check in the delegate method tableView:heightForRowAtIndexPath: that this fit into the cell height you are returning for the cell.
Hope it will help in your case.
Use round(); C functions are provided for a reason.
#define roundCGRectValues (frame) \
frame = CGRectMake(round(frame.origin.x),round(frame.origin.y),round(frame.size.width),round(frame.size.height));
All you need.
Does -setNeedsLayout get called even for dequeued reusable cells? If so, the cell will already have the label added to the content view, and you will draw it twice, making it blurry. You can inefficiently solve this by removing all of the content view's subviews before you add your subview:
for (UIView *subview in [[self contentView] subviews]) {
[subview removeFromSuperview];
}
A better solution would be to provide properties on your cell subclass to let you modify the content of a reused cell as-needed, rather than rebuilding its view hierarchy from scratch.
I have a (float) rating value as a percentage from 0..100 (where 50 = Just OK, 0 = terrible and 100=best).
What's a simple way to display this as a 5 star rating on the iphone, with the following requirements:
simple (ideally just using drawing operations based on a png of a single star or five of them, and without needing to resort to photoshop.)
reasonably fast (this is part of a cell in a table view)
includes half stars (or more fine grained)
displays something reasonable for a rating of 0 (or close to 0)
(This is display only so I don't need it to respond to touch events, though that would be nice - currently I'm just using a slider to capture the rating in the first place)
I've come up with a simple way to do this without using any PNGs at all. I've subclassed a UIView and then in the drawRect I've done this:
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
[textColor set];
NSString* stars=#"★★★★★";
rect=self.bounds;
UIFont *font = [UIFont boldSystemFontOfSize: 16];
CGSize starSize = [stars sizeWithFont: font];
rect.size=starSize;
[#"☆☆☆☆☆" drawInRect:rect withFont: font];
CGRect clip=rect;
clip.size.width=clip.size.width*rating/100;
CGContextClipToRect(context,clip);
[stars drawInRect:rect withFont:font];
}
(those strings are 5 empty and 5 full stars, in case they are only displaying on my mac. I just entered them within IB using the 'special characters' menu)
I've added a textColor (UIColor*) and rating (int) property, so that is where those are coming from.
To add the view within IB, I just change the class type to the name of my UIView subclass. Then I can add the view to any container within IB.
I would consider having two UIImageViews layered directly on top of each other. The bottom image view would contain a PNG of five 'empty' stars - the top layer a PNG of five 'full' stars. I would resize the width of the top layer depending on the rating to expose the empty starts underneath.
You can determine a suitable width of the top layer with something like the following:
newStarLayerWidth = fullStarLayerWidth * (percentage / 100)
You would need to ensure that the image in the top layer is not resized and is aligned left by setting the views contentMode to UIViewContentModeLeft, as well as set clipsToBounds = YES, otherwise your image will not clip based on the frame size.
If this is not fast enough I would take the same approach but draw the cell directly as described here - however you may find that this is not necessary.
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.