6

Summary:

In iOS 6, I centered text vertically in a UITextView by key-value-observing the text view's contentSize property (https://stackoverflow.com/a/12591299/1239263). When I upgraded to iOS 7, the latter technique worked inconsistently.

Rather than try to fix the KVO technique, I would rather use Text Kit to center text vertically in a UITextView.

I have designed a solution using Text Kit, but it breaks upon device rotation.

The UITextView is orange.

When the text view initially loads, the text is centered properly:

enter image description here

When the device rotates to landscape, the text is still centered properly:

enter image description here

However, when the device rotates back to portrait, the text is not centered properly. The text should be on one line, as it was in the first screenshot above.

enter image description here

For the latter screenshot, logging geometries to the console reveals that the text view's text-container width is too narrow.

Details:

@interface ViewController ()

// text view is created in storyboard; it's added to the root view and it's using auto layout
@property (weak, nonatomic) IBOutlet UITextView *textView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSTextContainer *textContainer = self.textView.textContainer;
   [textContainer setWidthTracksTextView:YES];  
}

- (void)centerText
{
    NSTextContainer *container = self.textView.textContainer;
    NSLayoutManager *layoutManager = container.layoutManager;

    CGRect textRect = [layoutManager usedRectForTextContainer:container];

    UIEdgeInsets inset = UIEdgeInsetsZero;
    inset.top = self.textView.bounds.size.height / 2 - textRect.size.height / 2;
    inset.left = self.textView.bounds.size.width / 2 - textRect.size.width / 2;

    self.textView.textContainerInset = inset;
}

- (void)viewDidLayoutSubviews
{
    [self centerText];
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];

    [self centerText];
}

What I have tried:

I tried to manually set the container size in the centerText method. Depending upon what value I set for the container size, setting the container size does actually work in some cases. Which cases? It depends upon the number of lines of text displayed.

// fixes problem if a single line of text; clips multi-line text
container.size = CGSizeZero; 

// fixes problem if multi-line text, but not if a single line of text
container.size = CGSizeMake(self.textView.bounds.size.width, FLT_MAX);

Since I'm setting widthTracksTextView to YES, I don't understand why I would need to set the text container size at all. And if I do need to set the text container width, why does the correct value seem to depend upon the number of lines displayed?

Community
  • 1
  • 1
bilobatum
  • 8,918
  • 6
  • 36
  • 50
  • Try using autolayout and constraints. I've had great luck with using them, as they query the system view objects for their intrinsic size. – David H Nov 14 '13 at 12:49
  • @DavidH The orange text view uses auto layout and the text view's frame responds appropriately to rotation. In IB, the text view's constraints are pinned to the four sides of the root view. I'll add this info. to the post. – bilobatum Nov 14 '13 at 20:16
  • Does the textView have its contentHugging property set to high for the horizontal axis? – David H Nov 14 '13 at 22:54
  • @DavidH Horizontal content hugging priority is set to the default 250 value. – bilobatum Nov 14 '13 at 23:25
  • Well, try setting it to high - hug content in h axis - its worth a try (one line of code) – David H Nov 14 '13 at 23:40
  • @DavidH I did, no change because I would also need to lower the priorities of the positioning constraints. I don't think tweaking constraints is going to lead to the result I'm looking for. The text view's layout behavior is exactly the way I want it. I want the text view to remain full screen; I don't want it to grow and shrink with its content. – bilobatum Nov 15 '13 at 00:00
  • Well, the problem is once its wrapped, it probably wants to stay wrapped. You might try on rotations, to delete the text, then re-instate the same text. Perturb it somehow. Or change the constraint constants so the view is pegged left to right, then set it back. I'm fishing - just trying to thing of how to attack this. Its possible you could remove the whole textView, create another exactly like it, set the text, and replace the original. Other than these I'm out of help. – David H Nov 15 '13 at 01:02
  • Does the text view must be full screen sized? Why don't you set the background color of super view and pin the text view to center of superview? I used a UILabel and manipulate the frame to center some text in the middle of screen. It works fine. If you are using text kit, have you relayout and redraw the text after device orientation changed? `[NSLayoutManager usedRectForTextContainer:]` doesn't layout text. – wcd Dec 05 '14 at 11:35

1 Answers1

0

I think I found both explanation and solution. The container itself has some problems with fragment of lines. This is what I saw in the package.

Returns the bounds of a line fragment rect inside the receiver for proposedRect. This is the intersection of proposedRect and the receiver's bounding rect defined by -size property. The regions defined by -exclusionPaths property are excluded from the return value. charIndex is the character location inside the text storage for the line fragment being processed. It is possible that proposedRect can be divided into multiple line fragments due to exclusion paths.

When you use rotation of screen, the exclusionPaths are excluded. So the text cannot save the orientation value you want. That is why when you define the CGRect, it solved the problem somehow. So the method you should use is:

- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(CGRect *)remainingRect;
Jerrold Gao
  • 61
  • 1
  • 9