Merge branch 'mbruce/bugfix' into 'develop'
Bugs/Refactor See merge request BPHV_MIPS/vds_ios!271
This commit is contained in:
commit
11af2e5eb9
@ -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()
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user