1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
//
// SettingsInputCell.swift
// MullvadVPN
//
// Created by Jon Petersson on 2023-05-05.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import UIKit
class SettingsInputCell: SelectableSettingsCell {
let textField = CustomTextField(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 30)))
var toolbarDoneButton = UIBarButtonItem()
var isValidInput: Bool {
didSet {
updateTextFieldInputValidity()
}
}
var inputDidChange: ((String) -> Void)?
var inputWasConfirmed: ((String) -> Void)?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
isValidInput = true
super.init(style: style, reuseIdentifier: reuseIdentifier)
toolbarDoneButton = UIBarButtonItem(
title: NSLocalizedString("Done", comment: ""),
style: .done,
target: self,
action: #selector(confirmInput)
)
accessoryView = textField
setUpTextField()
setUpTextFieldToolbar()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
reset()
}
func reset() {
textField.text = nil
UITextField.SearchTextFieldAppearance.inactive.apply(to: textField)
}
func setInput(_ text: String) {
textField.text = text
textFieldDidChange(textField)
}
@objc func confirmInput() {
_ = textFieldShouldReturn(textField)
}
@objc private func textFieldDidChange(_ textField: UITextField) {
if let text = textField.text {
inputDidChange?(text)
toolbarDoneButton.isEnabled = isValidInput
}
}
private func setUpTextField() {
textField.borderStyle = .none
textField.layer.cornerRadius = 4
textField.font = .preferredFont(forTextStyle: .body)
textField.textAlignment = .right
textField.delegate = self
textField.keyboardType = .numberPad
textField.returnKeyType = .done
textField.textMargins = UIMetrics.SettingsCell.inputCellTextFieldLayoutMargins
textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
UITextField.SearchTextFieldAppearance.inactive.apply(to: textField)
}
private func setUpTextFieldToolbar() {
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 44))
toolbar.items = [
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil),
toolbarDoneButton,
]
toolbar.sizeToFit()
textField.inputAccessoryView = toolbar
}
private func updateTextFieldInputValidity() {
if isValidInput {
textField.textColor = textField.isEditing ? .SearchTextField.textColor : .SearchTextField.inactiveTextColor
} else {
textField.textColor = UIColor.TextField.invalidInputTextColor
}
}
}
extension SettingsInputCell: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
inputDidChange?(textField.text ?? "")
toolbarDoneButton.isEnabled = isValidInput
UITextField.SearchTextFieldAppearance.active.apply(to: textField)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
guard isValidInput else { return false }
inputWasConfirmed?(textField.text ?? "")
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
UITextField.SearchTextFieldAppearance.inactive.apply(to: textField)
}
}
|