Skip to content

Commit

Permalink
Show error message in DoubleTextField, IntTextField
Browse files Browse the repository at this point in the history
  • Loading branch information
tekezo committed Oct 28, 2023
1 parent 408748f commit d1081ff
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 14 deletions.
52 changes: 40 additions & 12 deletions src/apps/share/swift/Views/DoubleTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ struct DoubleTextField: View {
// Specifically, if a user wants to enter "0.2" and enters a value of "0.", the value becomes "0.0".
// To avoid this, do not specify the formatter directly in the TextField, but use onChange to format the value.
@State private var text = ""
@State private var error = false

private let step: Double
private let range: ClosedRange<Double>
private let width: CGFloat
private let maximumFractionDigits: Int
private let formatter: NumberFormatter

init(
Expand All @@ -25,6 +27,7 @@ struct DoubleTextField: View {
self.step = step
self.range = range
self.width = width
self.maximumFractionDigits = maximumFractionDigits

formatter = NumberFormatter()
formatter.numberStyle = .decimal // Use .decimal number style for double values
Expand All @@ -36,12 +39,6 @@ struct DoubleTextField: View {
var body: some View {
HStack(spacing: 0) {
TextField("", text: $text).frame(width: width)
.onChange(of: text) { newText in
updateValue(newText)
}
.onChange(of: value) { newValue in
updateText(newValue)
}

Stepper(
value: $value,
Expand All @@ -58,27 +55,58 @@ struct DoubleTextField: View {
}
}
}

if error {
Text(
String(
format: "must be between %.\(maximumFractionDigits)f and %.\(maximumFractionDigits)f",
range.lowerBound,
range.upperBound)
)
.foregroundColor(Color.errorForeground)
.background(Color.errorBackground)
}
}
.onChange(of: text) { newText in
update(byText: newText)
}
.onChange(of: value) { newValue in
update(byValue: newValue)
}
}

private func updateText(_ newValue: Double) {
private func update(byValue newValue: Double) {
if let newText = formatter.string(for: newValue) {
if text != newText {
Task { @MainActor in
error = false

Task { @MainActor in
if value != newValue {
value = newValue
}
if text != newText {
text = newText
}
}
} else {
error = true
}
}

private func updateValue(_ newText: String) {
private func update(byText newText: String) {
if let number = formatter.number(from: newText) {
error = false

let newValue = number.doubleValue
if value != newValue {
Task { @MainActor in
Task { @MainActor in
if value != newValue {
value = newValue
}
if text != newText {
text = newText
}
}
} else {
error = true
}
}
}
58 changes: 56 additions & 2 deletions src/apps/share/swift/Views/IntTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import SwiftUI

struct IntTextField: View {
@Binding var value: Int
// When a formatter is applied directly to a TextField, unintended changes to the content may occur during input.
// Specifically, the moment the input content is cleared, the minimum value is automatically entered.
// To avoid this, do not apply the formatter directly to the TextField; instead, apply the formatting in the onChange event.
@State private var text = ""
@State private var error = false

private let step: Int
private let range: ClosedRange<Int>
Expand All @@ -15,6 +20,8 @@ struct IntTextField: View {
width: CGFloat
) {
_value = value
text = String(value.wrappedValue)

self.step = step
self.range = range
self.width = width
Expand All @@ -27,7 +34,7 @@ struct IntTextField: View {

var body: some View {
HStack(spacing: 0) {
TextField("", value: $value, formatter: formatter).frame(width: width)
TextField("", text: $text).frame(width: width)

Stepper(
value: $value,
Expand All @@ -39,11 +46,58 @@ struct IntTextField: View {
if hover {
// In macOS 13.0.1, if the corresponding TextField has the focus, changing the value by Stepper will not be reflected in the TextField.
// Therefore, we should remove the focus before Stepper will be clicked.
DispatchQueue.main.async {
Task { @MainActor in
NSApp.keyWindow?.makeFirstResponder(nil)
}
}
}

if error {
Text("must be between \(range.lowerBound) and \(range.upperBound)")
.foregroundColor(Color.errorForeground)
.background(Color.errorBackground)
}
}
.onChange(of: text) { newText in
update(byText: newText)
}
.onChange(of: value) { newValue in
update(byValue: newValue)
}
}

private func update(byValue newValue: Int) {
if let newText = formatter.string(for: newValue) {
error = false

Task { @MainActor in
if value != newValue {
value = newValue
}
if text != newText {
text = newText
}
}
} else {
error = true
}
}

private func update(byText newText: String) {
if let number = formatter.number(from: newText) {
error = false

let newValue = number.intValue
Task { @MainActor in
if value != newValue {
value = newValue
}
if text != newText {
text = newText
}
}
} else {
error = true
}
}
}

0 comments on commit d1081ff

Please sign in to comment.