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.
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.