0

I have a static image with an area on it that is meant to display some multi-line text. Like a speech bubble from a character in a video game. I grabbed a stock image that looks like this:

speech bubble

I have a UIImageView set up to be aspect-fit (see this question) inside of a subview of the main view. I have a UILabel also set up inside of the subview, which will hold the multi-line text. I want to be able to move the subview around the screen and have it be any size and still have the UIImageView stay the same aspect and have the UILabel fit inside the bubble of the image.

I made a sample project which has this set up already.

The way I intend to keep the UILabel's bounds inside the speech bubble area is by setting up constraints that are proportional to the center x and y of the UIImageView. For my image, the left edge multiplier is 0.65, the right is 1.8, the top is 0.19 and the bottom is 0.63.

I wrote a couple functions that extend from UIView to confirm this:

/**
Draws a vertical line proportional to the center x of the view.

A `proportional` value of 0 is the left edge, while 2 is the right edge.

:param: proportion The value from the left edge (0.0) to the right edge (2.0)
:param: inColor The color to draw the line in (red by default)
*/
func drawVertLineAtProportion(proportion: CGFloat, inColor: UIColor = UIColor.redColor()) {
  let size = self.frame.size
  
  UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
  let context = UIGraphicsGetCurrentContext()
  
  let x = self.bounds.origin.x + proportion*self.frame.size.width/2
  
  var vertPath = UIBezierPath()
  vertPath.lineWidth = size.height/150.0
  vertPath.moveToPoint(CGPointMake(x, 0))
  vertPath.addLineToPoint(CGPointMake(x, self.frame.size.height))
  inColor.setStroke()
  vertPath.stroke()
  
  let image = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext()
  
  self.addSubview(UIImageView(image: image))
}

And drawHorizLineAtProportion is similar, but for horizontal lines.

I can confirm that my multiplier values are correct by running these 4 lines in viewDidAppear:

imageView.drawVertLineAtProportion(0.65)
imageView.drawVertLineAtProportion(1.8)
imageView.drawHorizLineAtProportion(0.19)
imageView.drawHorizLineAtProportion(0.63)

And then the imageView looks like this:

redlines

I can change the size of the subView that contains the imageView to any size I want, and the imageView stays aspect-fit and the red box in the intersection of those red lines is always exactly what I want.

So how come when I set up the constraints of the edges of the UILabel to follow the same formula the edges don't line up?

It seems that when the width of the imageView is maxed the left and right edges are correct but the top and bottom are wrong:

width maxed

And if the height is maxed, then the top and bottom are correct, but the left and right are wrong:

height maxed

So how come the UILabel bounds aren't lining up with the red lines?

EDIT to specify that I know there is a runtime fix, but I want to know why storyboard doesn't work.

If I add these lines in viewDidAppear to fix the frame of the UILabel, it works:

let upperLeft: CGPoint = CGPointMake(imageView.frame.origin.x + 0.65*imageView.frame.size.width/2, imageView.frame.origin.y + 0.19*imageView.frame.size.height/2)
let lowerRight: CGPoint = CGPointMake(imageView.frame.origin.x + 1.8*imageView.frame.size.width/2, imageView.frame.origin.y + 0.63*imageView.frame.size.height/2)
let size: CGSize = CGSizeMake(lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y)

speechLabel.frame = CGRectMake(upperLeft.x, upperLeft.y, size.width, size.height)

But I still want to know why what I have set in storyboard doesn't work.

Community
  • 1
  • 1
Tim Fuqua
  • 1,635
  • 1
  • 16
  • 25

1 Answers1

0

I figured out a solution, fully in storyboard, that gets me what I want, although I do believe my original question shows that there is a bug in Xcode.

I was noticing that since Xcode didn't seem to respect that I wanted to align speechLabel to the sister-UI element imageView, I just made the UIView I was aligning to no longer the imageView.

The solution I implemented was to put the imageView and speechLabel into a new parent view, which I call aspectFitView. All of the constraints that I put on the imageView to make it aspect-fit, I put on aspectFitView instead.

Then I set the constraints of imageView to make it the size of aspectFitView: align center x and y and equal width and height.

Now that the parent of speechLabel is the exact size, position, and aspect ratio of the imageView, I set up my proportional constraints for the speechLabel against the aspectFitView instead, and voila. It all works beautifully. It looks correct on my storyboard, I don't have to add any post-viewDidAppear frame-correcting code, and it works perfectly on any device I run it on.

I updated the sample project (Xcode 6.4) with the solution for anyone interested.

Tim Fuqua
  • 1,635
  • 1
  • 16
  • 25