Skip to content

Instantly share code, notes, and snippets.

@romdevios
Last active December 21, 2021 10:36
Show Gist options
  • Save romdevios/db8d12b4578031e34c054d1cea096645 to your computer and use it in GitHub Desktop.
Save romdevios/db8d12b4578031e34c054d1cea096645 to your computer and use it in GitHub Desktop.
UIButton that align image and title vertically considering edge insets and content alignment.
//
// VerticalContentButton.swift
//
// Created by Roman Filippov on 10.11.2021.
// Copyright © 2021 com.its. All rights reserved.
//
import UIKit
/// Allign image and title vertically considering edge insets and content aligment
public class VerticalContentButton: UIButton {
public var imageSize: CGSize?
public override func layoutSubviews() {
super.layoutSubviews()
guard bounds != .zero,
let imageView = imageView,
let image = imageView.image,
let titleLabel = titleLabel else {
return
}
let imageSize = self.imageSize ?? image.size
let minTitleY = contentEdgeInsets.top + imageEdgeInsets.verticalSize() + titleEdgeInsets.top
let maxTitleWidth = bounds.width - contentEdgeInsets.verticalSize() - titleEdgeInsets.verticalSize()
let maxTitleHeight = bounds.height - contentEdgeInsets.bottom - titleEdgeInsets.bottom - minTitleY
let titleSize = titleLabel.sizeThatFits(CGSize(width: maxTitleWidth, height: maxTitleHeight))
let contentSize = CGSize(
width: imageSize.width + imageEdgeInsets.horizontalSize()
+ titleSize.width + titleEdgeInsets.horizontalSize(),
height: imageSize.height + imageEdgeInsets.verticalSize()
+ titleSize.height + titleEdgeInsets.verticalSize()
)
if let imageSize = self.imageSize {
imageView.bounds.size = imageSize
} else {
imageView.sizeToFit()
}
titleLabel.bounds.size = titleSize
let setContentVerticalCenter: (CGFloat)->Void = {
imageView.frame.origin.y = $0 - self.imageEdgeInsets.bottom - imageView.frame.height
titleLabel.frame.origin.y = $0 + self.titleEdgeInsets.top
}
switch contentVerticalAlignment {
case .center:
let availableSpaces = bounds.height - contentEdgeInsets.verticalSize() - contentSize.height
setContentVerticalCenter(contentEdgeInsets.top + availableSpaces / 2 + imageView.bounds.height + imageEdgeInsets.verticalSize())
case .top:
setContentVerticalCenter(contentEdgeInsets.top + imageEdgeInsets.verticalSize() + imageSize.height)
case .bottom:
setContentVerticalCenter(bounds.height - contentEdgeInsets.bottom + titleEdgeInsets.verticalSize() + titleSize.height)
case .fill:
// image.bottom and title.top will be in the center of button
setContentVerticalCenter((bounds.height - contentEdgeInsets.verticalSize()) / 2 + contentEdgeInsets.top)
@unknown default:
assertionFailure()
}
switch contentHorizontalAlignment {
case .center:
let contentCenter = (bounds.width - contentEdgeInsets.horizontalSize()) / 2 + contentEdgeInsets.left
imageView.frame.origin.x = contentCenter - imageView.bounds.midX
titleLabel.frame.origin.x = contentCenter - titleLabel.bounds.midX
case .right, .trailing:
imageView.frame.origin.x = contentEdgeInsets.right + imageEdgeInsets.right
titleLabel.frame.origin.x = contentEdgeInsets.right + titleEdgeInsets.right
case .fill:
imageView.frame.size.width = contentSize.width - imageEdgeInsets.horizontalSize()
titleLabel.frame.size.width = contentSize.width - titleEdgeInsets.horizontalSize()
fallthrough // to allign on left edge
case .left, .leading:
imageView.frame.origin.x = contentEdgeInsets.left + imageEdgeInsets.left
titleLabel.frame.origin.x = contentEdgeInsets.left + titleEdgeInsets.left
@unknown default:
assertionFailure()
}
}
public override var intrinsicContentSize: CGSize {
guard var imageSize = imageSize ?? imageView?.image?.size,
var titleSize = titleLabel?.sizeThatFits(CGSize(
width: CGFloat.greatestFiniteMagnitude,
height: CGFloat.greatestFiniteMagnitude)) else {
return super.intrinsicContentSize
}
imageSize.width += imageEdgeInsets.verticalSize() + contentEdgeInsets.verticalSize()
imageSize.height += imageEdgeInsets.horizontalSize() + contentEdgeInsets.horizontalSize()
titleSize.width += titleEdgeInsets.verticalSize() + contentEdgeInsets.verticalSize()
titleSize.height += titleEdgeInsets.horizontalSize() + contentEdgeInsets.horizontalSize()
return CGSize(
width: max(imageSize.width, titleSize.width),
height: max(imageSize.height, titleSize.height)
)
}
}
extension UIEdgeInsets {
fileprivate func verticalSize() -> CGFloat {
return top + bottom
}
fileprivate func horizontalSize() -> CGFloat {
return left + right
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment