From 8a38f01caafb91f9a957243db43d63e8ed920c79 Mon Sep 17 00:00:00 2001 From: RyosukeCla Date: Wed, 20 Dec 2023 17:14:39 +0900 Subject: [PATCH] impl validation for input form --- ios/Nativebrik/Nativebrik/forms.swift | 127 +++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/ios/Nativebrik/Nativebrik/forms.swift b/ios/Nativebrik/Nativebrik/forms.swift index 5810bcd..5ecf5bc 100644 --- a/ios/Nativebrik/Nativebrik/forms.swift +++ b/ios/Nativebrik/Nativebrik/forms.swift @@ -8,9 +8,106 @@ import Foundation import UIKit import YogaKit +import TipKit + +class TooltipViewController: UIViewController, UIPopoverPresentationControllerDelegate { + var message: String? = "" + init(message: String, source: UIView) { + super.init(nibName: nil, bundle: nil) + self.message = message + self.preferredContentSize = CGSize(width: 400, height: 32) + self.modalPresentationStyle = .popover + self.popoverPresentationController?.sourceView = source + self.popoverPresentationController?.delegate = self + self.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection([.down, .up]) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .init(white: 0, alpha: 0) + let label = UILabel(frame: CGRect(x: 16, y: 16, width: 280 - 32, height: 16)) + label.lineBreakMode = .byWordWrapping; + label.numberOfLines = 0; + label.text = self.message + label.font = .systemFont(ofSize: 16, weight: .semibold) + self.view.addSubview(label) + + let labelHeight = label.intrinsicContentSize.height + label.frame.size.height = labelHeight + self.preferredContentSize = CGSize(width: 400, height: 32 + labelHeight) + } + + // UIPopoverPresentationControllerDelegate + func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { + return .none + } +} + +class InputIconView: UIControl { + required init?(coder: NSCoder) { + super.init(coder: coder) + } + init(systemName: String, message: String?, color: UIColor?, size: Int?, padding: Int?) { + super.init(frame: .zero) + let size = size ?? 16 + let paddingRight = padding ?? 0 + let paddingLeft = padding ?? 4 + + self.configureLayout { layout in + layout.isEnabled = true + layout.paddingLeft = .init(integerLiteral: paddingLeft) + layout.paddingRight = .init(integerLiteral: paddingRight) + layout.height = .init(value: 100, unit: .percent) + layout.width = .init(integerLiteral: paddingLeft + paddingRight + size) + layout.alignItems = .center + layout.justifyContent = .center + } + + let iconView = UIImageView(image: UIImage(systemName: systemName)) + iconView.configureLayout { layout in + layout.isEnabled = true + layout.width = .init(integerLiteral: size) + layout.height = .init(integerLiteral: size) + } + iconView.sizeToFit() + if let color = color { + iconView.tintColor = color + } + + if #available(iOS 14.0, *), let message = message { + self.addAction(.init { _ in + if #available(iOS 17.0, *) { + let tooltip = TooltipViewController(message: message, source: iconView) + self.window?.rootViewController?.present(tooltip, animated: true) + } + }, for: .touchDown) + } + + self.addSubview(iconView) + } + + deinit { + if self.window?.rootViewController?.presentedViewController is TooltipViewController { + self.window?.rootViewController?.dismiss(animated: true) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + self.yoga.applyLayout(preservingOrigin: true) + } +} class TextInputView: UIView, UITextFieldDelegate { var textInput: UITextField? = nil + var validateRegex: String? = nil + var fontSize: Int? = nil + var paddingRight: Int? = nil + var errorMessage: UITooltipMessage? = nil required init?(coder: NSCoder) { super.init(coder: coder) @@ -19,6 +116,11 @@ class TextInputView: UIView, UITextFieldDelegate { init(block: UITextInputBlock) { super.init(frame: .zero) + self.fontSize = block.data?.size + self.paddingRight = block.data?.frame?.paddingRight + self.errorMessage = block.data?.errorMessage + self.validateRegex = block.data?.regex + // wrap layout self.configureLayout { layout in layout.isEnabled = true @@ -38,8 +140,8 @@ class TextInputView: UIView, UITextFieldDelegate { // input layout let textInput = UITextField() self.textInput = textInput + textInput.addTarget(self, action: #selector(onEditingChanged(sender: )), for: .editingChanged) textInput.textAlignment = parseTextAlign(block.data?.textAlign) - textInput.inputAccessoryView = toolbar textInput.delegate = self textInput.configureLayout { layout in @@ -55,12 +157,14 @@ class TextInputView: UIView, UITextFieldDelegate { textInput.rightView = UIView(frame: CGRect(x: 0, y: 0, width: paddingRight, height: 0)) textInput.rightViewMode = .always } + if let color = block.data?.color { textInput.textColor = parseColor(color) } else { textInput.textColor = .label } textInput.font = parseTextBlockDataToUIFont(block.data?.size, block.data?.weight, block.data?.design) + self.fontSize = block.data?.size if let placeholder = block.data?.placeholder { textInput.placeholder = placeholder } @@ -70,6 +174,8 @@ class TextInputView: UIView, UITextFieldDelegate { } else { textInput.autocorrectionType = .no } + } else { + textInput.autocorrectionType = .no } if let secure = block.data?.secure { textInput.isSecureTextEntry = secure @@ -87,6 +193,25 @@ class TextInputView: UIView, UITextFieldDelegate { self.textInput?.resignFirstResponder() } + @objc func onEditingChanged(sender: UITextField) { + guard let regexPattern = self.validateRegex else { + return + } + guard let text = sender.text else { + return + } + if containsPattern(text, regexPattern) { + let view = InputIconView(systemName: "checkmark.circle", message: nil, color: .systemBlue, size: self.fontSize, padding: self.paddingRight) + sender.rightView = view + sender.rightViewMode = .always + } else { + let view = InputIconView(systemName: "info.circle.fill", message: self.errorMessage?.title, color: .systemRed, size: self.fontSize, padding: self.paddingRight) + sender.rightView = view + sender.rightViewMode = .always + } + return + } + // UITextFieldDelegate func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder()