UITextView文本容器排除路径如果在文本容器顶部且宽度为全宽,则会失败

14
在iOS 8中,我正在尝试将UIImageView作为UITextView的子视图添加,类似于这里显示的内容-但是文本在图像下方。
我想使用排除路径来实现,因为在其他设备上,我可能会根据屏幕大小不同而定位图像。
但是,如果用于创建排除路径的CGRect具有0的Y原点,并占据textView的整个宽度,则排除失败,并且文本出现在排除路径内(因此您可以在该屏幕截图中看到文本显示在imageView的后面)。
为了测试这一点,我使用“单视图”Xcode模板构建了一个简单的应用程序,其中包括以下内容:
- (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,我在viewDidLoad()中设置了如下UITextView堆栈:

// 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.";

然后我在CustomTextContainer中调整Y轴原点,就像这样……但是这也同样失败了。
- (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;
}

我认为这可能是一个需要报告的苹果bug(除非我漏掉了什么显而易见的东西),但如果有人知道解决方法,那将不胜感激。


我也遇到了同样的问题(iOS8)。据我看来,这似乎是苹果的一个bug。如果有人找到了成功的解决方法,请说出来! - Geoff H
另一个解决方法是使用故事板布局,并为不同的屏幕大小类别设置不同的图像、文本和约束。 - Tim
不,这不是个bug。您需要仔细检查您的exclusionPaths,并确保该矩形包含在您的textView.textContainer中。@GeoffH,还需要检查textview.textContainerInsettextView.textContainer.lineFragmentPaddingtextView.contentInset - DawnSong
5个回答

7
这是一个旧的bug。在书籍《Pushing the Limits iOS 7 Programming》的第377页中,作者写道:

在编写时,Text Kit无法正确处理某些类型的排除路径。特别是,如果您的排除路径会强制一些行为空,则整个布局可能会失败。例如,如果您尝试以这种方式在圆形中布局文本,则圆形的顶部可能太小,无法包含任何文本,而NSLayoutManager将静默失败。此限制影响NSTextContainer的所有用途。具体来说,如果lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:返回空CGRect,则整个布局将失败。

也许你可以重写你的自定义NSTextContainer的lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:以解决问题。

...很棒...好的,我选择了一种不同的方法来解决这个问题,但它也带来了自己的问题。 - CQM

4

我也遇到了这个问题。如果您只需要排除textView顶部或底部的全角空格,则可以使用textContainerInset


那正是我所需要的。谢谢! - derpoliuk

2

解决建议:

在文本中添加一个换行符。

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


1
一个快速的解决方法:

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];
}

不幸的是,这似乎也不起作用。它以完全相同的方式失败,直到您将 origin.x 或 origin.y 调整到足够大,以便字符开始显示在左侧或顶部(并且取决于字体大小)。 - Jim Rhoades
尝试调整textView的框架,但这也会将imageView向下推。我还尝试调整textContainer.size.height...但是你不能因为它是只读的。一个丑陋的解决方法可能是将imageView作为self.view的子视图(以及按照您建议的调整textView框架)-并在textView滚动时调整imageView的位置。不确定是否要这样做。无论如何,我非常感谢您的建议。 - Jim Rhoades
还尝试调整textView框架,然后仅调整imageView的Y起点以将其向上移动(给它一个负Y起点)-但是大部分图像应该在的地方有空白的白色空间。 - Jim Rhoades

0

我试图通过排除路径来解决问题。

我的问题是顶级排除路径从未生效,它将内容推向顶部而不是中心,而底部内容的排除路径边距加倍。

下面是对我有效的方法:

  1. 覆盖UITextView
  2. 通过在init中添加以下代码并将其与您的文本容器一起初始化:

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

  3. 根据{{link1:Daniel的回答}},在layoutSubviews中添加以下代码:

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

  4. 在您的UITextView子类init()或故事板中,禁用滚动。

    self.isScrollEnabled = false

有关更多详细信息,请阅读{{link2:这个超级有用的线程}}。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接