Merge branch 'mbruce/bugfix' into 'develop'

Bugs/Refactor

See merge request BPHV_MIPS/vds_ios!271
This commit is contained in:
Bruce, Matt R 2024-07-19 17:09:20 +00:00
commit 11af2e5eb9
7 changed files with 296 additions and 168 deletions

View File

@ -356,6 +356,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
/// Updates the UI /// Updates the UI
open override func updateView() { open override func updateView() {
super.updateView() super.updateView()
updateRules()
updateContainerView() updateContainerView()
updateContainerWidth() updateContainerWidth()
updateTitleLabel() updateTitleLabel()
@ -418,7 +419,6 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
} }
open func validate(){ open func validate(){
updateRules()
validator = FormFieldValidator<EntryFieldBase>(field: self, rules: rules) validator = FormFieldValidator<EntryFieldBase>(field: self, rules: rules)
validator?.validate() validator?.validate()
setNeedsUpdate() setNeedsUpdate()

View File

@ -10,6 +10,35 @@ import UIKit
extension InputField { extension InputField {
public class TelephoneNumberValidator: Rule, Withable {
public var format: String
public var errorMessage: String = "Please enter a valid telephone number"
public init(format: String) {
self.format = format
}
public func isValid(value: String?) -> Bool {
guard let value, !value.isEmpty else { return true }
let regex = createRegex(from: format)
let predicate = NSPredicate(format: "SELF MATCHES %@", regex)
let valid = predicate.evaluate(with: value)
return valid
}
private func createRegex(from format: String) -> String {
// Escape special regex characters in the format string
let escapedFormat = NSRegularExpression.escapedPattern(for: format)
// Replace placeholder characters with regex patterns
let regex = escapedFormat
.replacingOccurrences(of: "X", with: "\\d")
return "^" + regex + "$"
}
}
class TelephoneHandler: FieldTypeHandler { class TelephoneHandler: FieldTypeHandler {
static let shared = TelephoneHandler() static let shared = TelephoneHandler()
@ -25,14 +54,7 @@ extension InputField {
} }
override func appendRules(_ inputField: InputField) { override func appendRules(_ inputField: InputField) {
if let text = inputField.textField.text, text.count > 0 { inputField.rules.append(.init(TelephoneNumberValidator(format: "XXX-XXX-XXXX")))
let rule = CharacterCountRule().copyWith {
$0.maxLength = "XXX-XXX-XXXX".count
$0.compareType = .equals
$0.errorMessage = "Enter a valid telephone."
}
inputField.rules.append(.init(rule))
}
} }
override func textField(_ inputField: InputField, textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { override func textField(_ inputField: InputField, textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
@ -49,7 +71,7 @@ extension InputField {
let rawNumber = newText.filter { $0.isNumber } let rawNumber = newText.filter { $0.isNumber }
// Format the number with dashes // Format the number with dashes
let formattedNumber = formatUSNumber(rawNumber) let formattedNumber = rawNumber.formatUSNumber()
// Set the formatted text // Set the formatted text
textField.text = formattedNumber textField.text = formattedNumber
@ -62,6 +84,8 @@ extension InputField {
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition) textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
} }
value = formattedNumber
// Prevent the default behavior // Prevent the default behavior
return false return false
@ -69,43 +93,45 @@ extension InputField {
override func textFieldDidEndEditing(_ inputField: InputField, textField: UITextField) { override func textFieldDidEndEditing(_ inputField: InputField, textField: UITextField) {
if let text = inputField.text { if let text = inputField.text {
let rawNumber = text.filter { $0.isNumber } textField.text = text.formatUSNumber()
textField.text = formatUSNumber(rawNumber) value = textField.text
} }
} }
func formatUSNumber(_ number: String) -> String {
// Format the number in the style XXX-XXX-XXXX
let areaCodeLength = 3
let centralOfficeCodeLength = 3
let lineNumberLength = 4
var formattedNumber = ""
if number.count > 0 {
formattedNumber.append(contentsOf: number.prefix(areaCodeLength))
}
if number.count > areaCodeLength {
let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength)
let endIndex = number.index(startIndex, offsetBy: min(centralOfficeCodeLength, number.count - areaCodeLength))
let centralOfficeCode = number[startIndex..<endIndex]
formattedNumber.append("-")
formattedNumber.append(contentsOf: centralOfficeCode)
}
if number.count > areaCodeLength + centralOfficeCodeLength {
let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength + centralOfficeCodeLength)
let endIndex = number.index(startIndex, offsetBy: min(lineNumberLength, number.count - areaCodeLength - centralOfficeCodeLength))
let lineNumber = number[startIndex..<endIndex]
formattedNumber.append("-")
formattedNumber.append(contentsOf: lineNumber)
}
return formattedNumber
}
} }
} }
extension String {
public func formatUSNumber() -> String {
// Format the number in the style XXX-XXX-XXXX
let areaCodeLength = 3
let centralOfficeCodeLength = 3
let lineNumberLength = 4
var formattedNumber = ""
let number = filter { $0.isNumber }
if number.count > 0 {
formattedNumber.append(contentsOf: number.prefix(areaCodeLength))
}
if number.count > areaCodeLength {
let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength)
let endIndex = number.index(startIndex, offsetBy: min(centralOfficeCodeLength, number.count - areaCodeLength))
let centralOfficeCode = number[startIndex..<endIndex]
formattedNumber.append("-")
formattedNumber.append(contentsOf: centralOfficeCode)
}
if number.count > areaCodeLength + centralOfficeCodeLength {
let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength + centralOfficeCodeLength)
let endIndex = number.index(startIndex, offsetBy: min(lineNumberLength, number.count - areaCodeLength - centralOfficeCodeLength))
let lineNumber = number[startIndex..<endIndex]
formattedNumber.append("-")
formattedNumber.append(contentsOf: lineNumber)
}
return formattedNumber
}
}

