I'm adding share functionalities to my iOS app (I have to support down to iOS 5.0
The following COde is to Strike a LABEL IT IS WORKING PROPERLY IN IOS5 BUT CRASHING IN IOS 6
Crashing at CGContextStrokePath(c);
CFIndex lineIndex = 0;
for (id line in lines) {
CGFloat ascent = 0.0f, descent = 0.0f, leading = 0.0f;
CGFloat width = CTLineGetTypographicBounds((__bridge CTLineRef)line, &ascent, &descent, &leading) ;
CGRect lineBounds = CGRectMake(0.0f, 0.0f, width, ascent + descent + leading) ;
lineBounds.origin.x = origins[lineIndex].x;
lineBounds.origin.y = origins[lineIndex].y;
for (id glyphRun in (__bridge NSArray *)CTLineGetGlyphRuns((__bridge CTLineRef)line)) {
NSDictionary *attributes = (__bridge NSDictionary *)CTRunGetAttributes((__bridge CTRunRef) glyphRun);
BOOL strikeOut = [[attributes objectForKey:kTTTStrikeOutAttributeName] boolValue];
NSInteger superscriptStyle = [[attributes objectForKey:(id)kCTSuperscriptAttributeName] integerValue];
if (strikeOut) {
CGRect runBounds = CGRectZero;
CGFloat runAscent = 0.0f;
CGFloat runDescent = 0.0f;
runBounds.size.width = CTRunGetTypographicBounds((__bridge CTRunRef)glyphRun, CFRangeMake(0, 0), &runAscent, &runDescent, NULL);
runBounds.size.height = runAscent + runDescent;
CGFloat xOffset = CTLineGetOffsetForStringIndex((__bridge CTLineRef)line, CTRunGetStringRange((__bridge CTRunRef)glyphRun).location, NULL);
runBounds.origin.x = origins[lineIndex].x + rect.origin.x + xOffset;
runBounds.origin.y = origins[lineIndex].y + rect.origin.y;
runBounds.origin.y -= runDescent;
// Don't draw strikeout too far to the right
if (CGRectGetWidth(runBounds) > CGRectGetWidth(lineBounds)) {
runBounds.size.width = CGRectGetWidth(lineBounds);
}
switch (superscriptStyle) {
case 1:
runBounds.origin.y -= runAscent * 0.47f;
break;
case -1:
runBounds.origin.y += runAscent * 0.25f;
break;
default:
break;
}
// Use text color, or default to black
id color = [attributes objectForKey:(id)kCTForegroundColorAttributeName];
if (color) {
CGContextSetStrokeColorWithColor(c, (__bridge CGColorRef)color);
} else {
CGContextSetGrayStrokeColor(c, 0.0f, 1.0);
}
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)self.font.fontName, self.font.pointSize, NULL);
CGContextSetLineWidth(c, CTFontGetUnderlineThickness(font));
CGFloat y = roundf(runBounds.origin.y + runBounds.size.height / 2.0f);
CGContextMoveToPoint(c, runBounds.origin.x, y);
CGContextAddLineToPoint(c, runBounds.origin.x + runBounds.size.width, y);
CGContextStrokePath(c);
}
}
lineIndex++;
}
}
Its a bug of TTTAttributedLabel check out this issue
Hopefully someone may resolve that and you will get solution. Watchout there until you get solution.
Related
To Erase I'm using kCGBlendModeClear.
e.g. I have one erased image, would like to get redraw on erased image when move on CGContext.
Image : https://www.dropbox.com/s/hsltjqa3ue2dpx8/Simulator%20Screen%20Shot%20-%20iPhone%208%20-%202020-05-09%20at%2019.56.15.png?dl=0
My erase code :
-(void)initialize {
self.userInteractionEnabled = YES;
_tilesFilled = 0;
dragVw = [[UIView alloc] init];
dragVw.backgroundColor = [UIColor redColor];
[dragVw removeFromSuperview];
[self addSubview:dragVw];
if (nil == self.image) {
_tilesX = _tilesY = 0;
self.maskedMatrix = nil;
if (NULL != _imageContext) {
CGContextRelease(_imageContext);
_imageContext = NULL;
}
if (NULL != _colorSpace) {
CGColorSpaceRelease(_colorSpace);
_colorSpace = NULL;
}
} else {
self.touchPoints = [NSMutableArray array];
// CGSize size = self.image.size;
// CGSize fromImage = [self aspectScaledImageSizeForImageView:self image:self.image];
CGSize size = /*CGSizeMake(fromImage.width * pinchScale, fromImage.height * pinchScale);*/ CGSizeMake(self.image.size.width * self.image.scale, self.image.size.height * self.image.scale);
// initalize bitmap context
if (NULL == _colorSpace) {
_colorSpace = CGColorSpaceCreateDeviceRGB();
}
if (NULL != _imageContext) {
CGContextRelease(_imageContext);
}
_imageContext = CGBitmapContextCreate(0, size.width, size.height, 8, size.width * 4 , _colorSpace, kCGImageAlphaPremultipliedLast);
CGContextDrawImage(_imageContext, CGRectMake(0, 0, size.width, size.height), self.image.CGImage);
int blendMode = kCGBlendModeClear;
CGContextSetBlendMode(_imageContext, (CGBlendMode) blendMode);
_tilesX = size.width / (2 * __radius);
_tilesY = size.height / (2 * __radius);
offset = 0;
}
-(void)moveTouches:(NSSet*)touches {
// CGSize fromImage = [self aspectScaledImageSizeForImageView:self image:self.image];
CGSize size = /*CGSizeMake(fromImage.width, fromImage.height);*/ CGSizeMake(self.image.size.width * self.image.scale, self.image.size.height * self.image.scale);
CGContextRef ctx = _imageContext;
CGContextSetFillColorWithColor(ctx,[UIColor clearColor].CGColor);
CGContextSetStrokeColorWithColor(ctx,[UIColor colorWithRed:0 green:0 blue:0 alpha:0].CGColor);
// int tempFilled = _tilesFilled;
// process touches
for (UITouch *touch in touches) {
CGContextBeginPath(ctx);
CGPoint touchPoint = CGPointMake([touch locationInView:self].x , [touch locationInView:self].y);
touchPoint = fromUItoQuartz(touchPoint, self.bounds.size);
touchPoint = scalePoint(touchPoint, self.bounds.size, size);
CGPoint dragPoint = [touch locationInView:self];
CGFloat fineRadius = __radius;
NSLog(#"__radius : %zu",__radius);
dispatch_async(dispatch_get_main_queue(), ^{
self->dragVw.frame = CGRectMake(dragPoint.x - fineRadius/2 , dragPoint.y - fineRadius/2, fineRadius, fineRadius);
self->dragVw.layer.cornerRadius = self->dragVw.frame.size.height / 2;
self->dragVw.alpha = 0;
// NSLog(#"DragVw frame : %#",NSStringFromCGRect(self->dragVw.frame));
});
if(UITouchPhaseBegan == touch.phase){
[self.touchPoints removeAllObjects];
[self.touchPoints addObject:[NSValue valueWithCGPoint:touchPoint]];
[self.touchPoints addObject:[NSValue valueWithCGPoint:touchPoint]];
// on begin, we just draw ellipse
CGRect rect = CGRectMake(touchPoint.x - __radius, touchPoint.y - __radius, __radius*2, __radius*2);
CGContextAddEllipseInRect(ctx, rect);
CGContextFillPath(ctx);
static const FillTileWithPointFunc fillTileFunc = (FillTileWithPointFunc) [self methodForSelector:#selector(fillTileWithPoint:)];
(*fillTileFunc)(self,#selector(fillTileWithPoint:),rect.origin);
} else if (UITouchPhaseMoved == touch.phase) {
[self.touchPoints addObject:[NSValue valueWithCGPoint:touchPoint]];
// then touch moved, we draw superior-width line
CGContextSetStrokeColor(ctx, CGColorGetComponents([UIColor clearColor].CGColor));
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetLineWidth(ctx, 2 * __radius);
while(self.touchPoints.count > 3){
CGPoint bezier[4];
bezier[0] = ((NSValue*)self.touchPoints[1]).CGPointValue;
bezier[3] = ((NSValue*)self.touchPoints[2]).CGPointValue;
CGFloat k = 0.3;
CGFloat len = sqrt(pow(bezier[3].x - bezier[0].x, 2) + pow(bezier[3].y - bezier[0].y, 2));
bezier[1] = ((NSValue*)self.touchPoints[0]).CGPointValue;
bezier[1] = [self normalizeVector:CGPointMake(bezier[0].x - bezier[1].x - (bezier[0].x - bezier[3].x), bezier[0].y - bezier[1].y - (bezier[0].y - bezier[3].y) )];
bezier[1].x *= len * k;
bezier[1].y *= len * k;
bezier[1].x += bezier[0].x;
bezier[1].y += bezier[0].y;
bezier[2] = ((NSValue*)self.touchPoints[3]).CGPointValue;
bezier[2] = [self normalizeVector:CGPointMake( (bezier[3].x - bezier[2].x) - (bezier[3].x - bezier[0].x), (bezier[3].y - bezier[2].y) - (bezier[3].y - bezier[0].y) )];
bezier[2].x *= len * k;
bezier[2].y *= len * k;
bezier[2].x += bezier[3].x;
bezier[2].y += bezier[3].y;
CGContextMoveToPoint(ctx, bezier[0].x , bezier[0].y + offset );
CGContextAddCurveToPoint(ctx, bezier[1].x , bezier[1].y + offset, bezier[2].x , bezier[2].y + offset, bezier[3].x , bezier[3].y + offset);
[self.touchPoints removeObjectAtIndex:0];
}
CGContextStrokePath(ctx);
CGPoint prevPoint = [touch previousLocationInView:self];
prevPoint = fromUItoQuartz(prevPoint, self.bounds.size);
prevPoint = scalePoint(prevPoint, self.bounds.size, size);
static const FillTileWithTwoPointsFunc fillTileFunc = (FillTileWithTwoPointsFunc) [self methodForSelector:#selector(fillTileWithTwoPoints:end:)];
(*fillTileFunc)(self,#selector(fillTileWithTwoPoints:end:),touchPoint, prevPoint);
}
}
}
The current WeexSDK version 0.18.0,I use show similar effect on the simulator and the real machine and the text can't be fully displayed as shown below.
Also I've added the script to show how to calculate the height of the text component in source code is done. suggestSize.heightis always the height I want, but this code is only executed under iOS10. The value of totalHeight is always worse when I have more text lines than iOS10. How do I solve this?
The effect is as follows:
The source code:
- (CGSize)calculateTextHeightWithWidth:(CGFloat)aWidth
{
CGFloat totalHeight = 0;
CGSize suggestSize = CGSizeZero;
NSAttributedString * attributedStringCpy = [self ctAttributedString];
if (!attributedStringCpy) {
return CGSizeZero;
}
if (isnan(aWidth)) {
aWidth = CGFLOAT_MAX;
}
aWidth = [attributedStringCpy boundingRectWithSize:CGSizeMake(aWidth, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil].size.width;
/* Must get ceil of aWidth. Or core text may not return correct bounds.
Maybe aWidth without ceiling triggered some critical conditions. */
aWidth = ceil(aWidth);
CTFramesetterRef ctframesetterRef = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attributedStringCpy));
suggestSize = CTFramesetterSuggestFrameSizeWithConstraints(ctframesetterRef, CFRangeMake(0, 0), NULL, CGSizeMake(aWidth, MAXFLOAT), NULL);
if (_lines == 0) {
// If not line limit use suggestSize directly.
CFRelease(ctframesetterRef);
return CGSizeMake(aWidth, suggestSize.height);
}
CGMutablePathRef path = NULL;
path = CGPathCreateMutable();
// sufficient height to draw text
CGPathAddRect(path, NULL, CGRectMake(0, 0, aWidth, suggestSize.height * 10));
CTFrameRef frameRef = NULL;
frameRef = CTFramesetterCreateFrame(ctframesetterRef, CFRangeMake(0, attributedStringCpy.length), path, NULL);
CGPathRelease(path);
CFRelease(ctframesetterRef);
if (NULL == frameRef) {
//try to protect unexpected crash.
return suggestSize;
}
CFArrayRef lines = CTFrameGetLines(frameRef);
CFIndex lineCount = CFArrayGetCount(lines);
CGFloat ascent = 0;
CGFloat descent = 0;
CGFloat leading = 0;
// height = ascent + descent + lineCount*leading
// ignore linespaing
NSUInteger actualLineCount = 0;
for (CFIndex lineIndex = 0; (!_lines|| lineIndex < _lines) && lineIndex < lineCount; lineIndex ++)
{
CTLineRef lineRef = NULL;
lineRef = (CTLineRef)CFArrayGetValueAtIndex(lines, lineIndex);
CTLineGetTypographicBounds(lineRef, &ascent, &descent, &leading);
totalHeight += ascent + descent;
actualLineCount ++;
}
totalHeight = totalHeight + actualLineCount * leading;
CFRelease(frameRef);
if (WX_SYS_VERSION_LESS_THAN(#"10.0")) {
// there is something wrong with coreText drawing text height, trying to fix this with more efficent way.
if(actualLineCount && actualLineCount < lineCount) {
suggestSize.height = suggestSize.height * actualLineCount / lineCount;
}
return CGSizeMake(aWidth, suggestSize.height);
}
return CGSizeMake(aWidth, totalHeight);
}
I have created a custom view that draws shapes of varying sides. The view is added to main view as a subview as given below. The shapes are of different dimensions.
My source code is given below
-(instancetype) initWithSides:(NSUInteger) sides andFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setBackgroundColor:[UIColor clearColor]];
self.sides = sides;
self.radius = CGRectGetWidth(self.frame) * 1.5 / sides);
}
return self;
}
-(void) drawRect:(CGRect) frame {
// Sides is passed as constructor argument. 4 sides means a quadrilateral etc.
// Get CGPAthRef instance
CGFloat angle = DEGREES_TO_RADIANS(360 / ((CGFloat) self.sides));
int count = 0;
CGFloat xCoord = CGRectGetMidX(self.frame);
CGFloat yCoord = CGRectGetMidY(self.frame);
while (count < self.sides) {
CGFloat xPosition =
xCoord + self.radius * cos(angle * ((CGFloat) count));
CGFloat yPosition =
yCoord + self.radius * sin(angle * ((CGFloat) count));
[self.points addObject:[NSValue valueWithCGPoint:CGPointMake(xPosition, yPosition)]];
count ++;
}
NSValue* v = [self.points firstObject];
CGPoint first = v.CGPointValue;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, first.x, first.y);
for (int ix = 1; ix < [self.points count]; ix++) {
NSValue* pValue = [self.points objectAtIndex:ix];
CGPoint p = pValue.CGPointValue;
CGPathAddLineToPoint(path, nil, p.x, p.y);
}
CGPathCloseSubpath(path);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, path);
[self colourView:context withPath:path];
}
-(void) colourView:(CGContextRef) context withPath:(CGPathRef) ref {
NSUInteger num = arc4random_uniform(8) + 1;
UIColor* color = nil;
switch (num) {
case 1:
color = [UIColor redColor];
break;
case 2:
color = [UIColor greenColor];
break;
case 3:
color = [UIColor yellowColor];
break;
case 4:
color = [UIColor blueColor];
break;
case 5:
color = [UIColor orangeColor];
break;
case 6:
color = [UIColor brownColor];
break;
case 7:
color = [UIColor purpleColor];
break;
case 8:
color = [UIColor blackColor];
break;
default:
break;
}
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillPath(context);
}
This constitutes one single shape. This is how I am drawing the rest of the views.
-(void) initDrawView {
NSUInteger width = CGRectGetWidth(self.view.bounds) / 8;
NSUInteger height = (CGRectGetHeight(self.view.frame))/ 8;
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSingleTap:)];
for (int i = 0 ; i < 8; i++) {
CGFloat yCoord = i * height + i * 15;
for (int j = 0; j < 8; j ++) {
int side = 3 + arc4random() % 8;
CGFloat xCoord = j * width;
SimpleRegularPolygonView* view =
[[SimpleRegularPolygonView alloc] initWithSides:side andFrame:CGRectMake(xCoord, yCoord, width, height)];
[view sizeToFit];
view.viewEffectsDelegate = self;
[view setTag: (8 * i + j)];
[self.view addGestureRecognizer:singleFingerTap];
[self.view addSubview:view];
}
}
}
1) I don't know how to make them have the same dimensions. How can I do that? (First image)
2) the images don't scale up to the size of the UIView (Second image).
Your current code makes the radius inversely proportional to the number of sides:
self.radius = CGRectGetWidth(self.frame) * 1.5 / sides;
so the more sides, the smaller the image. A quick fix would be to just make the radius half the frame width:
self.radius = CGRectGetWidth(self.frame) /2;
This will mean shapes with an even number of sides fill the frame width. But those with an odd number of sides will appear to have space to the left. If you want to adjust for that you will need more detailed calculations for the width, and you will also need to move the "centre" of the shape. For an odd number sides, the radius would need to be:
self.radius = CGRectGetWidth(self.frame) /(1 + cos(angle / 2));
and xCoord would need to be:
CGFloat xCoord = CGRectGetMinX(self.frame) + self.radius * cos(angle/2);
I've been adding shadows to my shapes using CGContextSetShadowWithColor. I'm trying to use the same blur offset height.
However the height of the shadow I'm seeing on different shapes is different. I've no idea why this occurring.
Here's some typical code.
CGSize offset = CGSizeMake(0.1, self.l_shadHeight);
CGContextSetShadowWithColor(context, offset, mauveBlurRadius, mauve.CGColor);
EDIT - FURTHER CODE
+ (NSInteger)setShadowHeight
{
NSInteger retVal = 10;
if (IS_IPHONE_6P)
{
retVal = 7;
}
else if (IS_IPHONE_6)
{
retVal = 7;
}
else if (IS_IPHONE_5 || TARGET_INTERFACE_BUILDER)
{
retVal = 6;
}
else if (IS_IPHONE_4_AND_OLDER || IS_IPHONE)
{
retVal = 6;
}
else if (isiPadPro)
{
retVal = 16;
}
else if (IS_IPAD)
{
retVal = 11;
}
return retVal;
}
//
-(void)setSizingClassValues
{
self.l_shadHeight = [DrawingConstants setShadowHeight];
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
[self setSizingClassValues];
//// General Declarations
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
//// Shadow Declarations
//UIColor* circleShadow = shadowColor2;
UIColor* circleShadow = [shadowColor2 colorWithAlphaComponent: [DrawingConstants SHADOW_ALPHA]];
CGSize circleShadowOffset = CGSizeMake(0.1, self.l_shadHeight);
//CGFloat circleShadowBlurRadius = 5;
CGFloat circleShadowBlurRadius = [DrawingConstants setShadowBlurRadius];
//
//// Frames
CGRect frame = self.bounds;
CGFloat xPadding = 18;
CGFloat YPadding = 19;
CGFloat wPadding = 39;
CGFloat hPadding = 54;
if (isiPadPro || IS_IPAD)
{
if (self.size == sizeSmallerPerformance)
{
//so we don't have to rework autolayout on other screens!
CGFloat fac = 2;
xPadding = xPadding / fac;
YPadding = YPadding / fac;
wPadding = wPadding / fac;
hPadding = hPadding / fac;
}
}
//// Subframes
CGRect group = CGRectMake(CGRectGetMinX(frame) + xPadding,
CGRectGetMinY(frame) + YPadding,
CGRectGetWidth(frame) - wPadding,
CGRectGetHeight(frame) - hPadding);
//
//// Abstracted Attributes
CGFloat circleSurroundStrokeWidth = self.l_borderWidth;
CGFloat tongueLeftStrokeWidth = self.l_strokeWidth;;
CGFloat tongueStrokeWidth = self.l_strokeWidth;
//// Group
{
//// CircleSurround Drawing
UIBezierPath* circleSurroundPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(CGRectGetMinX(group) + floor(CGRectGetWidth(group) * 0.00164) + 0.5, CGRectGetMinY(group) + floor(CGRectGetHeight(group) * 0.00161) + 0.5, floor(CGRectGetWidth(group) * 0.99836) - floor(CGRectGetWidth(group) * 0.00164), floor(CGRectGetHeight(group) * 0.99839) - floor(CGRectGetHeight(group) * 0.00161))];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, circleShadowOffset, circleShadowBlurRadius, circleShadow.CGColor);
[circleFillColour setFill];
[circleSurroundPath fill];
CGContextRestoreGState(context);
[whiteColour setStroke];
circleSurroundPath.lineWidth = circleSurroundStrokeWidth;
[circleSurroundPath stroke];
}
//// Cleanup
CGGradientRelease(innerFaceGradient);
CGColorSpaceRelease(colorSpace);
}
#end
I've found this issue, I had my shadow on the wrong thing. Instead I added it to my outermost group.
Thanks #kurtrevis
I want to calculate the height of a NSAttributedString with the NSParagraphStyle attribute.
I thought it will be easy to create a UILabel with higher spacing between the lines but i can't calculate the right height for my UITableViewCell.
I tried to calculate it with boundingRectWithSize:options: but it's not working at all…
I use NSLayoutManager's usedRectForTextContainer: with a TextView stack disconnected from a UITableView. I answered a similar Stack Overflow question and explained how to implement it.
When the convinient methods from apple dont work, this category provides a good aproximation in most cases.
#implementation NSAttributedString (PixLib)
- (CGFloat)heightForWidth:(CGFloat)width {
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, width, 99999));
CGFloat h = [self heightForPath:path];
CGPathRelease(path);
return h;
}
- (CGFloat)heightForPath:(CGPathRef)path {
CGFloat height = 0;
CTFrameRef frame = [self cfframeForPath:path];
if (frame != NULL) {
NSArray* lines = (__bridge NSArray*)CTFrameGetLines(frame);
int l = [lines count];
if (l > 1) {
CGPoint origins[l];
CTFrameGetLineOrigins(frame, CFRangeMake(0, l), origins);
CGFloat yFirst = origins[0].y;
CGFloat yLast = origins[l-1].y;
CGFloat ascent, descent, leading;
CTLineGetTypographicBounds((__bridge CTLineRef)[lines objectAtIndex:l-1], &ascent, &descent, &leading);
height = ceilf((ascent+descent+leading)*1.3) + yFirst-yLast;
} else {
if (l==1) {
CGFloat ascent, descent, leading;
CTLineGetTypographicBounds((__bridge CTLineRef)[lines objectAtIndex:0], &ascent, &descent, &leading);
height = ceilf(ascent+descent+leading)*1.3;
}
}
CFRelease(frame);
}
return height;
}
- (CTFrameRef)cfframeForPath:(CGPathRef)p {
// hack to avoid bugs width different behavior in iOS <4.3 and >4.3
CGMutablePathRef path = CGPathCreateMutable();
CGRect r = CGPathGetBoundingBox(p);
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformTranslate(t, r.origin.x, r.origin.y);
t = CGAffineTransformScale(t, 1, -1);
t = CGAffineTransformTranslate(t, r.origin.x, - ( r.origin.y + r.size.height ));
CGPathAddPath(path, &t, p);
CGPathMoveToPoint(path, NULL, 0, 0);
CGPathCloseSubpath(path);
// hack end
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CFRelease(framesetter);
CGPathRelease(path);
return frame;
}
#end