如何绘制非矩形的UITextView? [英] How to draw a non-rectangle UITextView?

查看:77
本文介绍了如何绘制非矩形的UITextView?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我只想创建一个这样的UITextView(不是两个textview,空白区域是uiimage)

解决方案

没有内置的UIView子类可以做到这一点(如果您编写正确的HTML和CSS,则UIWebView除外),但是这样做很容易使用核心文字.我已将测试项目放入我的ShapedLabel github存储库,如下所示:

该项目有一个名为ShapedLabelUIView子类.它是这样工作的.

创建一个名为ShapedLabelUIView子类.为其提供以下属性:

@property (nonatomic, copy) NSString *text;
@property (nonatomic) UITextAlignment textAlignment;
@property (nonatomic, copy) NSString *fontName;
@property (nonatomic) CGFloat fontSize;
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong) UIColor *shapeColor;
@property (nonatomic, copy) UIBezierPath *path;

您将要覆盖每个属性设置器方法以发送setNeedsDisplay,例如:

- (void)setFontName:(NSString *)fontName {
    _fontName = [fontName copy];
    [self setNeedsDisplay];
}

我依靠ARC担心释放_fontName的旧值.如果您不使用ARC ...,请开始.从iOS 4.0开始,它变得更加容易并且受支持.

无论如何,您将需要实现drawRect:,从而完成实际工作.首先,如果设置了shapeColor,我们将使用shapeColor填充形状:

- (void)drawRect:(CGRect)rect
{
    if (!_path)
        return;

    if (_shapeColor) {
        [_shapeColor setFill];
        [_path fill];
    }

我们进行检查以确保拥有所需的所有其他参数:

    if (!_text || !_textColor || !_fontName || _fontSize <= 0)
        return;

接下来,我们处理textAligment属性:

    CTTextAlignment textAlignment = NO ? 0
        : _textAlignment == UITextAlignmentCenter ? kCTCenterTextAlignment
        : _textAlignment == UITextAlignmentRight ? kCTRightTextAlignment
        : kCTLeftTextAlignment;
    CTParagraphStyleSetting paragraphStyleSettings[] = {
        {
            .spec = kCTParagraphStyleSpecifierAlignment,
            .valueSize = sizeof textAlignment,
            .value = &textAlignment
        }
    };
    CTParagraphStyleRef style = CTParagraphStyleCreate(paragraphStyleSettings, sizeof paragraphStyleSettings / sizeof *paragraphStyleSettings);

我们接下来创建CTFont.请注意,这不同于CGFontUIFont.您可以使用CTFontCreateWithGraphicsFontCGFont转换为CTFont,但是不能轻松地将UIFont转换为CTFont.无论如何,我们只是直接创建CTFont:

    CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)_fontName, _fontSize, NULL);

我们创建属性字典,该字典定义了我们要查看的所有样式属性:

    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
        (__bridge id)font, kCTFontAttributeName,
        _textColor.CGColor, kCTForegroundColorAttributeName,
        style, kCTParagraphStyleAttributeName,
        nil];
    CFRelease(font);
    CFRelease(style);

一旦有了属性字典,就可以创建将属性字典附加到文本字符串的属性字符串.这就是Core Text的用途:

    CFAttributedStringRef trib = CFAttributedStringCreate(NULL, (__bridge CFStringRef)_text, (__bridge CFDictionaryRef)attributes);

我们创建了一个核心文本框架设定器,该框架将布置属性字符串中的文本:

    CTFramesetterRef setter = CTFramesetterCreateWithAttributedString(trib);
    CFRelease(trib);

Core Text假定图形上下文将具有标准" Core Graphics坐标系,其原点位于左下方.但是,UIKit更改了上下文,以将原点放在左上方.我们假设在创建路径时就牢记了这一点.因此,我们需要一个垂直翻转坐标系的变换:

    // Core Text lays out text using the default Core Graphics coordinate system, with the origin at the lower left.  We need to compensate for that, both when laying out the text and when drawing it.
    CGAffineTransform textMatrix = CGAffineTransformIdentity;
    textMatrix = CGAffineTransformTranslate(textMatrix, 0, self.bounds.size.height);
    textMatrix = CGAffineTransformScale(textMatrix, 1, -1);

然后我们可以创建路径的翻转副本:

    CGPathRef flippedPath = CGPathCreateCopyByTransformingPath(_path.CGPath, &textMatrix);

最后,我们可以要求制图员对文本框进行布局.这实际上是使文本适合path属性定义的形状内的内容:

    CTFrameRef frame = CTFramesetterCreateFrame(setter, CFRangeMake(0, 0), flippedPath, NULL);
    CFRelease(flippedPath);
    CFRelease(setter);