View File

@ -364,11 +364,11 @@ extension InputField: UITextFieldDelegate {
open func textFieldDidChangeSelection(_ textField: UITextField) { open func textFieldDidChangeSelection(_ textField: UITextField) {
fieldType.handler().textFieldDidChangeSelection(self, textField: textField) fieldType.handler().textFieldDidChangeSelection(self, textField: textField)
text = textField.text
sendActions(for: .valueChanged)
if fieldType.handler().validateOnChange { if fieldType.handler().validateOnChange {
validate() validate()
} }
sendActions(for: .valueChanged)
setNeedsUpdate()
} }
open func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { open func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

View File

@ -111,6 +111,8 @@ open class TextArea: EntryFieldBase {
} }
didSet { didSet {
setNeedsUpdate()
if textView.isFirstResponder { if textView.isFirstResponder {
validate() validate()
} }
@ -191,8 +193,9 @@ open class TextArea: EntryFieldBase {
override func updateRules() { override func updateRules() {
super.updateRules() super.updateRules()
if let maxLength, maxLength > 0 {
rules.append(.init(countRule)) rules.append(.init(countRule))
}
} }
open override func getFieldContainer() -> UIView { open override func getFieldContainer() -> UIView {

View File

@ -74,7 +74,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
case custom(UIColor) case custom(UIColor)
private var reflectedValue: String { String(reflecting: self) } private var reflectedValue: String { String(reflecting: self) }
public static func == (lhs: Self, rhs: Self) -> Bool { public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.reflectedValue == rhs.reflectedValue lhs.reflectedValue == rhs.reflectedValue
} }
@ -86,7 +86,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
case gradient(UIColor, UIColor) case gradient(UIColor, UIColor)
case none case none
} }
/// Enum used to describe the aspect ratios used for this component. /// Enum used to describe the aspect ratios used for this component.
public enum AspectRatio: String, CaseIterable { public enum AspectRatio: String, CaseIterable {
case ratio1x1 = "1:1" case ratio1x1 = "1:1"
@ -109,7 +109,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
$0.contentMode = .scaleAspectFill $0.contentMode = .scaleAspectFill
$0.clipsToBounds = true $0.clipsToBounds = true
} }
open var containerView = View().with { open var containerView = View().with {
$0.setContentHuggingPriority(.defaultLow, for: .horizontal) $0.setContentHuggingPriority(.defaultLow, for: .horizontal)
$0.setContentHuggingPriority(.defaultLow, for: .vertical) $0.setContentHuggingPriority(.defaultLow, for: .vertical)
@ -125,27 +125,27 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
/// This is the container in which views will be pinned. /// This is the container in which views will be pinned.
open var contentView = View() open var contentView = View()
/// This is the view used to show the high light color for a onClick. /// This is the view used to show the high light color for a onClick.
open var highlightView = View().with { open var highlightView = View().with {
$0.isUserInteractionEnabled = false $0.isUserInteractionEnabled = false
} }
/// This controls the aspect ratio for the component. /// This controls the aspect ratio for the component.
open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } }
/// Sets the background color for the component. /// Sets the background color for the component.
open var color: BackgroundColor? { didSet { setNeedsUpdate() } } open var color: BackgroundColor? { didSet { setNeedsUpdate() } }
/// Sets the background effect for the component. /// Sets the background effect for the component.
open var backgroundEffect: BackgroundEffect = .none { didSet { setNeedsUpdate() } } open var backgroundEffect: BackgroundEffect = .none { didSet { setNeedsUpdate() } }
/// Sets the inside padding for the component /// Sets the inside padding for the component
open var padding: PaddingType = PaddingType.defaultValue { didSet { setNeedsUpdate() } } open var padding: PaddingType = PaddingType.defaultValue { didSet { setNeedsUpdate() } }
/// Applies a background color if backgroundImage prop fails or has trouble loading. /// Applies a background color if backgroundImage prop fails or has trouble loading.
open var imageFallbackColor: Surface = .light { didSet { setNeedsUpdate() } } open var imageFallbackColor: Surface = .light { didSet { setNeedsUpdate() } }
private var _width: CGFloat? private var _width: CGFloat?
/// Sets the width for the component. Accepts a pixel value. /// Sets the width for the component. Accepts a pixel value.
open var width: CGFloat? { open var width: CGFloat? {
@ -159,7 +159,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
setNeedsUpdate() setNeedsUpdate()
} }
} }
private var _height: CGFloat? private var _height: CGFloat?
/// Sets the height for the component. Accepts a pixel value. /// Sets the height for the component. Accepts a pixel value.
open var height: CGFloat? { open var height: CGFloat? {
@ -179,13 +179,14 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
/// Determines if there is a drop shadow or not. /// Determines if there is a drop shadow or not.
open var showDropShadow: Bool = false { didSet { setNeedsUpdate() } } open var showDropShadow: Bool = false { didSet { setNeedsUpdate() } }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Constraints // MARK: - Constraints
//-------------------------------------------------- //--------------------------------------------------
internal var widthConstraint: NSLayoutConstraint? internal var widthConstraint: NSLayoutConstraint?
internal var heightConstraint: NSLayoutConstraint? internal var heightConstraint: NSLayoutConstraint?
internal var aspectRatioConstraint: NSLayoutConstraint?
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Configuration // MARK: - Configuration
//-------------------------------------------------- //--------------------------------------------------
@ -228,13 +229,13 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
containerView.addSubview(backgroundImageView) containerView.addSubview(backgroundImageView)
backgroundImageView.pinToSuperView() backgroundImageView.pinToSuperView()
containerView.addSubview(contentView) containerView.addSubview(contentView)
contentView.pinToSuperView() contentView.pinToSuperView()
containerView.addSubview(highlightView) containerView.addSubview(highlightView)
highlightView.pinToSuperView() highlightView.pinToSuperView()
widthConstraint = widthAnchor.constraint(equalToConstant: 0).deactivate() widthConstraint = widthAnchor.constraint(equalToConstant: 0).deactivate()
heightConstraint = heightAnchor.constraint(equalToConstant: 0).deactivate() heightConstraint = heightAnchor.constraint(equalToConstant: 0).deactivate()
@ -266,7 +267,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
setNeedsUpdate() setNeedsUpdate()
} }
}.store(in: &subscribers) }.store(in: &subscribers)
} }
/// Overriden to take the hit if there is an onClickSubscriber and the view is not a UIControl /// Overriden to take the hit if there is an onClickSubscriber and the view is not a UIControl
@ -291,7 +292,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
shouldUpdateView = true shouldUpdateView = true
setNeedsUpdate() setNeedsUpdate()
} }
/// Used to make changes to the View based off a change events or from local properties. /// Used to make changes to the View based off a change events or from local properties.
open override func updateView() { open override func updateView() {
super.updateView() super.updateView()
@ -301,13 +302,14 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor
containerView.layer.borderWidth = showBorder ? VDSFormControls.borderWidth : 0 containerView.layer.borderWidth = showBorder ? VDSFormControls.borderWidth : 0
contentView.removeConstraints() contentView.removeConstraints()
contentView.pinToSuperView(.uniform(padding.value)) contentView.pinToSuperView(.uniform(padding.value))
updateContainerView() updateContainerView()
} }
open override var accessibilityElements: [Any]? { open override var accessibilityElements: [Any]? {
get { get {
var items = [Any]() var items = [Any]()
@ -328,7 +330,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
//append all children that are accessible //append all children that are accessible
items.append(contentsOf: elements) items.append(contentsOf: elements)
return items return items
} }
set {} set {}
@ -337,7 +339,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Methods // MARK: - Public Methods
//-------------------------------------------------- //--------------------------------------------------
/// This will place a view within the contentView of this component. /// This will place a view within the contentView of this component.
public func addContentView(_ view: UIView, shouldPin: Bool = true) { public func addContentView(_ view: UIView, shouldPin: Bool = true) {
view.removeFromSuperview() view.removeFromSuperview()
@ -346,7 +348,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
view.pinToSuperView() view.pinToSuperView()
} }
} }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Methods // MARK: - Private Methods
//-------------------------------------------------- //--------------------------------------------------
@ -379,55 +381,10 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
containerView.backgroundColor = color.withAlphaComponent(alphaConfiguration) containerView.backgroundColor = color.withAlphaComponent(alphaConfiguration)
} }
} }
private func ratioSize(for width: CGFloat) -> CGSize {
var height: CGFloat = width
switch aspectRatio {
case .ratio1x1:
break;
case .ratio3x4:
height = (4 / 3) * width
case .ratio4x3:
height = (3 / 4) * width
case .ratio2x3:
height = (3 / 2) * width
case .ratio3x2:
height = (2 / 3) * width
case .ratio9x16:
height = (16 / 9) * width
case .ratio16x9:
height = (9 / 16) * width
case .ratio1x2:
height = (2 / 1) * width
case .ratio2x1:
height = (1 / 2) * width
default:
break
}
return CGSize(width: width, height: height)
}
private func sizeContainerView(width: CGFloat? = nil, height: CGFloat? = nil) {
if let width, width > 0 {
widthConstraint?.constant = width
widthConstraint?.activate()
}
if let height, height > 0 {
heightConstraint?.constant = height
heightConstraint?.activate()
}
}
private func updateContainerView() { private func updateContainerView() {
applyBackgroundEffects() applyBackgroundEffects()
widthConstraint?.deactivate()
heightConstraint?.deactivate()
if showDropShadow, surface == .light { if showDropShadow, surface == .light {
containerView.addDropShadow(dropShadowConfiguration) containerView.addDropShadow(dropShadowConfiguration)
} else { } else {
@ -436,50 +393,100 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
containerView.dropShadowLayers?.forEach { $0.frame = containerView.bounds } containerView.dropShadowLayers?.forEach { $0.frame = containerView.bounds }
containerView.gradientLayers?.forEach { $0.frame = containerView.bounds } containerView.gradientLayers?.forEach { $0.frame = containerView.bounds }
//sizing the container with constraints
//Set local vars
var containerViewWidth: CGFloat? = width
let containerViewHeight: CGFloat? = height
let multiplier = aspectRatio.multiplier
if width != nil || height != nil { //turn off the constraints
var containerViewWidth: CGFloat? aspectRatioConstraint?.deactivate()
var containerViewHeight: CGFloat? widthConstraint?.deactivate()
//run logic to determine which to activate heightConstraint?.deactivate()
if let width, aspectRatio == .none && height == nil{
containerViewWidth = width
} else if let height, aspectRatio == .none && width == nil{
containerViewHeight = height
} else if let height, let width {
containerViewWidth = width
containerViewHeight = height
} else if let width {
let size = ratioSize(for: width)
containerViewWidth = size.width
containerViewHeight = size.height
} else if let height { //-------------------------------------------------------------------------
let size = ratioSize(for: height) //Overriding Nil Width Rules
containerViewWidth = size.width //-------------------------------------------------------------------------
containerViewHeight = size.height //Rule 1:
} //In the scenario where we only have a height but the multiplie is nil, we
sizeContainerView(width: containerViewWidth, height: containerViewHeight) //want to set the width with the parent's width which will more or less "fill"
} else { //the container horizontally
if let parentSize = horizontalPinnedSize() { //- height is set
//- width is not set
var containerViewWidth: CGFloat? //- aspectRatio is not set
var containerViewHeight: CGFloat? if let superviewWidth, superviewWidth > 0,
containerViewHeight != nil,
let size = ratioSize(for: parentSize.width) containerViewWidth == nil,
if aspectRatio == .none { multiplier == nil {
containerViewWidth = size.width containerViewWidth = superviewWidth
} else { }
containerViewWidth = size.width
containerViewHeight = size.height //Rule 2:
} //In the scenario where no width and height is set, want to set the width with the
//parent's width which will more or less "fill" the container horizontally
sizeContainerView(width: containerViewWidth, height: containerViewHeight) //- height is not set
} //- width is not set
else if let superviewWidth, superviewWidth > 0,
containerViewWidth == nil,
containerViewHeight == nil {
containerViewWidth = superviewWidth
}
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
//Width + AspectRatio Constraint - Will exit out if set
//-------------------------------------------------------------------------
if let containerViewWidth,
let multiplier,
containerViewWidth > 0,
containerViewHeight == nil {
widthConstraint?.constant = containerViewWidth
widthConstraint?.activate()
aspectRatioConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: multiplier)
aspectRatioConstraint?.activate()
return
}
//-------------------------------------------------------------------------
//Height + AspectRatio Constraint - Will exit out if set
//-------------------------------------------------------------------------
else if let containerViewHeight,
let multiplier,
containerViewHeight > 0,
containerViewWidth == nil {
heightConstraint?.constant = containerViewHeight
heightConstraint?.activate()
aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: multiplier)
aspectRatioConstraint?.activate()
return
}
//-------------------------------------------------------------------------
//Width Constraint
//-------------------------------------------------------------------------
if let containerViewWidth,
containerViewWidth > 0 {
widthConstraint?.constant = containerViewWidth
widthConstraint?.activate()
}
//-------------------------------------------------------------------------
//Height Constraint
//-------------------------------------------------------------------------
if let containerViewHeight,
containerViewHeight > 0 {
heightConstraint?.constant = containerViewHeight
heightConstraint?.activate()
} }
} }
/// This is the size of the superview's allowed space for this container first by constrained size which would include padding/inset values an
private var superviewWidth: CGFloat? {
horizontalPinnedWidth() ?? superview?.frame.size.width
}
} }
extension TileContainerBase { extension TileContainerBase {
@ -519,3 +526,30 @@ extension TileContainerBase {
} }
} }
} }
extension TileContainerBase.AspectRatio {
var multiplier: CGFloat? {
switch self {
case .ratio1x1:
return 1
case .ratio3x4:
return 4 / 3
case .ratio4x3:
return 3 / 4
case .ratio2x3:
return 3 / 2
case .ratio3x2:
return 2 / 3
case .ratio9x16:
return 16 / 9
case .ratio16x9:
return 9 / 16
case .ratio1x2:
return 2 / 1
case .ratio2x1:
return 1 / 2
case .none:
return nil
}
}
}

