Skip to content

jkolb/DrapierLayout

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DrapierLayout

Helps make layoutSubviews more readable

Examples

A simple view with a UIImageView and two UILabels.

class CustomView: UIView {
	var imageView: UIImageView!
	var titleLabel: UILabel!
	var detailLabel: UILabel!
	
	struct ViewLayout {
		let imageFrame: CGRect
		let titleFrame: CGRect
		let detailFrame: CGRect
	}
	
	func generateLayout(bounds: CGRect) -> ViewLayout {
		// Since a size is not specified, sizeThatFits is called on imageView with a 
		// size of 10,000 x 10,000 this allows the image to be as large as it can.
		var imageFrame = imageView.layout(
			Leading(equalTo: bounds.leading(layoutMargins)),
			Top(equalTo: bounds.top(layoutMargins))
		)
		
		let maximumImageWidth = CGFloat(50.0)
		
		if imageFrame.width > maximumImageWidth {
			// This time a size is specified, this attempts to make sure that the image
			// doesn't push the UILabels off the edge of the parent view.
			imageFrame = imageView.layout(
				Leading(equalTo: bounds.leading(layoutMargins)),
				Top(equalTo: bounds.top(layoutMargins)),
				Width(equalTo: maximumImageWidth),
				Height(equalTo: round(maximumImageWidth * imageFrame.inverseAspectRatio))
			)
		}
		
		// The title fits between the image and the trailing edge of the superview.
		// A capline is the line that marks the top of a capital letter for the 
		// UILabel's current font.
		let titleFrame = titleLabel.layout(
			Leading(equalTo: imageFrame.trailing, constant: 8.0),
			Trailing(equalTo: bounds.trailing(layoutMargins)),
			Capline(equalTo: imageFrame.top)
		)
		
		let detailFrame = detailLabel.layout(
			Leading(equalTo: titleFrame.leading),
			Trailing(equalTo: titleFrame.trailing),
			Capline(equalTo: titleFrame.baseline(font: titleLabel.font), constant: 8.0)
		)
		
		return ViewLayout(
			imageFrame: imageFrame,
			titleFrame: titleFrame,
			detailFrame: detailFrame
		)
	}
	
	override func sizeThatFits(size: CGSize) -> CGSize {
		let layout = generateLayout(size.rect())
		// Find the fitting height from the layout frame that is farthest toward the bottom, then
		// add in the layoutMargin bottom if needed to find the true height.
		let maxBottom = max(layout.imageFrame.bottom, layout.detailFrame.baseline(font: detailLabel.font))
		return CGSize(width: size.width, height: maxBottom + layoutMargins.bottom)
	}
	
	override func layoutSubviews() {
		super.layoutSubviews()
		
		// In most cases the layout code can be shared between sizeThatFits and layoutSubviews.
		let layout = generateLayout(bounds)
		
		imageView.frame = layout.imageFrame
		titleLabel.frame = layout.titleFrame
		detailLabel.frame = layout.detailFrame
	}
}

Contact

Justin Kolb
@nabobnick

License

DrapierLayout is available under the MIT license. See the LICENSE file for more info.