最后,我们绘制文本.我们需要再次

    CGContextRef gc = UIGraphicsGetCurrentContext();
    CGContextSaveGState(gc); {
        CGContextConcatCTM(gc, textMatrix);
        CTFrameDraw(frame, gc);
    } CGContextRestoreGState(gc);
    CFRelease(frame);
}

就是这样.现在,您可以在屏幕上贴一个漂亮的标签了.

为后代(如果我删除测试项目),这是ShapedLabel类的完整资源.

ShapedLabel.h

#import <UIKit/UIKit.h>

@interface ShapedLabel : UIView

@property (nonatomic, copy) NSString *text;
@property (nonatomic) UITextAlignment textAlignment;
@property (nonatomic, copy) NSString *fontName;
@property (nonatomic) CGFloat fontSize;
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong) UIColor *shapeColor;
@property (nonatomic, copy) UIBezierPath *path;

@end

ShapedLabel.m

#import "ShapedLabel.h"
#import <CoreText/CoreText.h>

@implementation ShapedLabel

@synthesize fontName = _fontName;
@synthesize fontSize = _fontSize;
@synthesize path = _path;
@synthesize text = _text;
@synthesize textColor = _textColor;
@synthesize shapeColor = _shapeColor;
@synthesize textAlignment = _textAlignment;

- (void)commonInit {
    _text = @"";
    _fontSize = UIFont.systemFontSize;
    // There is no API for just getting the system font name, grr...
    UIFont *uiFont = [UIFont systemFontOfSize:_fontSize];
    _fontName = [uiFont.fontName copy];
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (void)setFontName:(NSString *)fontName {
    _fontName = [fontName copy];
    [self setNeedsDisplay];
}

- (void)setFontSize:(CGFloat)fontSize {
    _fontSize = fontSize;
    [self setNeedsDisplay];
}

- (void)setPath:(UIBezierPath *)path {
    _path = [path copy];
    [self setNeedsDisplay];
}

- (void)setText:(NSString *)text {
    _text = [text copy];
    [self setNeedsDisplay];
}

- (void)setTextColor:(UIColor *)textColor {
    _textColor = textColor;
    [self setNeedsDisplay];
}

- (void)setTextAlignment:(UITextAlignment)textAlignment {
    _textAlignment = textAlignment;
    [self setNeedsDisplay];
}

- (void)setShapeColor:(UIColor *)shapeColor {
    _shapeColor = shapeColor;
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
    if (!_path)
        return;

    if (_shapeColor) {
        [_shapeColor setFill];
        [_path fill];
    }

    if (!_text || !_textColor || !_fontName || _fontSize <= 0)
        return;

    CTTextAlignment textAlignment = NO ? 0
    : _textAlignment == UITextAlignmentCenter ? kCTCenterTextAlignment
    : _textAlignment == UITextAlignmentRight ? kCTRightTextAlignment
    : kCTLeftTextAlignment;
    CTParagraphStyleSetting paragraphStyleSettings[] = {
        {
            .spec = kCTParagraphStyleSpecifierAlignment,
            .valueSize = sizeof textAlignment,
            .value = &textAlignment
        }
    };
    CTParagraphStyleRef style = CTParagraphStyleCreate(paragraphStyleSettings, sizeof paragraphStyleSettings / sizeof *paragraphStyleSettings);

    CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)_fontName, _fontSize, NULL);

    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                (__bridge id)font, kCTFontAttributeName,
                                _textColor.CGColor, kCTForegroundColorAttributeName,
                                style, kCTParagraphStyleAttributeName,
                                nil];
    CFRelease(font);
    CFRelease(style);

    CFAttributedStringRef trib = CFAttributedStringCreate(NULL, (__bridge CFStringRef)_text, (__bridge CFDictionaryRef)attributes);
    CTFramesetterRef setter = CTFramesetterCreateWithAttributedString(trib);
    CFRelease(trib);

    // Core Text lays out text using the default Core Graphics coordinate system, with the origin at the lower left.  We need to compensate for that, both when laying out the text and when drawing it.
    CGAffineTransform textMatrix = CGAffineTransformIdentity;
    textMatrix = CGAffineTransformTranslate(textMatrix, 0, self.bounds.size.height);
    textMatrix = CGAffineTransformScale(textMatrix, 1, -1);

    CGPathRef flippedPath = CGPathCreateCopyByTransformingPath(_path.CGPath, &textMatrix);
    CTFrameRef frame = CTFramesetterCreateFrame(setter, CFRangeMake(0, 0), flippedPath, NULL);
    CFRelease(flippedPath);
    CFRelease(setter);

    CGContextRef gc = UIGraphicsGetCurrentContext();
    CGContextSaveGState(gc); {
        CGContextConcatCTM(gc, textMatrix);
        CTFrameDraw(frame, gc);
    } CGContextRestoreGState(gc);
    CFRelease(frame);
}

@end

I just want to create a UITextView like this (not two textviews, the empty area is a uiimage)

解决方案

There's no built-in UIView subclass that does this (except UIWebView if you write the proper HTML and CSS), but it's quite easy to do using Core Text. I've put my test project in my ShapedLabel github repository, and here's what it looks like:

The project has a UIView subclass called ShapedLabel. Here's how it works.

Create a UIView subclass called ShapedLabel. Give it these properties:

@property (nonatomic, copy) NSString *text;
@property (nonatomic) UITextAlignment textAlignment;
@property (nonatomic, copy) NSString *fontName;
@property (nonatomic) CGFloat fontSize;
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong) UIColor *shapeColor;
@property (nonatomic, copy) UIBezierPath *path;

You'll want to override each property setter method to send setNeedsDisplay, like this for example:

- (void)setFontName:(NSString *)fontName {
    _fontName = [fontName copy];
    [self setNeedsDisplay];
}

I'm relying on ARC to worry about releasing the old value of _fontName. If you're not using ARC... start. It's so much easier and it's supported since iOS 4.0.

Anyway, then you'll need to implement drawRect:, where the real work gets done. First, we'll fill in the shape with the shapeColor if it's set:

- (void)drawRect:(CGRect)rect
{
    if (!_path)
        return;

    if (_shapeColor) {
        [_shapeColor setFill];
        [_path fill];
    }

We check to make sure we have all the other parameters we need:

    if (!_text || !_textColor || !_fontName || _fontSize <= 0)
        return;

Next we handle the textAligment property:

    CTTextAlignment textAlignment = NO ? 0
        : _textAlignment == UITextAlignmentCenter ? kCTCenterTextAlignment
        : _textAlignment == UITextAlignmentRight ? kCTRightTextAlignment
        : kCTLeftTextAlignment;
    CTParagraphStyleSetting paragraphStyleSettings[] = {
        {
            .spec = kCTParagraphStyleSpecifierAlignment,
            .valueSize = sizeof textAlignment,
            .value = &textAlignment
        }
    };
    CTParagraphStyleRef style = CTParagraphStyleCreate(paragraphStyleSettings, sizeof paragraphStyleSettings / sizeof *paragraphStyleSettings);

We create the CTFont next. Note that this is different than a CGFont or a UIFont. You can convert a CGFont to a CTFont using CTFontCreateWithGraphicsFont, but you cannot easily convert a UIFont to a CTFont. Anyway we just create the CTFont directly:

    CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)_fontName, _fontSize, NULL);

We create the attributes dictionary that defines all of the style attributes we want to see:

    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
        (__bridge id)font, kCTFontAttributeName,
        _textColor.CGColor, kCTForegroundColorAttributeName,
        style, kCTParagraphStyleAttributeName,
        nil];
    CFRelease(font);
    CFRelease(style);

Once we have the attributes dictionary, we can create the attributed string that attaches the attributes dictionary to the text string. This is what Core Text uses:

    CFAttributedStringRef trib = CFAttributedStringCreate(NULL, (__bridge CFStringRef)_text, (__bridge CFDictionaryRef)attributes);

We create a Core Text framesetter that will lay out the text from the attributed string:

    CTFramesetterRef setter = CTFramesetterCreateWithAttributedString(trib);
    CFRelease(trib);

Core Text assumes that the graphics context will have the "standard" Core Graphics coordinate system with the origin at the lower left. But UIKit changes the context to put the origin at the upper left. We'll assume that the path was created with that in mind. So we need a transform that flips the coordinate system vertically:

    // Core Text lays out text using the default Core Graphics coordinate system, with the origin at the lower left.  We need to compensate for that, both when laying out the text and when drawing it.
    CGAffineTransform textMatrix = CGAffineTransformIdentity;
    textMatrix = CGAffineTransformTranslate(textMatrix, 0, self.bounds.size.height);
    textMatrix = CGAffineTransformScale(textMatrix, 1, -1);

We can then create a flipped copy of the path:

    CGPathRef flippedPath = CGPathCreateCopyByTransformingPath(_path.CGPath, &textMatrix);

At last we can ask the framesetter to lay out a frame of text. This is what actually fits the text inside the shape defined by the path property:

    CTFrameRef frame = CTFramesetterCreateFrame(setter, CFRangeMake(0, 0), flippedPath, NULL);
    CFRelease(flippedPath);
    CFRelease(setter);

Finally we draw the text. We need to again

    CGContextRef gc = UIGraphicsGetCurrentContext();
    CGContextSaveGState(gc); {
        CGContextConcatCTM(gc, textMatrix);
        CTFrameDraw(frame, gc);
    } CGContextRestoreGState(gc);
    CFRelease(frame);
}

