creating a nice custom UITableViewCell - iphone

I am trying to create a table view which has a layout like what yobongo has:
and what I have now is really crappy, where the UIImage has different size, etc, etc...
How do I fix it to have something nice like that? I tried rearranging via IB but then mine looks like this:
I wanted to create a UIImage in the cell that has a fixed size (mine resizes here and there). How do I set that? I also want a rounded edge around the UIIMage... I have played around with the spring and struts via IB and I think I might have messed up something that I can't fix again..
I also want so that there exists a gap between rows and a border like in the picture below
I also wanted to implement a chat box like below where it expands if the text is more than it's limit. How can I do this?

Fixed image size
You have to set UIImageView frame for all image views. And then you have to play with UIImageView's contentMode property - where you can scale image to fit frame, fill frame, keep aspect ratio, etc. And you also have to set clipsToBounds to YES to clip "overlapping" image parts.
Round Corners
You can use CALayer for this, which is also available in UIImageView. It's matter of four lines ...
imageView.layer.cornerRadius = 3.0;
imageView.layer.masksToBounds = YES;
imageView.layer.borderColor = [UIColor blackColor].CGColor;
imageView.layer.borderWidth = 1.0;
Example:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier];
if ( self ) {
...
self.imageView.layer.cornerRadius = 3.0;
self.imageView.layer.masksToBounds = YES;
self.imageView.layer.borderColor = [UIColor blackColor].CGColor;
self.imageView.layer.borderWidth = 1.0;
...
}
return self;
}
Expandable Text Input
You have to prepare good background image for this. And then you can create stretchable image via UIImage class method: – stretchableImageWithLeftCapWidth:topCapHeight:
Each row will be subclassed UITableViewCell where you can handle all these things. Stretchable background, etc. Resizing via UITextView's delegate (textViewDidChange:), etc.
Google for some examples or search SO.
Gaps
UITableViewDelegate has method ...
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
... where you can specify row height. To create gap, add this to your custom cell ...
Header:
UIImageView *__backgroundImageView;
Initializer:
__backgroundImageView = [[UIImageView alloc] initWithImage:...stretchableImage...];
[self.contentView addSubview:__backgroundImageView];
[self.contentView sendSubviewToBack:__backgroundImageView];
Layouting:
- (void)layoutSubviews {
[super layoutSubviews];
// This draws background image over the whole cell and adds 5px gap top/bottom
CGRect rect = self.contentView.bounds;
rect.origin.y += 5; // Draw background image 5 pixels below cell top
rect.size.height -= 2 * 5; // Remove top/bottom gap from background image height
__backgroundImageView.frame = rect;
...
}
Memory Management:
- (void)dealloc {
[super dealloc];
[__backgroundImageView release]; __backgroundImage = nil;
...
}

If you want all images (of potentially different source sizes) to appear the same size in your UITableViewCell then you need to adjust the sizes of the images. The easiest way to do that is to use the ScaleToFill contentMode of your UIImageView, which will then do the work for you.
You can get rounded/bordered/colored corners on any view by doing this:
#import <QuartzCore/QuartzCore.h> // for layer.cornerRadius
...
self.comboTextView.layer.cornerRadius = 10.0;
self.comboTextView.layer.borderColor = [[UIColor grayColor] CGColor];
self.comboTextView.layer.borderWidth = 2;
Expanding TextView: I'd probably do this: implement the UITextViewDelegate function
- (void)textViewDidChange:(UITextView *)textView
then in that method get the textView.contentSize property, and set the textView.frame property to that size.

Create custom class that inherits from the UIView. Add ivar UIImageView* _img_v
Override setFrame:
-(void) setFrame:(CGRect) r {
[super setFrame: r];
if( _img_v.image && r.size.width*r.size.height ) {
CGSize isz = _img_v.image.size;
float sx = r.size.width/isz.width;
float sy = r.size.height/isz.height;
if( sx > sy ) {
sx = sy;
} else {
sy = sx;
}
CGRect img_frame = CGRectMake(0, 0, isz.width*sx, isz.height*sy);
img_frame.origin = CGPointMake((r.size.width - img_frame.size.width)/2.0, (r.size.height - img_frame.size.height)/2.0);
[_img_v setFrame: img_frame];
}
}
This won't crop the images, but you can also use scaleToFill for that.

Related

Implementing custom textView

I want to improve a super cool text view with placeholder and add to it super cool frame like textField has. To do it you simply need to add this code to your awakeFromNib method:
- (void)awakeFromNib
{
[super awakeFromNib];
if (self.editable) {
CALayer *selfLayer = self.layer;
UIImage *stretchableImage = [UIImage imageNamed:#"TextView"];
selfLayer.contents = (id)stretchableImage.CGImage;
selfLayer.contentsScale = [UIScreen mainScreen].scale; // Needed for the retina display, otherwise our image will not be scaled properly.
selfLayer.contentsCenter = CGRectMake(0.5, 0.5, 1.0/stretchableImage.size.width,1.0/stretchableImage.size.height);
self.backgroundColor = [UIColor clearColor];
}
}
The problem is if you add this if() to the above mentioned placeholderTextView's awakeFromNib the drawRect method of the placeholderTextView does not getting called! WHY ? Is it because of accessing layer property of this view? Please guide me through this graphics stuff..!

How to set fixed size for UITableViewCell's ImageView?

For my application's contact view, I have added individual users image based on his email id in an table view. Because of difference in image size of each user, image view is not unique for all user, some image view's width is more, So I set the frame size of UItableViewCell with fixed width, still the width size varies.
[cell.imageView setFrame:CGRectMake(0, 0, 55, 55)];
What should I do? Do I need to add new image view as subview for cell? Any idea?
UITableView's imageView property is read-only. You can resize your image before setting to your cell's imageView. To resize the image, you can get help from this StackOverflow answer.
Also, if you are loading your images from a web environment, you can use my fork of AFNetworking, I added image resizing options into the UIImageView extensions. https://github.com/polatolu/AFNetworking
You should set your imageview's property in order to display different images for different size.
For Example,
<imageview_name>.contentMode = UIViewContentModeScaleAspectFit;
Enjoy Programming!
I'm using the same JSON file to fill a UITableView, and here is what I'm using for a universal app in CustomCell:
- (void)layoutSubviews {
[super layoutSubviews];
if (UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPad) {
self.imageView.frame = CGRectMake(0,0,300,100);
}else if (UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPhone){
self.imageView.frame = CGRectMake(0,0,180,60);
}
float limgW = self.imageView.image.size.width;
if(limgW > 0) {
if (UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPad) {
self.textLabel.frame = CGRectMake(320,self.textLabel.frame.origin.y,self.textLabel.frame.size.width,self.textLabel.frame.size.height);
}else if (UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPhone){
self.textLabel.frame = CGRectMake(182,self.textLabel.frame.origin.y,self.textLabel.frame.size.width,self.textLabel.frame.size.height);
}
}
}
Subclass UITableViewCell and override this method;
- (void)layoutSubviews {
[super layoutSubviews];
self.imageView.frame = CGRectMake(0,0,55,55);
//Any additional setting that you want to do with image view
[self.imageView setAutoresizingMask:UIViewAutoresizingNone];
}

Draw rectangle in custom table cell

How would I draw a rectangle in a custom table cell class? The cell currently has a background image with a few text labels. I would like to draw a rectangle behind each of the labels so they are easier to read over the detailed background image.
I know I could just set the background colour of the label but I would like to have padding between the background colour and the text. If that is possible, I'd love to know how! :)
I'm subclassing a TTTableMessageItemCell in Three20, a method below gets called in which you can play with subviews of the cell,
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat padding = 16;
CGFloat boxWidth = self.contentView.width - 2*padding;
CGFloat textWidth = boxWidth - (padding*2);
CGFloat textHeight = 100;
CGFloat top = kTableCellSmallMargin;
// Position Heading Text
_titleLabel.frame = CGRectMake(padding, top, textWidth, _titleLabel.font.ttLineHeight);
top += _titleLabel.height;
// Position Detail Text
[self.detailTextLabel sizeToFit];
self.detailTextLabel.top = top+2*padding;
self.detailTextLabel.left = 2*padding;
self.detailTextLabel.width = textWidth;
self.detailTextLabel.height = 100;
}
I would like the rectangles to be placed behind the _titleLable and detailTextLabel labels.
edit
I have been able to add the right box using the following,
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor whiteColor];
view.frame = CGRectMake(padding, top, textWidth, textHeight+2*padding);
[self insertSubview:view belowSubview:self.detailTextLabel];
It is laying on top of the label and I cant seem to get it behind it...
edit
I was adding the view to the wrong subview, fixed it with,
[[self.subviews objectAtIndex:0] insertSubview:view atIndex:0];
You can add the labels to views and these to the cell.
You could use insertSubview:belowSubview: to add views behind your labels. With backgroundColor and the right frame they will do what you intend to.
You can also bring detailLabel to front

UITableViewCell: how to give the built-in imageView a margin on retina display

