I know that there are lots of questions to this topic here at StackOverflow, and i tried out many hints and answers hopefully. But nothing helped in this very plain and easy case.
--
In an ARC-based Application I use a UIView subclass called SpeechBubbleView from Ray Wunderlich. It subclasses a UIView and was made to draw a speech bubble containing text like in an SMS-Message-App.
It uses two instances of an UIImage. In the drawRect-Method of the View the bubbles where drawn.
In the iPhone-Simulator everything works fine. But on the iPhone 5 running iOS 7.1 there occure always an EXC_BAD_ACCESS exception every time the second Image is drawn, regardless if the first image was drawn or not.
I will provide some Code:
#property (nonatomic, retain) UIImage* lefthandImage;
#property (nonatomic, retain) UIImage* righthandImage;
At the initWithFrame-Method both images where loaded from the supported files folder:
self.lefthandImage = [[UIImage imageNamed:#"BubbleLefthand"]
stretchableImageWithLeftCapWidth:20 topCapHeight:19];
self.righthandImage = [[UIImage imageNamed:#"BubbleRighthand"]
stretchableImageWithLeftCapWidth:20 topCapHeight:19];
In the drawRect-Method of the SpeechBubbleView the images are drawn:
- (void)drawRect:(CGRect)rect
{
[self.backgroundColor setFill];
UIRectFill(rect);
CGRect bubbleRect = CGRectInset(self.bounds, VertPadding, HorzPadding);
CGRect textRect;
textRect.origin.y = bubbleRect.origin.y + TextTopMargin;
textRect.size.width = bubbleRect.size.width - TextLeftMargin - TextRightMargin;
textRect.size.height = bubbleRect.size.height - TextTopMargin - TextBottomMargin;
if (_bubbleType == BubbleTypeLefthand)
{
[self.lefthandImage drawInRect:bubbleRect];
textRect.origin.x = bubbleRect.origin.x + TextLeftMargin;
}
else
{
[self.righthandImage drawInRect:bubbleRect];
textRect.origin.x = bubbleRect.origin.x + TextRightMargin;
}
[[UIColor blackColor] set];
NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
textStyle.lineBreakMode = NSLineBreakByWordWrapping;
textStyle.alignment = NSTextAlignmentLeft;
[_text drawInRect:textRect withAttributes:#{NSFontAttributeName:font, NSParagraphStyleAttributeName:textStyle}];
}
At the code line, where the "RightHandImage" will be drawn, every time the BAD_ACCESS-Exception is thrown. If i comment this line out, everything works fine. The left-Hand-Bubble will be drawn perfectly as a "bubble".
Also, if i comment out the line where the first Image (left-Hand-Bubble) is drawn, the second Image will throw this exception.
Even if there is now multithreading implemented yet, i tried out to encapsulate the draw-Methods in #synthesize(...){...} following some hints here at StackOverflow to similar questions, but it was no solution and the Exception is still thrown every time.
I know that there is no other class holding an instance of this image to enter in access-rights-issues.
Can anyone give me some hints what to do here and now? Thank you!
EDIT: UITableViewCell-Hint and creating really, own Image-Copies
Maybe it's good to know that each Instance of the SpeechBubbleView is drawn in an reused UITableViewCell. Therefor, i do not use the UIImage-Instances returned by UIImage:imageNamed anymore, but create really copies of each using the following Code:
UIImage* tmpImage = [UIImage imageNamed:#"BubbleLefthand"];
self.lefthandImage = [[UIImage imageWithCGImage:[tmpImage CGImage] ]
stretchableImageWithLeftCapWidth:20 topCapHeight:19];
tmpImage = [UIImage imageNamed:#"BubbleRighthand"];
self.righthandImage = [[UIImage imageWithCGImage:[tmpImage CGImage] ]
stretchableImageWithLeftCapWidth:20 topCapHeight:19];
And, towards Putz1103 Suggestion, i added nil-guards around the draw-Methods:
if (_bubbleType == BubbleTypeLefthand)
{
if(self.lefthandImage != nil) {
[self.lefthandImage drawInRect:bubbleRect];
}
textRect.origin.x = bubbleRect.origin.x + TextLeftMargin;
}
else
{
if(self.righthandImage != nil) {
[self.righthandImage drawInRect:bubbleRect];
}
textRect.origin.x = bubbleRect.origin.x + TextRightMargin;
}
It still crashes on the iPhone, it only works in the iPhone-Simulator.
EDIT 2: Something with the Rect to draw in
It has something to do with the Rect. If i change the bubbleRect to a Rect starting at 0/0 with a width and height of 10 Pixel each, the Exception is not thrown.
If i use larger sizes of the rect the Exception occurs. So it seems that the Area is protected for something else
Change your code in the middle to
if (_bubbleType == BubbleTypeLefthand)
{
if(self.lefthandImage != nil)
[self.lefthandImage drawInRect:bubbleRect];
textRect.origin.x = bubbleRect.origin.x + TextLeftMargin;
}
else
{
if(self.righthandImage != nil)
[self.righthandImage drawInRect:bubbleRect];
textRect.origin.x = bubbleRect.origin.x + TextRightMargin;
}
Then see if it still crashes. If it does not (it shouldn't) you know it's because the images failed to load for one reason or another. Make sure the file names you are loading are case correct to the ones you are including in your project in the "Build Phases" section of the project properties.
Related
My application allow user to switch UIImageView back and front and then user can capture that screen. Here is my code to capture the screen into UIImage
-(UIImage *)imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 2.0f);
// I even tried view.layer.presentationLayer but still not working
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
I do not use [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES] because it is slower and sometimes (when view is not visible) drawn the screenshot in black.
But problem with renderInContext is the Z position that change between UIImageView ( imageView.layer.zPosition = 0.01;etc ). In my iPhone screen this work correctly when I assigned value to zPosition but in the capture screen it turns out wrong.
Are there anyway I can resolve this problem ? Thanks in advance
Edited:
Here is what I tried to do before capture the screenshot. I use this code to make one ImageView display in front of another one.
-(void)bringOneLevelUp:(UIImageView*)imageView
{
//_imgArray is sorted by Z position order (small to big)
NSUInteger currentObjectIndex = [_imgArray indexOfObject:imageView];
if(currentObjectIndex+1< _imgArray.count){
UIImageView *upperImageView = [_imgArray objectAtIndex:currentObjectIndex+1];
CGFloat currentZIndex = imageView.layer.zPosition;
CGFloat upperZIndex= upperImageView.layer.zPosition;
imageView.layer.zPosition = upperZIndex;
upperImageView.layer.zPosition = currentZIndex;
// swap position in array
[_imgArray exchangeObjectAtIndex:currentObjectIndex withObjectAtIndex:currentObjectIndex+1];
}
}
And liked I explained earlier the result of this code in the phone screen is correct. The newImageView is in front of lastImageView. But when I captured screenshot by renderInContext. They are not.
Swift 4.2
The answer of #RameshVel worked for me translating it to Swift, maybe someone needs this:
UIGraphicsBeginImageContext(drawingCanvas.frame.size)
self.renderInContext(ct: UIGraphicsGetCurrentContext()!)
guard let image = UIGraphicsGetImageFromCurrentImageContext() else {return}
UIGraphicsEndImageContext()
And in the renderInContext function:
func renderInContext(ct:CGContext){
ct.beginTransparencyLayer(auxiliaryInfo: nil)
// descendant ordered list of views based on its z-position
let orderedLayers = self.view.subviews.sorted(by: {
$0.layer.zPosition < $1.layer.zPosition
})
for l in orderedLayers {
l.layer.render(in: ct)
}
ct.endTransparencyLayer()
}
The same can be donde with CALayers if you are drawing and adding layers:
UIGraphicsBeginImageContext(drawingCanvas.frame.size)
//Canvas is a custom UIView for drawing
self.renderInContext(ct: UIGraphicsGetCurrentContext()!, canvas: canvas)
guard let image = UIGraphicsGetImageFromCurrentImageContext() else {return}
UIGraphicsEndImageContext()
renderInContext function:
func renderInContext(ct:CGContext, canvas:CustomCanvas){
ct.beginTransparencyLayer(auxiliaryInfo: nil)
let layers:[CALayer] = canvas.layer.sublayers!
let orderedLayers = layers.sorted(by: {
$0.zPosition < $1.zPosition
})
for v in orderedLayers {
v.render(in: ct)
}
ct.endTransparencyLayer()
}
I had this issue as well. I wanted text to draw on top of an image after using renderInContext(). I solved this by positioning my views when adding them to the window rather than using the layer to set the z-position.
I used to have a text field that would set its z-position upward:
layer?.zPosition = 1
I removed this code and instead, when adding my image to the window, I used:
addSubview(image, positioned: .Below, relativeTo: nil)
This solved the problem.
I faced the same issue and its a bummer.
I know this question was asked about 2 years ago, so heres the answer anyway if it helps anyone incase
So changing subviews zPosition renders perfectly fine in the app but in fact affects the renderByContext when used to create a screenshot. I don't know why, I assume it's a bug.
So in order to get the screenshot to appear correctly as it renders in the app, we need to manually call the renderByContext on all subviews in the correct order as specified by zPosition.
UIGraphicsBeginImageContext(self.view.frame.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self renderInContext:ctx];
UIImage *screenShotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
and
-(void) renderInContext:(CGContextRef)ctx {
CGContextBeginTransparencyLayer(ctx, NULL);
//self.layers is a desc ordered list of views based on its zPosition
for(UIView *view in self.layers){
[view.layer renderInContext:ctx];
}
CGContextEndTransparencyLayer(ctx);
}
This is an annoying bug of Apple's, but the solution is easy, simply re-order the view you are snapshotting's subviews (and their subviews (and so on)) recursively, based on their zPositions, using a comparator.
Just call the following method, passing the view you are about to snapshot, directly before snapshotting it.
///UIGraphicsGetCurrentContext doesn't obey layer zPositions so have to adjust view heirarchy (integer index) to match zpositions (float values) for all nested subviews.
-(void)recursivelyAdjustHeirarchyOfSubviewsBasedOnZPosition:(UIView *)parentView {
NSArray *sortedSubviews = [[parentView subviews] sortedArrayUsingComparator:
^NSComparisonResult(UIView *view1, UIView *view2)
{
float zpos1 = view1.layer.zPosition;
float zpos2 = view2.layer.zPosition;
return zpos1 > zpos2;
}];
for (UIView *childView in sortedSubviews) {
[parentView bringSubviewToFront:childView];
[self recursivelyAdjustHeirarchyOfSubviewsBasedOnZPosition:childView];
}
}
How do I make a specific portion of a UILabel look like a blockquote, or have there be a vertical line on the left side of the text? Would TextKit come in here? If so, how?
Mail.app does this (see the colored portions and the line on the side of them):
How would I replicate this effect without using multiple UILabels (which as I'm creating it dynamically would be rather gross)?
Create a view (XIB) with this general layout like the picture above. There is a UILabel, a UITextView and a UIView (the blue rectangle is a UIView with the background color set). Let's call it ThreadView.xib. Hook up the label, textview and view as properties to the view.
We can then have a method to generate one of these views for us to use and a method to add more ThreadViews as subviews based on how many comments/replies a post has.
+ (instancetype)threadViewWithLabelText:(NSString *)labelText
textViewText:(NSString *)textViewText
color:(UIColor *)color
{
ThreadView *threadView = [[[NSBundle mainBundle] loadNibNamed:#"ThreadView"
owner:self
options:nil] firstObject];
if (threadView) {
threadView.label.text = labelText;
threadView.textView.text = textViewText;
threadView.colorView.backgroundColor = color;
}
return threadView;
}
- (void)addCommentView:(ThreadView *)threadView
toViewController:(UIViewController *)viewController
{
threadView.frame = CGRectMake(self.frame.origin.x + 25,
self.textView.frame.origin.y + self.textView.frame.size.height,
self.frame.size.width - (self.frame.origin.x + 10),
self.frame.size.height - (self.textView.frame.origin.y + self.textView.frame.size.height));
[viewController.view addSubview:threadView];
}
Now, in the main view controller, we can create and add these views with just these two method calls:
- (void)viewDidLoad
{
[super viewDidLoad];
// Load the first post
ThreadView *originalPost = [ThreadView threadViewWithLabelText:#"10 Some Words 2014 More Words"
textViewText:loremIpsum
color:[UIColor blueColor]];
originalPost.frame = CGRectMake(self.view.frame.origin.x + 8,
self.view.frame.origin.y + 15,
self.view.frame.size.width - 8,
self.view.frame.size.height - 15);
[self.view addSubview:originalPost];
// Load a comment post
ThreadView *commentPost = [ThreadView threadViewWithLabelText:#"12 December 2014 Maybe A Username"
textViewText:loremIpsum
color:[UIColor greenColor]];
[originalPost addCommentView:commentPost
toViewController:self];
}
This will give us a result like in the picture below. This code could use some refactoring/restructuring, but this should get you started. You can also mix up use of autolayout and/or setting the frames of the views.
Try this?
NSString *html =[NSString stringWithFormat:
#"<html>"
" <head>"
" <style type='text/css'>"
"ul"
"{"
" list-style-type: none;"
"}"
" </style>"
" </head>"
" <body>"
"%# - PARENT"
"<ul>"
"<li>"
"%# - CHILD 1"
"</li>"
"<li>"
"%# - CHILD 2 "
"</li>"
"</ul>"
"</body>"
"</html>"
,#"Parent Title", #"Child Description 1", #"Child Description 2"];
NSError *err = nil;
_label.attributedText =
[[NSAttributedString alloc]
initWithData: [html dataUsingEncoding:NSUTF8StringEncoding]
options: #{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType }
documentAttributes: nil
error: &err];
if(err)
NSLog(#"Unable to parse label text: %#", err);
And the Result is like this .
This can be easily done with Text Kit. I do stuff like this in my app. The difference is I use boxes (nested if needed) to mark each text block. Here is what you should do:
Parse html string (or whatever you use to mark text), mark each text block quote with a custom attribute, like MyTextBlockAttribute, save ranges of each text block (i.e. block quote) and add it as a attribute to the related range of the attributed string(construct this attributed string from your content) and the list attached to the content. Lets call this list MyTextBlockList.
draw text with Text Kit yourself. draw background first (white color, light gray color.. etc, whatever), draw text or vertical lines next. Since you can get each text block's range by loop through the list, you can get bounding rect of these blocks with method [NSLayoutManager range: inTextContainer:textContainer].
Here is the code I used in my app:
// subclass of NSTextContainer
#import "MyTextContainer.h"
#import "MyBlockAttribute.h"
#interface MyTextContainer ()
#property (nonatomic) BOOL isBlock;
#end
#implementation MyTextContainer
- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect
atIndex:(NSUInteger)characterIndex
writingDirection:(NSWritingDirection)baseWritingDirection
remainingRect:(CGRect *)remainingRect {
CGRect output = [super lineFragmentRectForProposedRect:proposedRect
atIndex:characterIndex
writingDirection:baseWritingDirection
remainingRect:remainingRect];
NSUInteger length = self.layoutManager.textStorage.length;
MyTextBlockAttribute *blockAttribute;
if (characterIndex < length) {
blockAttribute = [self.layoutManager.textStorage attribute:MyTextBlockAttributeName atIndex:characterIndex effectiveRange:NULL]; // MyTextBlockAttributeName is a global NSString constant
}
if (blockAttribute) { // text block detected, enter "block" layout mode!
output = CGRectInset(output, blockAttribute.padding, 0.0f); // set the padding when constructing the attributed string from raw html string, use padding to control nesting, inner boxes have bigger padding, again, this is done in parsing pass
if (!self.isBlock) {
self.isBlock = YES;
output = CGRectOffset(output, 0.0f, blockAttribute.padding);
}
} else if (self.isBlock) {
self.isBlock = NO; // just finished a block, return back to the "normal" layout mode
}
// no text block detected, not just finished a block either, do nothing, just return super implementation's output
return output;
}
#end
// drawing code, with drawRect: or other drawing technique, like drawing into bitmap context, doesn't matter
- (void)drawBlockList:(NSArray *)blockList content:(MyContent *)content {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 0.5f);
[[UIColor colorWithWhite:0.98f alpha:1.0f] setFill];
CGContextSaveGState(context);
MyTextContainer *textContainer = content.textContainer;
// since I draw boxes, I have to draw inner text block first, so use reverse enumerator
for (MyTextBlockAttribute *blockAttribute in [blockList reverseObjectEnumerator]) {
if (blockAttribute.noBackground) { // sometimes I don't draw boxes in some conditions
continue;
}
CGRect frame = CGRectIntegral([content.layoutManager boundingRectForGlyphRange:blockAttribute.range inTextContainer:textContainer]);
frame.size.width = textContainer.size.width - 2 * (blockAttribute.padding - MyDefaultMargin); // yeah... there is some margin around the boxes, like html's box model, just some simple math to calculate the accurate rectangles of text blocks
frame.origin.x = blockAttribute.padding - MyDefaultMargin;
frame = CGRectInset(frame, 0, -MyDefaultMargin);
if (blockAttribute.backgroundColor) { // some text blocks may have specific background color
CGContextSaveGState(context);
[blockAttribute.backgroundColor setFill];
CGContextFillRect(context, frame);
CGContextRestoreGState(context);
} else {
CGContextFillRect(context, frame);
}
CGContextStrokeRect(context, frame); // draw borders of text blocks in the last
}
CGContextRestoreGState(context);
}
- (UIImage *)drawContent:(MyContent *)content {
UIImage *output;
UIGraphicsBeginImageContextWithOptions(content.bounds.size, YES, 0.0f); // bounds is calculated in other places
[[UIColor whiteColor] setFill];
UIBezierPath *path = [UIBezierPath bezierPathWithRect:content.bounds];
[path fill];
[self drawBlockList:content.blockList content:content]; // draw background first!
[content.layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, content.textStorage.length) atPoint:CGPointZero]; // every content object has a set of Text Kit core objects, textStorage, textContainer, layoutManager
output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return output;
}
In your case, you don't draw boxes, you draw left borders instead. The technique is the same, hope this can help you!
If you are targeting iOS lower than 7, You can do somethingsimilar by using Core Text, but since Core Text is kind of old C opaque types implementation, I suggest you to use DTCoreText.
If you are using >=iOS7 you can use NSAttributed string and NSXMLDocument. Even if attributed string are available from 3.x they only added them into UIKIT objects into ios6 and changed radically the UIKit behavior in managing them into iOS7.
NSXMLDocument it's helpful because you can render your string representing them as HTML.
This may sound counterintuitive, but have you considered popping it all in a tableView ? you can exploit the indentLevelAtIndexPath: stuff....
so i am trying to make an app that will let the user change the color of the UIImage, for that i am using this function i found
- (UIImage *)imageWithTintColor:(UIColor *)color fraction:(CGFloat)fraction
{
if (color)
{
UIImage *image;
if ([UIScreen instancesRespondToSelector:#selector(scale)])
{
UIGraphicsBeginImageContextWithOptions([self size], NO, 0.f);
}
else
{
UIGraphicsBeginImageContext([self size]);
}
CGRect rect = CGRectZero;
rect.size = [self size];
[color set];
UIRectFill(rect);
[self drawInRect:rect blendMode:kCGBlendModeDestinationIn alpha:1.0];
if (fraction > 0.0)
{
[self drawInRect:rect blendMode:kCGBlendModeSourceAtop alpha:fraction];
}
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
return self;
}
everything works but the CG raster Data is growing in memory
I found the problem, and it was my bad logic, i am using 2 views one to show and one to work with ex:resize, move, rotate. And each time i was addingSubview to both where one of them need to hold just 1 at a time, a simple:
for (UIView *view in 2cndView.subviews)
{
[view removeFromSuperview];
}
did the trick for me
I have been fighting with my app, that suddenly would not launch properly, for some time now. It turned out that when I had switched a number of images' Render as to Template in the Image asset file, it caused the app to totally bomb out. CG Raster Data was growing exponentially and finally caused the app to stop and Xcode just said
Lost connection with iPhone.. check connections etc
It would appear that during every launch the images get reprocessed for this 'Template' setting, which consumed a disgusting amount of RAM and actually left it unable to boot. To solve this, I lowered the resolution of the images - as simple as that.
Here is my function for which I draw a metronome image within my iOS program.
When I load it, the metronome image is loaded, but "flies" down from the top left of the screen to the desired position. This is not the behavior I wanted - I simply want the image to appear at the desired position. Could someone enlighten me why this happens, and how to fix it?
Many thanks in advance.
Pier.
- (void) drawMetronomeForBeat: (int) beatNumber withNumberOfBeats:(int) noBeats
{
// remove any previous instances first
if (metronomeImageView)
{
[metronomeImageView removeFromSuperview];
}
NSString * imageName = #"metronome";
NSString * noBeatsStr = [NSString stringWithFormat:#"%d", noBeats];
NSString * beatNumberStr = [NSString stringWithFormat:#"%d", beatNumber];
imageName = [imageName stringByAppendingString:noBeatsStr];
imageName = [imageName stringByAppendingString:#"b"];
imageName = [imageName stringByAppendingString:beatNumberStr];
UIImage* image = [UIImage imageNamed: imageName];
UIImageView * symbolImageView = [[UIImageView alloc] initWithImage:image];
[symbolImageView setFrame:CGRectMake(410.0f, 215.0f, symbolImageView.frame.size.width, symbolImageView.frame.size.height)];
metronomeImageView = symbolImageView;
[self.view addSubview:symbolImageView];
}
Replace this line
[symbolImageView setFrame:CGRectMake(410.0f, 215.0f, symbolImageView.frame.size.width, symbolImageView.frame.size.height)];
with
[symbolImageView setFrame:CGRectMake(410.0f, 215.0f, 22, 22)]; //Change 22, 22 according to size that you need.
The image view will become larger or smaller according to the size of "real image" you are displaying. So it will move some where so that imageview will stay center aligned.
EDIT:
I think you are setting different frame for symbolImageView.
Try this
symbolImageView.frame = metronomeImageView.frame;
I was using the Controller to display the images. I restructured my code so that the images are always "drawn" in the drawRect of the view code, like this..
- (void)drawRect:(CGRect)rect
{
if (metronomeImage)
{
[metronomeImage drawInRect:CGRectMake(410.0f, 215.0f, metronomeImage.size.width, metronomeImage.size.height)];
}
}
This works with no problems - note that I just used drawInRect and just UIImage instead of setFrame and UIImageView. I suppose I'll just follow the convention that anything graphical should be done in the view code, and not the controller.
I'm trying to draw images on the iPhone using with rounded corners, a la the contact images in the Contacts app. I've got code that generally work, but it occasionally crashes inside of the UIImage drawing routines with an EXEC_BAD_ACCESS - KERN_INVALID_ADDRESS. I thought this might be related to the cropping question I asked a few weeks back, but I believe I'm setting up the clipping path correctly.
Here's the code I'm using - when it doesn't crash, the result looks fine and anybody looking to get a similar look is free to borrow the code.
- (UIImage *)borderedImageWithRect: (CGRect)dstRect radius:(CGFloat)radius {
UIImage *maskedImage = nil;
radius = MIN(radius, .5 * MIN(CGRectGetWidth(dstRect), CGRectGetHeight(dstRect)));
CGRect interiorRect = CGRectInset(dstRect, radius, radius);
UIGraphicsBeginImageContext(dstRect.size);
CGContextRef maskedContextRef = UIGraphicsGetCurrentContext();
CGContextSaveGState(maskedContextRef);
CGMutablePathRef borderPath = CGPathCreateMutable();
CGPathAddArc(borderPath, NULL, CGRectGetMinX(interiorRect), CGRectGetMinY(interiorRect), radius, PNDegreeToRadian(180), PNDegreeToRadian(270), NO);
CGPathAddArc(borderPath, NULL, CGRectGetMaxX(interiorRect), CGRectGetMinY(interiorRect), radius, PNDegreeToRadian(270.0), PNDegreeToRadian(360.0), NO);
CGPathAddArc(borderPath, NULL, CGRectGetMaxX(interiorRect), CGRectGetMaxY(interiorRect), radius, PNDegreeToRadian(0.0), PNDegreeToRadian(90.0), NO);
CGPathAddArc(borderPath, NULL, CGRectGetMinX(interiorRect), CGRectGetMaxY(interiorRect), radius, PNDegreeToRadian(90.0), PNDegreeToRadian(180.0), NO);
CGContextBeginPath(maskedContextRef);
CGContextAddPath(maskedContextRef, borderPath);
CGContextClosePath(maskedContextRef);
CGContextClip(maskedContextRef);
[self drawInRect: dstRect];
maskedImage = UIGraphicsGetImageFromCurrentImageContext();
CGContextRestoreGState(maskedContextRef);
UIGraphicsEndImageContext();
return maskedImage;
}
and here's the crash log. It looks the same whenever I get one of these crashes
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x6e2e6181
Crashed Thread: 0
Thread 0 Crashed:
0 com.apple.CoreGraphics 0x30fe56d8 CGGStateGetRenderingIntent + 4
1 libRIP.A.dylib 0x33c4a7d8 ripc_RenderImage + 104
2 libRIP.A.dylib 0x33c51868 ripc_DrawImage + 3860
3 com.apple.CoreGraphics 0x30fecad4 CGContextDelegateDrawImage + 80
4 com.apple.CoreGraphics 0x30feca40 CGContextDrawImage + 368
5 UIKit 0x30a6a708 -[UIImage drawInRect:blendMode:alpha:] + 1460
6 UIKit 0x30a66904 -[UIImage drawInRect:] + 72
7 MyApp 0x0003f8a8 -[UIImage(PNAdditions) borderedImageWithRect:radius:] (UIImage+PNAdditions.m:187)
Here is an even easier method that is available in iPhone 3.0 and up. Every View-based object has an associated layer. Each layer can have a corner radius set, this will give you just what you want:
UIImageView * roundedView = [[UIImageView alloc] initWithImage: [UIImage imageNamed:#"wood.jpg"]];
// Get the Layer of any view
CALayer * l = [roundedView layer];
[l setMasksToBounds:YES];
[l setCornerRadius:10.0];
// You can even add a border
[l setBorderWidth:4.0];
[l setBorderColor:[[UIColor blueColor] CGColor]];
I'm gonna go ahead here and actually answer the question in the title.
Try this category.
UIImage+additions.h
#import <UIKit/UIKit.h>
#interface UIImage (additions)
-(UIImage*)makeRoundCornersWithRadius:(const CGFloat)RADIUS;
#end
UIImage+additions.m
#import "UIImage+additions.h"
#implementation UIImage (additions)
-(UIImage*)makeRoundCornersWithRadius:(const CGFloat)RADIUS {
UIImage *image = self;
// 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);
const 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:RADIUS] addClip];
// Draw your image
[image drawInRect:RECT];
// Get the image, here setting the UIImageView image
//imageView.image
UIImage* imageNew = UIGraphicsGetImageFromCurrentImageContext();
// Lets forget about that we were drawing
UIGraphicsEndImageContext();
return imageNew;
}
#end
If appIconImage is an UIImageView, then:
appIconImage.image = [UIImage imageWithContentsOfFile:#"image.png"];
appIconImage.layer.masksToBounds = YES;
appIconImage.layer.cornerRadius = 10.0;
appIconImage.layer.borderWidth = 1.0;
appIconImage.layer.borderColor = [[UIColor grayColor] CGColor];
And also remember:
#import <QuartzCore/QuartzCore.h>
I cant offer any insight into your crash, but I thought I would offer another option for rounding the corners. I had a similar problem arise in an application i was working on. Rather than write any code I am overlaying another image which masks off the corners.
If you are calling your method (borderedImageWithRect) in a background thread, crashes might occur since UIGraphics-functions are not thread-safe. In such a case, you must create a context using CGBitmapContextCreate() - see the "Reflection" sample code from the SDK.
The easiest way is to embed a disabled[!] round-rect [not custom!] button in your view (can even do it all in the Interface Builder) and associate your image with it. The image-setting message is different for UIButton (compared to UIImageView), but the overall kludge works like a charm. Use setImage:forState: if you want a centered icon or setBackgroundImage:forState: if you want the whole image with corners cut (like Contacts). Of course if you want to display lots of these images in your drawRect this isn't the right approach, but more likely an embedded view is exactly what you needed anyway...
I would reiterate fjoachim's answer: be cautious when attempting to draw while running on a separate thread, or you may get EXC_BAD_ACCESS errors.
My workaround went something like this:
UIImage *originalImage = [UIImage imageNamed:#"OriginalImage.png"]
[self performSelectorOnMainThread:#selector(displayImageWithRoundedCorners:) withObject:originalImage waitUntilDone:YES];
(In my case I was resizing / scaling UIImages.)
I actually had a chance to talk about this with somebody from Apple at the iPhone Tech Talk in New York. When we talked about it, he was pretty sure it wasn't a threading issued. Instead, he thought that I needed to retain the graphics context that was generated when calling UIGraphicsBeginImageContext. This seems to violate the general rules dealing with retain rules and naming schemes, but this fellow was pretty sure he'd seen the issue previously.
If the memory was getting scribbled, perhaps by another thread, that would certainly explain why I was only seeing the issue occasionally.
I haven't had time to revisit the code and test out the fix, but PCheese's comment made me realize I hadn't posted the info here.
...unless I wrote that down wrong and UIGraphicsBeginImageContext should've been CGBitmapContextCreate...
If it only crashes some of the time, figure out what the crash cases have in common. Is dstRect the same every time? Are the images ever a different size?
Also, you need to CGPathRelease(borderPath), although I doubt that leak is causing your problem.
UIImage *originalImage = [UIImage imageNamed:#"OriginalImage.png"]
[self performSelectorOnMainThread:#selector(displayImageWithRoundedCorners:) withObject:originalImage waitUntilDone:YES];
In Swift 4.2 and Xcode 10.1
let imgView = UIImageView()
imgView.frame = CGRect(x: 200, y: 200, width: 200, height: 200)
imgView.image = UIImage(named: "yourimagename")
imgView.imgViewCorners()
//If you want complete round shape
//imgView.imgViewCorners(width: imgView.frame.width)//Pass ImageView width
view.addSubview(imgView)
extension UIImageView {
//If you want only round corners
func imgViewCorners() {
layer.cornerRadius = 10
layer.borderWidth = 1.0
layer.borderColor = UIColor.red.cgColor
layer.masksToBounds = true
}
//If you want complete round shape
func imgViewCorners(width:CGFloat) {
layer.cornerRadius = width/2
layer.borderWidth = 1.0
layer.borderColor = UIColor.red.cgColor
layer.masksToBounds = true
}
Set the Image in xib or storyboard (image width and height 41x41).
FirstViewController.h
#interface....
IBOutlet UIImageView *testImg;
#end
FirstViewController.m
-(void)viewDidLoad{
testImg.layer.backgroundColor=[[UIColor clearColor] CGColor];
testImg.layer.cornerRadius=20;
testImg.layer.masksToBounds = YES;
}