有没有办法控制由Quartz在iPhone中绘制的多行文字的行高?

时间:2011-11-06 13:21:01

标签: iphone objective-c cocoa-touch quartz-graphics multiline

我在表格视图中使用Quartz进行单元格渲染。它工作得很好,但为了满足设计理念,我必须以某种方式找到调整多行文字行高的方法。 目前我正在使用方便的UIKit添加到NSString来呈现文本:

– drawInRect:withFont:lineBreakMode:alignment:

但是,我无法在文档中找到设置行间距的方法。 UIWebView具有这种能力,因此它必须使用一些较低级别的API来计算行高CSS属性 你能推荐与iOS 3.0,3.1兼容的解决方案吗? 我知道我可以尝试使用Core Text,但可以从iOS 3.2获得。

1 个答案:

答案 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