Text
Attributed Strings
在 iOS 6以前,像UILabel,UITextView这些显示文字的控件仅仅支持单一的字体和大小,如果你想设置部分文字的特殊类型,你必须使用底层的技术,Core Text。为了显示这些特殊的文字效果,你可以使用CATextLayer或者用Core Text来绘制,这将是非常复杂的工作。
从iOS 6开始,NSAttributedString集成了这些功能,你可以直接绘制不同类型的文字,或者可以随便地控制文字的显示。
文字的属性是用字典来表示的:
NSFontAttributeName
一个UIFont,包括font family,style和size。
NSForegroundColorAttributeName
文字颜色,一个UIColor
NSBackgroundColorAttributeName
文字后面的颜色,一个UIColor。例如你可以使用这个Key来高亮显示一个文字。
NSLigatureAttributeName
一个0到1范围的NSNumber,表示是否使用连字(对于中文没有效果)
NSKernAttributeName
一个包装了浮点类型的字距的NSNumber。负值表示同一行中字与字之间的距离变短,正值表示同一行中字与字之间的距离变长。
字符间距 (Kerning) 的设定可以增加或减少某些字符之间的空格以改善其外观。大多数字体都包括有自动减少某些字母对之间的空格大小的信息,如 TA 或 Va 等。在默认情况下,Fireworks 在显示文本的时候会使用字体的字间距信息的,但如果是在查看比较小的字体或者没有经过消除锯齿处理的文本时你可以将字间距关闭。当这个Key的值为[NSNull null]时,如果字体支持自动字间距,则会开启。
NSStrikethroughStyleAttributeName
一个0到1范围的NSNumber
NSUnderlineStyleAttributeName
一个0到1范围的NSNumber
NSStrokeColorAttributeName
描边的颜色,一个UIColor
NSStrokeWidthAttributeName
一个包装了浮点数据的NSNumber。如果不是0,就是正数或者负数。如果是正数,字形会被描边但是不填充,给人一种轮廓效果,如果没有没有定义stroke color,那么会使用foreground color来描边。如果是负数,字形会被描边,也会被填充(foreground color用来填充,stroke color用来描边)。
NSShadowAttributeName
一个NSShadow对象。这个对象包含了shadowOffset,shadowColor,shadowBlurRadius
NSParagraphStyleAttributeName
一个NSParagraphStyle对象。有下列的属性:
-
alignment
— NSTextAlignmentLeft
— NSTextAlignmentCenter
— NSTextAlignmentRight
— NSTextAlignmentJustified (iOS 6新增)
— NSTextAlignmentNatural (根据书写方向,左对齐或者右对齐)
-
lineBreakMode
— NSLineBreakByWordWrapping
— NSLineBreakByCharWrapping
— NSLineBreakByClipping
— NSLineBreakByTruncatingHead
— NSLineBreakByTruncatingTail
— NSLineBreakByTruncatingMiddle
-
firstLineHeadIndent, headIndent (左边距), tailIndent (右边距)
-
lineHeightMultiple,maximumLineHeight,minimumLineHeight
-
lineSpacing
-
paragraphSpacing,paragraphSpacingBefore
-
hyphenationFactor (一个0到1之间的浮点类型)
下面我们实践一下:
NSString* s1 = @"The Gettysburg Address, as delivered on a certain occasion "
@"(namely Thursday, November 19, 1863) by A. Lincoln";
NSMutableAttributedString* content =
[[NSMutableAttributedString alloc]
initWithString:s1
attributes:
@{
NSFontAttributeName:
[UIFont fontWithName:@"Arial-BoldMT" size:15],
NSForegroundColorAttributeName:
[UIColor colorWithRed:0.251 green:0.000 blue:0.502 alpha:1]
}];
NSRange r = [s1 rangeOfString:@"Gettysburg Address"];
[content addAttributes:
@{
NSStrokeColorAttributeName:[UIColor redColor],
NSStrokeWidthAttributeName: @-2.0
} range:r];
添加段落的类型:
NSMutableParagraphStyle* para = [NSMutableParagraphStyle new];
para.headIndent = 10;
para.firstLineHeadIndent = 10;
para.tailIndent = -10;
para.lineBreakMode = NSLineBreakByWordWrapping;
para.alignment = NSTextAlignmentCenter;
para.paragraphSpacing = 15;
[content addAttribute:NSParagraphStyleAttributeName
value:para range:NSMakeRange(0,1)];
结果如上图所示!
下面生成如下图的效果:
首先添加一段新的文字:
NSString* s2 = @"Fourscore and seven years ago, our fathers brought forth "
@"upon this continent a new nation, conceived in liberty and dedicated "
@"to the proposition that all men are created equal.";
NSMutableAttributedString* content2 =
[[NSMutableAttributedString alloc]
initWithString:s2
attributes:
@{
NSFontAttributeName:
[UIFont fontWithName:@"HoeflerText-Black" size:16]
}];
[content2 setAttributes:
@{
NSFontAttributeName:[UIFont fontWithName:@"HoeflerText-Black" size:24]
} range:NSMakeRange(0,1)];
[content2 addAttributes:
@{
NSKernAttributeName:@-4
} range:NSMakeRange(0,1)];
然后添加段落类型:
NSMutableParagraphStyle* para2 = [NSMutableParagraphStyle new];
para2.headIndent = 10;
para2.firstLineHeadIndent = 10;
para2.tailIndent = -10;
para2.lineBreakMode = NSLineBreakByWordWrapping;
para2.alignment = NSTextAlignmentJustified;
para2.lineHeightMultiple = 1.2;
para2.hyphenationFactor = 1.0;
[content2 addAttribute:NSParagraphStyleAttributeName
value:para2 range:NSMakeRange(0,1)];
最后把上面的content 和这里的 content2组合在一起:
int end = content.length;
[content replaceCharactersInRange:NSMakeRange(end, 0) withString:@"\n"];
[content appendAttributedString:content2];
我们可以遍历一个NSAttributedString 每个字符的属性和类型:
[content enumerateAttribute:NSFontAttributeName
inRange:NSMakeRange(0,content.length)
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(id value, NSRange range, BOOL *stop)
{
UIFont* font = value;
if (font.pointSize == 15)
}];
我们也可以不借助UILabel来管理一个attributed string,我们可以直接绘制:
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
[[UIColor whiteColor] setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
[self.content drawInRect:rect];
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
然后你可以把这个UIImage显示出来,同样的,也可以在UIView 的 drawRect:中直接绘制:
- (void)drawRect:(CGRect)rect {
CGRect r = CGRectOffset(rect, 0, 2); // shoved down a little from top
[self.attributedText drawWithRect:r
}
同样的,我们可以在UITableView委托中的tableView:heightForRowAtIndexPath:方法里,计算或者限制attributed string的高度:
CGRect r =
[s boundingRectWithSize:CGSizeMake(320,10000)
options:NSStringDrawingUsesLineFragmentOrigin context:nil];
CGFloat result = r.size.height;
if (result > 200) // set arbitrary limit on cell heights
result = 200;
Text Field 委托和 控制事件消息
UITextField中,有一个委托方法textField:shouldChangeCharactersInRange:replacementString:。我们可以在这里控制和调整用户输入的文字和类型:
-(BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
NSString* lc = [string lowercaseString];
if ([string isEqualToString:lc])
return YES;
textField.text = [textField.text stringByReplacingCharactersInRange:range
withString:lc];
return NO;
}
在 iOS 6中,UITextField 增加了一个typingAttributes属性,用来设置用户即将要输入的文字的样式,但是也不是所有的属性都可以设置,比如下划线就不可以:
-(BOOL)textField:(UITextField )textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString )string { NSDictionary d = textField.typingAttributes; NSMutableDictionary md = [d mutableCopy]; [md addEntriesFromDictionary: @{NSForegroundColorAttributeName:[UIColor redColor]}]; textField.typingAttributes = md; return YES; }
Text Field 菜单
当一个用户在UITextField里面双击或者长按时,一个菜单会出现。它包括的菜单项有:Select,Select All, Paste,Copy,Cut 和 Suggest。这个菜单可以自定义。但是你不知道选择的text field的信息,有时候会比较难判断应该显示什么菜单。有几点关键的事实需要知道:
-
你可以通过一个全局单例UIMenuController对象添加菜单项。它的menuItems属性是一个包含了自定义菜单项的数组。一个菜单项就是一个UIMenuItem,只是一个简单的标题和一个动作选择器(action selector)。当用户点击这个菜单时,对应的动作会无目标地沿着响应链向上发送。
-
标准的菜单项动作都是无目标的。标准的动作包括:
— cut:
— copy:
— select:
— selectAll:
— paste:
— delete:
— _promptForReplace:
— _define:
— _showTextStyleOptions: — toggleBoldface:
— toggleItalics:
— toggleUnderline:
-
通过实现UIResponder 方法 canPerformAction:withSender:来在响应链中控制任意菜单的显示和不显示。
下面我们实现一个当用户在text field中选择了美国的两个字母缩写的州的名字时,可以选择expand 菜单,然后就会显示这个州的全名“California”。
首先,我们在appDelegate中,在app启动时,添加一个全局的菜单项:
UIMenuItem *mi = [[UIMenuItem alloc] initWithTitle:@"Expand"
action:@selector(expand:)];
UIMenuController *mc = [UIMenuController sharedMenuController];
mc.menuItems = @[mi];
然后,在我们的UITextField子类中,实现canPerformAction:withSender:方法来显示这个菜单,这个方法会在text field成为第一响应者时被调用,因此其它的UITextField不会显示我们的菜单,只有使用了这个子类的UITextField才会显示。
- (BOOL) canPerformAction:(SEL)action withSender: (id) sender {
if (action == @selector(expand:))
return ([self.text length] == 2); // 这里可以添加更多的逻辑
return NO;
}
当用户点击Expand 菜单时,expand:消息会被发送到响应者链。我们在这个UITextField里面捕获这个消息:
- (void) expand: (id) sender {
NSString* s = self.text;
// ... alter s here ...
self.text = s;
}
我们还可以同时让copy菜单也显示出来:
- (BOOL) canPerformAction:(SEL)action withSender:(id)sender {
if (action == @selector(expand:))
return ([self.text length] == 2);
if (action == @selector(copy:))
return [super canPerformAction:action withSender:sender];
return NO;
}
现在我们可以实现copy:方法,以及修改它的行为:
- (void) copy: (id) sender {
[super copy: sender];
UIPasteboard* pb = [UIPasteboard generalPasteboard];
NSString* s = pb.string;
// ... alter s here ....
pb.string = s;
}