【发布时间】:2012-03-05 11:21:33
【问题描述】:
我只想像这样创建一个UITextView(不是两个textview,空白区域是一个uiimage)
【问题讨论】:
-
空白区域是一个uiimage :)
-
你想让用户编辑文本吗?还是只需要以非矩形形状显示文本?
标签: ios objective-c uitextview
我只想像这样创建一个UITextView(不是两个textview,空白区域是一个uiimage)
【问题讨论】:
标签: ios objective-c uitextview
没有内置的UIView 子类可以做到这一点(UIWebView 除外,如果你编写了正确的 HTML 和 CSS),但使用 Core Text 很容易做到。我已经把我的测试项目放在了my ShapedLabel github repository,它的样子是这样的:
该项目有一个名为ShapedLabel 的UIView 子类。以下是它的工作原理。
创建一个名为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;
您需要覆盖每个属性设置器方法以发送setNeedsDisplay,例如:
- (void)setFontName:(NSString *)fontName {
_fontName = [fontName copy];
[self setNeedsDisplay];
}
我依靠 ARC 来担心释放 _fontName 的旧值。如果您不使用 ARC... 开始。从 iOS 4.0 开始,它就变得简单多了。
无论如何,您需要实现drawRect:,真正的工作在此完成。首先,如果已设置,我们将使用 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。请注意,这与CGFont 或UIFont 不同。您可以使用CTFontCreateWithGraphicsFont 将CGFont 转换为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);
我们创建一个 Core Text 框架设置器,它将根据属性字符串布置文本:
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 类的完整源代码。
#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
#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
【讨论】:
正如Dannie P建议的here,使用textView.textContainer.exclusionPaths
Swift 中的示例:
class WrappingTextVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = "ropcap example. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam vulputate ex. Fusce interdum ultricies justo in tempus. Sed ornare justo in purus dignissim, et rutrum diam pulvinar. Quisque tristique eros ligula, at dictum odio tempor sed. Fusce non nisi sapien. Donec libero orci, finibus ac libero ac, tristique pretium ex. Aenean eu lorem ut nulla elementum imperdiet. Ut posuere, nulla ut tincidunt viverra, diam massa tincidunt arcu, in lobortis erat ex sed quam. Mauris lobortis libero magna, suscipit luctus lacus imperdiet eu. Ut non dignissim lacus. Vivamus eget odio massa. Aenean pretium eget erat sed ornare. In quis tortor urna. Quisque euismod, augue vel pretium suscipit, magna diam consequat urna, id aliquet est ligula id eros. Duis eget tristique orci, quis porta turpis. Donec commodo ullamcorper purus. Suspendisse et hendrerit mi. Nulla pellentesque semper nibh vitae vulputate. Pellentesque quis volutpat velit, ut bibendum magna. Morbi sagittis, erat rutrum Suspendisse potenti. Nulla facilisi. Praesent libero est, tincidunt sit amet tempus id, blandit sit amet mi. Morbi sed odio nunc. Mauris lobortis elementum orci, at consectetur nisl egestas a. Pellentesque vel lectus maximus, semper lorem eget, accumsan mi. Etiam semper tellus ac leo porta lobortis."
textView.backgroundColor = .lightGray
textView.textColor = .black
view.addSubview(textView)
textView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
textView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
textView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40).isActive = true
let imageView = UIImageView(image: UIImage(named: "so-icon"))
imageView.backgroundColor = .lightText
imageView.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
imageView.sizeToFit()
textView.addSubview(imageView)
textView.textContainer.exclusionPaths = [UIBezierPath(rect: imageView.frame)]
}
}
结果:
【讨论】: