1
votes

It's been a while since I'm trying to create a custom view that has at the top right corner a simple badge.
The custom view is composed by 3 part.

  1. In red there is the custom view itself, that contains:
  2. in blue the container view
  3. in green the badge view a simple UILabel

Container view and the badge view are sibling.
At the moment the container view contains a UIImageView

enter image description here

That view must fit those requirements:

  1. Full auto layout approach
  2. Made programmatically
  3. The custom view must be aligned only considering the blue frame(the container view)

Why that? imagine that you need to position that view by aligning the top edge to the top edge of another view or button, wouldn't be nice if the only the content showed int he container is taken into account?
Here you can see how I set the constraints. The label is placed at the top right corner of the container view.

func setUpConstraint() {
        var horContraints = [NSLayoutConstraint]()
        horContraints.append(NSLayoutConstraint(item: containerView, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1, constant: 0))
        horContraints.append(NSLayoutConstraint(item: containerView, attribute: .Trailing, relatedBy: .Equal, toItem: badge, attribute: .CenterX, multiplier: 1, constant: 0))
        horContraints.append(NSLayoutConstraint(item: badge, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: 0))
        var verContraints = [NSLayoutConstraint]()
        verContraints.append(NSLayoutConstraint(item: containerView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0))
        verContraints.append(NSLayoutConstraint(item: containerView, attribute: .Top, relatedBy: .Equal, toItem: badge, attribute: .CenterY, multiplier: 1, constant: 0))
        verContraints.append(NSLayoutConstraint(item: badge, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0))
        addConstraints(verContraints + horContraints)        
        containerView.addConstraint(NSLayoutConstraint(item: containerView, attribute: .Height, relatedBy: .Equal, toItem: containerView, attribute: .Width, multiplier: 1, constant: 0))
}

The container view has also an aspect ratio constraint to keep a square size.
As you can see from the picture everything seems to be fine, except for the fact that when I try to constraint the custom view to the center of its superview, it seems misaligned, because I want the view to be centered respect to the container view (the one with the image). The badge is a kind of decoration such as a shadow and I don't want it to be consider.

enter image description here
To align it correctly I'm trying to override the alignment rect by adding an insets that would "cut" half the label size.

   override func alignmentRectInsets() -> UIEdgeInsets {
        let badgeSize = badge.bounds.size
        return UIEdgeInsets(top: badgeSize.height / 2, left: 0, bottom: 0, right: badgeSize.width / 2)
    }

I tried also different configurations but I never was able to fit in the wanted position
enter image description here
If I try to use the other 2 methods alignmentRectForFrame and frameForAlignmentRect (deleting alignmentRectInsets) they are never be called.
Here is what I'd like to obtain:
enter image description here
I've created a little sample code

1

1 Answers

1
votes

If the problem is that you want the other view (the "content view", showing the image) to be centered in the ultimate superview, then simply make centering constraints (center x to center x, center y to center y) between the superview and the content view. No law says that a constraint has to be between a view and its direct superview; you can make constraints between any pair of views (that is one of the wonderful things about constraints).

I made a quick mock-up that looks a lot like your "here's what I'd like to obtain" image:

enter image description here

As you can see, Moe (the middle Pep Boy, in the middle of the image) is exactly centered in the superview (shown by the green lines).

For simplicity, the entire interface is created in code, so that I can show you the whole thing. Here it is:

// create the custom view
let customView = UIView()
customView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(customView)
customView.backgroundColor = UIColor.redColor()
NSLayoutConstraint.activateConstraints([
    customView.widthAnchor.constraintEqualToConstant(100),
    customView.heightAnchor.constraintEqualToConstant(100),
    ])

// add the content view (image) to the custom view
let contentView = UIImageView(image: UIImage(named:"pep.jpg")!)
contentView.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(contentView)
NSLayoutConstraint.activateConstraints([
    contentView.leadingAnchor.constraintEqualToAnchor(customView.leadingAnchor),
    contentView.bottomAnchor.constraintEqualToAnchor(customView.bottomAnchor),
    contentView.heightAnchor.constraintEqualToAnchor(customView.heightAnchor, constant: -10),
    contentView.widthAnchor.constraintEqualToAnchor(customView.widthAnchor, constant: -10)
    ])

// add the badge (label) to the custom view
let badge = UILabel()
badge.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(badge)
badge.backgroundColor = UIColor.greenColor().colorWithAlphaComponent(0.5)
badge.font = UIFont(name: "GillSans", size: 14)
badge.textAlignment = .Center
badge.text = "567"
NSLayoutConstraint.activateConstraints([
    badge.centerXAnchor.constraintEqualToAnchor(contentView.trailingAnchor),
    badge.centerYAnchor.constraintEqualToAnchor(contentView.topAnchor),
    ])


// position the whole thing with respect to the content view
NSLayoutConstraint.activateConstraints([
    contentView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor),
    contentView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor),
    ])

But this is not a complete answer to your question. You should now be asking: Yes, but what went wrong when I tried to use the alignmentRectInsets? The answer is: you forgot that the alignmentRectInsets affect alignment with internal views as well as external views. In other words, you certainly can use that approach instead, but then you must adjust the position of the content view accordingly.

So, here's a rewrite in which I use the alignmentRectInsets. First, I'll define a custom view subclass for our custom view:

class AlignedView : UIView {
    override func alignmentRectInsets() -> UIEdgeInsets {
        return UIEdgeInsetsMake(10, 0, 0, 10)
    }
}

Now here's the rewrite. I've put a star (*) next to all the lines that have changed from the previous example:

// create the custom view
let customView = AlignedView() // *
customView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(customView)
customView.backgroundColor = UIColor.redColor()
NSLayoutConstraint.activateConstraints([
    customView.widthAnchor.constraintEqualToConstant(100),
    customView.heightAnchor.constraintEqualToConstant(100),
    ])

// add the content view (image) to the custom view
let contentView = UIImageView(image: UIImage(named:"pep.jpg")!)
contentView.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(contentView)
NSLayoutConstraint.activateConstraints([
    contentView.leadingAnchor.constraintEqualToAnchor(customView.leadingAnchor),
    contentView.bottomAnchor.constraintEqualToAnchor(customView.bottomAnchor),
    contentView.heightAnchor.constraintEqualToAnchor(customView.heightAnchor), // *
    contentView.widthAnchor.constraintEqualToAnchor(customView.widthAnchor) // *
    ])

// add the badge (label) to the custom view
let badge = UILabel()
badge.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(badge)
badge.backgroundColor = UIColor.greenColor().colorWithAlphaComponent(0.5)
badge.font = UIFont(name: "GillSans", size: 14)
badge.textAlignment = .Center
badge.text = "567"
NSLayoutConstraint.activateConstraints([
    badge.centerXAnchor.constraintEqualToAnchor(contentView.trailingAnchor),
    badge.centerYAnchor.constraintEqualToAnchor(contentView.topAnchor),
    ])


// position the whole thing with respect to the custom view

NSLayoutConstraint.activateConstraints([
    customView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor), // *
    customView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor), // *
    ])

That gives exactly the same visual result as before.