Skip to content

Commit

Permalink
new error handling system and optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
NikSativa committed Jul 14, 2024
1 parent b416b68 commit 5ba6254
Show file tree
Hide file tree
Showing 67 changed files with 785 additions and 653 deletions.
15 changes: 1 addition & 14 deletions Source/Formatter/TextFormatable.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
import Foundation

public protocol TextFormatable {
var uniqueID: String { get }
func formatText(_ string: String) -> String
func format(_ value: String) -> String
}

public extension TextFormatable {
var uniqueID: String {
makeUniqueID()
}

func makeUniqueID() -> String {
String(describing: type(of: self))
}

func toFormatter() -> TextFormatter {
return .init(self)
}

static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.uniqueID == rhs.uniqueID
}
}
4 changes: 2 additions & 2 deletions Source/Formatter/TextFormatter.Custom.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ private struct CustomFormatter: TextFormatable {
self.formatter = formatter
}

func formatText(_ string: String) -> String {
return formatter(string)
func format(_ value: String) -> String {
return formatter(value)
}
}
4 changes: 2 additions & 2 deletions Source/Formatter/TextFormatter.Email.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ private struct EmailTextFormatter: TextFormatable {
+ "1234567890"
+ "-_.+"

public func formatText(_ string: String) -> String {
public func format(_ value: String) -> String {
var hasDomain = false
return string.filter {
return value.filter {
if EmailTextFormatter.kAllowedCharacters.contains($0) {
return true
} else if $0 == "@" {
Expand Down
4 changes: 2 additions & 2 deletions Source/Formatter/TextFormatter.Identity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public extension TextFormatter {
}

private struct IdentityTextFormatter: TextFormatable {
public func formatText(_ string: String) -> String {
return string
public func format(_ value: String) -> String {
return value
}
}
8 changes: 2 additions & 6 deletions Source/Formatter/TextFormatter.LengthLimited.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ private struct MaxCharsFormatter: TextFormatable {
self.maxChars = maxChars
}

public func formatText(_ string: String) -> String {
return string[maxLength: maxChars]
}

var uniqueID: String {
return makeUniqueID() + " \(maxChars)"
public func format(_ value: String) -> String {
return value[maxLength: maxChars]
}
}
4 changes: 2 additions & 2 deletions Source/Formatter/TextFormatter.NumbersOnly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ public extension TextFormatter {
}

private struct NumbersOnlyFormatter: TextFormatable {
public func formatText(_ string: String) -> String {
public func format(_ value: String) -> String {
let kAllowedCharacters = "0123456789"
let formattedText = string.filter { character in
let formattedText = value.filter { character in
return kAllowedCharacters.contains(character)
}
return formattedText
Expand Down
4 changes: 2 additions & 2 deletions Source/Formatter/TextFormatter.StripLeadingSpaces.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ public extension TextFormatter {
}

private struct StripLeadingSpaces: TextFormatable {
public func formatText(_ string: String) -> String {
let formattedString = string.reduce("") { result, nextCharacter -> String in
public func format(_ value: String) -> String {
let formattedString = value.reduce("") { result, nextCharacter -> String in
if nextCharacter == " ", result.isEmpty {
return result
}
Expand Down
4 changes: 2 additions & 2 deletions Source/Formatter/TextFormatter.StripSpaces.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public extension TextFormatter {
}

private struct StripLeadingAndTrailingSpaces: TextFormatable {
func formatText(_ string: String) -> String {
return string.trimmingCharacters(in: .whitespacesAndNewlines)
func format(_ value: String) -> String {
return value.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
14 changes: 4 additions & 10 deletions Source/Formatter/TextFormatter.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public final class TextFormatter: Equatable, ExpressibleByArrayLiteral {
public final class TextFormatter: ExpressibleByArrayLiteral {
private let formatters: [TextFormatable]

public required init(_ formatables: [TextFormatable]) {
Expand All @@ -21,10 +21,10 @@ public final class TextFormatter: Equatable, ExpressibleByArrayLiteral {
self.init(formatables)
}

public func formatText(_ text: String) -> String {
var formattedText = text
public func format(_ value: String) -> String {
var formattedText = value
for formatter in formatters {
formattedText = formatter.formatText(formattedText)
formattedText = formatter.format(formattedText)
}
return formattedText
}
Expand All @@ -43,10 +43,4 @@ public final class TextFormatter: Equatable, ExpressibleByArrayLiteral {
let combinedValidations = lhs.formatters + rhs.formatters
return .init(combinedValidations)
}

public static func ==(lhs: TextFormatter, rhs: TextFormatter) -> Bool {
let lhsIDs = Set(lhs.formatters.map(\.uniqueID))
let rhsIDs = Set(rhs.formatters.map(\.uniqueID))
return lhsIDs == rhsIDs
}
}
24 changes: 24 additions & 0 deletions Source/RangeExt.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Foundation

public extension Array {
func combineRanges<T: Comparable>() -> [Range<T>] where Element == Range<T> {
var result: [Range<T>] = []
var lastRange: Range<T>?
for range in self {
if let _lastRange = lastRange {
if range.lowerBound <= _lastRange.upperBound {
lastRange = _lastRange.lowerBound..<range.upperBound
} else {
result.append(_lastRange)
lastRange = range
}
} else {
lastRange = range
}
}
if let lastRange {
result.append(lastRange)
}
return result
}
}
16 changes: 4 additions & 12 deletions Source/SmartTextField.Eventier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public extension SmartTextField {
public var didBeginEditing: () -> Void

public var textDidChanged: (_ newValue: String) -> Void
public var errorDidChanged: (_ state: TextValidationResult) -> Void
public var errorDidChanged: (_ state: [TextValidationResult]) -> Void

public var didEndEditing: () -> Void
/// Return `true` if you want to resign first responder
Expand All @@ -25,7 +25,7 @@ public extension SmartTextField {
didBeginEditing: @escaping () -> Void = {},
dateDidChanged: @escaping (Date) -> Void = { _ in },
textDidChanged: @escaping (String) -> Void = { _ in },
errorDidChanged: @escaping (TextValidationResult) -> Void = { _ in },
errorDidChanged: @escaping ([TextValidationResult]) -> Void = { _ in },
didEndEditing: @escaping () -> Void = {},
didTapReturnButton: @escaping () -> Bool = { true },
didTapToolbarDoneButton: @escaping () -> Void = {},
Expand All @@ -47,7 +47,7 @@ public extension SmartTextField {
didBeginEditing: @escaping () -> Void = {},
dateDidChanged: @escaping (Date) -> Void = { _ in },
textDidChanged: @escaping (String) -> Void = { _ in },
errorDidChanged: @escaping (TextValidationResult) -> Void = { _ in },
errorDidChanged: @escaping ([TextValidationResult]) -> Void = { _ in },
didEndEditing: @escaping () -> Void = {},
didTapReturnButton: @escaping () -> Bool = { true },
clearButton: @escaping () -> Void = {}) {
Expand All @@ -65,7 +65,7 @@ public extension SmartTextField {
public required init(shouldBeginEditing: @escaping () -> Bool = { true },
didBeginEditing: @escaping () -> Void = {},
textDidChanged: @escaping (String) -> Void = { _ in },
errorDidChanged: @escaping (TextValidationResult) -> Void = { _ in },
errorDidChanged: @escaping ([TextValidationResult]) -> Void = { _ in },
didEndEditing: @escaping () -> Void = {},
didTapReturnButton: @escaping () -> Bool = { true },
clearButton: @escaping () -> Void = {}) {
Expand All @@ -80,12 +80,4 @@ public extension SmartTextField {
#endif
}
}

// MARK: - SmartTextField.Eventier + Equatable

extension SmartTextField.Eventier: Equatable {
public static func ==(lhs: SmartTextField.Eventier, rhs: SmartTextField.Eventier) -> Bool {
return lhs === rhs
}
}
#endif
4 changes: 2 additions & 2 deletions Source/SmartTextField.State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public extension SmartTextField {
}

#if os(iOS) || targetEnvironment(macCatalyst) || os(visionOS)
struct DatePicker: Equatable {
struct DatePicker {
public let calendar: Calendar
public let dateFormatter: Foundation.DateFormatter
public let minDate: Date
Expand All @@ -36,7 +36,7 @@ public extension SmartTextField {
}
#endif

struct Configuration: Equatable {
struct Configuration {
public enum Placeholder: Equatable, ExpressibleByStringLiteral {
case text(String)
case attributed(NSAttributedString)
Expand Down
12 changes: 6 additions & 6 deletions Source/SmartTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ public final class SmartTextField: UIView {
}
}

public var silentErrorState: TextValidationResult {
return textValidator.isTextValid(real.text ?? "")
public var validationState: [TextValidationResult] {
return textValidator.validate(real.text ?? "")
}

#if os(iOS) || targetEnvironment(macCatalyst) || os(visionOS)
Expand Down Expand Up @@ -88,13 +88,13 @@ public final class SmartTextField: UIView {
textFormatter = viewState.textFormatter
textValidator = viewState.textValidator

real.text = textFormatter.formatText(real.text ?? "")
real.text = textFormatter.format(real.text ?? "")
}

public func setText(_ text: String?) {
let oldText = real.text

let newText = text.map(textFormatter.formatText) ?? ""
let newText = text.map(textFormatter.format) ?? ""
real.text = newText

if oldText != newText {
Expand Down Expand Up @@ -223,7 +223,7 @@ public final class SmartTextField: UIView {

private func checkError() {
if !isFirstResponder {
let state = textValidator.isTextValid(real.text ?? "")
let state = textValidator.validate(real.text ?? "")
eventier.errorDidChanged(state)
}
}
Expand All @@ -247,7 +247,7 @@ extension SmartTextField: UITextFieldDelegate {

let original = textField.text ?? ""
let updated = (original as NSString).replacingCharacters(in: range, with: string) as String
let formatted = textFormatter.formatText(updated)
let formatted = textFormatter.format(updated)
textField.text = formatted

var offset = 0
Expand Down
24 changes: 24 additions & 0 deletions Source/StringExt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,28 @@ public extension String {
let end = index(startIndex, offsetBy: min(from + maxLength, count))
return String(self[start..<end])
}

func ranges(of substring: String,
options: CompareOptions = [],
locale: Locale? = nil) -> [Range<Index>] {
var ranges: [Range<Index>] = []
while let range = range(of: substring,
options: options,
range: (ranges.last?.upperBound ?? startIndex)..<endIndex,
locale: locale) {
ranges.append(range)
}
return ranges
}

func ranges(of aSet: CharacterSet,
options: CompareOptions = []) -> [Range<Index>] {
var ranges: [Range<Index>] = []
while let range = rangeOfCharacter(from: aSet,
options: options,
range: (ranges.last?.upperBound ?? startIndex)..<endIndex) {
ranges.append(range)
}
return ranges.combineRanges()
}
}
16 changes: 1 addition & 15 deletions Source/Validator/TextValidatable.swift
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
import Foundation

public protocol TextValidatable {
var uniqueID: String { get }
var errorText: String? { get }
func isValid(string: String) -> Bool
func validate(_ value: String) -> TextValidationResult
}

public extension TextValidatable {
var uniqueID: String {
return makeUniqueID()
}

func makeUniqueID() -> String {
return String(describing: type(of: self)) + (errorText.map { " errorText: \($0)" } ?? "")
}

func toValidator() -> TextValidator {
return .init(self)
}

static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.uniqueID == rhs.uniqueID
}
}
42 changes: 22 additions & 20 deletions Source/Validator/TextValidationResult.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import Foundation

public enum TextValidationResult: Equatable {
case valid
case invalid
case invalidWithErrorText(String)
public struct TextValidationResult: Equatable {
public let invalidRanges: [Range<String.Index>]
public let errorText: String?
public let isValid: Bool

public var isValid: Bool {
switch self {
case .invalid,
.invalidWithErrorText:
return false
case .valid:
return true
}
public static let valid: Self = .init(invalidRanges: [], errorText: nil, isValid: true)
public static func invalid(withErrorText: String? = nil) -> Self {
return .init(invalidRanges: [], errorText: withErrorText, isValid: false)
}

public var errorText: String? {
switch self {
case .invalidWithErrorText(let text):
return text
case .invalid,
.valid:
return nil
}
public init(invalidRanges: [Range<String.Index>] = [],
errorText: String? = nil,
isValid: Bool) {
self.invalidRanges = invalidRanges
self.errorText = errorText
self.isValid = isValid
}
}

public extension TextValidationResult {
static let invalid: Self = .init(isValid: false)
}

public extension [TextValidationResult] {
static let invalid: Self = [.invalid]
static let valid: Self = []
}
Loading

0 comments on commit 5ba6254

Please sign in to comment.