SwiftUI计算器无法计算求助:实现运算与等号求值功能
Hey there! Let's get your SwiftUI calculator actually crunching numbers instead of just showing buttons. The main issue right now is that your button tap handler only handles digit input—none of the operation, equals, or advanced function logic is hooked up. Let's fix that with targeted updates to your code:
Step 1: Enhance the DisplayingActions View Model
First, we need to expand your view model to track selected operations, store the first operand, and handle core calculations. We'll add error handling for edge cases like division by zero and invalid factorial inputs too:
class DisplayingActions: ObservableObject { @Published var display = "0" @Published var userIsTyping = false var storedNumber: Double? // Track the selected math operation var selectedOperation: CalcButtons? var displayValue: Double { get { // Safely convert display string to Double (avoid force unwrap crashes) return Double(display) ?? 0 } set { // Clean up display: show integers as whole numbers, decimals as-is display = newValue.truncatingRemainder(dividingBy: 1) == 0 ? String(Int(newValue)) : String(newValue) } } // Core calculation logic func compute() { guard let stored = storedNumber, let operation = selectedOperation else { return } var result = 0.0 switch operation { case .plus: result = stored + displayValue case .minus: result = stored - displayValue case .multiply: result = stored * displayValue case .divide: if displayValue != 0 { result = stored / displayValue } else { display = "Error" userIsTyping = false return } case .rootExtraction: result = sqrt(stored) case .factorial: result = factorial(of: stored) case .percent: result = stored * (displayValue / 100) default: break } displayValue = result storedNumber = nil selectedOperation = nil userIsTyping = false } // Helper for factorial calculation private func factorial(of number: Double) -> Double { guard number >= 0, number.truncatingRemainder(dividingBy: 1) == 0 else { display = "Error" userIsTyping = false return 0 } let intNumber = Int(number) return (1...intNumber).reduce(1, *) } // Reset calculator to initial state func reset() { display = "0" userIsTyping = false storedNumber = nil selectedOperation = nil } // Delete last digit from display func deleteDigit() { if display.count > 1 { display.removeLast() } else { display = "0" userIsTyping = false } } }
Step 2: Update Button Tap Handling
Next, we'll rewrite the button tap logic in CalculatorButtonsView to handle different button types (digits, operations, equals, etc.) appropriately. This replaces the generic digit-only handler you had before:
struct CalculatorButtonsView: View { var button: CalcButtons @EnvironmentObject var actions: DisplayingActions var body: some View{ Button(action: handleButtonTap) { Text(button.title) .font(.system(size: 30)) .frame(width: buttonWidth(button: button), height: (UIScreen.main.bounds.width - 5 * 3 ) / 5) .foregroundColor(.white) .background(button.backgroundButtonsColor) .cornerRadius(buttonWidth(button: button)) } } private func handleButtonTap() { switch button { // Handle digit and decimal input case .zero, .one, .two, .three, .four, .five, .six, .seven, .eight, .nine, .decimal: handleDigitInput() // Handle math operations case .plus, .minus, .multiply, .divide, .rootExtraction, .factorial, .percent: handleOperationInput() // Trigger calculation case .equals: actions.compute() // Reset calculator case .ac: actions.reset() // Toggle positive/negative value case .minusPlus: actions.displayValue *= -1 // Delete last digit case .deleteOneDigit: actions.deleteDigit() // Handle 10^a operation case .tenInExponentiation: actions.displayValue = pow(10, actions.displayValue) actions.userIsTyping = false // Handle X^10 operation case .numberInExponentiation: actions.displayValue = pow(actions.displayValue, 10) actions.userIsTyping = false } } private func handleDigitInput() { if actions.userIsTyping { // Prevent multiple decimals if button == .decimal, actions.display.contains(".") { return } actions.display += button.title } else { // Start with 0. if decimal is tapped first actions.display = button == .decimal ? "0." : button.title actions.userIsTyping = true } } private func handleOperationInput() { // Calculate prior operation if one exists if actions.storedNumber != nil, actions.selectedOperation != nil { actions.compute() } // Store current value and selected operation actions.storedNumber = actions.displayValue actions.selectedOperation = button actions.userIsTyping = false // Execute single-operand operations immediately (sqrt, factorial) if button == .rootExtraction || button == .factorial { actions.compute() } } private func buttonWidth(button: CalcButtons) -> CGFloat { if button == .zero { return (UIScreen.main.bounds.width - 4 * 3 ) / 4 * 1.6 } return (UIScreen.main.bounds.width - 5 * 3 ) / 5 } }
Key Improvements Explained
- Separate Logic by Button Type: Each button category (digits, operations, controls) has dedicated handling, making the code easier to maintain.
- Error Handling: We added checks for division by zero and non-integer factorial inputs, showing an "Error" message instead of crashing.
- Clean Display: Integers are shown without trailing decimals, and decimal input is restricted to one per number.
- Full Feature Support: All your defined buttons (AC, delete, sqrt, factorial, exponents) now work as intended.
- Chained Calculations: You can now do sequences like
5 + 3 * 2and get the correct result (since we compute prior operations when a new one is selected).
Try testing these flows to verify:
- Basic arithmetic:
5 + 3 =→ 8 - Single-operand functions:
4 √x→ 2;5 X!→ 120 - Edge cases:
10 / 0→ "Error"
内容的提问来源于stack exchange,提问作者Brian Anderson

