Created
March 27, 2021 08:28
-
-
Save m1kah/461e1ac7c2025aa01b7193758ceab875 to your computer and use it in GitHub Desktop.
Partial SwiftUI field that allows input of hours and minutes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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