Table View UIImage rendering issue - ios

I'm running into a rendering issue with my tableView UIImages and was wondering if anyone has encountered the same problem and knows how to fix it.
Here is my cellForRowAtIndexPath
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.textLabel.text = exerciseDisplayName;
cell.textLabel.numberOfLines = 0;
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
[tableView setSeparatorInset:UIEdgeInsetsZero];
UtilityMethods *commonMethods = [[UtilityMethods alloc]init];
UIImage *rowImage = [commonMethods imageForRow:tempPlaceholder.bodyPart];
cell.imageView.image = rowImage;
return cell;
}
Here is my height for row.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 96;
}
There are lots of lines and squiggles in the images in the table. I was wondering if anyone knowns any UIImage properties that I might need to apply to my image to fix the problem. Increasing the height for row in table fixes the problem at the expense of increasing the height of the table row. The number that seems to work is 128 in heightForRow. When using 128 the squiggles are much less noticeable. Now I'm pretty sure this has something to do with how iOS is rendering the image. Ive taken the image and resized it to 76x76 using Microsoft Paint just to see if I would see the same problem, and the images appear just fine without all the squiggles. The images are .png format. The original size of the images is 1024x1024. Ive just resized them downwards as I've needed them. If anyone has any tips or advice on how to fix this I'd really appreciate it.

You are going to need to resample the image to the size you need. Viewing a large image in a small space looks rather bad on iOS devices (most any really). But if you use built in functions to create a new UIImage of the proper size everything looks much better. Scaling down a UIImage when displaying will always look worse than creating a new image of the proper size and displaying that. The way to do this is as follows (taken from here):
- (UIImage*)imageByScalingAndCroppingForSize:(CGSize)targetSize
{
UIImage *sourceImage = self;
UIImage *newImage = nil;
CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;
CGPoint thumbnailPoint = CGPointMake(0.0,0.0);
if (CGSizeEqualToSize(imageSize, targetSize) == NO)
{
CGFloat widthFactor = targetWidth / width;
CGFloat heightFactor = targetHeight / height;
if (widthFactor > heightFactor)
{
scaleFactor = widthFactor; // scale to fit height
}
else
{
scaleFactor = heightFactor; // scale to fit width
}
scaledWidth = width * scaleFactor;
scaledHeight = height * scaleFactor;
// center the image
if (widthFactor > heightFactor)
{
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
}
else
{
if (widthFactor < heightFactor)
{
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
}
}
}
UIGraphicsBeginImageContextWithOptions(targetSize, 0, NO); // this will crop
CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect];
newImage = UIGraphicsGetImageFromCurrentImageContext();
if(newImage == nil)
{
NSLog(#"could not scale image");
}
//pop the context to get back to the default
UIGraphicsEndImageContext();
return newImage;
}
That function does a bit more than you are looking for, but you should be able to cut it does to only what you need.
Make sure to use the UIGraphicsBeginImageContextWithOptions function instead of UIGraphicsBeginImageContext so you deal with retina displays properly, otherwise this will make your images more blurry than they should be and you will have a second problem to deal with.

Related

How to resize and layout image/attachment in TextKit?

I'm building a magazine app via TextKit, here is a TextKit demo (check out the developer branch). It loads a NSAttributeString from a rtfd file as text storage object, all the pages have the same size with custom NSTextContainer object, the pagination feature is done.
When I tried to add image to the source rtfd file, the image attachment shows in UITextView directly without any additional code, that's great! However, some big images will be clipped by default in text view frame. I tried all kinds of delegate methods and override methods to resize and re-layout it but failed at the end.
- (void)setAttachmentSize:(CGSize)attachmentSize forGlyphRange:(NSRange)glyphRange
- (CGSize)attachmentSizeForGlyphAtIndex:(NSUInteger)glyphIndex;
The setter method is called in glyph layout process, the latter getter method is called in glyph draw process from the call stack.
- (BOOL)layoutManager:(NSLayoutManager *)layoutManager shouldSetLineFragmentRect:(inout CGRect *)lineFragmentRect lineFragmentUsedRect:(inout CGRect *)lineFragmentUsedRect baselineOffset:(inout CGFloat *)baselineOffset inTextContainer:(NSTextContainer *)textContainer forGlyphRange:(NSRange)glyphRange
{
NSTextAttachment *attachment = ...;
NSUInteger characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphRange.location];
UIImage *image = [attachment imageForBounds:*lineFragmentRect textContainer:textContainer characterIndex:characterIndex];
CGSize imageSize = GetScaledToFitSize(image.size, self.textContainerSize);
CGFloat ratio = imageSize.width / imageSize.height;
CGRect rect = *lineFragmentRect, usedRect = *lineFragmentUsedRect;
CGFloat dy = *baselineOffset - imageSize.height;
if (dy > 0) {
*baselineOffset -= dy;
usedRect.size.height -= dy;
usedRect.size.width = ratio * usedRect.size.height;
}
if (!CGRectContainsRect(usedRect, rect)) {
if (rect.size.height > usedRect.size.height) {
*baselineOffset -= rect.size.height - usedRect.size.height;
rect.size.height = usedRect.size.height;
rect.size.width = ratio * usedRect.size.height;
}
if (rect.size.width > usedRect.size.width) {
//...
}
}
*lineFragmentRect = rect;
*lineFragmentUsedRect = usedRect;
return YES;
}
This delegate method could resize the layout size but not affect to the final width and image scale. I tried serval solutions with no luck. It seems there aren't many threads about images on TextKit on SO and Apple example code.
I have ever done similar work for image attachment auto resize. How about handle it just after you get the attributed string.
That is, enumerate the original string with NSAttachmentAttributeName, replace the attachment with a subclass of NSTextAttachment, with implies NSTextAttachmentContainer protocol.
- (CGRect)attachmentBoundsForTextContainer:(nullable NSTextContainer *)textContainer proposedLineFragment:(CGRect)lineFrag glyphPosition:(CGPoint)position characterIndex:(NSUInteger)charIndex {
CGFloat lineWidth = CGRectGetWidth(lineFrag);
CGSize size = self.bounds.size;
size.height *= (size.width > 0) ? (lineWidth / size.width) : 0;
size.width = lineWidth;
return CGRectMake(0, 0, size.width, size.height);
}
Code above resize attachment to fit the width, you don't need to resize image, since it will be auto resize to the bound when drawing.
Hoping to be helpful.

How to avoid losing image quality using UIImageView's contentmode UIViewContentModeScaleAspectFit keeping height constant?

