├── .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 | 
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 |
--------------------------------------------------------------------------------