Путь исключения UITextView textContainer завершается неудачно, если он на всю ширину и расположен в верхней части textContainer

В iOS 8 я пытаюсь добавить UIImageView в качестве подпредставления UITextView, аналогично тому, что показано здесь - но с текстом под изображением.

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

Однако существует проблема, когда если CGRect, используемый для создания пути исключения, имеет начало Y, равное 0, и занимает всю ширину textView, исключение завершается неудачно, и текст появляется в пути исключения (так что текст отображается за imageView, как вы можете видеть на этом скриншоте).

Чтобы проверить это, я построил простое приложение, использующее шаблон Xcode "single view", со следующим:

- (void)viewDidLoad {
    [super viewDidLoad];

    // set up the textView
    CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
    UITextView *textView = [[UITextView alloc] initWithFrame:frame];
    [textView setFont:[UIFont systemFontOfSize:36.0]];
    [self.view addSubview:textView];
    textView.text = @"I wish this text appeared below the exclusion rect and not within it.";


    // add the photo

    CGFloat textViewWidth = textView.frame.size.width;

    // uncomment if you want to see that the exclusion path DOES work when not taking up the full width:
    // textViewWidth = textViewWidth / 2.0;

    CGFloat originY = 0.0;

    // uncomment if you want to see that the exclusion path DOES work if the Y origin isn't 0
    // originY = 54.0;

    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"photo_thumbnail"]];
    imageView.frame = CGRectMake(0, originY, textViewWidth, textViewWidth);
    imageView.alpha = 0.7; // just so you can see that the text is within the exclusion path (behind photo)
    [textView addSubview:imageView];


    // set the exclusion path (to the same rect as the imageView)
    CGRect exclusionRect = [textView convertRect:imageView.bounds fromView:imageView];
    UIBezierPath *exclusionPath = [UIBezierPath bezierPathWithRect:exclusionRect];
    textView.textContainer.exclusionPaths = @[exclusionPath];
}

Я также попытался создать подкласс NSTextContainer и переопределить метод -lineFragmentRectForProposedRect, но настройка источника Y там, похоже, тоже не помогает.

Чтобы использовать пользовательский NSTextContainer, я настроил стек UITextView следующим образом в viewDidLoad():

// set up the textView stack
NSTextStorage *textStorage = [[NSTextStorage alloc] init];

NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];

CGSize containerSize = CGSizeMake(self.view.frame.size.width, CGFLOAT_MAX);
CustomTextContainer *textContainer = [[CustomTextContainer alloc] initWithSize:containerSize];
[layoutManager addTextContainer:textContainer];

CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
UITextView *textView = [[UITextView alloc] initWithFrame:frame textContainer:textContainer];
[textView setFont:[UIFont systemFontOfSize:36.0]];
[self.view addSubview:textView];

textView.text = @"I wish this text appeared below the exclusion rect and not within it.";

Затем я настраиваю Y-начало в CustomTextContainer следующим образом... но это не так впечатляюще:

- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(CGRect *)remainingRect {

    CGRect correctedRect = proposedRect;

    if (correctedRect.origin.y == 0) {
        correctedRect.origin.y += 414.0;
    }

    correctedRect = [super lineFragmentRectForProposedRect:correctedRect atIndex:characterIndex writingDirection:baseWritingDirection remainingRect:remainingRect];

    NSLog(@"origin.y: %f | characterIndex: %lu", correctedRect.origin.y, (unsigned long)characterIndex);

    return correctedRect;
}

Я полагаю, что это можно считать ошибкой Apple, о которой мне нужно сообщить (если я не пропустил что-то очевидное), но если у кого-то есть обходной путь, это будет высоко оценено.

4 ответа

Это старая ошибка. В книге "Программирование под iOS 7" автор пишет об этом на странице 377:

На момент написания текстовый набор неправильно обрабатывает некоторые виды путей исключения. В частности, если ваши пути исключения приведут к тому, что некоторые строки будут пустыми, весь макет может потерпеть неудачу. Например, если вы попытаетесь расположить текст по кругу таким образом, его верхняя часть может оказаться слишком маленькой, чтобы включить какой-либо текст, и NSLayoutManager будет молча завершаться ошибкой. Это ограничение влияет на все виды использования NSTextContainer. В частности, если lineFragmentRectForProposedRect:atIndex:writingDirection: stillRect: когда-либо возвращает пустой CGRect, весь макет завершится ошибкой.

Может быть, вы можете переопределить lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect: из вашего нестандартного NSTextContainer для обхода.

Я тоже сталкивался с этой проблемой. Если вам нужно только исключить пространство полной ширины в верхней или нижней части textView, вы можете использовать textContainerInset,

Обходной путь предложения:

Добавьте разрыв строки в ваш текст.

textView.text = "\n" + textView.text

Быстрый обходной путь:

CGRect exclusionRect = [textView convertRect:imageView.bounds fromView:imageView];
if (exclusionRect.origin.x <= 0 && exclusionRect.origin.y <= 0 && exclusionRect.size.width >= textView.bounds.size.width) {
  exclusionRect.origin.x = 1;
  exclusionRect.origin.y = 1;
  exclusionRect.size.width -= 2;
}

Ваше изображение по-прежнему будет выглядеть так же, и если вы не используете шрифт с глифами шириной 1px (я даже не уверен, что это возможно при кернинге и т. Д.), Ваш exclusionRect будет гарантированно меньше полной ширины.

Мне было бы интересно узнать, какие результаты вы увидите, если разрешите перемещать прямоугольник в режиме реального времени. Прикрепить UIPanGestureRecognizer и обновите свой exclusionRect пока вы двигаетесь по экрану. В какой момент текст попадает в изображение?

Изменить: Если вы видите проблемы, пока он не может вместить хотя бы один символ, возможно, попробуйте настроить текстовый фрейм.

if (exclusionRect.origin.x <= 0 && exclusionRect.origin.y <= 0 && exclusionRect.size.width >= textView.bounds.size.width) {
  frame.origin.y += CGRectGetMaxY(exclusionRect);
  frame.size.height -= CGRectGetMaxY(exclusionRect);
  [textView setFrame:frame];
}

Я слишком старался поиграть с путем исключения.

Моя проблема заключалась в том, что путь исключения верхнего уровня никогда не работал, он подталкивал контент вверх, а не в центр, в то время как нижний контент имел двойное поле пути исключения.

Вот что у меня сработало:

  1. Переопределить UITextView
  2. Запустите его с помощью текстового контейнера, поместив его внутрь init:

    super.init(frame: frame, textContainer: textContainer)

  3. Согласно ответу Даниэля, вставьте внутрьlayoutSubviews:

    self.textContainerInset = UIEdgeInsets.init(top: t, left: l, bottom: b, right: r)

  4. Внутри вашего UITextView подкласс init() или в раскадровке отключите прокрутку.

    self.isScrollEnabled = false

Для получения более подробной информации прочтите эту очень полезную ветку.

Другие вопросы по тегам