View File

@ -20,6 +20,9 @@ public protocol FormFieldable {
/// Protocol for FormFieldable that require internal validation. /// Protocol for FormFieldable that require internal validation.
public protocol FormFieldInternalValidatable: FormFieldable, Errorable { public protocol FormFieldInternalValidatable: FormFieldable, Errorable {
/// Rules that drive the validator
var rules: [AnyRule<ValueType>] { get set }
/// Is there an internalError /// Is there an internalError
var hasInternalError: Bool { get } var hasInternalError: Bool { get }
/// Internal Error Message that will show. /// Internal Error Message that will show.

View File

@ -705,11 +705,11 @@ extension LayoutConstraintable {
} }
// Method to check if the view is pinned to its superview // Method to check if the view is pinned to its superview
public func isPinnedToSuperview() -> Bool { public func isPinnedEqual() -> Bool {
isPinnedVerticallyToSuperview() && isPinnedHorizontallyToSuperview() isPinnedEqualVertically() && isPinnedEqualHorizontally()
} }
public func horizontalPinnedSize() -> CGSize? { public func horizontalPinnedWidth() -> CGFloat? {
guard let view = self as? UIView, let superview = view.superview else { return nil } guard let view = self as? UIView, let superview = view.superview else { return nil }
let constraints = superview.constraints let constraints = superview.constraints
@ -735,44 +735,106 @@ extension LayoutConstraintable {
if let leadingView = leadingObject as? UIView, let trailingView = trailingObject as? UIView { if let leadingView = leadingObject as? UIView, let trailingView = trailingObject as? UIView {
let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x
let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width
return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) return trailingPosition - leadingPosition
} else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingGuide = trailingObject as? UILayoutGuide { } else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingGuide = trailingObject as? UILayoutGuide {
let leadingPosition = leadingGuide.layoutFrame.minX let leadingPosition = leadingGuide.layoutFrame.minX
let trailingPosition = trailingGuide.layoutFrame.maxX let trailingPosition = trailingGuide.layoutFrame.maxX
return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) return trailingPosition - leadingPosition
} else if let leadingView = leadingObject as? UIView, let trailingGuide = trailingObject as? UILayoutGuide { } else if let leadingView = leadingObject as? UIView, let trailingGuide = trailingObject as? UILayoutGuide {
let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x
let trailingPosition = trailingGuide.layoutFrame.maxX let trailingPosition = trailingGuide.layoutFrame.maxX
return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) return trailingPosition - leadingPosition
} else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingView = trailingObject as? UIView { } else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingView = trailingObject as? UIView {
let leadingPosition = leadingGuide.layoutFrame.minX let leadingPosition = leadingGuide.layoutFrame.minX
let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width
return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) return trailingPosition - leadingPosition
} }
} else if let pinnedObject = leadingPinnedObject { } else if let pinnedObject = leadingPinnedObject {
if let view = pinnedObject as? UIView { if let view = pinnedObject as? UIView {
return view.bounds.size return view.bounds.size.width
} else if let layoutGuide = pinnedObject as? UILayoutGuide { } else if let layoutGuide = pinnedObject as? UILayoutGuide {
return layoutGuide.layoutFrame.size return layoutGuide.layoutFrame.size.width
} }
} else if let pinnedObject = trailingPinnedObject { } else if let pinnedObject = trailingPinnedObject {
if let view = pinnedObject as? UIView { if let view = pinnedObject as? UIView {
return view.bounds.size return view.bounds.size.width
} else if let layoutGuide = pinnedObject as? UILayoutGuide { } else if let layoutGuide = pinnedObject as? UILayoutGuide {
return layoutGuide.layoutFrame.size return layoutGuide.layoutFrame.size.width
} }
} }
return nil return nil
} }
public func verticalPinnedHeight() -> CGFloat? {
guard let view = self as? UIView, let superview = view.superview else { return nil }
let constraints = superview.constraints
var topPinnedObject: AnyObject?
var bottomPinnedObject: AnyObject?
for constraint in constraints {
if (constraint.firstItem === view && (constraint.firstAttribute == .top || constraint.firstAttribute == .topMargin)) {
topPinnedObject = constraint.secondItem as AnyObject?
} else if (constraint.secondItem === view && (constraint.secondAttribute == .top || constraint.secondAttribute == .topMargin)) {
topPinnedObject = constraint.firstItem as AnyObject?
} else if (constraint.firstItem === view && (constraint.firstAttribute == .bottom || constraint.firstAttribute == .bottomMargin)) {
bottomPinnedObject = constraint.secondItem as AnyObject?
} else if (constraint.secondItem === view && (constraint.secondAttribute == .bottom || constraint.secondAttribute == .bottomMargin)) {
bottomPinnedObject = constraint.firstItem as AnyObject?
}
}
// Ensure both top and bottom pinned objects are identified
if let topObject = topPinnedObject, let bottomObject = bottomPinnedObject {
// Calculate the size based on the pinned objects
if let topView = topObject as? UIView, let bottomView = bottomObject as? UIView {
let topPosition = topView.convert(topView.bounds.origin, to: superview).y
let bottomPosition = bottomView.convert(bottomView.bounds.origin, to: superview).y + bottomView.bounds.height
return bottomPosition - topPosition
} else if let topGuide = topObject as? UILayoutGuide, let bottomGuide = bottomObject as? UILayoutGuide {
let topPosition = topGuide.layoutFrame.minY
let bottomPosition = bottomGuide.layoutFrame.maxY
return bottomPosition - topPosition
} else if let topView = topObject as? UIView, let bottomGuide = bottomObject as? UILayoutGuide {
let topPosition = topView.convert(topView.bounds.origin, to: superview).y
let bottomPosition = bottomGuide.layoutFrame.maxY
return bottomPosition - topPosition
} else if let topGuide = topObject as? UILayoutGuide, let bottomView = bottomObject as? UIView {
let topPosition = topGuide.layoutFrame.minY
let bottomPosition = bottomView.convert(bottomView.bounds.origin, to: superview).y + bottomView.bounds.height
return bottomPosition - topPosition
}
} else if let pinnedObject = topPinnedObject {
if let view = pinnedObject as? UIView {
return view.bounds.size.height
} else if let layoutGuide = pinnedObject as? UILayoutGuide {
return layoutGuide.layoutFrame.size.height
}
} else if let pinnedObject = bottomPinnedObject {
if let view = pinnedObject as? UIView {
return view.bounds.size.height
} else if let layoutGuide = pinnedObject as? UILayoutGuide {
return layoutGuide.layoutFrame.size.height
}
}
return nil
}
public func isPinnedHorizontallyToSuperview() -> Bool { public func isPinnedEqualHorizontally() -> Bool {
guard let view = self as? UIView, let superview = view.superview else { return false } guard let view = self as? UIView, let superview = view.superview else { return false }
let constraints = superview.constraints let constraints = superview.constraints
var leadingPinned = false var leadingPinned = false
@ -796,7 +858,7 @@ extension LayoutConstraintable {
return leadingPinned && trailingPinned return leadingPinned && trailingPinned
} }
public func isPinnedVerticallyToSuperview() -> Bool { public func isPinnedEqualVertically() -> Bool {
guard let view = self as? UIView, let superview = view.superview else { return false } guard let view = self as? UIView, let superview = view.superview else { return false }
let constraints = superview.constraints let constraints = superview.constraints
var topPinned = false var topPinned = false