I am using a collectionview inside a tableview for showing images which are being downloaded asynchronously. What I need to do is to show those images without squeezing. I am keeping height of the collectionview constant, and calculating the images' sizes after they are downloaded. I am keeping content mode of the UIIamgeView UIViewContentModeScaleAspectFit. This is what I have done to achieve my intended result.
- (void)configureMediaCell:(MediaContainerViewCell *)weakCell withMediaItem:(MSZWallPostMedia *)mediaItem withDownloadImage:(UIImage *)image{
if (mediaItem.isFrameSet == NO) {
weakCell.isFrameSet = YES;
weakCell.cellImageView.image = image;
weakCell.cellImageView.contentMode = UIViewContentModeScaleAspectFit;
CGRect frame = weakCell.cellImageView.frame;
//Image Scaling
CGSize scaleSize = [self imageScale:weakCell.cellImageView];
scaleSize.width = scaleSize.width * image.size.width;
scaleSize.height = scaleSize.height * image.size.height;
frame.size.width = scaleSize.width;
frame.size.height = scaleSize.height;
weakCell.cellImageView.frame = frame;
mediaItem.frame = frame;
mediaItem.isFrameSet = YES;
[weakCell.activityIndicatorView stopAnimating];
[self.collectionView reloadItemsAtIndexPaths:#[weakCell.indexPath]];
} else {
[weakCell.activityIndicatorView stopAnimating];
weakCell.cellImageView.image = image;
weakCell.cellImageView.frame = mediaItem.frame;
weakCell.cellImageView.contentMode = UIViewContentModeScaleAspectFit;
}
}
For Image Scaling:
- (CGSize)imageScale : (UIImageView *)imageView {
CGFloat sx = imageView.frame.size.width / imageView.image.size.width;
CGFloat sy = imageView.frame.size.height / imageView.image.size.height;
CGFloat s = 1.0;
switch (imageView.contentMode) {
case UIViewContentModeScaleAspectFit:
s = fminf(sx, sy);
return CGSizeMake(s, s);
break;
case UIViewContentModeScaleAspectFill:
s = fmaxf(sx, sy);
return CGSizeMake(s, s);
break;
case UIViewContentModeScaleToFill:
return CGSizeMake(sx, sy);
default:
return CGSizeMake(s, s);
}
}
The problem in this approach is that I get the correct widths and heights but some images are shown smaller which is obvious because I am using Aspectfit property of imageview which is shown in the attached screen.
Any help regarding this would be appreciated. I need to keep images as they are keeping height of collectionviewitem fixed and variable width.
If I understand you correctly. You just need to find image width/height ratio and multiply desired height.
Example:
Image
width = 30, height = 10
Cell
width = ?, height = 7
Ratio = 30 / 10
cell width = 7 * (30/10) = 21
Cell size will become (21,7)
Hope this is what you meant. But you will face the problem if the image is smaller than cell. Then it might become distorted.

Image resizing inside tableviewcell

I ran into a problem that can not solve 2 days. On the server image and come before inserting them in the box, I cut them on the client to fit the screen. Everything is good but the images change randomly height. Now the height is calculated independently - in proportion to the width.
Normal image:
Bad image
I can not ask explicitly UIImageView because I dynamically I rely all the cells to different devices.
My resize function:
-(UIImage *)resizeImage :(UIImage *)theImage :(CGSize)theNewSize {
UIGraphicsBeginImageContextWithOptions(theNewSize, NO, 1.0);
CGFloat height = theImage.size.height;
CGFloat newHeight = 0;
newHeight = (theNewSize.width * height) / theImage.size.width;
newHeight = floorf(newHeight);
NSLog(#"new height image %f", newHeight);
[theImage drawInRect:CGRectMake(0, 0, theNewSize.width, newHeight)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
inside layoutSubviews:
if(device == thisDeviceClass_iPhone5) {
[self.imageView setFrame:CGRectMake(0,0, 320, 255)]; //180
offset = 0;
padding = 5;
} else if(device == thisDeviceClass_iPhone6){
[self.imageView setFrame:CGRectMake(0,0, 375, 255)]; //211
offset = 25;
} else if(device == thisDeviceClass_iPhone6plus) {
[self.imageView setFrame:CGRectMake(0,0, 414, 255)]; //233
offset = 40;
}
Your approach is very old school. You are "hard coding" the size of the image which means that if next year Apple came up with a new iPhone that have, yet again, a different size you'll be in trouble. You should consider using auto-layout constrains which is Apple's recommended approach (here is a good tutorial: http://www.raywenderlich.com/50317/beginning-auto-layout-tutorial-in-ios-7-part-1).
You can also set the ImageView.contentMode to UIViewContentModeScaleAspectFill which will do the crop and resize for you.

How to make imageView fixed size and with rounded corners

I'm using Parse.com to build a simple app. I want to know if there is any way to make the imageView size fixed (for example 30x30px) and round the corners of the image?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
//-------------------------------------------------------------------------------------------------------------------------------------------------
{
PFTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil) cell = [[PFTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"];
PFUser *user = users[indexPath.row];
cell.textLabel.text = user[PF_USER_FULLNAME];
PFImageView *imageView = [[PFImageView alloc] init];
[imageView setBackgroundColor:[UIColor clearColor]];
cell.imageView.image = [UIImage imageNamed:#"tab_profile.png"]; // placeholder image
cell.imageView.file = (PFFile *)user[PF_USER_THUMBNAIL]; // remote image
[cell.imageView loadInBackground];
return cell;
}
Please help with any advice, I'm new to Xcode and Parse SDK...
1)
2)
This is what I usually use to get a circle on an imageView:
Crop (not resize) the image to make it a square. The method below resizes the image to whatever size you pass as a CGSize:
(UIImage *)squareImageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize
{
double ratio;
double delta;
CGPoint offset;
//make a new square size, that is the resized imaged width
CGSize sz = CGSizeMake(newSize.width, newSize.width);
//figure out if the picture is landscape or portrait, then
//calculate scale factor and offset
if (image.size.width > image.size.height)
{
ratio = newSize.width / image.size.width;
delta = (ratio*image.size.width - ratio*image.size.height);
offset = CGPointMake(delta/2, 0);
} else {
ratio = newSize.width / image.size.height;
delta = (ratio*image.size.height - ratio*image.size.width);
offset = CGPointMake(0, delta/2);
}
//make the final clipping rect based on the calculated values
CGRect clipRect = CGRectMake(-offset.x, -offset.y,
(ratio * image.size.width) + delta,
(ratio * image.size.height) + delta);
//start a new context, with scale factor 0.0 so retina displays get
//high quality image
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)]) {
UIGraphicsBeginImageContextWithOptions(sz, YES, 0.0);
} else {
UIGraphicsBeginImageContext(sz);
}
UIRectClip(clipRect);
[image drawInRect:clipRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Then, call the method just created, and apply the corner radius (in this case it will be a circle):
imageView.image = [self squareImageWithImage:imageNormal scaledToSize:CGSizeMake(50, 50)]; // assuming you want a 50x50px image
// convert imageview to circle
imageView.layer.cornerRadius = cell.imageView.frame.size.width / 2;
imageView.clipsToBounds = YES;
You can reduce the amount of the cornerRadius to something else with this same call.
This is how I do it:
Image with fix size
If it's possible use Storyboard and create 2 size constraints for the PFImageView with the 30 px value, but if the project doesn't uses size classes it should work without constraints, just set the proper size for the image. If you experience shape changes you could play with View Mode also.
TooManyEduardos's solution for the rounded image is correct, if you don't want circle just play with the numbers.
//1
imageView.layer.cornerRadius = cell.imageView.frame.size.width / 2;
//2
imageView.layer.cornerRadius = cell.imageView.frame.size.width / 4;
//3
imageView.layer.cornerRadius = cell.imageView.frame.size.width / 6;
//4
imageView.layer.cornerRadius = cell.imageView.frame.size.width / 8;

Resize UIImage with aspect ratio?

I'm using this code to resize an image on the iPhone:
CGRect screenRect = CGRectMake(0, 0, 320.0, 480.0);
UIGraphicsBeginImageContext(screenRect.size);
[value drawInRect:screenRect blendMode:kCGBlendModePlusDarker alpha:1];
UIImage *tmpValue = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Which is working great, as long as the aspect ratio of the image matches that of the new resized image. I'd like to modify this so that it keeps the correct aspect ratio and just puts a black background anywhere the image doesn't show up. So I would still end up with a 320x480 image but with black on the top and bottom or sides, depending on the original image size.
Is there an easy way to do this similar to what I'm doing? Thanks!
After you set your screen rect, do something like the following to decide what rect to draw the image in:
float hfactor = value.bounds.size.width / screenRect.size.width;
float vfactor = value.bounds.size.height / screenRect.size.height;
float factor = fmax(hfactor, vfactor);
// Divide the size by the greater of the vertical or horizontal shrinkage factor
float newWidth = value.bounds.size.width / factor;
float newHeight = value.bounds.size.height / factor;
// Then figure out if you need to offset it to center vertically or horizontally
float leftOffset = (screenRect.size.width - newWidth) / 2;
float topOffset = (screenRect.size.height - newHeight) / 2;
CGRect newRect = CGRectMake(leftOffset, topOffset, newWidth, newHeight);
If you don't want to enlarge images smaller than the screenRect, make sure factor is greater than or equal to one (e.g. factor = fmax(factor, 1)).
To get the black background, you would probably just want to set the context color to black and call fillRect before drawing the image.
I know this is very old, but thanks for that post -- it redirected me from attempting to use scale to drawing the image. In case it is of benefit to anyone, I made an extension class I'll throw in here. It allows you to resize an image like this:
UIImage imgNew = img.Fit(40.0f, 40.0f);
I don't need a fit option, but it could easily be extended to support Fill as well.
using CoreGraphics;
using System;
using UIKit;
namespace SomeApp.iOS.Extensions
{
public static class UIImageExtensions
{
public static CGSize Fit(this CGSize sizeImage,
CGSize sizeTarget)
{
CGSize ret;
float fw;
float fh;
float f;
fw = (float) (sizeTarget.Width / sizeImage.Width);
fh = (float) (sizeTarget.Height / sizeImage.Height);
f = Math.Min(fw, fh);
ret = new CGSize
{
Width = sizeImage.Width * f,
Height = sizeImage.Height * f
};
return ret;
}
public static UIImage Fit(this UIImage image,
float width,
float height,
bool opaque = false,
float scale = 1.0f)
{
UIImage ret;
ret = image.Fit(new CGSize(width, height),
opaque,
scale);
return ret;
}
public static UIImage Fit(this UIImage image,
CGSize sizeTarget,
bool opaque = false,
float scale = 1.0f)
{
CGSize sizeNewImage;
CGSize size;
UIImage ret;
size = image.Size;
sizeNewImage = size.Fit(sizeTarget);
UIGraphics.BeginImageContextWithOptions(sizeNewImage,
opaque,
1.0f);
using (CGContext context = UIGraphics.GetCurrentContext())
{
context.ScaleCTM(1, -1);
context.TranslateCTM(0, -sizeNewImage.Height);
context.DrawImage(new CGRect(CGPoint.Empty, sizeNewImage),
image.CGImage);
ret = UIGraphics.GetImageFromCurrentImageContext();
}
UIGraphics.EndImageContext();
return ret;
}
}
}
As per the post above, it starts a new context for an image, then for that image it figures out aspect and then paints into the image. If you haven't done any Swift xcode dev time, UIGraphics is a bit backwards to most systems I work with but not bad. One issue is that bitmaps by default paint bottom to top. To get around that,
context.ScaleCTM(1, -1);
context.TranslateCTM(0, -sizeNewImage.Height);
Changes the orientation of drawing to the more common top-left to bottom-right... but then you need to move the origin as well hence the TranslateCTM.
Hopefully, it saves someone some time.
Cheers

Resources