SmartText

Swift 库,提供了一系列有用的文本格式化和验证工具,这些工具在您的项目中可能会很有用。

SwiftUI

'SmartTextField' 封装了 UITextField,以便利用这些强大的工具,并使其易于与 SwiftUI 一起使用。 它处理文本格式化、验证、错误消息以及格式化文本时光标的正确位置。

#Preview("SmartTextField") {
    @State var text: String = ""
    @State var errors: [TextValidationResult] = []
    
    return VStack {
        Spacer()
        SmartTextField(text: $text,
                       errors: $errors,
                       configuration: .init(placeholder: "Email",
                                            textFormatter: .email,
                                            textValidator: .email()))
            .fixedSize(horizontal: false, vertical: true)
        Spacer()
    }
}

UIKit

'UISmartTextField' 封装了 UITextField,以便利用这些强大的工具,并使其易于与 UIKit 一起使用。 它处理文本格式化、验证、错误消息以及格式化文本时光标的正确位置。 UITextField 事件可以通过 'UISmartTextField.Eventier' 中的 'Smart' 闭包来处理。

let email = UISmartTextField()
let emailConfig: UISmartTextField.Configuration
emailConfig = .init(placeholder: "Email",
                    textFormatter: [
                        .email,
                        .stripLeadingAndTrailingSpaces
                    ],
                    textValidator: [
                        .notEmpty(errorText: "Email is required"),
                        .email(errorText: "Invalid email format")
                    ],
                    textContentType: .emailAddress,
                    keyboardType: .emailAddress)
email.configure(with: emailConfig)

let password = UISmartTextField()
let passwordConfig: UISmartTextField.Configuration
passwordConfig = .init(placeholder: "Password",
                       textFormatter: .stripLeadingAndTrailingSpaces,
                       textValidator: [
                           .notEmpty(errorText: "Password is required"),
                           .includesLowerAndUppercase(errorText: "Password must contain lower and uppercase characters"),
                           .minLengthLimited(8, errorText: "Password must be at least 8 characters long")
                       ],
                       textContentType: .emailAddress,
                       keyboardType: .emailAddress)
password.configure(with: passwordConfig)

键盘和工具栏的技巧

您可以在 SmartTextField.Configuration 中声明 UIToolbar,以便在表单中的文本字段之间导航。

@FocusState var focus: AuthTextFieldForm?
enum AuthTextFieldForm: Int, TextFieldsFormContract {
    case email
    case password
}
protocol TextFieldsFormContract: RawRepresentable, Hashable, CaseIterable where RawValue == Int {
    func next() -> Self?
    func previous() -> Self?
}

extension TextFieldsFormContract where RawValue == Int {
    func next() -> Self? {
        return .init(rawValue: rawValue + 1)
    }

    func previous() -> Self? {
        return .init(rawValue: rawValue - 1)
    }
}

如何在 Swift 中使用 previousnextdone 按钮创建 UIToolbar?

static func toolbar<FocusType: TextFieldsFormContract>(with type: FocusType, focus: FocusState<FocusType?>.Binding) -> UIToolbar {
    let doneButton = BlockBarButtonItem(barButtonSystemItem: .done, actionHandler: { focus.wrappedValue = nil })

    let prev = type.previous()
    let prevButton = BlockBarButtonItem(image: UIImage(systemName: "chevron.left")!, actionHandler: { focus.wrappedValue = prev })
    prevButton.isEnabled = prev != nil

    let next = type.next()
    let nextButton = BlockBarButtonItem(image: UIImage(systemName: "chevron.right")!, actionHandler: { focus.wrappedValue = next })
    nextButton.isEnabled = next != nil

    let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)

    let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 40))
    toolBar.setItems([prevButton, nextButton, flexibleSpace, doneButton], animated: false)
    return toolBar
}

如何在 Swift 中使用闭包创建 UIBarButtonItem?

 final class BlockBarButtonItem: UIBarButtonItem {
    private var actionHandler: (() -> Void)?

    convenience init(barButtonSystemItem systemItem: UIBarButtonItem.SystemItem, actionHandler: (() -> Void)?) {
        self.init(barButtonSystemItem: systemItem, target: nil, action: #selector(barButtonItemPressed))
        self.target = self
        self.actionHandler = actionHandler
    }

    convenience init(title: String, style: UIBarButtonItem.Style = .plain, actionHandler: (() -> Void)?) {
        self.init(title: title, style: style, target: nil, action: #selector(barButtonItemPressed))
        self.target = self
        self.actionHandler = actionHandler
    }

    convenience init(image: UIImage, style: UIBarButtonItem.Style = .plain, actionHandler: (() -> Void)?) {
        self.init(image: image, style: style, target: nil, action: #selector(barButtonItemPressed))
        self.target = self
        self.actionHandler = actionHandler
    }

    @objc
    private func barButtonItemPressed(sender: UIBarButtonItem) {
        actionHandler?()
    }
}