Instantly share code, notes, and snippets.
Last active
December 21, 2021 10:36
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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