Constrain content size to size of stacked element, how?

Hi, you can tell I'm new to creating iOS apps and XCode.

So, I have this Storyboard Scene with a horizontally stacked view and a number of plain views in it to which I then add subviews programatically. The subviews are image views.

When I'm done with setting all that, I can see the stacked view distributed the views evenly as intended. But the images loaded into the image view are their natural size and not constrained by the container and thus overlap.

So, the hierarchy is Root View -> stacked view -> view [0-n] -> image view I've tried setting different content modes on the image view. On the other views I have found bounds that could possibly be set, but I really don't want to calculate the width based on screen width etc. ... esp. if the stacked view already spreads its elements correctly.

What am I missing?

TIA,

c.

Answered by DTS Engineer in 762290022

What size do you actually want to use for the images? Or, if not a specific size, what set of rules do you want to apply? The trick here is to formulate a "good" (consistent) set of rules, then translate those into auto-layout constraints.

Hmm, I can see how "Clips to Bounds" keeps the images from overlapping. Maybe now I only need to find out how to make the image view scale the image down to the bounds that are smaller than the images.

Accepted Answer

What size do you actually want to use for the images? Or, if not a specific size, what set of rules do you want to apply? The trick here is to formulate a "good" (consistent) set of rules, then translate those into auto-layout constraints.

Well, not sure if I completely understand your question, but let's see ...

I'm playing around with the idea of a card game and the generic views in the horizontal stack are supposed to be the stacks of cards. So, for each card on the stack, I add an image view to the generic view. So, the hierarchy is actually Root View -> stacked view -> view [0-n] -> image view [0-n] The images I'm trying to use are considerably larger than what the stacked view allocates for each card stack's width. I expect everything to work if the width of the image matched exactly what the stacked view determines to be the right width. However that can change depending on number of card stacks or even display size. Also, if the image was smaller than the width the stacked view allocates for the subviews, I expect things would work as expected, because the images would be scaled up to fill the space. But it doesn't seem right to select the smallest possible images for the smallest display out there and let scaling accommodate larger displays.

In the above view hierarchy, everything to the left of the image view is designed in the UI builder. I think it's when I programatically add the image views to their respective superviews, their frame and bounds aren't set automatically. When I create the image view, the bounds are apparently set based on the image passed in the constructor, and they stay the same after being added to the hierarchy below the stacked view. It seems to work, if I set the bounds programatically based on what the stacked view calculated for its direct subviews, like this:

    func updateStacks() {
        for stack in 0...7 {
            for stackCard:Int in 0...dealt[stack].count-1 {

                let image = UIImage(named: getImageNameForCard(cardType: dealt[stack][stackCard]))
                let imageView = UIImageView(image: image)
                imageView.contentMode = UIView.ContentMode.scaleAspectFit
                //imageView.translatesAutoresizingMaskIntoConstraints = false;
                imageView.isOpaque = true;
                imageView.preservesSuperviewLayoutMargins = true;
                
                var iVbounds:CGRect = CGRect(x: 0, y: 0, width: stackTopViews[stack].bounds.width, height: stackTopViews[stack].bounds.height)
                imageView.bounds = iVbounds
                
                stackTopViews[stack].addSubview(imageView)

                imageView.frame.origin.x = 0
                if (stackCard > 0) {
                    imageView.frame.origin.y = 0 + CGFloat((30 * stackCard));
                } else {
                    imageView.frame.origin.y = 0
                }
            }
            
        }
    }

I guess I was expecting for the image views to automatically inherit their bounds from their superior view, when they're being added. Maybe I was expecting too much.

For the sake of posterity, turns out the above seemed to work as desired, initially, but causes more trouble down the road (such as not using more space if more than the image width is available for each card stack.) The much better idea apparently is to actually try and get ones head around how auto-layout works and where constraints need to be for stuff to work. So, don't copy the above approach, do something like this instead:

    func createCardView(stack: Int!, stackCard: Int!, cardType: String!) -> TouchImageView {
        
        let image = UIImage(named: getImageNameForCard(cardType: cardType))

        let imageView = TouchImageView(image: image)
        
        stackTopViews[stack].addSubview(imageView)
        
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.leadingAnchor.constraint(equalTo: imageView.superview!.leadingAnchor).isActive = true
        imageView.trailingAnchor.constraint(equalTo: imageView.superview!.trailingAnchor).isActive = true
        imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: (imageView.image!.size.height) / imageView.image!.size.width).isActive = true
        
        if (stackCard > 0) {
            imageView.topAnchor.constraint(equalTo: imageView.superview!.topAnchor, constant: CGFloat((GameViewController.CARD_OFFSET * stackCard))).isActive = true
        }
        

        imageView.isOpaque = true;
        //imageView.preservesSuperviewLayoutMargins = true;
        imageView.clipsToBounds = true;
        imageView.contentMode = UIView.ContentMode.scaleAspectFit
        //imageView.setNeedsLayout()
        //imageView.layoutIfNeeded()
                                
        imageView.stack = stack
        imageView.stackCard = stackCard
        imageView.cardType = cardType
        
        return imageView
    }

I agree, cards are a really hard use-case, because you don't want them arbitrarily resizable. Both very big and very small sizes would look bad. You can set ranges of height/width using auto-layout constraints, but in this case you might also want to consider calculating a desired size programmatically (based on the current size of the view that contains them), and then modify the explicit height/width auto-layout constraints to fix them at that size.

You might need to recalculate the sizes when the containing view changes, and the calculation is tricky because you also have to decide whether height or width is the dominating factor. In the end, this is probably something you just have to experiment with, and to be willing to try different approaches.

Constrain content size to size of stacked element, how?
 
 
Q