That's pretty much it. You can now put a nice shaped label on the screen.

For posterity (in case I delete the test project), here's the complete source for the ShapedLabel class.

ShapedLabel.h

#import <UIKit/UIKit.h>

@interface ShapedLabel : UIView

@property (nonatomic, copy) NSString *text;
@property (nonatomic) UITextAlignment textAlignment;
@property (nonatomic, copy) NSString *fontName;
@property (nonatomic) CGFloat fontSize;
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong) UIColor *shapeColor;
@property (nonatomic, copy) UIBezierPath *path;

@end

ShapedLabel.m

#import "ShapedLabel.h"
#import <CoreText/CoreText.h>

@implementation ShapedLabel

@synthesize fontName = _fontName;
@synthesize fontSize = _fontSize;
@synthesize path = _path;
@synthesize text = _text;
@synthesize textColor = _textColor;
@synthesize shapeColor = _shapeColor;
@synthesize textAlignment = _textAlignment;

- (void)commonInit {
    _text = @"";
    _fontSize = UIFont.systemFontSize;
    // There is no API for just getting the system font name, grr...
    UIFont *uiFont = [UIFont systemFontOfSize:_fontSize];
    _fontName = [uiFont.fontName copy];
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (void)setFontName:(NSString *)fontName {
    _fontName = [fontName copy];
    [self setNeedsDisplay];
}

- (void)setFontSize:(CGFloat)fontSize {
    _fontSize = fontSize;
    [self setNeedsDisplay];
}

- (void)setPath:(UIBezierPath *)path {
    _path = [path copy];
    [self setNeedsDisplay];
}

- (void)setText:(NSString *)text {
    _text = [text copy];
    [self setNeedsDisplay];
}

- (void)setTextColor:(UIColor *)textColor {
    _textColor = textColor;
    [self setNeedsDisplay];
}

- (void)setTextAlignment:(UITextAlignment)textAlignment {
    _textAlignment = textAlignment;
    [self setNeedsDisplay];
}

- (void)setShapeColor:(UIColor *)shapeColor {
    _shapeColor = shapeColor;
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
    if (!_path)
        return;

    if (_shapeColor) {
        [_shapeColor setFill];
        [_path fill];
    }

    if (!_text || !_textColor || !_fontName || _fontSize <= 0)
        return;

    CTTextAlignment textAlignment = NO ? 0
    : _textAlignment == UITextAlignmentCenter ? kCTCenterTextAlignment
    : _textAlignment == UITextAlignmentRight ? kCTRightTextAlignment
    : kCTLeftTextAlignment;
    CTParagraphStyleSetting paragraphStyleSettings[] = {
        {
            .spec = kCTParagraphStyleSpecifierAlignment,
            .valueSize = sizeof textAlignment,
            .value = &textAlignment
        }
    };
    CTParagraphStyleRef style = CTParagraphStyleCreate(paragraphStyleSettings, sizeof paragraphStyleSettings / sizeof *paragraphStyleSettings);

    CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)_fontName, _fontSize, NULL);

    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                (__bridge id)font, kCTFontAttributeName,
                                _textColor.CGColor, kCTForegroundColorAttributeName,
                                style, kCTParagraphStyleAttributeName,
                                nil];
    CFRelease(font);
    CFRelease(style);

    CFAttributedStringRef trib = CFAttributedStringCreate(NULL, (__bridge CFStringRef)_text, (__bridge CFDictionaryRef)attributes);
    CTFramesetterRef setter = CTFramesetterCreateWithAttributedString(trib);
    CFRelease(trib);

    // Core Text lays out text using the default Core Graphics coordinate system, with the origin at the lower left.  We need to compensate for that, both when laying out the text and when drawing it.
    CGAffineTransform textMatrix = CGAffineTransformIdentity;
    textMatrix = CGAffineTransformTranslate(textMatrix, 0, self.bounds.size.height);
    textMatrix = CGAffineTransformScale(textMatrix, 1, -1);

    CGPathRef flippedPath = CGPathCreateCopyByTransformingPath(_path.CGPath, &textMatrix);
    CTFrameRef frame = CTFramesetterCreateFrame(setter, CFRangeMake(0, 0), flippedPath, NULL);
    CFRelease(flippedPath);
    CFRelease(setter);

    CGContextRef gc = UIGraphicsGetCurrentContext();
    CGContextSaveGState(gc); {
        CGContextConcatCTM(gc, textMatrix);
        CTFrameDraw(frame, gc);
    } CGContextRestoreGState(gc);
    CFRelease(frame);
}

@end

这篇关于如何绘制非矩形的UITextView?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