I draw a background image in a view with the following code:
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
UIImage *image = [UIImage imageNamed:#"background.jpg"];
[image drawAsPatternInRect:rect];
CGContextRestoreGState(currentContext);
This works well. But when I change the size of that view animated the drawn image is scaled until it get's redraw. How can I ignore this transformation during the animation?
At the moment I don't think that's possible. Since the iPhone doesn't have a very fast processor Apple choose to disable the LiveRedraw-feature (that actually is available on Mac).
Try setting the view's contentMode to UIViewContentModeRedraw.
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Get the view to redraw when it's frame is changed
self.contentMode = UIViewContentModeRedraw;
}
return self;
}
I finally found a solution. I use contentMode UIViewContentModeTopLeft so it doesn't get scaled. And before the resize animation I only redraw if the new size is greater then the old one. And after the animation I always redraw.
Related
I'm using a UIImageView for each of my UITableViewCells, as thumbnails. My code uses SDWebImage to asynchronously grab those images from my backend and load them in, and then caching them. This is working fine.
My UIImageView is a 50x50 square, created in Interface Builder. Its background is opaque, white color (same as my UITableViewCell background color, for performance). However, I'd like to use smooth corners for better aesthetics. If I do this:
UIImageView *itemImageView = (UIImageView *)[cell viewWithTag:100];
itemImageView.layer.cornerRadius = 2.5f;
itemImageView.layer.masksToBounds = NO;
itemImageView.clipsToBounds = YES;
My tableview drops around 5-6 frames immediately, capping about 56 FPS during fast scrolling (which is okay), but when I drag and pull the refresh control, it lags a bit and drops to around 40 FPS. If I remove the cornerRadius line, all is fine and no lag. This has been tested on an iPod touch 5G using Instruments.
Is there any other way I could have a rounded UIImageView for my cells and not suffer a performance hit? I'd already optimized my cellForRowAtIndexPath and I get 56-59 FPS while fast scrolling with no cornerRadius.
Yes, that's because cornerRadius and clipToBounds requires offscreen rendering, I suggest you to read these answer from one of my question. I also quote two WWDC session thatyou should see. The best thing you can do is grab the image right after is downloaded and on another thread dispatch a method that round the images. Is preferable that you work on the image instead of the imageview.
// Get your image somehow
UIImage *image = [UIImage imageNamed:#"image.jpg"];
// Begin a new image that will be the new image with the rounded corners
// (here with the size of an UIImageView)
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
// Add a clip before drawing anything, in the shape of an rounded rect
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:10.0] addClip];
// Draw your image
[image drawInRect:imageView.bounds];
// Get the image, here setting the UIImageView image
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
// Lets forget about that we were drawing
UIGraphicsEndImageContext();
Method grabbed here
You can also subclass the tableviewcell and override the drawRect method.
The dirty but very effective way is draw a mask in photoshop with inside alpha and around the matching color of the background of the cell and add another imageView, not opaque with clear background color, on the one with images.
There is another good solution for this. I did it a few times in my projects. If you want to create a rounded corners or something else you could just use a cover image in front of your main image.
For example, you want to make rounded corners. In this case you need a square image layer with a cut out circle in the center.
By using this method you will get 60fps on scrolling inside UITableView or UICollectionView. Because this method not required offscreen rendering for customizing UIImageView's (like avatars and etc.).
Non blocking solution
As a follow up to Andrea's response, here is a function that will run his code in the background.
+ (void)roundedImage:(UIImage *)image
completion:(void (^)(UIImage *image))completion {
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Begin a new image that will be the new image with the rounded corners
// (here with the size of an UIImageView)
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
CGRect rect = CGRectMake(0, 0, image.size.width,image.size.height);
// Add a clip before drawing anything, in the shape of an rounded rect
[[UIBezierPath bezierPathWithRoundedRect:rect
cornerRadius:image.size.width/2] addClip];
// Draw your image
[image drawInRect:rect];
// Get the image, here setting the UIImageView image
UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext();
// Lets forget about that we were drawing
UIGraphicsEndImageContext();
dispatch_async( dispatch_get_main_queue(), ^{
if (completion) {
completion(roundedImage);
}
});
});
}
Swift 3 version of thedeveloper3124's answer
func roundedImage(image: UIImage, completion: #escaping ((UIImage?)->(Void))) {
DispatchQueue.global().async {
// Begin a new image that will be the new image with the rounded corners
// (here with the size of an UIImageView)
UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
let rect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
// Add a clip before drawing anything, in the shape of an rounded rect
UIBezierPath(roundedRect: rect, cornerRadius: image.size.width/2).addClip()
// Draw your image
image.draw(in: rect)
// Get the image, here setting the UIImageView image
guard let roundedImage = UIGraphicsGetImageFromCurrentImageContext() else {
print("UIGraphicsGetImageFromCurrentImageContext failed")
completion(nil)
return
}
// Lets forget about that we were drawing
UIGraphicsEndImageContext()
DispatchQueue.main.async {
completion(roundedImage)
}
}
}
Looks like simple task. But when I try to resize using setFrame method I got glitches. There are some other UIViews resized using setFrame method and it works perfectly. I made custom application with slider bar and map view. SlideBar changes X position for MKMapView, but keeps width equals to screen width. This approach works fine for all Views. But MKMapView resizes with smooth troubles. Can anyone please give a clue why it's happening and how to solve it?
I had the same problem, which seems to occur only on iOS 6 (Apple Maps) and not on iOS 5 (Google Maps).
My solution was to take a "screenshot" of the map when the user start dragging the divider handle, replace the map with this screenshot during the drag, and put the map back when the finger is released.
I used the code from How to capture UIView to UIImage without loss of quality on retina display for UIView screenshot and Nikolai Ruhe's answer at How do I release a CGImageRef in iOS for a nice background color.
My UIPanGestureRecognizer action is something like this (the (MKMapView)self.map is a subview of (UIView)self.mapContainer with autoresizing set on Interface Builder):
- (IBAction)handleMapPullup:(UIPanGestureRecognizer *)sender
{
CGPoint translation = [sender translationInView:self.mapContainer];
// save current map center
static CLLocationCoordinate2D centerCoordinate;
switch (sender.state) {
case UIGestureRecognizerStateBegan: {
// Save map center coordinate
centerCoordinate = self.map.centerCoordinate;
// Take a "screenshot" of the map and set the size adjustments
UIImage *mapScreenshot = [UIImage imageWithView:self.map];
self.mapImage = [[UIImageView alloc] initWithImage:mapScreenshot];
self.mapImage.autoresizingMask = self.map.autoresizingMask;
self.mapImage.contentMode = UIViewContentModeCenter;
self.mapImage.clipsToBounds = YES;
self.mapImage.backgroundColor = [mapScreenshot mergedColor];
// Replace the map with a screenshot
[self.map removeFromSuperview];
[self.mapContainer insertSubview:self.mapImage atIndex:0];
} break;
case UIGestureRecognizerStateChanged:
break;
default:
// Resize the map to the new dimension
self.map.frame = self.mapImage.frame;
// Replace the screenshot with the resized map
[self.mapImage removeFromSuperview];
[self.mapContainer insertSubview:self.map atIndex:0];
// Empty screenshot memory
self.mapImage = nil;
break;
}
// resize map container according do the translation value
CGRect mapFrame = self.mapContainer.frame;
mapFrame.size.height += translation.y;
// reset translation to make a relative read on next event
[sender setTranslation:CGPointZero inView:self.mapContainer];
self.mapContainer.frame = mapFrame;
self.map.centerCoordinate = centerCoordinate; // keep center
}
I'm having problems scaling an image in nsimageview correctly. What happens is when the image is loaded, it doesn't fill up the entire nsimageview. I used NSScaleToFit and it completely stretched the image. Since I don't know the dimensions of the photo (it changes, depending on what image is used) I can't set the nsimageview to a size and leave it there. What I want to do is fill the entire nsimageview with an image, proportionally, even if some of the image is cut off.
This is the code that I'm using:
NSString *image_path = [applicationSupportDirectory stringByAppendingPathComponent:desktop_name];
NSImage *imageFromBundle = [[NSImage alloc] initWithContentsOfFile:image_path];
[ViewImage setImageScaling:NSScaleProportionally];
[ViewImage setImage: imageFromBundle];
I had the same problem. I wanted to have the image to be scaled to fill but keeping the aspect ratio of the original image. Strangely, this is not as simple as it seems, and does not come out of the box with NSImageView. I wanted the NSImageView scale nicely while it resize with superview(s). I made a drop-in NSImageView subclass you can find on github: KPCScaleToFillNSImageView
I don't think something like this is built-in; you'll likely have to transform the image yourself.
It is relatively straightforward though; you can see an example implementation here.
I made my own solution, since the ones that were built-in didn't work.
NSRect frame = [MyWindow frame];
if (abs(windowHeight-[imageFromBundle size].width) > abs(windowWidth-[imageFromBundle size].height)) {
frame.size.height = windowWidth;
frame.size.width = 1+windowWidth*(([imageFromBundle size].width/[imageFromBundle size].height-(10*pow([imageFromBundle size].width, 2))/(pow([imageFromBundle size].height, 2)* windowHeight)));
} else {
frame.size.width = windowHeight;
frame.size.height = ((windowHeight/[imageFromBundle size].width)*[imageFromBundle size].height);
}
[MyWindow setFrame: frame
display: YES
animate: YES];
I'm placing a UIImageView inside of a UIScrollView, basing my code off of the answer in this question. The problem I'm having is that there is a significant amount of white space to the bottom and right, and I can't scroll to some of the image in the top and left. I figure this is due to me incorrectly setting the contentSize of the scrollView. Here's the relevant code:
- (void)viewDidLoad
{
[super viewDidLoad];
_imageView.image = _image;
_imageView.bounds = CGRectMake(0, 0, _imageView.image.size.width,_imageView.image.size.height);
_scroller.contentSize = _imageView.image.size;
}
The view controller I'm in has three properties, a UIScrollView (_scroller), a UIImageView (_imageView), and a UIImage (_image).
You're setting the UIImageView's bounds property. You want to be setting its frame property instead. Setting the bounds will resize it around its center point (assuming you haven't changed the underlying CALayer's anchorPoint property), which is causing the frame origin to end up negative, which is why you can't see the upper-left.
_imageView.frame = CGRectMake(0, 0, _imageView.image.size.width, _imageView.image.size.height);
Alternate syntax:
_imageView.frame = (CGRect){CGPointZero, _imageView.image.size};
I am trying to draw an image into a UIView subclass overriding drawRect. This image drawing class needs moving and zooming capabilities, so I made it a subview of a scrollview.
My problem is that my image seems to get cropped, either by the screen or the scrollView bounds (the image is larger than the screen). I don't have a clue why, this seems to e pretty basic stuff.
The view hierarchy in my ViewController looks like this:
View Controller
View
ScrollView
MapView
I've created outlets for the scroll view and the map view (which is the view implementing drawRect).
My ViewController initial setup code for configuring the scroll view looks like this:
- (void)viewDidLoad
{
[super viewDidLoad];
scrollView.delegate = self;
scrollView.contentSize = CGSizeMake(450, 476);
scrollView.minimumZoomScale = 0.6;
scrollView.maximumZoomScale = 6.0;
}
My drawRect implementation:
- (void)drawRect:(CGRect)rect
{
UIImage *myImage = [UIImage imageNamed:#"map.jpg"];
CGRect rectangle = CGRectMake(0,0,450,476);
[myImage drawInRect:rectangle];
}
The image is draggable and zoomable within the scroll view, but it's cropped at what seems to be the screen bounds.
Any help would be appreciated.
drawRect can't draw outside the rect passed as a parameter to the drawRect method, which is determined by the view frame. Make your view 1200,1200 and make sure that clipToBounds is disabled on all the containing views.
Your problem is probably in drawInRect. From documentation of UIImage class:
drawInRect:
Draws the entire image in the specified rectangle, scaling it as needed to fit.
Make that rectangle bigger as you wish and center it in UIImageView. This question is answered here UIScrollView image/photo viewer with paging enabled and zooming