Как нарисовать не прямоугольник UITextView? - PullRequest
6 голосов
/ 14 февраля 2012

Я просто хочу создать UITextView вот так (не два текстовых просмотра, пустая область - это изображение)

enter image description here

Ответы [ 2 ]

15 голосов
/ 14 февраля 2012

Нет встроенного подкласса UIView, который делает это (кроме UIWebView, если вы пишете правильный HTML и CSS), но это довольно легко сделать с помощью Core Text. Я поместил свой тестовый проект в мой репозиторий ShapedLabel github , и вот как это выглядит:

ShapedLabel screen shot

Проект имеет подкласс UIView с именем ShapedLabel. Вот как это работает.

Создать подкласс UIView с именем ShapedLabel. Дайте ему эти свойства:

@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. Вы можете конвертировать CGFont в CTFont, используя CTFontCreateWithGraphicsFont, но вы не можете легко конвертировать 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.

ShapedLabel.h

#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

ShapedLabel.m

#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
0 голосов
/ 20 ноября 2018

Как Данни П предложил здесь , используйте 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)]
  }
}

Результат:

Text wraps around image

Полный пример на github

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...