Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
7151D4102CE5970500620914 /* Exceptions for "CalculatorApp-Codebase" folder in "CalculatorApp-Codebase" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
App/Info.plist,
);
target = 7151D3E72CE5970400620914 /* CalculatorApp-Codebase */;
};
Expand Down Expand Up @@ -307,11 +307,13 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "CalculatorApp-Codebase/Info.plist";
INFOPLIST_FILE = "CalculatorApp-Codebase/App/Info.plist";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDarkContent;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -321,7 +323,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
Expand All @@ -333,11 +335,13 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "CalculatorApp-Codebase/Info.plist";
INFOPLIST_FILE = "CalculatorApp-Codebase/App/Info.plist";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDarkContent;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -347,7 +351,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,34 @@ import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {



func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}


/// Specifies the supported interface orientations for the application.
/// - Note: This method restricts the app to portrait orientation only.
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return .portrait // Allow Only Portrait Mode
}

// MARK: UISceneSession Lifecycle

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//
// CalculatorLogic.swift
// CalculatorApp-Codebase
//
// Created by t0000-m0112 on 2024-11-20.
//

import UIKit

// MARK: CalculatorLogic Class
/// Handles all the calculation logic, including managing expressions and results.
class CalculatorLogic {
weak var delegate: CalculatorLogicDelegate?

// MARK: Properties
private var isLastInputOperator = false
private var isLastInputZero = true
private var expression = "0" {
didSet {
delegate?.didUpdateExpression(expression)
}
}
}

// MARK: - Input Handler
/// Handles input actions and updates the expression accordingly.
extension CalculatorLogic {
internal func buttonAction(from sender: UIButton) {
guard let buttonTitle = sender.titleLabel?.text else { return }

switch buttonTitle {
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
handleLeadingZeroIfNeeded() // Input Invalidation: Numbers with starting zero (Case B)
appendNumberToExpression(buttonTitle)
handleFirstZeroIfNeeded() // Input Invalidation: Numbers with starting zero (Case A)
case "+", "-", "×", "÷":
handleLastOperatorIfNeeded() // Input Invalidation: Expressions with duplicated operators
appendOperatorToExpression(buttonTitle)
case "AC":
resetExpression()
case "=":
handleLastOperatorIfNeeded() // Input Invalidation: Expressions with an operator in the end
calculateExpression()
default:
break
}
}
}

// MARK: - Input Validation (Exception Handling)
/// Validates and corrects invalid input cases like duplicated operators or starting zero.
extension CalculatorLogic {
/// Handles the case where the expression starts with a leading zero (e.g., "0123") - Case A.
/// If the second character in the expression is a number, the leading zero is removed.
private func handleFirstZeroIfNeeded() {
guard self.expression.count > 1 else { return }
if self.expression[expression.startIndex] == "0" && self.expression[expression.index(expression.startIndex, offsetBy: 1)].isNumber {
self.expression.removeFirst()
}
}

/// Handles the case where the expression has an invalid leading zero (e.g., "+001" or "×03") - Case B.
/// Removes the last zero if it is invalid.
private func handleLeadingZeroIfNeeded() {
guard self.expression.count > 2 else { return }
if isLastInputZero && !self.expression[expression.index(expression.endIndex, offsetBy: -2)].isNumber {
self.expression.removeLast()
}
}

/// Handles the case where the last input is an operator.
/// If the last character in the expression is an operator, it is removed.
private func handleLastOperatorIfNeeded() {
if isLastInputOperator {
self.expression.removeLast()
}
}

/// Determines whether handling zero input is necessary.
/// - Parameter input: The current input character.
/// - Returns: A boolean indicating if the input can proceed.
/// - If the input is "0", checks if the last character is "÷" to prevent division by zero.
/// - Updates the `isLastInputZero` flag based on the input.
private func isHandlingZeroInputNeeded(_ input: String) -> Bool {
if input == "0" {
guard self.expression[expression.index(expression.endIndex, offsetBy: -1)] != "÷" else { return false }
self.isLastInputZero = true
} else {
self.isLastInputZero = false
}
return true
}
}

// MARK: - Expression Modification
/// Modifies the current expression by appending numbers or operators.
extension CalculatorLogic {
internal func appendNumberToExpression(_ input: String) {
guard isHandlingZeroInputNeeded(input) else { return } // Input Invalidation: Expressions that devides with zero
self.expression.append(input)
self.isLastInputOperator = false
}

internal func appendOperatorToExpression(_ input: String) {
self.expression.append(input)
self.isLastInputOperator = true
self.isLastInputZero = false
}

internal func resetExpression() {
self.expression = "0"
self.isLastInputOperator = false
self.isLastInputZero = true
}

internal func calculateExpression() {
if let result = calculate(expression) {
self.expression = String(result)
self.isLastInputOperator = false
self.isLastInputZero = false
}
}
}

// MARK: - Math Engine
/// Handles the mathematical calculations based on the current expression.
extension CalculatorLogic {
private func calculate(_ expression: String) -> Int? {
let expression = NSExpression(format: changeMathSymbols(expression))
if let result = expression.expressionValue(with: nil, context: nil) as? Int {
return result
} else {
return nil
}
}

private func changeMathSymbols(_ expression: String) -> String {
expression
.replacingOccurrences(of: "×", with: "*")
.replacingOccurrences(of: "÷", with: "/")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// CalculatorLogicDelegate.swift.swift
// CalculatorApp-Codebase
//
// Created by t0000-m0112 on 2024-11-20.
//

// MARK: CalculatorLogicDelegate Protocol
/// A protocol to handle updates from the CalculatorLogic class.
protocol CalculatorLogicDelegate: AnyObject {
/// Called when the expression is updated.
/// - Parameter expression: The updated expression string.
func didUpdateExpression(_ expression: String)
}
Loading