Skip to content

Instantly share code, notes, and snippets.

@m1kah
Created March 27, 2021 08:28
Show Gist options
  • Save m1kah/461e1ac7c2025aa01b7193758ceab875 to your computer and use it in GitHub Desktop.
Save m1kah/461e1ac7c2025aa01b7193758ceab875 to your computer and use it in GitHub Desktop.
Partial SwiftUI field that allows input of hours and minutes
struct UIDurationField: UIViewRepresentable {
func makeUIView(context: Context) -> UITextField {
let coordinator = context.coordinator
let textField = PaddedTextField()
textField.delegate = coordinator
textField.backgroundColor = MyUIColor.textFieldBackground
textField.textAlignment = .center
textField.keyboardType = .numberPad
textField.placeholder = "00" + String(coordinator.separator) + "00"
textField.font = UIFont.systemFont(ofSize: 22)
textField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
textField.layer.cornerRadius = 6
textField.layer.borderWidth = 2
textField.layer.borderColor = UIColor.clear.cgColor
return textField
}
func updateUIView(_ textField: UITextField, context: Context) {
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator: NSObject, UITextFieldDelegate {
let separator: Character = DateFormat.timeSeparator()
private let mask = "##" + String(DateFormat.timeSeparator()) + "##"
private let placeholderColor = UIColor(red: 0, green: 0, blue: 0.0980392, alpha: 0.22)
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.backgroundColor = UIColor.clear
textField.layer.borderColor = MyUIColor.tint.cgColor
}
func textFieldDidEndEditing(_ textField: UITextField) {
textField.backgroundColor = MyUIColor.textFieldBackground
textField.layer.borderColor = UIColor.clear.cgColor
}
func textField(
_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String
) -> Bool {
guard let input = textField.text else { return false }
// Keep cursor at end of the field
let end = textField.textRange(from: textField.endOfDocument, to: textField.endOfDocument)
textField.selectedTextRange = end
// Max 4 digits, allow spaces and follow mask
let edited =
input.filter { $0.isNumber }.count >= 4 && !string.isEmpty
? string
: (input as NSString).replacingCharacters(in: range, with: string)
let cleanText = onlyAllowedCharacters(edited)
let newText = removeLeadingSpace(cleanText, count: range.length + 1)
let padded = pad(newText)
let maskedText = StringUtil.mask(padded, mask: mask)
guard newText != maskedText else { return true }
textField.text = maskedText
guard string != "" else { return false }
return false
}
private func onlyAllowedCharacters(_ text: String) -> String {
return text.filter {
$0.isNumber || $0 == " "
}
}
private func pad(_ text: String) -> String {
if text.count < 4 {
let padding = String(repeating: " ", count: 4 - text.count)
return padding + text
}
return text
}
private func removeLeadingSpace(_ text: String, count: Int) -> String {
let removedCharacterCount = text.filter { $0 == " "}.count
let toRemoveCount = min(count, removedCharacterCount)
let startIndex = text.index(text.startIndex, offsetBy: toRemoveCount)
return String(text[startIndex..<text.endIndex])
}
}
}
class PaddedTextField: UITextField {
let padding = UIEdgeInsets(top: 2, left: 8, bottom: 2, right: 8)
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
bounds.insetBy(dx: 8, dy: 2)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
bounds.insetBy(dx: 8, dy: 2)
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
bounds.insetBy(dx: 8, dy: 2)
}
override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
[]
}
override func caretRect(for position: UITextPosition) -> CGRect {
.zero
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
false
}
}
class StringUtil {
static func mask(_ text: String, mask: String) -> String {
var result = ""
var textIndex = text.startIndex
var maskIndex = mask.startIndex
while textIndex < text.endIndex && maskIndex < mask.endIndex {
if mask[maskIndex] == "#" {
result.append(text[textIndex])
textIndex = text.index(after: textIndex)
} else {
result.append(mask[maskIndex])
}
maskIndex = mask.index(after: maskIndex)
}
return result
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment