0
votes

I'm working with some JSON that is giving me a series of views and subviews to lay out inside a UIView, and I'm wanting to use AutoLayout to achieve it. Here's an example of the JSON:

"component": {
    "width": 150,
    "height": 100,
    "elements": [
        {
            "x": 0.1,
            "y": 0.1,
            "w": 0.8,
            "h": 0.8
        }   
    ]
}

In this example, the outer view (the component view) has asked for a size of 150 by 100. I've got that laying out maintaining aspect ratio based on screen width like so:

private func fittedSize(for dimensions: Dimensions) -> CGSize {
    var width: CGFloat
    var height: CGFloat

    if dimensions.width.value > dimensions.height.value {
        let aspectRatio = dimensions.height.value / dimensions.width.value
        width = dimensions.width.value
        height = dimensions.width.value * aspectRatio
    } else {
        let aspectRatio = dimensions.width.value / dimensions.height.value
        height = dimensions.height.value
        width = dimensions.height.value * aspectRatio


    let apertureSize = CGSize(width: width, height: height)

    if apertureSize.width > apertureSize.height {
        let scale = bounds.width / apertureSize.width
        return CGSize(width: apertureSize.width * scale, height: apertureSize.height * scale)
    } else {
        let scale = bounds.height / apertureSize.height
        return CGSize(width: apertureSize.width * scale, height: apertureSize.height * scale)
    }
}

The above function calculates the maximum size a view can be while maintaining the aspect ratio specified by its dimensions, so for example, at a screen width of 320, that 100x150 view would be 320x480.

I now need to layout subviews of that view, containing the elements within that component. Those values are specified as percentages, so in the example above, the element should be positioned 10% of the width in from the left and top, and 80% of the width and height of the superview.

I can get the correct width and height easily using constraints with multipliers, like so:

let w = elementView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: element.position.width)
let h = elementView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: element.position.height)

This'll take that 0.8, and make the subview 80% of its parent width and height. The trouble comes in that I also want to use multipliers for its position (x and y). AutoLayout does not support this, and that's the answer I'm looking for.

How can I position and size one UIView inside another using percentage values, using AutoLayout rather than resorting to manually positioning frames?

Notes:

Here's an example of how things are currently laying out. The red area is the subview and the pale blue the superview. Note that the size of the subview is correct, 80% of the width and height, but it's not displaying 10% of the way in from the top and left, that's what I'm missing.

enter image description here

2

2 Answers

1
votes

You can use UILayoutGuide or dummy views with constraint to leading edge of a superview and leading edge of your view, and constraint this "spacer" view width to its superview width with multiplier.

let l = view.leadingAnchor.constraint(equalTo: view.leadingSpacer.leadingAnchor) let w = leadingSpacer.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: element.position.x) let t = leadingSpacer.trailingAnchor.constraint(to: elementView.leadingAnchor)

And activate this constraints

And do a similar thing for top spacer. I've used dummy views for this problem, but UILayoutGuide is proposed as more efficient solution, as it only interacts with AutoLayout engine and does not take part in rendering. Hope this helps.

0
votes

You will need to give top and left constraints as well. Something like below

elementView.topAnchor.constraint(equalTo: view.topAnchor, constant: height / 10),
elementView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: width / 10),