Путь исключения 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];
}
Я слишком старался поиграть с путем исключения.
Моя проблема заключалась в том, что путь исключения верхнего уровня никогда не работал, он подталкивал контент вверх, а не в центр, в то время как нижний контент имел двойное поле пути исключения.
Вот что у меня сработало:
- Переопределить
UITextView
Запустите его с помощью текстового контейнера, поместив его внутрь
init
:super.init(frame: frame, textContainer: textContainer)
Согласно ответу Даниэля, вставьте внутрь
layoutSubviews
:self.textContainerInset = UIEdgeInsets.init(top: t, left: l, bottom: b, right: r)
Внутри вашего
UITextView
подклассinit()
или в раскадровке отключите прокрутку.self.isScrollEnabled = false
Для получения более подробной информации прочтите эту очень полезную ветку.