I'd like to apply a margin to the UIImageView on the left of a plain old UITableViewCell (grouped style).
The only way I've found to do this (via here) is to resize the UIImage itself before attaching it to the UIImageView. If the image is smaller then the cell, it will be centred; leaving the desired margin as a side-effect.
Well, that works, but now my image is blurry because the 100 unit row height is not 100 pixels on an iPhone4, its 200. So I end up with a UIImage scaled to 90x90 pixels that produces a 90x90 unit (180x180 pixel) UIImageView image. Enter ugly blurriness.
So my question is: how do I achieve a margin around the imageView without over-downsampling my image?
(ideally without downsampling at all - I need to keep the original for later anyway).
I feel like I'm missing something obvious; I really don't want to implement a custom cell class just for this.
Thanks guys, I've come up with a 'solution' that doesn't require subclassing.
What I do is still downsample the image, but to the 2x size (180x180 from the example in the question).
Then, when I come to create the final UIImage from the processed CGImage I use:
UIImage: +(UIImage *)imageWithCGImage:(CGImageRef)imageRef scale:(CGFloat)scale orientation:(UIImageOrientation)orientation
and set scale: to 2. Now everything works. But I'm still creating duplicate images just to keep UIKit happy.
Implement this method in your UITableViewCell subclass
-(void) layoutSubviews
{
[super layoutSubviews];
self.imageView.frame = CGRectInset(self.imageView.frame, 5, 5);
}
Note: I haven't tested any of this, so it's possible that UITableViewCell will override some of these settings to lay its subviews out according to its own internal logic.
Have you tried just adjusting the image view's frame? Try this:
UITableViewCell * tableViewCell = [[[UITableViewCell alloc] init] autorelease];
tableViewCell.imageView.image = [UIImage imageNamed:#"table-view-image"];
CGRect imageViewFrame = tableViewCell.imageView.frame;
imageViewFrame.origin.x += 10.0f;
tableViewCell.imageView.frame = imageViewFrame;
That will just pad the image view 10 points more than its normal x coordinate. If you want to pad both sides, you can also set the contentMode property on the image view to UIViewContentModeCenter and adjust its width:
UITableViewCell * tableViewCell = [[[UITableViewCell alloc] init] autorelease];
tableViewCell.imageView.image = [UIImage imageNamed:#"table-view-image"];
tableViewCell.imageView.contentMode = UIViewContentModeCenter;
CGRect imageViewFrame = tableViewCell.imageView.frame;
imageViewFrame.size.width += 20.0f;
tableViewCell.imageView.frame = imageViewFrame;
That will make the image view 20 points wider, but because the content mode is set to center, the image will be drawn without stretching. If the dimensions of your image are right, this will effectively pad the image by 10 points on the left and right. However, you need to be aware that if you do this, the UIImage you provide must already be the exact dimensions to fit in the image view. contentMode's default setting is UIViewContentModeScaleToFill, so it will automatically scale images to fill the image view's frame. Setting it to UIViewContentModeCenter will no longer do this, but it will center the actual image.
In my case I used my own subclass inherited from UITableViewCell.
It's very clean and easier to use.
Also, I could use different sizes of image to be well resized for this cell.
The point is to use additional property that will replace regular imageView
1: In the subclass, I added a property mainImageView which can be used instead of imageView.
#property (nonatomic, retain) UIImageView *mainImageView;
2: Then in - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier.
I allocated and initialized mainImageView.
You can set any frame(rect) for mainImageView.
Add it as a subview to the contentView.
I used insetImageView to align it to the vertical middle of `contentView'.
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code.
mainImageView = [[UIImageView alloc] initWithFrame:frameCellMainImageView];
[self.contentView addSubview:mainImageView];
insetImageView = (sizeRowHeightTwoLinedDetail - frameCellMainImageView.size.height) / 2.0;
}
return self;
}
3: Override - (void)layoutSubviews to make sure other properties like textLabel, detailTextLabel set their frames to be well-situated with added property mainImageView
- (void)layoutSubviews {
[super layoutSubviews];
CGRect frameImageView = frameCellMainImageView;
frameImageView.origin.x = insetImageView;
frameImageView.origin.y = insetImageView;
[self.mainImageView setFrame:frameImageView];
// Use changed frame
frameImageView = self.mainImageView.frame;
CGFloat newLeftInset = frameImageView.size.width + insetImageView;
CGRect frameTextLabel = self.textLabel.frame;
CGRect frameDetailLabel = self.detailTextLabel.frame;
frameTextLabel.origin.x += newLeftInset;
frameDetailLabel.origin.x += newLeftInset;
CGFloat newTextWidth = 320.0;
newTextWidth -= newLeftInset;
newTextWidth -= insetImageView;
newTextWidth -= insetImageView;
frameTextLabel.size.width = newTextWidth;
frameDetailLabel.size.width = newTextWidth;
[self.textLabel setFrame:frameTextLabel];
[self.detailTextLabel setFrame:frameDetailLabel];
}
4: When using this cell in UITableDataSourceDelegate methods, use cell.mainImageView to receive messages, instead of regular cell.imageView

Zoom UILabel & Re-render font at correct size

I have a multi-line UILabel that I want to enable zooming on.
I embedded it with a UIScrollView and set min zoom to .25 and max zoom to 4. This works well, however my UILabel's font looks rather gross at any zoom level other than 1.
I can handle this method:
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
in order to re-size the font of my UILabel to something larger, but the view is still zoomed in, so it always looks awful.
Is there any way to make the label's text re-render one I'm done zooming?
It is important that the users current scrolled position in the text not be lost.
(To get a feel for what I'm going for, notice how in Mobile Safari when you zoom the text is scaled/anti-aliased for a split second then it clears up to render well at your current zoom scale)
The UIScrollView's built-in scaling only applies a transform to your content view, which results in blurriness at anything above a scale factor of 1.0. For truly sharp rendering, you'll need to handle the scaling yourself. I describe a chunk of the process in this answer.
You'll need to keep track of the scale factor of the content view manually, then in the -scrollViewDidEndZooming:withView:atScale: delegate method you'll apply that scale. For your UILabel, that will mean changing the font size to reflect the new scale.
In order to maintain the correct scroll position, you'll need to grab the contentOffset of the UIScrollView within the above delegate method and calculate what position that corresponds to in the newly scaled UILabel. You then set the contentSize of the scroll view to match the new size of the UILabel and use -setContentOffset:animated: to set the newly calculated content offset (with animated set to NO).
It's a little tricky to get the math right, but I do this when scaling text in one of my applications, which can be seen in the video demonstration of that application (at about the 1/3 mark).
Thanks valvoline & Scrimmers for your answers. My solution was a mix between yours.
Subclass UILabel like this:
UILabelZoomable.h
+ (Class) layerClass;
UILabelZoomable.m
+ (Class) layerClass
{
return [CATiledLayer class];
}
Now, when you use your fancy new UILabelZoomable in your app, remember to do this:
YourViewController.m
CATiledLayer *tiledLayer = (CATiledLayer*)textoLabel.layer;
tiledLayer.levelsOfDetail = 10;
tiledLayer.levelsOfDetailBias = 10;
Remember to add the QuartzCore framework!
#import <QuartzCore/QuartzCore.h>
Enjoy sharp and a beautiful rendered text:
[UIView animateWithDuration:1 animations:^
{
yourLabel.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(200, 200), CGAffineTransformMakeRotation(1));
}];
iOS 4+
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
scrollView.contentScaleFactor = scale;
for (UIView *subview in scrollView.subviews) {
subview.contentScaleFactor = scale;
}
}
If this gets too intensive and performance is slow, just try setting the contentScaleFactor for only your labels.
Here is something that I have come up with:
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
label.font = UIFont(name: "YourFont", size: fontSize * scale)
label.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1 / scale, 1 / scale), CGAffineTransformMakeRotation(0))
}
Updated for Swift 4.0:
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
label.font = label.font.withSize(fontSize * scale)
label.transform = CGAffineTransform(scaleX: 1 / scale, y: 1 / scale).concatenating(CGAffineTransform(rotationAngle: 0))
}
And also remember that the label should be large enough to show the whole text. It sometimes takes a tiny bit to redraw, but this method is fairly simple.
I implemented Scrimmers's solution by subclassing UILabel as DetailedUILabel with overriding methods like this;
import QuartzCore
#import <QuartzCore/QuartzCore.h>
override init method, initWithFrame whichever you want.
- (id)init
{
self = [super init];
if (self) {
CATiledLayer *tiledLayer = (CATiledLayer *)self.layer;
tiledLayer.levelsOfDetailBias = 4;
tiledLayer.levelsOfDetail = 4;
self.opaque = YES;
}
return self;
}
and layerClass, class method.
+ (Class)layerClass {
return [CATiledLayer class];
}
the correct way to substitute a CALayer with a CATiledLayer is as follow:
+ (Class)layerClass {
return [CATiledLayer class];
}
apart, you've to set your bias detail and whatever you need.
I tested JEzu's code in iOS 5.1 and 6 beta, it is working for a const text label, but cause other normal label require updating text content fail, i.e. the text is not changed...
I subclass the UILabel as ZoomableLabel with JEzu's answer, and apply the class as the label's custom class and it works fine.
There is a simpler way to do this..
Replace the layerClass of the UILabel with a CATiledLayer and set the level of detail appropriately.
+ (Class)layerClass
{
CATiledLayer *layerForView = (CATiledLayer *)self.layer;
layerForView.levelsOfDetailBias = 2;
layerForView.levelsOfDetail = 2;
return [CATiledLayer class];
}
Job done