├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Sources └── MagicTimer │ ├── Date+Extension.swift │ ├── Double+Extension.swift │ ├── MagicTimerState.swift │ ├── Int+Extension.swift │ ├── MagicTimerExecutive.swift │ ├── MagicTimerCounter.swift │ ├── MagicTimerBackgroundCalculator.swift │ └── MagicTimer.swift ├── Tests └── MagicTimerTests │ └── MagicTimerTests.swift ├── Package.resolved ├── .github └── FUNDING.yml ├── MagicTimer.podspec ├── Package.swift ├── LICENSE ├── docs └── MagicTimer.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/MagicTimer/Date+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Date { 4 | static func - (lhs: Date, rhs: Date) -> TimeInterval { 5 | return lhs.timeIntervalSinceReferenceDate.minus(rhs.timeIntervalSinceReferenceDate) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/MagicTimer/Double+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension TimeInterval { 4 | /// Cast TimeInterval to Int. 5 | func convertToInteger() -> Int { 6 | return Int(self) 7 | } 8 | /// Cast TimeInterval to String. 9 | func convertToString() -> String { 10 | return String(self) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/MagicTimerTests/MagicTimerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import MagicTimer 3 | 4 | final class MagicTimerTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "MathOperators", 6 | "repositoryURL": "https://github.com/sadeghgoo/MathOperators.git", 7 | "state": { 8 | "branch": "main", 9 | "revision": "1bedb10d9a5914040606ca1b26d9c44a21fb19a6", 10 | "version": null 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Sources/MagicTimer/MagicTimerState.swift: -------------------------------------------------------------------------------- 1 | @available(*, deprecated, message: "MGStateManager is no longer available. Use MagicTimerState insted ") 2 | public class MGStateManager { 3 | static let shared: MGStateManager = .init() 4 | public enum TimerState { 5 | case fired 6 | case stopped 7 | case restarted 8 | case none 9 | } 10 | public var currentTimerState: TimerState = .none 11 | } 12 | 13 | public enum MagicTimerState { 14 | case fired 15 | case stopped 16 | case restarted 17 | case none 18 | } 19 | -------------------------------------------------------------------------------- /Sources/MagicTimer/Int+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Int { 4 | /// Cast Int to TimeInterval 5 | func convertToTimeInterval() -> TimeInterval { 6 | return TimeInterval(self) 7 | } 8 | /// Cast Int to String 9 | func convertToString() -> String { 10 | return String(self) 11 | } 12 | 13 | } 14 | 15 | public extension Double { 16 | /// Check the number is positive(bigger than zero) 17 | var isPositive: Bool { 18 | get { 19 | return self > 0 20 | } 21 | } 22 | /// Cast Double to TimeInterval 23 | func convertToTimeInterval() -> TimeInterval { 24 | return TimeInterval(self) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # sadgehbt 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /MagicTimer.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'MagicTimer' 3 | s.version = '1.0.7' 4 | s.summary = 'MagicTimer framework, your ultimate solution for handling timers in your iOS applications. This framework provides a powerful and flexible timer implementation with various features to meet your timer needs.' 5 | 6 | s.homepage = 'https://github.com/MagicTimerFW/MagicTimer' 7 | s.license = { :type => 'MIT', :file => 'LICENSE' } 8 | s.author = { 'sadeghgoo' => 'sadeghitunes2@gmail.com' } 9 | s.source = { :git => 'https://github.com/MagicTimerFW/MagicTimer.git', :tag => s.version.to_s } 10 | 11 | s.ios.deployment_target = '11.0' 12 | s.swift_versions = ['5.0'] 13 | s.source_files = 'Sources/**/*' 14 | s.frameworks = 'Foundation' 15 | s.dependency 'MathOperators' 16 | end 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "MagicTimer", 8 | platforms: [.iOS("11.0")], 9 | products: [ 10 | .library( 11 | name: "MagicTimer", 12 | targets: ["MagicTimer"]), 13 | ], 14 | dependencies: [ 15 | .package(url: "https://github.com/sadeghgoo/MathOperators.git", branch: "main") 16 | ], 17 | targets: [ 18 | .target( 19 | name: "MagicTimer", 20 | dependencies: [ 21 | .product( 22 | name: "MathOperators", 23 | package: "MathOperators"), 24 | ]), 25 | .testTarget( 26 | name: "MagicTimerTests", 27 | dependencies: ["MagicTimer"]), 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 MagicTimer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/MagicTimer/MagicTimerExecutive.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol MagicTimerExecutiveInterface { 4 | /// Timer time interval. 5 | var timeInterval: TimeInterval { get set } 6 | /// Fire the timer. 7 | func fire(compeltionHandler: (() -> Void)?) 8 | /// Suspand the timer. 9 | func suspand(completionHandler: (() -> Void)?) 10 | /// A call back which is called on every time interval. 11 | var scheduleTimerHandler: (() -> Void)? { get set } 12 | } 13 | 14 | public final class MagicTimerExecutive: MagicTimerExecutiveInterface { 15 | 16 | // MARK: - Public properties 17 | public var scheduleTimerHandler: (() -> Void)? 18 | public var timeInterval: TimeInterval = 1.0 19 | 20 | // MARK: - Private 21 | private var scheduleTimer: Timer? 22 | private var isTimerAlreadyStarted: Bool = false 23 | 24 | // MARK: - Constructors 25 | public init() { 26 | scheduleTimer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true, block: { [weak self] _ in 27 | self?.scheduleTimerHandler?() 28 | }) 29 | } 30 | 31 | // MARK: - Public methods 32 | public func fire(compeltionHandler: (() -> Void)?) { 33 | isTimerAlreadyStarted = true 34 | scheduleTimer?.fire() 35 | RunLoop.main.add(scheduleTimer!, forMode: .common) 36 | compeltionHandler?() 37 | } 38 | 39 | public func suspand(completionHandler: (() -> Void)?) { 40 | scheduleTimer?.invalidate() 41 | isTimerAlreadyStarted = false 42 | completionHandler?() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/MagicTimer/MagicTimerCounter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MathOperators 3 | 4 | public protocol MagicTimerCounterInterface { 5 | /// The total counted value. 6 | var totalCountedValue: TimeInterval { get set } 7 | /// A number which is added or minused on each ``timeInterval``. 8 | var effectiveValue: TimeInterval { get set } 9 | /// The initial value of counter. 10 | var defultValue: TimeInterval { get set } 11 | /// Add effectiveValue to totalCountedValue. 12 | func add() 13 | /// Subtract effectiveValue from totalCountedValue. 14 | func subtract() 15 | /// Reset totalCountedValue to zero. 16 | func resetTotalCounted() 17 | /// Reset totalCountedValue to defultValue. 18 | func resetToDefaultValue() 19 | } 20 | 21 | public final class MagicTimerCounter: MagicTimerCounterInterface { 22 | 23 | // MARK: - Public properties 24 | public var totalCountedValue: TimeInterval = 0 25 | public var effectiveValue: TimeInterval = 1.0 26 | public var defultValue: TimeInterval = 0.0 { 27 | didSet { 28 | totalCountedValue = totalCountedValue.plus(defultValue) 29 | } 30 | } 31 | 32 | // MARK: - Constructors 33 | public init() { } 34 | 35 | // MARK: - Public methods 36 | public func add() { 37 | totalCountedValue = totalCountedValue.plus(effectiveValue) 38 | } 39 | 40 | public func subtract() { 41 | totalCountedValue = totalCountedValue.minus(effectiveValue) 42 | } 43 | 44 | public func resetTotalCounted() { 45 | totalCountedValue = 0 46 | } 47 | 48 | public func resetToDefaultValue() { 49 | totalCountedValue = defultValue 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Sources/MagicTimer/MagicTimerBackgroundCalculator.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MathOperators 3 | 4 | public protocol MagicTimerBackgroundCalculatorInterface { 5 | /// The timer fired date. 6 | var timerFiredDate: Date? { get set } 7 | /// By changing this type timer decides to whether calcualte the time in background. 8 | var isActiveBackgroundMode: Bool { get set } 9 | 10 | var backgroundTimeCalculateHandler: ((TimeInterval) -> Void)? { get set } 11 | } 12 | 13 | public final class MagicTimerBackgroundCalculator: MagicTimerBackgroundCalculatorInterface { 14 | 15 | public var timerFiredDate: Date? 16 | public var isActiveBackgroundMode: Bool = true 17 | public var backgroundTimeCalculateHandler: ((TimeInterval) -> Void)? 18 | 19 | private var shouldCalculate: Bool = false 20 | 21 | public init() { 22 | NotificationCenter.default.addObserver(self, selector: #selector(willEnterForegroundNotification), name: UIApplication.willEnterForegroundNotification, object: nil) 23 | NotificationCenter.default.addObserver(self, selector: #selector(willEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil) 24 | } 25 | 26 | deinit { 27 | NotificationCenter.default.removeObserver(self) 28 | } 29 | 30 | private func invalidateFiredDate() { 31 | timerFiredDate = nil 32 | } 33 | 34 | private func calculateDateDiffrence() -> TimeInterval? { 35 | guard let timerFiredDate else { return nil } 36 | let validTimeSubtraction = floor(abs(timerFiredDate - Date())) 37 | return validTimeSubtraction.convertToTimeInterval() 38 | } 39 | 40 | @objc 41 | private func willEnterForegroundNotification() { 42 | guard isActiveBackgroundMode, shouldCalculate else { return } 43 | 44 | if let timeInterval = calculateDateDiffrence() { 45 | backgroundTimeCalculateHandler?(timeInterval) 46 | } 47 | } 48 | 49 | @objc 50 | private func willEnterBackgroundNotification() { 51 | shouldCalculate = true 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /docs/MagicTimer.md: -------------------------------------------------------------------------------- 1 | # MagicTimer Class 2 | 3 | The `MagicTimer` class is a timer implementation that provides various functionalities to start, stop, reset, and calculate elapsed time. It supports different timer modes, background time calculation, and event handlers. 4 | 5 | ## Properties 6 | 7 | ### Handlers 8 | 9 | - `lastStateDidChangeHandler`: A handler called when the timer state changes. It provides the last state of the timer. 10 | - `elapsedTimeDidChangeHandler`: A handler called on each time interval. It provides the elapsed time. 11 | 12 | ### Get-only Properties 13 | 14 | - `lastState`: The last state of the timer. It can be one of the following states: `.none`, `.fired`, `.stopped`, or `.restarted`. 15 | - `elapsedTime`: The elapsed time from when the timer started. 16 | 17 | ### Timer Configuration Properties 18 | 19 | - `countMode`: The timer count mode. It can be either `.stopWatch` or `.countDown(fromSeconds: TimeInterval)`. 20 | - `defultValue`: The timer default value. This value is used when resetting the timer. 21 | - `effectiveValue`: The value added or subtracted on each time interval. 22 | - `timeInterval`: The time interval between each timer tick. 23 | - `isActiveInBackground`: Determines whether the timer calculates time in the background. 24 | 25 | ## Constructors 26 | 27 | - `init(counter: MagicTimerCounterInterface = MagicTimerCounter(), executive: MagicTimerExecutiveInterface = MagicTimerExecutive(), backgroundCalculator: MagicTimerBackgroundCalculatorInterface = MagicTimerBackgroundCalculator())`: Initializes a new instance of the `MagicTimer` class with the specified counter, executive, and background calculator. 28 | 29 | ## Methods 30 | 31 | - `start()`: Starts the timer. 32 | - `stop()`: Stops the timer. 33 | - `reset()`: Resets the timer, setting the elapsed time to zero. 34 | - `resetToDefault()`: Resets the timer to the default value. 35 | 36 | 37 | 38 | > Note: The `MagicTimer` class uses the `MagicTimerCounterInterface`, `MagicTimerExecutiveInterface`, and `MagicTimerBackgroundCalculatorInterface` protocols for counter, executive, and background calculator implementations, respectively. 39 | 40 | Checkout the documentations: 41 | [MagicTimerCounterInterface](https://github.com/MagicTimerFW/MagicTimerCore/blob/main/docs/MagicTimerCounter.md]MagicTimerCounterInterface), [MagicTimerExecutiveInterface](https://github.com/MagicTimerFW/MagicTimerCore/blob/main/docs/MagicTimerExecutive.md), [MagicTimerBackgroundCalculatorInterface](https://github.com/MagicTimerFW/MagicTimerCore/blob/main/docs/MagicTimerBackgroundCalculator.md 42 | ), 43 | 44 | ## Usage 45 | 46 | ```swift 47 | // Create an instance of MagicTimer 48 | let timer = MagicTimer() 49 | 50 | // Configure event handlers 51 | timer.lastStateDidChangeHandler = { state in 52 | // Handle timer state changes 53 | } 54 | 55 | timer.elapsedTimeDidChangeHandler = { elapsedTime in 56 | // Handle elapsed time changes 57 | } 58 | 59 | // Start the timer 60 | timer.start() 61 | 62 | // ... 63 | 64 | // Stop the timer 65 | timer.stop() 66 | 67 | // ... 68 | 69 | // Reset the timer 70 | timer.reset() 71 | 72 | // ... 73 | 74 | // Reset the timer to the default value 75 | timer.resetToDefault() 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MagicTimer 2 | ![MagicTimer logo](https://user-images.githubusercontent.com/43542836/83555945-53b50780-a524-11ea-850e-d10b0839f63b.png) 3 | 4 | Welcome to the MagicTimer framework, your ultimate solution for handling timers in your iOS applications. This framework provides a powerful and flexible timer implementation with various features to meet your timer needs. 5 | 6 | ## Features 7 | 8 | - **Easy-to-use**: MagicTimer offers a simple and intuitive API that allows you to effortlessly manage timers in your iOS apps. 9 | - **Timer Modes**: Choose between two timer modes: Stopwatch and Countdown. Use the Stopwatch mode to measure elapsed time, or Countdown mode to count down from a specified time. 10 | - **Event Handlers**: Take advantage of event handlers to respond to timer state changes and elapsed time updates. 11 | - **Background Time Calculation**: Enable background time calculation to accurately track elapsed time even when the app is in the background. 12 | - **Highly Configurable**: Customize timer properties such as time interval, default value, and effective value to fine-tune your timer behavior. 13 | 14 | ## Why Use MagicTimer? 15 | 16 | - **Saves Development Time**: With MagicTimer, you can quickly integrate timer functionality into your app without spending excessive time on implementation. 17 | - **Flexible Timer Modes**: Whether you need to measure elapsed time or create countdowns, MagicTimer has got you covered. 18 | - **Smooth Background Time Calculation**: Ensure accurate time tracking, even when your app goes into the background. 19 | - **Simplified Event Handling**: Leverage event handlers to handle timer state changes and elapsed time updates effortlessly. 20 | - **Fully Customizable**: Adjust timer properties to match your app's specific requirements. 21 | 22 | ## Getting Started 23 | 24 | To start using the MagicTimer framework in your iOS project, follow these simple steps: 25 | 26 | 1. Install MagicTimer via Swift Package Manager or by manually adding the framework files to your project. 27 | 2. Import the MagicTimer module into your source code files. 28 | 3. Create an instance of `MagicTimer` and configure its properties as needed. 29 | 4. Set up event handlers to respond to timer state changes and elapsed time updates. 30 | 5. Start the timer using the `start()` method. 31 | 6. Enjoy the power and convenience of MagicTimer in your app! 32 | 33 | ### Code Example 34 | 35 | ```swift 36 | import MagicTimer 37 | 38 | // Create an instance of MagicTimer 39 | let timer = MagicTimer() 40 | 41 | // Configure the timer properties 42 | timer.countMode = .stopWatch 43 | timer.defultValue = 0 44 | timer.effectiveValue = 1 45 | timer.timeInterval = 1 46 | timer.isActiveInBackground = true 47 | 48 | // Set up event handlers 49 | timer.lastStateDidChangeHandler = { state in 50 | print("Timer state changed: \(state)") 51 | } 52 | 53 | timer.elapsedTimeDidChangeHandler = { elapsedTime in 54 | print("Elapsed time updated: \(elapsedTime)") 55 | } 56 | 57 | // Start the timer 58 | timer.start() 59 | ``` 60 | 61 | > **Note:** For detailed usage instructions and API documentation, please refer to the [MagicTimer Documentation](./docs/MagicTimer.md) file. 62 | 63 | ## Requirements 64 | 65 | - iOS 11.0+ 66 | - Swift 5.0+ 67 | 68 | ## Installation 69 | 70 | ### Swift Package Manager 71 | 72 | You can use Swift Package Manager to integrate MagicTimer into your Xcode project. Simply add the package dependency to your `Package.swift` file: 73 | 74 | ```swift 75 | dependencies: [ 76 | .package(url: "https://github.com/MagicTimerFW/MagicTimer", from: "2.0.1") 77 | ] 78 | ``` 79 | ### Manual Installation 80 | 81 | If you prefer manual installation, you can download the MagicTimer framework from the [GitHub repository](https://github.com/MagicTimerFW/MagicTimer). After downloading, add the necessary files to your Xcode project. 82 | 83 | ## Warning 84 | ⚠️ ```MagicTimerView``` is no longer available. Create your own UIView and connect ```MagicTimer``` to it. 85 | 86 | ## Contribute 87 | 88 | We welcome contributions from the community to enhance the MagicTimer framework. If you encounter any issues or have ideas for improvements, please submit a pull request or open an issue on the [GitHub repository](https://github.com/MagicTimerFW/MagicTimer). 89 | 90 | ## License 91 | 92 | MagicTimer is released under the [MIT License](https://opensource.org/licenses/MIT). See the [LICENSE](./LICENSE) file for more details. 93 | 94 | This Markdown file provides an overview of the MagicTimer framework, highlights its features and benefits, guides developers on getting started, provides installation instructions, and encourages contributions. It also includes information on requirements, licensing, and ways to connect 95 | -------------------------------------------------------------------------------- /Sources/MagicTimer/MagicTimer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MathOperators 3 | 4 | @available(*, unavailable, renamed: "MagicTimerMode") 5 | /// The timer counting mode. 6 | public enum MGCountMode { 7 | case stopWatch 8 | case countDown(fromSeconds: TimeInterval) 9 | } 10 | 11 | public enum MagicTimerMode { 12 | case stopWatch 13 | case countDown(fromSeconds: TimeInterval) 14 | } 15 | 16 | /// The MagicTimer class is a timer implementation. It provides various functionalities to start, stop, reset, background time calculation and more. 17 | public class MagicTimer { 18 | 19 | // MARK: - Typealias 20 | public typealias StateHandler = ((MagicTimerState) -> Void) 21 | public typealias ElapsedTimeHandler = ((TimeInterval) -> Void) 22 | 23 | // MARK: - Public properties 24 | // MARK: - Handlers 25 | /// Last state of the timer handler. It calls when state of timer changes. 26 | public var lastStateDidChangeHandler: StateHandler? 27 | 28 | /// Elapsed time handler. It calls on each timeInterval. 29 | public var elapsedTimeDidChangeHandler: ElapsedTimeHandler? 30 | 31 | // MARK: - Get only properties 32 | 33 | /// Last state of the timer. Checkout ```MagicTimerState```. 34 | public private(set) var lastState: MagicTimerState = .none { 35 | didSet { 36 | lastStateDidChangeHandler?(lastState) 37 | } 38 | } 39 | 40 | /// Elapsed time from where the timer started. Default is 0. 41 | public private(set) var elapsedTime: TimeInterval = 0 { 42 | didSet { 43 | elapsedTimeDidChangeHandler?(elapsedTime) 44 | } 45 | } 46 | 47 | /// Timer count mode. Default is `.stopWatch`. Checkout ```MagicTimerMode```. 48 | public var countMode: MagicTimerMode = .stopWatch { 49 | didSet { 50 | switch countMode { 51 | case .stopWatch: 52 | break 53 | case .countDown(let fromSeconds): 54 | // Checking if defaultValue plus fromSeconds not going to invalid format(negative seconds). 55 | guard (defultValue + fromSeconds).truncatingRemainder(dividingBy: effectiveValue).isEqual(to: .zero) else { 56 | fatalError("The time does not lead to a valid format. Use valid effetiveValue") 57 | } 58 | counter.totalCountedValue = fromSeconds 59 | } 60 | } 61 | } 62 | 63 | /// Timer default value. Default is 0. 64 | public var defultValue: TimeInterval = 0 { 65 | didSet { 66 | guard defultValue.isBiggerThanOrEqual(.zero) else { 67 | fatalError("The defultValue should be greater or equal to zero.") 68 | } 69 | counter.defultValue = defultValue 70 | } 71 | } 72 | 73 | /// A number which is added or minused on each ``timeInterval``. Default is 1. 74 | public var effectiveValue: TimeInterval = 1 { 75 | didSet { 76 | guard effectiveValue.isBiggerThanOrEqual(.zero) else { 77 | fatalError("The effectiveValue should be greater or equal to zero.") 78 | } 79 | counter.effectiveValue = effectiveValue 80 | } 81 | } 82 | 83 | /// Timer time interval. Default is 1. 84 | public var timeInterval: TimeInterval = 1 { 85 | didSet { 86 | guard timeInterval.isBiggerThanOrEqual(.zero) else { 87 | fatalError("The timeInterval should be greater or equal to zero.") 88 | } 89 | executive.timeInterval = timeInterval 90 | } 91 | } 92 | 93 | /// By changing this type timer decides to whether calcualte the time in background. Default is true. 94 | public var isActiveInBackground: Bool = true { 95 | didSet { 96 | backgroundCalculator.isActiveBackgroundMode = isActiveInBackground 97 | } 98 | } 99 | 100 | // MARK: - Private properties 101 | private var counter: MagicTimerCounterInterface 102 | private var executive: MagicTimerExecutiveInterface 103 | private var backgroundCalculator: MagicTimerBackgroundCalculatorInterface 104 | 105 | // MARK: - Unavailable 106 | /// A elapsed time that can observe 107 | /// - Warning: renamed: "elapsedTimeDidChangeHandler" 108 | public var observeElapsedTime: ((TimeInterval) -> Void)? 109 | 110 | /// The current state of the timer. 111 | /// - Warning: renamed: "lastState" 112 | public var currentState: MagicTimerState { 113 | return lastState 114 | } 115 | 116 | /// Timer state callback 117 | /// - Warning: renamed: "lastStateDidChangeHandler" 118 | public var didStateChange: ((MagicTimerState) -> Void)? 119 | 120 | // MARK: - Constructors 121 | public init(counter: MagicTimerCounterInterface = MagicTimerCounter(), 122 | executive: MagicTimerExecutiveInterface = MagicTimerExecutive(), 123 | backgroundCalculator: MagicTimerBackgroundCalculatorInterface = MagicTimerBackgroundCalculator()) { 124 | self.counter = counter 125 | self.executive = executive 126 | self.backgroundCalculator = backgroundCalculator 127 | self.backgroundCalculator.backgroundTimeCalculateHandler = { elapsedTime in 128 | self.calclulateBackgroundTime(elapsedTime: elapsedTime) 129 | } 130 | } 131 | 132 | // MARK: - Public methods 133 | /// Start counting the timer. 134 | public func start() { 135 | executive.fire { 136 | self.backgroundCalculator.timerFiredDate = Date() 137 | self.lastState = .fired 138 | self.observeScheduleTimer() 139 | } 140 | } 141 | 142 | /// Stop counting the timer. 143 | public func stop() { 144 | executive.suspand { 145 | self.lastState = .stopped 146 | } 147 | } 148 | 149 | /// Reset the timer. It will set the elapsed time to zero. 150 | public func reset() { 151 | executive.suspand { 152 | self.counter.resetTotalCounted() 153 | self.lastState = .restarted 154 | } 155 | } 156 | 157 | /// Reset the timer to the ``defaultvalue``. 158 | public func resetToDefault() { 159 | executive.suspand { 160 | self.counter.resetToDefaultValue() 161 | self.lastState = .restarted 162 | } 163 | } 164 | 165 | // MARK: - Private methods 166 | // It calculates the elapsed time user was in background. 167 | private func calclulateBackgroundTime(elapsedTime: TimeInterval) { 168 | switch countMode { 169 | case .stopWatch: 170 | counter.totalCountedValue = elapsedTime 171 | case let .countDown(fromSeconds: countDownSeconds): 172 | 173 | let subtraction = countDownSeconds - elapsedTime 174 | if subtraction.isPositive { 175 | counter.totalCountedValue = subtraction 176 | } else { 177 | counter.totalCountedValue = 1 178 | } 179 | } 180 | } 181 | 182 | private func observeScheduleTimer() { 183 | executive.scheduleTimerHandler = { [weak self] in 184 | guard let self else { return } 185 | switch countMode { 186 | case .stopWatch: 187 | counter.add() 188 | case .countDown(_): 189 | guard counter.totalCountedValue.isBiggerThan(.zero) else { 190 | executive.suspand { 191 | self.lastState = .stopped 192 | } 193 | return 194 | } 195 | counter.subtract() 196 | } 197 | elapsedTime = counter.totalCountedValue 198 | } 199 | } 200 | } 201 | 202 | --------------------------------------------------------------------------------