@objc public enum HorizontalAlignment : Int { case center case left case right } @objc public enum VerticalAlignment: Int { case center case top case bottom } public let NoIntrinsicSize = CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric) extension UIView { @objc public func wmf_sizeThatFits(_ size: CGSize) -> CGSize { return sizeThatFits(size) } @objc @discardableResult public func wmf_preferredFrame(at point: CGPoint, maximumSize: CGSize = NoIntrinsicSize, minimumSize: CGSize = NoIntrinsicSize, horizontalAlignment: HorizontalAlignment = .center, verticalAlignment: VerticalAlignment = .center, apply: Bool = false) -> CGRect { let viewSize: CGSize = wmf_sizeThatFits(maximumSize) var x: CGFloat = point.x var y: CGFloat = point.y let viewWidth: CGFloat let widthToFit: CGFloat if minimumSize.width != UIView.noIntrinsicMetric && maximumSize.width != UIView.noIntrinsicMetric { // max and min defined viewWidth = max(min(maximumSize.width, viewSize.width), minimumSize.width) widthToFit = maximumSize.width } else if minimumSize.width != UIView.noIntrinsicMetric && maximumSize.width == UIView.noIntrinsicMetric { // only min defined viewWidth = max(minimumSize.width, viewSize.width) widthToFit = viewWidth } else if minimumSize.width == UIView.noIntrinsicMetric && maximumSize.width != UIView.noIntrinsicMetric { // only max defined viewWidth = min(maximumSize.width, viewSize.width) widthToFit = maximumSize.width } else { // neither defined viewWidth = viewSize.width widthToFit = viewWidth } let viewHeight: CGFloat let heightToFit: CGFloat if minimumSize.height != UIView.noIntrinsicMetric && maximumSize.height != UIView.noIntrinsicMetric { // max and min defined viewHeight = max(min(maximumSize.height, viewSize.height), minimumSize.height) heightToFit = maximumSize.height } else if minimumSize.height != UIView.noIntrinsicMetric && maximumSize.height == UIView.noIntrinsicMetric { // only min defined viewHeight = max(minimumSize.height, viewSize.height) heightToFit = viewHeight } else if minimumSize.height == UIView.noIntrinsicMetric && maximumSize.height != UIView.noIntrinsicMetric { // only max defined viewHeight = min(maximumSize.height, viewSize.height) heightToFit = maximumSize.height } else { // neither defined viewHeight = viewSize.height heightToFit = viewHeight } switch verticalAlignment { case .center: y += floor(0.5*heightToFit - 0.5*viewHeight) case .bottom: y += (heightToFit - viewHeight) case .top: break } switch horizontalAlignment { case .center: x += floor(0.5*widthToFit - 0.5*viewWidth) case .right: x += (widthToFit - viewWidth) case .left: break } let fitFrame = CGRect(x: round(x), y: round(y), width: ceil(viewWidth), height: ceil(viewHeight)) if apply { frame = fitFrame } return fitFrame } @discardableResult public func wmf_preferredFrame(at point: CGPoint, maximumSize: CGSize = NoIntrinsicSize, minimumSize: CGSize = NoIntrinsicSize, horizontalAlignment: HorizontalAlignment, apply: Bool) -> CGRect { return wmf_preferredFrame(at: point, maximumSize: maximumSize, minimumSize: minimumSize, horizontalAlignment: horizontalAlignment, verticalAlignment: .top, apply: apply) } @discardableResult public func wmf_preferredFrame(at point: CGPoint, maximumSize: CGSize, minimumSize: CGSize = NoIntrinsicSize, alignedBy semanticContentAttribute: UISemanticContentAttribute, apply: Bool) -> CGRect { let horizontalAlignment: HorizontalAlignment = semanticContentAttribute == .forceRightToLeft ? .right : .left return wmf_preferredFrame(at: point, maximumSize: maximumSize, minimumSize: minimumSize, horizontalAlignment: horizontalAlignment, apply: apply) } @discardableResult public func wmf_preferredFrame(at point: CGPoint, maximumWidth: CGFloat, minimumWidth: CGFloat = UIView.noIntrinsicMetric, horizontalAlignment: HorizontalAlignment, apply: Bool) -> CGRect { let minimumSize = CGSize(width: minimumWidth, height: UIView.noIntrinsicMetric) let maximumSize = CGSize(width: maximumWidth, height: UIView.noIntrinsicMetric) return wmf_preferredFrame(at: point, maximumSize: maximumSize, minimumSize: minimumSize, horizontalAlignment: horizontalAlignment, apply: apply) } @discardableResult public func wmf_preferredFrame(at point: CGPoint, maximumWidth: CGFloat, minimumWidth: CGFloat = UIView.noIntrinsicMetric, alignedBy semanticContentAttribute: UISemanticContentAttribute, apply: Bool) -> CGRect { let horizontalAlignment: HorizontalAlignment = semanticContentAttribute == .forceRightToLeft ? .right : .left return wmf_preferredFrame(at: point, maximumWidth: maximumWidth, minimumWidth: minimumWidth, horizontalAlignment: horizontalAlignment, apply: apply) } @discardableResult public func wmf_preferredHeight(at point: CGPoint, maximumWidth: CGFloat, minimumWidth: CGFloat = UIView.noIntrinsicMetric, alignedBy semanticContentAttribute: UISemanticContentAttribute, spacing: CGFloat, apply: Bool) -> CGFloat { return wmf_preferredFrame(at: point, maximumWidth: maximumWidth, minimumWidth: minimumWidth, alignedBy: semanticContentAttribute, apply: apply).layoutHeight(with: spacing) } @discardableResult public func wmf_preferredHeight(at point: CGPoint, maximumWidth: CGFloat, minimumWidth: CGFloat = UIView.noIntrinsicMetric, horizontalAlignment: HorizontalAlignment, spacing: CGFloat, apply: Bool) -> CGFloat { return wmf_preferredFrame(at: point, maximumWidth: maximumWidth, minimumWidth: minimumWidth, horizontalAlignment: horizontalAlignment, apply: apply).layoutHeight(with: spacing) } } extension UIButton { public override func wmf_sizeThatFits(_ maximumSize: CGSize) -> CGSize { var buttonAdjustedSize = maximumSize var heightAdjustment = contentEdgeInsets.top + contentEdgeInsets.bottom var widthAdjustment = contentEdgeInsets.left + contentEdgeInsets.right var imageHeight: CGFloat = 0 if let image = image(for: .normal) { widthAdjustment += imageEdgeInsets.left + imageEdgeInsets.right + image.size.width imageHeight = image.size.height + imageEdgeInsets.top + imageEdgeInsets.bottom + contentEdgeInsets.top + contentEdgeInsets.bottom } heightAdjustment += titleEdgeInsets.top + titleEdgeInsets.bottom widthAdjustment += titleEdgeInsets.left + titleEdgeInsets.right if buttonAdjustedSize.width != UIView.noIntrinsicMetric { buttonAdjustedSize.width = buttonAdjustedSize.width - widthAdjustment } if buttonAdjustedSize.height != UIView.noIntrinsicMetric { buttonAdjustedSize.height = buttonAdjustedSize.height - heightAdjustment } let buttonLabelSize: CGSize if let titleLabel = titleLabel { buttonLabelSize = titleLabel.sizeThatFits(buttonAdjustedSize) } else { buttonLabelSize = .zero } let maxHeight = max(imageHeight, buttonLabelSize.height + heightAdjustment) return CGSize(width: buttonLabelSize.width + widthAdjustment, height: maxHeight) } } extension AlignedImageButton { @discardableResult override public func wmf_preferredFrame(at point: CGPoint, maximumSize: CGSize = NoIntrinsicSize, minimumSize: CGSize = NoIntrinsicSize, horizontalAlignment: HorizontalAlignment = .center, verticalAlignment: VerticalAlignment = .center, apply: Bool = false) -> CGRect { let adjustedPoint = CGPoint(x: point.x - leftPadding, y: point.y - verticalPadding) var adjustedSize = maximumSize if adjustedSize.width != UIView.noIntrinsicMetric { adjustedSize.width = adjustedSize.width + leftPadding + rightPadding } if adjustedSize.height != UIView.noIntrinsicMetric { adjustedSize.height = adjustedSize.height + 2 * verticalPadding } return super.wmf_preferredFrame(at: adjustedPoint, maximumSize: adjustedSize, minimumSize: minimumSize, horizontalAlignment: horizontalAlignment, verticalAlignment: verticalAlignment, apply: apply) } }