Есть ли способ контролировать высоту строки для многострочного текста, нарисованного Quartz в iPhone?

Я использую Quartz для рендеринга ячеек в своих табличных представлениях. Это работает хорошо, но для соответствия концепции дизайна я должен каким-то образом отрегулировать высоту строки многострочного текста. На данный момент я использую удобные дополнения UIKit к NSString для отображения текста:

– drawInRect:withFont:lineBreakMode:alignment:

Однако я нигде не могу найти документацию по настройке межстрочного интервала. UIWebView способен на это, поэтому он должен использовать API более низкого уровня для вычисления свойства CSS высоты строки.
Можете ли вы порекомендовать решение, совместимое с iOS 3.0, 3.1? Я знаю, что мог бы попробовать использовать основной текст, но он доступен в версии iOS 3.2.


person Lukasz    schedule 06.11.2011    source источник
comment
Вы действительно хотите сохранить совместимость с iOS 3? Единственные люди, которые не обновляются, — это те, кто, вероятно, никогда не использует магазин приложений (или не подключает свои устройства к своим настольным компьютерам).   -  person Michael Dautermann    schedule 06.11.2011
comment
Это интересный момент, и я думал об этом. Но возможно ли, что есть люди с iPhone 3G, но не обновляющиеся до iOS4 из соображений производительности?   -  person Lukasz    schedule 06.11.2011


Ответы (1)


Я думаю, что короткий ответ здесь нет. Я работал с этой проблемой некоторое время, и в конечном итоге мне пришлось реализовать собственное решение для этикетки. Я опубликую это решение ниже. Надеюсь, это поможет вам реализовать что-то самостоятельно. Извините за длину и количество файлов, я разбил его для удобства тестирования, и он также обрабатывает многоточие.

//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
person Micah Hainline    schedule 24.02.2012