我在表格视图中使用Quartz进行单元格渲染。它工作得很好,但为了满足设计理念,我必须以某种方式找到调整多行文字行高的方法。 目前我正在使用方便的UIKit添加到NSString来呈现文本:
– drawInRect:withFont:lineBreakMode:alignment:
但是,我无法在文档中找到设置行间距的方法。 UIWebView具有这种能力,因此它必须使用一些较低级别的API来计算行高CSS属性 你能推荐与iOS 3.0,3.1兼容的解决方案吗? 我知道我可以尝试使用Core Text,但可以从iOS 3.2获得。
答案 0 :(得分:2)
我认为这里简短的答案是否定的。我已经解决了这个问题一段时间了,并且最终必须为标签实现我自己的解决方案。我将在下面发布此解决方案。希望它有助于自己实现一些东西。请原谅文件的长度和数量,我将其分解为可测试性,并且它也处理椭圆化。
//MHLabel.h
#import <UIKit/UIKit.h>
#import "MHTextRuler.h"
@interface MHLabel : UIView<MHTextRuler> {
}
@property (nonatomic, copy) NSString *text;
@property (nonatomic, retain) UIFont *font;
@property (nonatomic, retain) UIColor *textColor;
@property (nonatomic) UITextAlignment textAlignment;
@property (nonatomic) CGFloat lineSpacingMuliplier;
@property (nonatomic) int maxLines;
@property (nonatomic, readonly) int currentLines;
@property (nonatomic, readonly) BOOL ellipsized;
- (CGFloat) constrainHeightForCurrentWidth;
@end
//MHLabel.m
#import "MHLabel.h"
#import "UILabel+MH.h"
#import "MHUI.h"
#import "MHLabelLines.h"
#import "MHLabelLayout.h"
@interface MHLabel()
- (MHLabelLines *) linesForWidth: (CGFloat) width;
@property (nonatomic, retain) NSMutableDictionary *lineCache;
@end
@implementation MHLabel
@synthesize text = _text;
@synthesize font = _font;
@synthesize textColor = _textColor;
@synthesize textAlignment = _textAlignment;
@synthesize lineSpacingMuliplier = _lineSpacingMuliplier;
@synthesize maxLines = _maxLines;
@synthesize lineCache = _lineCache;
- (id) initWithFrame: (CGRect) frame {
if ((self = [super initWithFrame: frame])) {
self.opaque = NO;
self.maxLines = 0;
self.lineSpacingMuliplier = 1;
self.textAlignment = UITextAlignmentLeft;
self.font = [MHUI fontOfSize: 16];
self.textColor = [MHUI darkGrayText];
self.lineCache = [NSMutableDictionary dictionary];
self.contentMode = UIViewContentModeTopLeft;
}
return self;
}
- (void) dealloc {
self.text = nil;
self.font = nil;
self.textColor = nil;
self.lineCache = nil;
[super dealloc];
}
- (void) setFrame: (CGRect) frame {
[super setFrame: frame];
[self setNeedsDisplay];
}
- (void) setText: (NSString *) text {
if (![text isEqualToString: _text]) {
[_text release];
_text = [text copy];
[self.lineCache removeAllObjects];
[self setNeedsDisplay];
}
}
- (void) setFont: (UIFont *) font {
if (![font isEqual: _font]) {
[_font release];
_font = font;
[_font retain];
[self.lineCache removeAllObjects];
[self setNeedsDisplay];
}
}
- (void) setMaxLines: (NSInteger) maxLines {
if (maxLines != _maxLines) {
_maxLines = maxLines;
[self.lineCache removeAllObjects];
[self setNeedsDisplay];
}
}
- (void) setTextColor: (UIColor *) textColor {
if (![textColor isEqual: _textColor]) {
[_textColor release];
_textColor = textColor;
[_textColor retain];
[self setNeedsDisplay];
}
}
- (void) setLineSpacingMuliplier: (CGFloat) lineSpacingMuliplier {
if (lineSpacingMuliplier != _lineSpacingMuliplier) {
_lineSpacingMuliplier = lineSpacingMuliplier;
[self setNeedsDisplay];
}
}
- (MHLabelLines *) linesForWidth: (CGFloat) width {
NSString *key = [NSString stringWithFormat: @"w%d", (int) width];
if (![self.lineCache objectForKey: key]) {
MHLabelLines *labelLines = [MHLabelLayout linesForText: self.text constrainedToLines: self.maxLines andWidth: width withRuler: self];
[self.lineCache setObject: labelLines forKey: key];
}
return [self.lineCache objectForKey: key];
}
- (CGFloat) constrainHeightForCurrentWidth {
CGSize size = [self sizeThatFits: CGSizeMake(self.bounds.size.width, CGFLOAT_MAX)];
CGRect newFrame = self.frame;
newFrame.size = size;
self.frame = newFrame;
return size.height;
}
- (CGSize) sizeThatFits: (CGSize) size {
CGFloat width = size.width;
NSArray *lines = [self linesForWidth: width].lines;
CGFloat textHeight = [@"X" sizeWithFont: self.font].height;
CGFloat height = floorf(textHeight * self.lineSpacingMuliplier) * [lines count];
return CGSizeMake(width, height);
}
- (void) drawRect: (CGRect) rect {
CGFloat width = rect.size.width;
NSArray *lines = [self linesForWidth: width].lines;
CGFloat y = 0;
CGFloat textHeight = [@"X" sizeWithFont: self.font].height;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [self.textColor CGColor]);
for (NSString *string in lines) {
CGFloat x;
if (self.textAlignment == UITextAlignmentLeft) {
x = 0;
} else {
CGFloat textWidth = [string sizeWithFont: self.font].width;
if (self.textAlignment == UITextAlignmentCenter) {
x = MAX(0, roundf((width - textWidth) / 2));
} else {
x = MAX(0, width - textWidth);
}
}
[string drawAtPoint: CGPointMake(x, y) forWidth: width withFont: self.font lineBreakMode: UILineBreakModeMiddleTruncation];
y = floorf(y + (textHeight * self.lineSpacingMuliplier));
}
}
- (int) currentLines {
return [[self linesForWidth: self.bounds.size.width].lines count];
}
- (BOOL) ellipsized {
return [self linesForWidth: self.bounds.size.width].ellipsized;
}
- (CGFloat) widthForText: (NSString *) text {
return [text sizeWithFont: self.font].width;
}
@end
//MHLabelLines.h
#import <Foundation/Foundation.h>
@interface MHLabelLines : NSObject {
}
@property (nonatomic, retain) NSArray *lines;
@property (nonatomic) BOOL ellipsized;
@end
//MHLabelLines.m
#import "MHLabelLines.h"
@implementation MHLabelLines
@synthesize lines = _lines;
@synthesize ellipsized = _ellipsized;
- (void) dealloc {
self.lines = nil;
[super dealloc];
}
@end
//MHLabelLayout.h
#import <Foundation/Foundation.h>
#import "MHLabelLines.h"
#import "MHTextRuler.h"
@interface MHLabelLayout : NSObject {
}
+ (MHLabelLines *) linesForText: (NSString *) text constrainedToLines: (int) maxLines andWidth: (CGFloat) width withRuler: (id<MHTextRuler>) ruler;
@end
//MHLabelLayout.m
#import "MHLabelLayout.h"
@interface MHLabelLayout()
+ (int) lastIndexForString: (NSString *) string thatFits: (CGFloat) width withRuler: (id<MHTextRuler>) ruler;
@end
@implementation MHLabelLayout
+ (MHLabelLines *) linesForText: (NSString *) text constrainedToLines: (int) maxLines andWidth: (CGFloat) width withRuler: (id<MHTextRuler>) ruler {
MHLabelLines *labelLines = [[[MHLabelLines alloc] init] autorelease];
NSMutableArray *lines = [NSMutableArray array];
NSString *remainingText = text;
while ([remainingText length] > 0 && (maxLines == 0 || [lines count] < maxLines)) {
int nextLineLastIndex = [self lastIndexForString: remainingText thatFits: width withRuler: ruler];
NSString *nextString = [remainingText substringToIndex: nextLineLastIndex];
remainingText = [remainingText substringFromIndex: MIN(nextLineLastIndex + 1, [remainingText length])];
if ([lines count] + 1 == maxLines && [remainingText length]) {
labelLines.ellipsized = YES;
nextString = [nextString stringByAppendingString: @"..."];
int ellipsizedIndex = [self lastIndexForString: nextString thatFits: width withRuler: ruler];
while (ellipsizedIndex + 1 < [nextString length]) {
nextString = [[nextString substringToIndex: ellipsizedIndex] stringByAppendingString: @"..."];
ellipsizedIndex = [self lastIndexForString: nextString thatFits: width withRuler: ruler];
}
}
[lines addObject: nextString];
}
labelLines.lines = lines;
return labelLines;
}
+ (int) lastIndexForString: (NSString *) string thatFits: (CGFloat) width withRuler: (id<MHTextRuler>) ruler {
int index = 0;
int nextIndex = 0;
int stringLength = [string length];
while (index < stringLength) {
nextIndex = index + 1;
NSRange searchRange = NSMakeRange(nextIndex, stringLength - nextIndex);
NSRange foundRange = [string rangeOfString: @" " options: NSLiteralSearch range: searchRange];
if (foundRange.location == NSNotFound) {
nextIndex = stringLength;
} else {
nextIndex = foundRange.location;
}
CGFloat nextStringWidth = [ruler widthForText: [string substringToIndex: nextIndex]];
if (nextStringWidth > width) {
if (index == 0) {
index = nextIndex;
}
break;
} else {
index = nextIndex;
}
}
NSRange newlineRange = [string rangeOfString: @"\n" options: NSLiteralSearch range: NSMakeRange(0, index)];
if (newlineRange.location != NSNotFound) {
index = newlineRange.location;
}
return index;
}
@end
//MHTextRuler.h
#import <Foundation/Foundation.h>
@protocol MHTextRuler <NSObject>
- (CGFloat) widthForText: (NSString *) text;
@end