diff --git a/Template/System/Sources/Assets/Asset.swift b/Template/System/Sources/Assets/Asset.swift new file mode 100644 index 0000000..371b1f9 --- /dev/null +++ b/Template/System/Sources/Assets/Asset.swift @@ -0,0 +1,13 @@ +// +// Asset.swift +// System +// +// Created by Domagoj Kulundzic on 13/08/2019. +// Copyright © 2019 Martian & Machine. All rights reserved. +// + +import Foundation + +public protocol Asset { + var name: String { get } +} diff --git a/Template/System/Sources/Assets/ColorAsset.swift b/Template/System/Sources/Assets/ColorAsset.swift new file mode 100644 index 0000000..7c5f5d9 --- /dev/null +++ b/Template/System/Sources/Assets/ColorAsset.swift @@ -0,0 +1,28 @@ +// +// ColorAsset.swift +// System +// +// Created by Domagoj Kulundzic on 13/08/2019. +// Copyright © 2019 Martian & Machine. All rights reserved. +// + +import Foundation + +public protocol ColorAsset: Asset { + var color: UIColor { get } +} + +public extension ColorAsset { + var color: UIColor { + guard let color = UIColor(named: name) else { + fatalError("Can't find the \(name) image asset.") + } + return color + } +} + +public extension ColorAsset where Self: RawRepresentable, Self.RawValue == String { + var name: String { + return rawValue + } +} diff --git a/Template/System/Sources/Assets/ImageAsset.swift b/Template/System/Sources/Assets/ImageAsset.swift new file mode 100644 index 0000000..99b829a --- /dev/null +++ b/Template/System/Sources/Assets/ImageAsset.swift @@ -0,0 +1,36 @@ +// +// ImageAsset.swift +// System +// +// Created by Domagoj Kulundzic on 13/08/2019. +// Copyright © 2019 Martian & Machine. All rights reserved. +// + +import Foundation + +public protocol ImageAsset: Asset { + var image: UIImage { get } +} + +public extension ImageAsset { + var image: UIImage { + guard let image = UIImage(named: name) else { + fatalError("Can't find the \(name) image asset.") + } + return image + } + + var imageTemplate: UIImage { + return image.withRenderingMode(.alwaysTemplate) + } + + var imageOriginal: UIImage { + return image.withRenderingMode(.alwaysOriginal) + } +} + +public extension ImageAsset where Self: RawRepresentable, Self.RawValue == String { + var name: String { + return rawValue + } +} diff --git a/Template/System/Sources/Environment.swift b/Template/System/Sources/Environment.swift new file mode 100644 index 0000000..95b1c85 --- /dev/null +++ b/Template/System/Sources/Environment.swift @@ -0,0 +1,39 @@ +// +// Environment.swift +// Template +// +// Created by Domagoj Kulundzic on 07/08/2019. +// Copyright © 2019 Martian & Machine. All rights reserved. +// + +import Foundation + +public enum Environment: String { + case debug + case staging + case release +} + +public extension Environment { + static var current: Environment { + #if DEBUG + return .debug + #elseif STAGING + return .staging + #elseif RELEASE + return .release + #endif + } + + static var isDebug: Bool { + return current == .debug + } + + static var isStaging: Bool { + return current == .staging + } + + static var isProduction: Bool { + return current == .release + } +} diff --git a/Template/Template/Common/Extensions/Foundation/Bundle-Template.swift b/Template/System/Sources/Extensions/Bundle-System.swift similarity index 100% rename from Template/Template/Common/Extensions/Foundation/Bundle-Template.swift rename to Template/System/Sources/Extensions/Bundle-System.swift diff --git a/Template/Template/Common/Extensions/Foundation/String-Template.swift b/Template/System/Sources/Extensions/String-System.swift similarity index 100% rename from Template/Template/Common/Extensions/Foundation/String-Template.swift rename to Template/System/Sources/Extensions/String-System.swift diff --git a/Template/Template/Common/Extensions/Foundation/URL-Template.swift b/Template/System/Sources/Extensions/URL-System.swift similarity index 100% rename from Template/Template/Common/Extensions/Foundation/URL-Template.swift rename to Template/System/Sources/Extensions/URL-System.swift diff --git a/Template/System/Sources/Global.swift b/Template/System/Sources/Global.swift new file mode 100644 index 0000000..4a221a9 --- /dev/null +++ b/Template/System/Sources/Global.swift @@ -0,0 +1,39 @@ +// +// Global.swift +// Template +// +// Created by Domagoj Kulundzic on 24/07/2018. +// Copyright © 2018 Martian & Machine. All rights reserved. +// + +import Foundation + +/// Typealiases a single argumentless/voidless closure. +public typealias Action = () -> Void + +/// Typealiases a parametrised single voidless closure. +public typealias ParametrisedAction = (T) -> Void + +/// Typealiases a success handler. +public typealias SuccessHandler = ((T) -> Void)? + +/// Typealiases a argumentless success handler. +public typealias ArgumentlessSuccessHandler = (() -> Void)? + +/// Typealiases a failure handler. +public typealias FailureHandler = ((Error) -> Void)? + +/// Invokes the action only if the current configuration is DEBUG. +public func whenDebug(_ action: Action?) { + #if DEBUG + action?() + #endif +} + +/// Invokes the action only if the current configuration is RELEASE. +public func whenRelease(_ action: Action?) { + #if DEBUG + #else + action?() + #endif +} diff --git a/Template/System/Sources/KeyboardObserver.swift b/Template/System/Sources/KeyboardObserver.swift new file mode 100644 index 0000000..3f212cf --- /dev/null +++ b/Template/System/Sources/KeyboardObserver.swift @@ -0,0 +1,72 @@ +// +// KeyboardObserver.swift +// Template +// +// Created by Domagoj Kulundzic on 03/10/2018. +// Copyright © 2018 Martian & Machine. All rights reserved. +// + +import UIKit + +/// Defines methods to respond to keyboard appearance events. All methods have empty default implementations. +public protocol KeyboardObserverDelegate: class { + func keyboardWillShow(keyboardHeight: CGFloat, animationDuration: TimeInterval, animationCurve: UIView.AnimationCurve) + func keyboardWillHide(keyboardHeight: CGFloat, animationDuration: TimeInterval, animationCurve: UIView.AnimationCurve) + func keyboardDidShow(keyboardHeight: CGFloat, animationDuration: TimeInterval, animationCurve: UIView.AnimationCurve) + func keyboardDidHide(keyboardHeight: CGFloat, animationDuration: TimeInterval, animationCurve: UIView.AnimationCurve) +} + +public extension KeyboardObserverDelegate { + func keyboardWillShow(keyboardHeight: CGFloat, animationDuration: TimeInterval, animationCurve: UIView.AnimationCurve) { } + func keyboardWillHide(keyboardHeight: CGFloat, animationDuration: TimeInterval, animationCurve: UIView.AnimationCurve) { } + func keyboardDidShow(keyboardHeight: CGFloat, animationDuration: TimeInterval, animationCurve: UIView.AnimationCurve) { } + func keyboardDidHide(keyboardHeight: CGFloat, animationDuration: TimeInterval, animationCurve: UIView.AnimationCurve) { } +} + +/// A convenience utility that eases responding to keyboard appearance events by +/// wrapping around the keyboard appearance notifications. +/// +/// The supported appearance methods are defined by the **KeyboardObserverDelegate**. +public final class KeyboardObserver { + /// The delegate to notify of keyboard appearance events. + public weak var delegate: KeyboardObserverDelegate? + + /// Initialises a new instance. + /// - parameter delegate: A **KeyboardObserverDelegate** conforming type. Defaults to **nil**. + public init(delegate: KeyboardObserverDelegate? = nil) { + self.delegate = delegate + setupKeyboardObserving() + } + + /// Since the **KeyboardObserver** will (in most cases) be defined as a **lazy var**, this method + /// provides a sensical way of triggering the instance creation. + public func observe() { } +} + +private extension KeyboardObserver { + func setupKeyboardObserving() { + _ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: nil) { [weak self] in + self?.processKeyboardNotification($0) { self?.delegate?.keyboardWillShow(keyboardHeight: $0, animationDuration: $1, animationCurve: $2) } + } + _ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: nil) { [weak self] in + self?.processKeyboardNotification($0) { self?.delegate?.keyboardWillHide(keyboardHeight: $0, animationDuration: $1, animationCurve: $2) } + } + _ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: nil) { [weak self] in + self?.processKeyboardNotification($0) { self?.delegate?.keyboardDidShow(keyboardHeight: $0, animationDuration: $1, animationCurve: $2) } + } + _ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification, object: nil, queue: nil) { [weak self] in + self?.processKeyboardNotification($0) { self?.delegate?.keyboardDidHide(keyboardHeight: $0, animationDuration: $1, animationCurve: $2) } + } + } + + func processKeyboardNotification(_ notification: Foundation.Notification, processingHandler: ((CGFloat, TimeInterval, UIView.AnimationCurve) -> Void)?) { + guard + let keyboardHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height, + let animationDuration = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue, + let animationCurveRawValue = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.intValue, + let animationCurve = UIView.AnimationCurve(rawValue: animationCurveRawValue) else { + return + } + processingHandler?(keyboardHeight, animationDuration, animationCurve) + } +} diff --git a/Template/System/Sources/PropertyWrappers.swift b/Template/System/Sources/PropertyWrappers.swift new file mode 100644 index 0000000..b494c35 --- /dev/null +++ b/Template/System/Sources/PropertyWrappers.swift @@ -0,0 +1,52 @@ +// +// PropertyWrappers.swift +// Template +// +// Created by Domagoj Kulundzic on 02/10/2019. +// Copyright © 2019 Martian & Machine. All rights reserved. +// + +import Foundation + +@propertyWrapper +public struct UserDefault { + public let key: String + public let defaultValue: T + + public init(_ key: String, defaultValue: T) { + self.key = key + self.defaultValue = defaultValue + } + + public var wrappedValue: T { + get { return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } + set { UserDefaults.standard.set(newValue, forKey: key) } + } +} + +@propertyWrapper +public struct Atomic { + private var value: T + private let lock = NSLock() + + public init(wrappedValue value: T) { + self.value = value + } + + public var wrappedValue: T { + get { return load() } + set { store(newValue: newValue) } + } + + public func load() -> T { + lock.lock() + defer { lock.unlock() } + return value + } + + public mutating func store(newValue: T) { + lock.lock() + defer { lock.unlock() } + value = newValue + } +} diff --git a/Template/System/Sources/Protocols/DataSourceProtocol.swift b/Template/System/Sources/Protocols/DataSourceProtocol.swift index e0edb4c..3a4d2fc 100644 --- a/Template/System/Sources/Protocols/DataSourceProtocol.swift +++ b/Template/System/Sources/Protocols/DataSourceProtocol.swift @@ -1,6 +1,6 @@ // // DataSourceProtocol.swift -// O2O +// Template // // Created by Domagoj Kulundzic on 17/07/2018. // Copyright © 2018 Martian & Machine. All rights reserved. @@ -8,13 +8,13 @@ import Foundation -protocol DataSourceProtocol { +public protocol DataSourceProtocol { associatedtype Section: SectionProtocol var sections: [Section] { get } var isEmpty: Bool { get } } -extension DataSourceProtocol { +public extension DataSourceProtocol { var isEmpty: Bool { guard numberOfSections() > 0 else { return true diff --git a/Template/System/Sources/Protocols/SectionProtocol.swift b/Template/System/Sources/Protocols/SectionProtocol.swift index fef8ffc..2103a91 100644 --- a/Template/System/Sources/Protocols/SectionProtocol.swift +++ b/Template/System/Sources/Protocols/SectionProtocol.swift @@ -1,6 +1,6 @@ // // SectionProtocol.swift -// O2O +// Template // // Created by Domagoj Kulundzic on 17/07/2018. // Copyright © 2018 Martian & Machine. All rights reserved. @@ -8,13 +8,13 @@ import Foundation -protocol SectionProtocol { +public protocol SectionProtocol { associatedtype Item var items: [Item] { get } var isCollapsed: Bool { get } } -extension SectionProtocol { +public extension SectionProtocol { var isCollapsed: Bool { return false } diff --git a/Template/System/Sources/SimpleLogger.swift b/Template/System/Sources/SimpleLogger.swift new file mode 100644 index 0000000..e3425be --- /dev/null +++ b/Template/System/Sources/SimpleLogger.swift @@ -0,0 +1,74 @@ +// +// SimpleLogger.swift +// Template +// +// Created by Domagoj Kulundzic on 12/10/2018. +// Copyright © 2018 Martian & Machine. All rights reserved. +// + +import Foundation + +public class SimpleLogger { + /// Defines the type of the log to be emitted. + public enum LogType { + case none + case debug + case info + case warning + case error + + /** + Returns the **LogType** marker that's pre-pended to the Logger output statement, as follows: + - **none** - no marker + - **debug** - 🖥️ + - **info** - ℹ️ + - **warning** - ⚠️ + - **error** - ❌ + */ + public var marker: String? { + switch self { + case .none: + return nil + case .debug: + return "🖥️" + case .info: + return "ℹ️" + case .warning: + return "⚠️" + case .error: + return "❌" + } + } + } + + public static func log(_ message: CustomStringConvertible, file: String = #file, function: String = #function, line: Int = #line) { + flush(.none, message: message, file: file, function: function, line: line) + } + + public static func debug(_ message: CustomStringConvertible, file: String = #file, function: String = #function, line: Int = #line) { + flush(.debug, message: message, file: file, function: function, line: line) + } + + public static func info(_ message: CustomStringConvertible, file: String = #file, function: String = #function, line: Int = #line) { + flush(.info, message: message, file: file, function: function, line: line) + } + + public static func warning(_ message: CustomStringConvertible, file: String = #file, function: String = #function, line: Int = #line) { + flush(.warning, message: message, file: file, function: function, line: line) + } + + public static func error(_ message: CustomStringConvertible, file: String = #file, function: String = #function, line: Int = #line) { + flush(.error, message: message, file: file, function: function, line: line) + } +} + +private extension SimpleLogger { + static func flush(_ logType: LogType, message: CustomStringConvertible, file: String, function: String, line: Int) { + let fileName = URL(fileURLWithPath: file).lastPathComponent.components(separatedBy: ".").first ?? "" + if let marker = logType.marker { + debugPrint("\(marker): \(fileName).\(function)[\(line)]: \(message)") + } else { + debugPrint("\(fileName).\(function)[\(line)]: \(message)") + } + } +} diff --git a/Template/System/Sources/StartupProcess.swift b/Template/System/Sources/StartupProcess.swift new file mode 100644 index 0000000..a3437b9 --- /dev/null +++ b/Template/System/Sources/StartupProcess.swift @@ -0,0 +1,17 @@ +// +// StartupProcess.swift +// Template +// +// Created by Domagoj Kulundzic on 18/07/2018. +// Copyright © 2018 Martian & Machine. All rights reserved. +// + +import Foundation + +/** + An abstraction for a predefined set of functionality, aimed to be ran once, at app startup. +*/ +public protocol StartupProcess { + var id: String { get } + func run(completion: @escaping (Bool) -> Void) +} diff --git a/Template/System/Sources/StartupProcessService.swift b/Template/System/Sources/StartupProcessService.swift new file mode 100644 index 0000000..3e2cdb0 --- /dev/null +++ b/Template/System/Sources/StartupProcessService.swift @@ -0,0 +1,30 @@ +// +// StartupProcessService.swift +// Template +// +// Created by Domagoj Kulundzic on 18/07/2018. +// Copyright © 2018 Martian & Machine. All rights reserved. +// + +import Foundation + +/// A simple abstraction providing a clean way of executing +/// StartupProcess types. +public final class StartupProcessService { + /// The default initialiser. + public init() { } +} + +public extension StartupProcessService { + /// Executes a StartupProcess process. + /// - parameter process: A concrete type conforming to StartupProcess + @discardableResult + func execute(process: StartupProcess) -> Self { + process.run { success in + if success { + print("Successfully ran the \(type(of: process)) process.") + } + } + return self + } +} diff --git a/Template/System/Sources/TaskThrottler.swift b/Template/System/Sources/TaskThrottler.swift new file mode 100644 index 0000000..d147e03 --- /dev/null +++ b/Template/System/Sources/TaskThrottler.swift @@ -0,0 +1,50 @@ +// +// TaskThrottler.swift +// Template +// +// Created by Domagoj Kulundzic on 04/10/2018. +// Copyright © 2018 Martian & Machine. All rights reserved. +// + +import Foundation + +/// A type that's used to throttle/delay task execution by a certain time interval. +/// - note: An example **TaskThrottler** usage is performing an network request for validating +/// a username, where the request shouldn't be fired for each character the user types in, but +/// rather after the user stops typing for a short interval. +public class TaskThrottler { + public var delay: TimeInterval + private let queue: DispatchQueue + private var job: DispatchWorkItem? + + /// Initialises a new **TaskThrottler** instance. + /// - parameter queue: The **DispatchQueue** on which the throttler will execute it's tasks. Defaults to **.main**. + /// - parameter delay: The **TimeInterval** that the throttler will use to delay task execution. Defaults to **0.5**. + public init(queue: DispatchQueue = .main, delay: TimeInterval = 0.5) { + self.queue = queue + self.delay = delay + } +} + +public extension TaskThrottler { + var isExecuting: Bool { + return job != nil + } + + /// Schedules task execution, while applying any required throttling. + /// - parameter work: A closure defining a task to be executed. + func execute(work: @escaping () -> Void) { + job?.cancel() + let newJob = DispatchWorkItem { [weak self] in + work() + self?.job = nil + } + job = newJob + queue.asyncAfter(deadline: .now() + delay, execute: newJob) + } + + /// Cancels a scheduled task, if it exists. + func cancelPendingJob() { + job?.cancel() + } +} diff --git a/Template/Template.xcodeproj/project.pbxproj b/Template/Template.xcodeproj/project.pbxproj index ceae7d9..bd29501 100644 --- a/Template/Template.xcodeproj/project.pbxproj +++ b/Template/Template.xcodeproj/project.pbxproj @@ -12,10 +12,10 @@ 480E55F2229BEE6F0031BDF7 /* UIColor-Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55EE229BEE6F0031BDF7 /* UIColor-Template.swift */; }; 480E55F3229BEE6F0031BDF7 /* UICollectionView-Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55EF229BEE6F0031BDF7 /* UICollectionView-Template.swift */; }; 480E55F6229BEEF20031BDF7 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55F5229BEEF20031BDF7 /* ReusableView.swift */; }; - 480E55F8229BEF2C0031BDF7 /* String-Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55F7229BEF2B0031BDF7 /* String-Template.swift */; }; + 480E55F8229BEF2C0031BDF7 /* String-System.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55F7229BEF2B0031BDF7 /* String-System.swift */; }; 480E55FA229BEF430031BDF7 /* Collection-Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55F9229BEF430031BDF7 /* Collection-Template.swift */; }; - 480E55FC229BEF550031BDF7 /* URL-Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55FB229BEF550031BDF7 /* URL-Template.swift */; }; - 480E55FE229BEF860031BDF7 /* Bundle-Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55FD229BEF860031BDF7 /* Bundle-Template.swift */; }; + 480E55FC229BEF550031BDF7 /* URL-System.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55FB229BEF550031BDF7 /* URL-System.swift */; }; + 480E55FE229BEF860031BDF7 /* Bundle-System.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55FD229BEF860031BDF7 /* Bundle-System.swift */; }; 480E5600229BEFC20031BDF7 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E55FF229BEFC20031BDF7 /* AttributedStringBuilder.swift */; }; 480E5603229BEFE20031BDF7 /* BroadcastObjectWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E5601229BEFE20031BDF7 /* BroadcastObjectWrapper.swift */; }; 480E5604229BEFE20031BDF7 /* Broadcast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480E5602229BEFE20031BDF7 /* Broadcast.swift */; }; @@ -57,6 +57,17 @@ 482201CF2298252D003319F0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 482201CE2298252D003319F0 /* LaunchScreen.storyboard */; }; 488240552349447000CB6FFE /* SectionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488240532349447000CB6FFE /* SectionProtocol.swift */; }; 488240562349447000CB6FFE /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488240542349447000CB6FFE /* DataSourceProtocol.swift */; }; + 4882405D234945A300CB6FFE /* StartupProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4882405B234945A200CB6FFE /* StartupProcess.swift */; }; + 4882405E234945A300CB6FFE /* StartupProcessService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4882405C234945A300CB6FFE /* StartupProcessService.swift */; }; + 48824060234945B500CB6FFE /* SimpleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4882405F234945B500CB6FFE /* SimpleLogger.swift */; }; + 48824064234945D000CB6FFE /* Asset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48824061234945CF00CB6FFE /* Asset.swift */; }; + 48824065234945D000CB6FFE /* ColorAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48824062234945CF00CB6FFE /* ColorAsset.swift */; }; + 48824066234945D000CB6FFE /* ImageAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48824063234945D000CB6FFE /* ImageAsset.swift */; }; + 4882406A2349464B00CB6FFE /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488240682349464A00CB6FFE /* Environment.swift */; }; + 4882406B2349464B00CB6FFE /* Global.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488240692349464B00CB6FFE /* Global.swift */; }; + 4882406E2349466C00CB6FFE /* KeyboardObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4882406D2349466C00CB6FFE /* KeyboardObserver.swift */; }; + 488240702349468B00CB6FFE /* TaskThrottler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4882406F2349468B00CB6FFE /* TaskThrottler.swift */; }; + 488240722349469C00CB6FFE /* PropertyWrappers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488240712349469C00CB6FFE /* PropertyWrappers.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -168,10 +179,10 @@ 480E55EE229BEE6F0031BDF7 /* UIColor-Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor-Template.swift"; sourceTree = ""; }; 480E55EF229BEE6F0031BDF7 /* UICollectionView-Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView-Template.swift"; sourceTree = ""; }; 480E55F5229BEEF20031BDF7 /* ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; }; - 480E55F7229BEF2B0031BDF7 /* String-Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Template.swift"; sourceTree = ""; }; + 480E55F7229BEF2B0031BDF7 /* String-System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-System.swift"; sourceTree = ""; }; 480E55F9229BEF430031BDF7 /* Collection-Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection-Template.swift"; sourceTree = ""; }; - 480E55FB229BEF550031BDF7 /* URL-Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL-Template.swift"; sourceTree = ""; }; - 480E55FD229BEF860031BDF7 /* Bundle-Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle-Template.swift"; sourceTree = ""; }; + 480E55FB229BEF550031BDF7 /* URL-System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL-System.swift"; sourceTree = ""; }; + 480E55FD229BEF860031BDF7 /* Bundle-System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle-System.swift"; sourceTree = ""; }; 480E55FF229BEFC20031BDF7 /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = ""; }; 480E5601229BEFE20031BDF7 /* BroadcastObjectWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BroadcastObjectWrapper.swift; sourceTree = ""; }; 480E5602229BEFE20031BDF7 /* Broadcast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Broadcast.swift; sourceTree = ""; }; @@ -218,6 +229,17 @@ 482201CE2298252D003319F0 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 488240532349447000CB6FFE /* SectionProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionProtocol.swift; sourceTree = ""; }; 488240542349447000CB6FFE /* DataSourceProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataSourceProtocol.swift; sourceTree = ""; }; + 4882405B234945A200CB6FFE /* StartupProcess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StartupProcess.swift; sourceTree = ""; }; + 4882405C234945A300CB6FFE /* StartupProcessService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StartupProcessService.swift; sourceTree = ""; }; + 4882405F234945B500CB6FFE /* SimpleLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleLogger.swift; sourceTree = ""; }; + 48824061234945CF00CB6FFE /* Asset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Asset.swift; sourceTree = ""; }; + 48824062234945CF00CB6FFE /* ColorAsset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorAsset.swift; sourceTree = ""; }; + 48824063234945D000CB6FFE /* ImageAsset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageAsset.swift; sourceTree = ""; }; + 488240682349464A00CB6FFE /* Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; + 488240692349464B00CB6FFE /* Global.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Global.swift; sourceTree = ""; }; + 4882406D2349466C00CB6FFE /* KeyboardObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardObserver.swift; sourceTree = ""; }; + 4882406F2349468B00CB6FFE /* TaskThrottler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskThrottler.swift; sourceTree = ""; }; + 488240712349469C00CB6FFE /* PropertyWrappers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyWrappers.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -542,10 +564,20 @@ 482201C822982451003319F0 /* Sources */ = { isa = PBXGroup; children = ( + 48824067234945E900CB6FFE /* Extensions */, 488240522349446600CB6FFE /* Protocols */, + 4882406C2349464E00CB6FFE /* Assets */, 480E55FF229BEFC20031BDF7 /* AttributedStringBuilder.swift */, 480E5602229BEFE20031BDF7 /* Broadcast.swift */, 480E5601229BEFE20031BDF7 /* BroadcastObjectWrapper.swift */, + 4882405B234945A200CB6FFE /* StartupProcess.swift */, + 4882405C234945A300CB6FFE /* StartupProcessService.swift */, + 4882405F234945B500CB6FFE /* SimpleLogger.swift */, + 488240682349464A00CB6FFE /* Environment.swift */, + 488240692349464B00CB6FFE /* Global.swift */, + 4882406D2349466C00CB6FFE /* KeyboardObserver.swift */, + 4882406F2349468B00CB6FFE /* TaskThrottler.swift */, + 488240712349469C00CB6FFE /* PropertyWrappers.swift */, ); path = Sources; sourceTree = ""; @@ -596,10 +628,7 @@ 482201D222982541003319F0 /* Foundation */ = { isa = PBXGroup; children = ( - 480E55F7229BEF2B0031BDF7 /* String-Template.swift */, 480E55F9229BEF430031BDF7 /* Collection-Template.swift */, - 480E55FB229BEF550031BDF7 /* URL-Template.swift */, - 480E55FD229BEF860031BDF7 /* Bundle-Template.swift */, ); path = Foundation; sourceTree = ""; @@ -647,6 +676,26 @@ path = Protocols; sourceTree = ""; }; + 48824067234945E900CB6FFE /* Extensions */ = { + isa = PBXGroup; + children = ( + 480E55F7229BEF2B0031BDF7 /* String-System.swift */, + 480E55FB229BEF550031BDF7 /* URL-System.swift */, + 480E55FD229BEF860031BDF7 /* Bundle-System.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 4882406C2349464E00CB6FFE /* Assets */ = { + isa = PBXGroup; + children = ( + 48824061234945CF00CB6FFE /* Asset.swift */, + 48824062234945CF00CB6FFE /* ColorAsset.swift */, + 48824063234945D000CB6FFE /* ImageAsset.swift */, + ); + path = Assets; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1006,7 +1055,7 @@ 480E56BE229C14970031BDF7 /* ExampleNavigationViewController.swift in Sources */, 488240552349447000CB6FFE /* SectionProtocol.swift in Sources */, 480E55F3229BEE6F0031BDF7 /* UICollectionView-Template.swift in Sources */, - 480E55FC229BEF550031BDF7 /* URL-Template.swift in Sources */, + 480E55FC229BEF550031BDF7 /* URL-System.swift in Sources */, 480E55FA229BEF430031BDF7 /* Collection-Template.swift in Sources */, 480E5689229C04BD0031BDF7 /* ExampleContentView.swift in Sources */, 480E55F2229BEE6F0031BDF7 /* UIColor-Template.swift in Sources */, @@ -1015,8 +1064,8 @@ 480E5692229C05F10031BDF7 /* ExampleTodosWorker.swift in Sources */, 480E55F0229BEE6F0031BDF7 /* UITableView-Template.swift in Sources */, 480E56BD229C14970031BDF7 /* ExampleNavigationRouter.swift in Sources */, - 480E55F8229BEF2C0031BDF7 /* String-Template.swift in Sources */, - 480E55FE229BEF860031BDF7 /* Bundle-Template.swift in Sources */, + 480E55F8229BEF2C0031BDF7 /* String-System.swift in Sources */, + 480E55FE229BEF860031BDF7 /* Bundle-System.swift in Sources */, 480E56BC229C14970031BDF7 /* ExampleNavigationPresenter.swift in Sources */, 480E568B229C04BD0031BDF7 /* ExampleRouter.swift in Sources */, 480E568A229C04BD0031BDF7 /* ExamplePresenter.swift in Sources */, @@ -1056,9 +1105,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 488240722349469C00CB6FFE /* PropertyWrappers.swift in Sources */, + 4882405E234945A300CB6FFE /* StartupProcessService.swift in Sources */, + 48824064234945D000CB6FFE /* Asset.swift in Sources */, + 48824065234945D000CB6FFE /* ColorAsset.swift in Sources */, + 488240702349468B00CB6FFE /* TaskThrottler.swift in Sources */, 480E5604229BEFE20031BDF7 /* Broadcast.swift in Sources */, + 4882405D234945A300CB6FFE /* StartupProcess.swift in Sources */, 480E5603229BEFE20031BDF7 /* BroadcastObjectWrapper.swift in Sources */, + 4882406B2349464B00CB6FFE /* Global.swift in Sources */, + 48824066234945D000CB6FFE /* ImageAsset.swift in Sources */, 480E5600229BEFC20031BDF7 /* AttributedStringBuilder.swift in Sources */, + 48824060234945B500CB6FFE /* SimpleLogger.swift in Sources */, + 4882406E2349466C00CB6FFE /* KeyboardObserver.swift in Sources */, + 4882406A2349464B00CB6FFE /* Environment.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };