├── .gitignore ├── .swift-version ├── .travis.yml ├── Comet.podspec.json ├── Comet ├── Core │ ├── CollectionGroups.swift │ ├── CometErrors.swift │ ├── FileAttribute.swift │ ├── KeyboardManager.swift │ ├── KeyboardPlacehoder.swift │ └── Path.swift └── Extensions │ ├── Bundle+Comet.swift │ ├── Date+Comet.swift │ ├── Dispatch+Comet.swift │ ├── Numbers+Comet.swift │ ├── String+Comet.swift │ ├── Text+Localize.swift │ ├── UIAlertController+Comet.swift │ ├── UICollectionView+Comet.swift │ ├── UIColor+Comet.swift │ ├── UIDevice+Comet.swift │ ├── UINavigationBar+Comet.swift │ ├── UINavigationController+Comet.swift │ ├── UIResponder+Comet.swift │ ├── UIScrollView+Comet.swift │ ├── UITableView+Comet.swift │ ├── UITextField+Comet.swift │ ├── UIVIew+Comet.swift │ └── UIViewController+Comet.swift ├── Example ├── Comet.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Comet-Example.xcscheme │ └── xcuserdata │ │ ├── Harley-xk.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── Harley.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── Comet.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ ├── Harley-xk.xcuserdatad │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── Harley.xcuserdatad │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist ├── Comet │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── CoreSample.storyboard │ ├── Exttensions.storyboard │ ├── GrouperSampleViewController.swift │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── Playground.playground │ │ ├── Contents.swift │ │ └── contents.xcplayground │ ├── ScrollViewExtension.swift │ └── ViewController.swift ├── Podfile ├── Podfile.lock └── Pods │ ├── Local Podspecs │ ├── Comet.podspec.json │ └── SwiftRandom.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ ├── project.pbxproj │ └── xcuserdata │ │ ├── Harley-xk.xcuserdatad │ │ └── xcschemes │ │ │ ├── Comet.xcscheme │ │ │ ├── Pods-Comet_Example.xcscheme │ │ │ ├── SwiftRandom.xcscheme │ │ │ └── xcschememanagement.plist │ │ └── Harley.xcuserdatad │ │ └── xcschemes │ │ ├── Comet.xcscheme │ │ ├── Pods-Comet_Example.xcscheme │ │ └── xcschememanagement.plist │ ├── SwiftRandom │ ├── LICENSE │ ├── README.md │ └── SwiftRandom │ │ └── Randoms.swift │ └── Target Support Files │ ├── Comet │ ├── Comet-Info.plist │ ├── Comet-dummy.m │ ├── Comet-prefix.pch │ ├── Comet-umbrella.h │ ├── Comet.debug.xcconfig │ ├── Comet.modulemap │ ├── Comet.release.xcconfig │ ├── Comet.xcconfig │ └── Info.plist │ ├── Pods-Comet_Example │ ├── Info.plist │ ├── Pods-Comet_Example-Info.plist │ ├── Pods-Comet_Example-acknowledgements.markdown │ ├── Pods-Comet_Example-acknowledgements.plist │ ├── Pods-Comet_Example-dummy.m │ ├── Pods-Comet_Example-frameworks.sh │ ├── Pods-Comet_Example-resources.sh │ ├── Pods-Comet_Example-umbrella.h │ ├── Pods-Comet_Example.debug.xcconfig │ ├── Pods-Comet_Example.modulemap │ └── Pods-Comet_Example.release.xcconfig │ └── SwiftRandom │ ├── SwiftRandom-Info.plist │ ├── SwiftRandom-dummy.m │ ├── SwiftRandom-prefix.pch │ ├── SwiftRandom-umbrella.h │ ├── SwiftRandom.debug.xcconfig │ ├── SwiftRandom.modulemap │ ├── SwiftRandom.release.xcconfig │ └── SwiftRandom.xcconfig ├── Images ├── img_0.png ├── img_1.png ├── img_2.png └── img_3.png ├── LICENSE ├── Package.swift ├── README.md └── icon.png /.gitignore: -------------------------------------------------------------------------------- 1 | Example/Comet.xcworkspace/xcuserdata 2 | Example/Comet.xcodeproj/xcuserdata 3 | /Example/Comet.xcworkspace/xcshareddata 4 | .DS_Store 5 | /.swiftpm 6 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | os: osx 3 | osx_image: xcode12 4 | 5 | podfile: Example/Podfile 6 | script: xcodebuild -workspace Example/Comet.xcworkspace -scheme Comet-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO 7 | -------------------------------------------------------------------------------- /Comet.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Comet", 3 | "version": "2.0.1", 4 | "summary": "iOS 项目的 Swift 基础库,提供一些常用组件、便利方法等。", 5 | "description": "iOS 项目的 Swift 基础库,提供一些常用组件、便利方法等。支持 Swift 5.3", 6 | "homepage": "https://github.com/Harley-xk/Comet", 7 | "license": { 8 | "type": "MIT", 9 | "file": "LICENSE" 10 | }, 11 | "authors": { 12 | "Harley.xk": "harley.gb@foxmail.com" 13 | }, 14 | "source": { 15 | "git": "https://github.com/Harley-xk/Comet.git", 16 | "tag": "2.0.1" 17 | }, 18 | "platforms": { 19 | "ios": "10.0" 20 | }, 21 | "source_files": "Comet/**/*" 22 | } 23 | -------------------------------------------------------------------------------- /Comet/Core/CollectionGroups.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Grouper.swift 3 | // Comet 4 | // 5 | // Created by Harley-xk on 2019/4/11. 6 | // 7 | 8 | import Foundation 9 | 10 | /// 集合分组后的数据实体 11 | public class Group { 12 | public var index: T 13 | public var elements: [Element] 14 | public init(index: T, elements: [Element]) { 15 | self.index = index 16 | self.elements = elements 17 | } 18 | } 19 | 20 | /// 集合分组器,保存分组结果 21 | open class CollectionGroups { 22 | 23 | public typealias SortFunction = (C.Element, C.Element) -> Bool 24 | private var sortFunction: SortFunction? 25 | 26 | /// 分组后的数组集合 27 | public private(set) var results: [Key: [C.Element]] = [:] 28 | 29 | /// 对集合按照制定的属性进行分组 30 | public convenience init( 31 | group collection: C, 32 | by property: KeyPath, 33 | sorted sort: SortFunction? = nil 34 | ) { 35 | self.init(group: collection, by: { $0[keyPath: property] }, sorted: sort) 36 | } 37 | 38 | /// 对集合按照制定的属性进行分组 39 | public init( 40 | group collection: C, 41 | by rule: (C.Element) -> Key, 42 | sorted sort: SortFunction? = nil 43 | ) { 44 | var array: [C.Element] 45 | if let s = sort { 46 | sortFunction = s 47 | array = collection.sorted(by: s) 48 | } else { 49 | array = Array(collection) 50 | } 51 | results = Dictionary(grouping: array, by: rule) 52 | } 53 | 54 | public lazy var sortedGroups: [Group] = { 55 | var groups = results.map { 56 | Group(index: $0, elements: $1) 57 | } 58 | if let sort = sortFunction { 59 | groups = groups.sorted(by: { left, right in 60 | if left.elements.count <= 0 { return false } 61 | if right.elements.count <= 0 { return true } 62 | return sort(left.elements.first!, right.elements.first!) 63 | }) 64 | } 65 | return groups 66 | }() 67 | } 68 | 69 | public extension Array { 70 | 71 | func grouped(by property: KeyPath) -> CollectionGroups<[Element], T> { 72 | return CollectionGroups(group: self, by: property) 73 | } 74 | 75 | func grouped(by rule: (Element) -> T) -> CollectionGroups<[Element], T> { 76 | return CollectionGroups(group: self, by: rule) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Comet/Core/CometErrors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CometErrors.swift 3 | // Comet 4 | // 5 | // Created by Harley-xk on 2020/9/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Comet 框架所有异常信息定义 11 | enum CometError: Error { 12 | 13 | /// 设备不支持打电话 14 | case phoneCallNotSupported 15 | 16 | 17 | /// 未定义的错误 18 | case unknown 19 | } 20 | 21 | extension CometError: LocalizedError { 22 | 23 | var errorDescription: String? { 24 | switch self { 25 | case .phoneCallNotSupported: return "设备不支持拨打电话" 26 | default: return "未知错误" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Comet/Core/FileAttribute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileAttribute.swift 3 | // Comet 4 | // 5 | // Created by Harley-xk on 2019/5/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct FileAttribute { 11 | 12 | public internal(set) var size: Int64 13 | 14 | public internal(set) var fileType: FileAttributeType 15 | 16 | public internal(set) var creationDate: Date? 17 | public internal(set) var modificationDate: Date? 18 | 19 | public internal(set) var ownerAccountID: Int? 20 | public internal(set) var ownerAccountName: String? 21 | 22 | public internal(set) var groupOwnerAccountID: Int? 23 | public internal(set) var groupOwnerAccountName: String? 24 | 25 | public internal(set) var immutable: Bool 26 | public internal(set) var appendOnly: Bool 27 | 28 | public internal(set) var extensionHidden: Bool 29 | 30 | public internal(set) var systemNumber: Int 31 | public internal(set) var referenceCount: Int 32 | public internal(set) var posixPermissions: Int 33 | public internal(set) var systemFileNumber: Int 34 | public internal(set) var hfsCreatorCode: OSType 35 | public internal(set) var hfsTypeCode: OSType 36 | 37 | init(attributes: [FileAttributeKey : Any]) { 38 | 39 | size = attributes [.size] as? Int64 ?? 0 40 | fileType = FileAttributeType(rawValue: attributes[.type] as? String ?? "") 41 | 42 | creationDate = attributes[.creationDate] as? Date 43 | modificationDate = attributes[.modificationDate] as? Date 44 | 45 | ownerAccountID = attributes[.ownerAccountID] as? Int 46 | ownerAccountName = attributes[.ownerAccountName] as? String 47 | 48 | groupOwnerAccountID = attributes[.groupOwnerAccountID] as? Int 49 | groupOwnerAccountName = attributes[.groupOwnerAccountName] as? String 50 | 51 | immutable = attributes[.immutable] as? Bool ?? false 52 | appendOnly = attributes[.appendOnly] as? Bool ?? false 53 | 54 | extensionHidden = attributes[.extensionHidden] as? Bool ?? false 55 | 56 | systemNumber = attributes[.systemNumber] as? Int ?? 0 57 | referenceCount = attributes[.referenceCount] as? Int ?? 0 58 | posixPermissions = attributes[.posixPermissions] as? Int ?? 0 59 | systemFileNumber = attributes[.systemFileNumber] as? Int ?? 0 60 | hfsCreatorCode = attributes[.hfsCreatorCode] as? OSType ?? 0 61 | hfsTypeCode = attributes[.hfsTypeCode] as? OSType ?? 0 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Comet/Core/KeyboardManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardManager.swift 3 | // Comet 4 | // 5 | // Created by Harley.xk on 16/6/28. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | /// 被 KeyboardManager 代理者的协议 13 | public protocol KeyboardManagerDelegatingTarget { 14 | 15 | /// 被代理者可以在函数中注册一些 UI 更新,以便界面上其他 UI 与键盘同步更新,这些更新将会随着键盘动画一起执行 16 | /// 17 | /// - Attention: 这个函数将会被包在一个动画块中调用 18 | /// - Parameters: 19 | /// - keyboardStatus: 当前键盘状态 20 | /// - offset: 键盘距离当前状态变化的高度 21 | /// - notification: 键盘事件通知的原始内容 22 | func animationsAlongsidesKeyboardFrame(keyboardStatus: KeyboardManager.KeyboardStatus, offset: CGFloat, notification: Notification) 23 | } 24 | 25 | public extension KeyboardManagerDelegatingTarget { 26 | /// 提供 animationsAlongsidesKeyboardFrame 的默认空实现 27 | func animationsAlongsidesKeyboardFrame(keyboardStatus: KeyboardManager.KeyboardStatus, offset: CGFloat, notification: Notification) {} 28 | } 29 | 30 | 31 | /** 32 | * 键盘管理器,用于在键盘弹出或收起时调整相应视图,以保证相关内容始终可见 33 | * 34 | * 使用方法: 35 | * 1、将需要始终可见的视图放置于 UIScrollView 或其子类中 36 | * 2、设置合适的约束 37 | * 3、获取全局 HKKeyboardManager,并关联对应的约束和视图 38 | */ 39 | 40 | open class KeyboardManager { 41 | 42 | /// 代理对象,必须是实现 KeyboardManagerDelegatingTarget 协议的视图控制器 43 | public typealias DelegatingTarget = (UIViewController & KeyboardManagerDelegatingTarget) 44 | 45 | /// 全局的默认键盘管理器,适合短时间独占使用键盘的场景 46 | open class var `default`: KeyboardManager { 47 | return defaultKeyboardManager 48 | } 49 | 50 | /// 创建单独的键盘管理器 51 | /// 对于需要长时间占用键盘管理,并且可能会切换到其他使用键盘的场景的,建议为其单独创建键盘管理器 52 | public convenience init(target: DelegatingTarget, positionConstraint: NSLayoutConstraint, viewToAdjust: UIView) { 53 | self.init() 54 | self.delegate(for: target, positionConstraint: positionConstraint, viewToAdjust: viewToAdjust) 55 | } 56 | 57 | /// Set KeyboardManager to delegate keyboard events for viewController 58 | /// 将键盘管理器设置为指定视图控制器的代理,处理键盘事件 59 | /// 60 | /// - Parameters: 61 | /// - positionConstraint: 键盘UI变化时需要调整的约束 62 | /// - viewToAdjust: 键盘变化时需要调整的视图 63 | open func delegate(for viewController: DelegatingTarget, positionConstraint: NSLayoutConstraint, viewToAdjust: UIView) { 64 | self.viewController = viewController 65 | self.viewToAdjust = viewToAdjust 66 | self.positionConstraint = positionConstraint 67 | self.originalConstant = positionConstraint.constant 68 | self.originalBottomSpace = viewBottomSpace() 69 | enabled = true 70 | } 71 | 72 | /// 在视图退出后解除代理任务 73 | /// 如果此时已经绑定了其它的控制器,则该方法会跳过,不会再次尝试停止代理 74 | open func stopDelegate(for viewController: DelegatingTarget) { 75 | guard let vc = self.viewController, vc == viewController else { 76 | return 77 | } 78 | self.viewController = nil 79 | self.viewToAdjust = nil 80 | self.positionConstraint = nil 81 | UIResponder.resignAnyFirstResponder() 82 | keyboardStatus = .hidden 83 | enabled = false 84 | } 85 | 86 | /** 87 | * 临时启用或关闭管理器 88 | */ 89 | open var enabled = true 90 | 91 | /** 92 | * 是否与键盘同时执行调整动画,默认为 YES。设置为 NO 后,将会在键盘显示之后再执行动画。 93 | */ 94 | open var animateAlongwithKeyboard = true 95 | 96 | public enum KeyboardStatus { 97 | case hidden 98 | case hidding 99 | case shown 100 | case showing 101 | } 102 | private var keyboardStatus = KeyboardStatus.hidden 103 | 104 | private weak var viewController: DelegatingTarget? 105 | private weak var viewToAdjust: UIView? 106 | private weak var positionConstraint: NSLayoutConstraint? 107 | 108 | private var originalConstant: CGFloat = 0 109 | private var originalBottomSpace: CGFloat = 0 110 | private var currentKeyboardHeight: CGFloat = 0 111 | 112 | fileprivate init() { 113 | registerKeyboardEvents() 114 | } 115 | 116 | deinit { 117 | NotificationCenter.default.removeObserver(self) 118 | } 119 | 120 | // MARK: - Private Logics 121 | private func registerKeyboardEvents() { 122 | let notificationCenter = NotificationCenter.default 123 | notificationCenter.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) 124 | notificationCenter.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) 125 | notificationCenter.addObserver(self, selector: #selector(keyboardDidShow), name: UIResponder.keyboardDidShowNotification, object: nil) 126 | notificationCenter.addObserver(self, selector: #selector(keyboardDidHide), name: UIResponder.keyboardDidHideNotification, object: nil) 127 | notificationCenter.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) 128 | } 129 | 130 | private func viewBottomSpace() -> CGFloat { 131 | guard let vc = viewController, let view = viewToAdjust else { 132 | return 0 133 | } 134 | var bottomOffSet: CGFloat = 0 135 | if let scrollView = view as? UIScrollView { 136 | bottomOffSet = scrollView.contentInset.bottom 137 | bottomOffSet = max(0, bottomOffSet) 138 | } 139 | return vc.view.frame.size.height - view.frame.size.height - view.frame.origin.y + bottomOffSet; 140 | } 141 | 142 | @objc 143 | private func keyboardWillShow(_ notification: Notification) { 144 | guard keyboardStatus == .hidden else { 145 | return 146 | } 147 | 148 | self.originalBottomSpace = viewBottomSpace(); 149 | keyboardStatus = .showing 150 | if (self.animateAlongwithKeyboard) { 151 | updateForKeyboard(withNotification: notification) 152 | } 153 | } 154 | 155 | @objc 156 | private func keyboardDidShow(_ notification: Notification) { 157 | guard keyboardStatus == .showing else { 158 | return 159 | } 160 | 161 | keyboardStatus = .shown 162 | if (!self.animateAlongwithKeyboard) { 163 | updateForKeyboard(withNotification: notification) 164 | } 165 | } 166 | 167 | @objc 168 | private func keyboardDidHide(_ notification: Notification) { 169 | guard keyboardStatus == .hidding else { 170 | return 171 | } 172 | keyboardStatus = .hidden 173 | } 174 | 175 | @objc 176 | private func keyboardWillChangeFrame(_ notification: Notification) { 177 | guard keyboardStatus == .shown else { 178 | return 179 | } 180 | updateForKeyboard(withNotification: notification) 181 | } 182 | 183 | @objc 184 | private func keyboardWillHide(_ notification: Notification) { 185 | guard keyboardStatus != .hidden, let viewController = self.viewController, let positionConstraint = self.positionConstraint else { 186 | return 187 | } 188 | keyboardStatus = .hidding 189 | if (enabled) { 190 | let userInfo = notification.userInfo! 191 | let timeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue 192 | let option = UIView.AnimationOptions(rawValue: UInt(truncating: userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber)) 193 | let keyboardChangedHeight = self.originalConstant - positionConstraint.constant 194 | 195 | UIView.animate(withDuration: timeInterval, delay: 0, options:option, animations: { 196 | positionConstraint.constant = self.originalConstant 197 | self.viewController?.animationsAlongsidesKeyboardFrame(keyboardStatus: self.keyboardStatus, offset: keyboardChangedHeight, notification: notification) 198 | viewController.view.layoutIfNeeded() 199 | }, completion: nil) 200 | currentKeyboardHeight = 0; 201 | } 202 | } 203 | 204 | private func updateForKeyboard(withNotification notification: Notification) { 205 | guard let viewController = self.viewController, let positionConstraint = self.positionConstraint, enabled else { 206 | return 207 | } 208 | 209 | let userInfo = notification.userInfo! 210 | let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue 211 | let frameInView = viewController.view.convert(endFrame, from: viewController.view.window) 212 | var keyBoardHeight = viewController.view.frame.size.height - frameInView.origin.y 213 | keyBoardHeight = max(0, keyBoardHeight) 214 | 215 | let bottomSpace = self.originalBottomSpace; 216 | if (bottomSpace > keyBoardHeight) { 217 | return; 218 | } 219 | 220 | let offset = keyBoardHeight - bottomSpace; 221 | 222 | let timeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue 223 | let option = UIView.AnimationOptions(rawValue: UInt(truncating: userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber)) 224 | 225 | let keyboardChangedHeight = self.originalConstant + offset - positionConstraint.constant 226 | UIView.animate(withDuration: timeInterval, delay: 0, options:option, animations: { 227 | self.viewController?.animationsAlongsidesKeyboardFrame(keyboardStatus: self.keyboardStatus, offset: keyboardChangedHeight, notification: notification) 228 | positionConstraint.constant = self.originalConstant + offset 229 | viewController.view.layoutIfNeeded() 230 | }, completion: nil); 231 | 232 | self.currentKeyboardHeight = keyBoardHeight; 233 | } 234 | } 235 | 236 | fileprivate let defaultKeyboardManager = KeyboardManager() 237 | -------------------------------------------------------------------------------- /Comet/Core/KeyboardPlacehoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardPlacehoderView.swift 3 | // Comet 4 | // 5 | // Created by Harley-xk on 2019/3/4. 6 | // 7 | 8 | import UIKit 9 | 10 | /** 11 | * 键盘占位器,在键盘弹出时调整自身尺寸,保证与键盘在父试图中的投影同步 12 | */ 13 | 14 | open class KeyboardPlacehoder: UIView { 15 | 16 | /// 代理对象, 用来接收键盘动画事件,用于处理某些需要与键盘同步响应的效果 17 | public typealias DelegatingTarget = (AnyObject & KeyboardManagerDelegatingTarget) 18 | public var delegate: DelegatingTarget? 19 | 20 | /** 21 | * 临时启用或关闭管理器 22 | */ 23 | public var enabled = true 24 | 25 | 26 | // MARK: - Private Logics 27 | private var heightConstraint: NSLayoutConstraint! 28 | private var keyboardStatus = KeyboardManager.KeyboardStatus.hidden 29 | private var currentKeyboardHeight: CGFloat = 0 30 | 31 | private func registerKeyboardEvents() { 32 | let notificationCenter = NotificationCenter.default 33 | notificationCenter.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) 34 | notificationCenter.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) 35 | notificationCenter.addObserver(self, selector: #selector(keyboardDidShow), name: UIResponder.keyboardDidShowNotification, object: nil) 36 | notificationCenter.addObserver(self, selector: #selector(keyboardDidHide), name: UIResponder.keyboardDidHideNotification, object: nil) 37 | notificationCenter.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) 38 | } 39 | 40 | @objc private func keyboardWillShow(_ notification: Notification) { 41 | guard keyboardStatus == .hidden else { 42 | return 43 | } 44 | 45 | keyboardStatus = .showing 46 | updateForKeyboard(withNotification: notification) 47 | } 48 | 49 | @objc private func keyboardDidShow(_ notification: Notification) { 50 | guard keyboardStatus == .showing else { 51 | return 52 | } 53 | 54 | keyboardStatus = .shown 55 | updateForKeyboard(withNotification: notification) 56 | } 57 | 58 | @objc private func keyboardDidHide(_ notification: Notification) { 59 | guard keyboardStatus == .hidding else { 60 | return 61 | } 62 | keyboardStatus = .hidden 63 | } 64 | 65 | @objc private func keyboardWillChangeFrame(_ notification: Notification) { 66 | guard keyboardStatus == .shown else { 67 | return 68 | } 69 | updateForKeyboard(withNotification: notification) 70 | } 71 | 72 | @objc private func keyboardWillHide(_ notification: Notification) { 73 | guard keyboardStatus != .hidden, currentKeyboardHeight > 0 else { 74 | return 75 | } 76 | keyboardStatus = .hidding 77 | 78 | guard enabled else { 79 | return 80 | } 81 | 82 | let userInfo = notification.userInfo! 83 | let timeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue 84 | let option = UIView.AnimationOptions(rawValue: UInt(truncating: userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber)) 85 | 86 | let keyboardChangedHeight = currentKeyboardHeight 87 | currentKeyboardHeight = 0; 88 | 89 | // 异步执行动画,防止把其他的系统动画框到同一个动画 context 中来 90 | DispatchQueue.main.async { 91 | UIView.animate(withDuration: timeInterval, delay: 0, options:option, animations: { 92 | self.heightConstraint.constant = 0 93 | self.delegate?.animationsAlongsidesKeyboardFrame(keyboardStatus: self.keyboardStatus, offset: keyboardChangedHeight, notification: notification) 94 | self.rootView.layoutIfNeeded() 95 | }, completion: nil) 96 | } 97 | } 98 | 99 | private func updateForKeyboard(withNotification notification: Notification) { 100 | guard enabled, let superview = superview else { 101 | return 102 | } 103 | 104 | let userInfo = notification.userInfo! 105 | let endFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue 106 | let frameInView = superview.convert(endFrame, from: superview.window) 107 | let superViewHeight = superview.bounds.height 108 | let bottomOffset = max(0, superViewHeight - (frameInView.origin.y + frameInView.size.height)) 109 | let bottomInset = max(0, superViewHeight - (frame.origin.y + frame.size.height)) 110 | var keyBoardHeight = superViewHeight - frameInView.origin.y - bottomOffset - bottomInset 111 | keyBoardHeight = max(0, keyBoardHeight) 112 | 113 | let timeInterval = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue 114 | let option = UIView.AnimationOptions(rawValue: UInt(truncating: userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber)) 115 | 116 | let keyboardChangedHeight = keyBoardHeight - currentKeyboardHeight 117 | currentKeyboardHeight = keyBoardHeight; 118 | 119 | // 异步执行动画,防止把其他的系统动画框到同一个动画 context 中来 120 | DispatchQueue.main.async { 121 | UIView.animate(withDuration: timeInterval, delay: 0, options:option, animations: { 122 | self.delegate?.animationsAlongsidesKeyboardFrame(keyboardStatus: self.keyboardStatus, offset: keyboardChangedHeight, notification: notification) 123 | self.heightConstraint.constant = keyBoardHeight 124 | self.rootView.layoutIfNeeded() 125 | }, completion: nil); 126 | } 127 | } 128 | 129 | // MARK: - On Enter 130 | override init(frame: CGRect) { 131 | super.init(frame: frame) 132 | setup() 133 | } 134 | 135 | required public init?(coder aDecoder: NSCoder) { 136 | super.init(coder: aDecoder) 137 | } 138 | 139 | override open func awakeFromNib() { 140 | super.awakeFromNib() 141 | setup() 142 | } 143 | 144 | private func setup() { 145 | heightConstraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .greaterThanOrEqual, toItem: nil, attribute: .height, multiplier: 1, constant: 0) 146 | addConstraint(heightConstraint) 147 | registerKeyboardEvents() 148 | } 149 | 150 | private var rootView: UIView { 151 | var father: UIView = self 152 | while let view = father.superview { 153 | father = view 154 | } 155 | return father 156 | } 157 | 158 | // MARK: - deinit 159 | 160 | deinit { 161 | NotificationCenter.default.removeObserver(self) 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /Comet/Core/Path.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Path.swift 3 | // Comet 4 | // 5 | // Created by Harley.xk on 16/6/27. 6 | // 7 | // 8 | 9 | import Foundation 10 | import MobileCoreServices 11 | 12 | // MARK: - 文件路径,快速获取各种文件路径 13 | 14 | open class Path { 15 | 16 | /// 使用路径字符串构建实例 17 | public init(_ path: String) { 18 | url = URL(fileURLWithPath: path) 19 | } 20 | 21 | /// 包装 URL 路径 22 | public init(_ path: URL) { 23 | url = path 24 | } 25 | 26 | public init(directory: FileManager.SearchPathDirectory, create: Bool = false) throws { 27 | url = try FileManager.default.url(for: directory, in: .userDomainMask, appropriateFor: nil, create: create) 28 | } 29 | 30 | /// 完整路径字符串 31 | open var string: String { 32 | return url.relativePath 33 | } 34 | 35 | /// URL 实例 36 | open var url: URL 37 | 38 | /// 获取沙盒 Documents 路 39 | open class func documents() -> Path { 40 | return try! Path(directory: .documentDirectory) 41 | } 42 | 43 | /// 获取沙盒 Library 路径 44 | open class func library() -> Path { 45 | return try! Path(directory: .libraryDirectory) 46 | } 47 | 48 | /// 获取沙盒 Cache 路径 49 | open class func cache() -> Path { 50 | return try! Path(directory: .cachesDirectory) 51 | } 52 | 53 | /// 获取沙盒 Temp 路径 54 | open class func temp() -> Path { 55 | return Path(NSTemporaryDirectory()) 56 | } 57 | 58 | /// 获取沙盒 Application Support 路径,不存在时会自动创建 59 | open class func applicationSupport(autoCreate: Bool = true) throws -> Path { 60 | let path = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: autoCreate) 61 | return Path(path) 62 | } 63 | 64 | /// 获取当前目录下的资源文件路径 65 | /// 66 | /// - Parameter name: 资源文件名(含扩展名) 67 | open func resource(_ name: String) -> Path { 68 | let res = url.appendingPathComponent(name, isDirectory: false) 69 | return Path(res) 70 | } 71 | 72 | /// 获取当前目录下的子目录 73 | /// 74 | /// - Parameter name: 资源文件名(含扩展名) 75 | open func folder(_ name: String) -> Path { 76 | let res = url.appendingPathComponent(name, isDirectory: true) 77 | return Path(res) 78 | } 79 | 80 | // MARK: - Path Utils 81 | /// 文件管理器 82 | open var fileManager: FileManager { 83 | return FileManager.default 84 | } 85 | 86 | /// 路径是否存在,无论文件或者文件夹 87 | open var exist: Bool { 88 | return fileManager.fileExists(atPath: string) 89 | } 90 | 91 | /// 文件是否存在, 返回是否存在,以及是否是文件 92 | open var fileExist: (exist: Bool, isFile: Bool) { 93 | var isDirectory = ObjCBool(false) 94 | let exist = fileManager.fileExists(atPath: string, isDirectory: &isDirectory) 95 | return (exist, !isDirectory.boolValue) 96 | } 97 | 98 | /// 文件夹是否存在, 返回是否存在,(以及必须是文件夹) 99 | open var folderExist: Bool { 100 | let info = fileExist 101 | return info.exist && !info.isFile 102 | } 103 | 104 | /// 文件扩展名 105 | open var pathExtension: String? { 106 | return url.pathExtension 107 | } 108 | 109 | /// 创建路径 110 | open func createDirectory() throws { 111 | try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) 112 | } 113 | 114 | /// 删除当前路径指向的物理文件(夹) 115 | open func removeFromDisk() throws { 116 | try fileManager.removeItem(at: url) 117 | } 118 | 119 | /// 获取文件夹子目录 120 | open func getChildren(skipsHiddenFiles: Bool = true) throws -> [Path] { 121 | guard folderExist else { 122 | return [] 123 | } 124 | var options = FileManager.DirectoryEnumerationOptions() 125 | if skipsHiddenFiles { 126 | options = options.union(.skipsHiddenFiles) 127 | } 128 | let contents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: options) 129 | return contents.map { Path($0) } 130 | } 131 | 132 | open var attributes: FileAttribute? { 133 | if let attributes = try? fileManager.attributesOfItem(atPath: string) { 134 | return FileAttribute(attributes: attributes) 135 | } 136 | return nil 137 | } 138 | 139 | /// 获取文件 mime type 140 | open var mimeType: String? { 141 | if let ext = pathExtension as NSString? { 142 | if let UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, nil)?.takeUnretainedValue() { 143 | if let MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType) { 144 | let mimeTypeCFString = MIMEType.takeUnretainedValue() as CFString 145 | return mimeTypeCFString as String 146 | } 147 | } 148 | } 149 | return nil 150 | } 151 | 152 | /// 对该目录禁用 iCloud 自动备份 153 | open func disableAutoBackup() { 154 | let existInfo = fileExist 155 | guard existInfo.exist, !existInfo.isFile else { 156 | return 157 | } 158 | var url = self.url 159 | url.setTemporaryResourceValue(true, forKey: .isExcludedFromBackupKey) 160 | } 161 | 162 | /// 获取文件大小,如果是文件夹,会遍历整个目录及子目录计算所有文件大小 163 | open var size: UInt64 { 164 | let fileExist = self.fileExist 165 | if fileExist.exist { 166 | if fileExist.isFile { 167 | return fileSize() 168 | }else { 169 | return folderSize() 170 | } 171 | } 172 | return 0 173 | } 174 | 175 | /// 获取文件夹大小,并且格式化为可读字符串 176 | open var sizeString: String { 177 | return Path.string(fromBytes: size) 178 | } 179 | 180 | /// 转换字节数为最大单位可读字符串 181 | open class func string(fromBytes bytes: UInt64) -> String { 182 | let kb = Double(bytes)/1024; 183 | if (kb < 1) { 184 | return "\(bytes)B" 185 | } 186 | let mb = kb/1024.0; 187 | if (mb < 1) { 188 | return String(format: "%.0fKB", kb) 189 | } 190 | let gb = mb/1024.0; 191 | if (gb < 1) { 192 | return String(format: "%.1fMB", mb) 193 | } 194 | let tb = gb/1024.0; 195 | if (tb < 1) { 196 | return String(format: "%.1fG", gb) 197 | } else { 198 | return String(format: "%.1fT", tb) 199 | } 200 | } 201 | 202 | // MARK: - Datas 203 | open func readData(options: Data.ReadingOptions = []) throws -> Data { 204 | return try Data(contentsOf: url, options: options) 205 | } 206 | 207 | // MARK: - Private 208 | internal func fileSize() -> UInt64 { 209 | let fileExist = self.fileExist 210 | if fileExist.exist && fileExist.isFile { 211 | if let attributes = try? fileManager.attributesOfItem(atPath: string) as NSDictionary { 212 | return attributes.fileSize() 213 | } 214 | } 215 | return 0 216 | } 217 | 218 | internal func folderSize() -> UInt64 { 219 | var folderSize: UInt64 = 0 220 | 221 | let fileExist = self.fileExist 222 | if fileExist.exist && !fileExist.isFile { 223 | if let contents = try? fileManager.contentsOfDirectory(atPath: string) { 224 | for file in contents { 225 | let path = resource(file) 226 | let subFileExist = path.fileExist 227 | if subFileExist.exist { 228 | if subFileExist.isFile { 229 | folderSize += path.fileSize() 230 | }else { 231 | folderSize += path.folderSize() 232 | } 233 | } 234 | } 235 | } 236 | } 237 | return folderSize 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Comet/Extensions/Bundle+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley-xk on 2020/9/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Bundle { 11 | 12 | /// Bundle Identifier 13 | open var identifier: String { 14 | return infoDictionary?["CFBundleIdentifier"] as? String ?? "" 15 | } 16 | 17 | /// Bundle 版本号 18 | open var version: String { 19 | return infoDictionary?["CFBundleShortVersionString"] as? String ?? "0" 20 | } 21 | 22 | /// Bundle Build 号 23 | open var build: String { 24 | return infoDictionary?["CFBundleVersion"] as? String ?? "0" 25 | } 26 | 27 | /// 获取 Bundle 中指定名称资源的路径 28 | /// 29 | /// - Parameters: 30 | /// - name: 资源名称 31 | /// - Returns: 返回资源路径, 资源不存在时返回空 32 | func resource(_ name: String) -> Path? { 33 | let path = name as NSString 34 | let pathExtension = path.pathExtension 35 | let nameWithoutExtension = pathExtension.isEmpty ? name : path.deletingPathExtension 36 | if let url = url(forResource: nameWithoutExtension, withExtension: pathExtension) { 37 | return Path(url) 38 | } 39 | return nil 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Comet/Extensions/Date+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley on 2016/11/9. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Date { 12 | 13 | /// 日期单元 14 | struct DateUnit { 15 | var component: Calendar.Component 16 | var value: Int 17 | public static func year(_ value: Int) -> DateUnit { return DateUnit(component: .year, value: value) } 18 | public static func month(_ value: Int) -> DateUnit { return DateUnit(component: .month, value: value) } 19 | public static func day(_ value: Int) -> DateUnit { return DateUnit(component: .day, value: value) } 20 | public static func hour(_ value: Int) -> DateUnit { return DateUnit(component: .hour, value: value) } 21 | public static func minute(_ value: Int) -> DateUnit { return DateUnit(component: .minute, value: value) } 22 | public static func second(_ value: Int) -> DateUnit { return DateUnit(component: .second, value: value) } 23 | } 24 | 25 | /// 从日期字符串创建日期对象 26 | init?(string: String, format: String = "yyyy-MM-dd HH:mm:ss", timeZone: TimeZone = TimeZone.current) { 27 | 28 | let formatter = DateFormatter() 29 | formatter.dateFormat = format 30 | formatter.timeZone = timeZone 31 | 32 | if let date = formatter.date(from: string) { 33 | self = date 34 | }else { 35 | return nil 36 | } 37 | } 38 | 39 | /// 将日期转换为指定格式的字符串 40 | func string(format: String = "yyyy-MM-dd HH:mm:ss", timeZone: TimeZone = TimeZone.current) -> String { 41 | let formatter = DateFormatter() 42 | formatter.dateFormat = format 43 | formatter.timeZone = timeZone 44 | return formatter.string(from: self) 45 | } 46 | 47 | /// 转换为年月日的字符串 48 | func dateString() -> String { 49 | return string(format: "yyyy-MM-dd") 50 | } 51 | 52 | /// 日期计算,返回当前日期加上指定单位值之后的日期,会自动进位或减位 53 | /// 返回计算后的新日期 54 | func add(_ unit: DateUnit) -> Date { 55 | 56 | let calendar = Calendar.current 57 | var components = calendar.dateComponents(Set(Calendar.Component.dateAndTime), from: self) 58 | components.timeZone = TimeZone.current 59 | 60 | if let oriValue = components.value(for: unit.component) { 61 | components.setValue(oriValue + unit.value, for: unit.component) 62 | } 63 | let date = calendar.date(from: components) 64 | return date ?? self 65 | } 66 | 67 | /// 将指定单位设置为指定的值,返回修改后的新日期 68 | /// 如果设置的值大于当前单位的最大值或者小于最小值,会自动进位或减位 69 | func set(_ unit: DateUnit) -> Date { 70 | let calendar = Calendar.current 71 | var components = calendar.dateComponents(Set(Calendar.Component.dateAndTime), from: self) 72 | components.timeZone = TimeZone.current 73 | components.setValue(unit.value, for: unit.component) 74 | let date = calendar.date(from: components) 75 | return date ?? self 76 | } 77 | 78 | /// 忽略精确时间(时/分/秒)的日期 79 | var withoutTime: Date { 80 | let calendar = Calendar.current 81 | var components = calendar.dateComponents([.year, .month, .day], from: self) 82 | components.timeZone = TimeZone.current 83 | let date = calendar.date(from: components) 84 | return date ?? self 85 | } 86 | 87 | /// 某个日期的开始,即 0 时 0 分 0 秒 88 | var beginTime: Date { 89 | return withoutTime 90 | } 91 | 92 | /// 某个日期的结束,即 23 时 59 分 59 秒 93 | var endTime: Date { 94 | let calendar = Calendar.current 95 | var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self) 96 | components.timeZone = TimeZone.current 97 | components.hour = 23 98 | components.minute = 59 99 | components.second = 59 100 | 101 | let date = calendar.date(from: components) 102 | return date ?? self 103 | } 104 | 105 | /// 某个单位的值 106 | func unit(_ unit: Calendar.Component) -> Int { 107 | let calendar = Calendar.current 108 | var components = calendar.dateComponents([unit], from: self) 109 | components.timeZone = TimeZone.current 110 | return components.value(for: unit) ?? 0 111 | } 112 | 113 | /// 周几,周日为0 114 | var weekday: Int { 115 | let calendar = Calendar.current 116 | var components = calendar.dateComponents([.weekday], from: self) 117 | components.timeZone = TimeZone.current 118 | return (components.weekday ?? 1) - 1 119 | } 120 | 121 | // 两个日期相隔的分钟数 122 | func minutesSince(_ date: Date) -> Double { 123 | let timeInterval = timeIntervalSince(date) 124 | let minute = timeInterval / 60 125 | return minute 126 | } 127 | 128 | // 两个日期相隔的小时数 129 | func hoursSince(_ date: Date) -> Double { 130 | let minute = minutesSince(date) 131 | return minute / 60 132 | } 133 | 134 | /// 两个日期相隔的天数 135 | /// 136 | /// - Parameters: 137 | /// - date: 与当前日期比较的日期 138 | /// - withoutTime: 是否忽略精确的时分秒,可以启用该属性来比较两个日期的物理天数(即昨天、前天等) 139 | /// - Returns: 天数 140 | func daysSince(_ date: Date, withoutTime: Bool = false) -> Double { 141 | var date1 = self 142 | var date2 = date 143 | if withoutTime { 144 | date1 = self.withoutTime 145 | date2 = date.withoutTime 146 | } 147 | let hours = date1.hoursSince(date2) 148 | return hours / 24 149 | } 150 | 151 | 152 | /// 判断两个日期是否在同一天内 153 | func isSameDay(as date: Date?) -> Bool { 154 | guard let date = date else { 155 | return false 156 | } 157 | let days = daysSince(date, withoutTime: true) 158 | return days == 0 159 | } 160 | } 161 | 162 | public func +(lhs: Date, rhs: Date.DateUnit) -> Date { 163 | return lhs.add(rhs) 164 | } 165 | 166 | public func -(lhs: Date, rhs: Date.DateUnit) -> Date { 167 | var r = rhs 168 | r.value = -rhs.value 169 | return lhs.add(r) 170 | } 171 | 172 | public extension TimeZone { 173 | 174 | /// 中国时区(东8区) 175 | static var china: TimeZone { 176 | return TimeZone(identifier: "Asia/Shanghai")! 177 | } 178 | 179 | // UTC 0 时区 180 | static var utc: TimeZone { 181 | return TimeZone(abbreviation: "UTC")! 182 | } 183 | 184 | } 185 | 186 | public extension Calendar.Component { 187 | static var date: [Calendar.Component] { 188 | return [.year, .month, .day] 189 | } 190 | static var time: [Calendar.Component] { 191 | return [.hour, .minute, .second] 192 | } 193 | static var dateAndTime: [Calendar.Component] { 194 | return [.year, .month, .day, .hour, .minute, .second] 195 | } 196 | } 197 | 198 | 199 | public extension Locale { 200 | 201 | /// 中国地区 202 | static var china: Locale { 203 | return Locale(identifier: "zh_Hans_CN") 204 | } 205 | 206 | /// 美国地区 207 | static var usa: Locale { 208 | return Locale(identifier: "es_US") 209 | } 210 | } 211 | 212 | 213 | -------------------------------------------------------------------------------- /Comet/Extensions/Dispatch+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dispatch+Comet.swift 3 | // Pods 4 | // 5 | // Created by Harley on 2016/12/9. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | extension DispatchQueue { 12 | 13 | public func asyncAfter(delay: DispatchTimeInterval, execute work: @escaping @convention(block) () -> Swift.Void) { 14 | asyncAfter(deadline: .now() + delay, execute: work) 15 | } 16 | 17 | public func asyncAfter(delay seconds: TimeInterval, execute work: @escaping @convention(block) () -> Swift.Void) { 18 | asyncAfter(deadline: .now() + seconds, execute: work) 19 | } 20 | 21 | public func asyncAfter(delay: DispatchTimeInterval, execute: DispatchWorkItem) { 22 | asyncAfter(deadline: .now() + delay, execute: execute) 23 | } 24 | 25 | public func asyncAfter(delay seconds: TimeInterval, execute: DispatchWorkItem) { 26 | asyncAfter(deadline: .now() + seconds, execute: execute) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Comet/Extensions/Numbers+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Numbers+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley.xk on 2017/8/18. 6 | // 7 | 8 | import Foundation 9 | import CoreGraphics 10 | 11 | // Convert String to Numbers 12 | public extension String { 13 | 14 | /// convert string to int 15 | /// - Returns: returns 0 if failed 16 | func toInt() -> Int { 17 | return Int(self) ?? 0 18 | } 19 | 20 | /// convert string to double 21 | /// - Returns: 0 if failed 22 | func toDouble() -> Double { 23 | return Double(self) ?? 0 24 | } 25 | 26 | /// convert string to float 27 | /// - Returns: 0 if failed 28 | func toFloat() -> Float { 29 | return Float(self) ?? 0 30 | } 31 | 32 | /// convert string to float 33 | /// - Returns: 0 if failed 34 | func toCGFloat() -> CGFloat { 35 | return CGFloat(toDouble()) 36 | } 37 | } 38 | 39 | // Convert Float to String 40 | public extension Float { 41 | 42 | // 返回指定小数位数的字符串 43 | func toString(decimals: Int) -> String { 44 | return String(format: "%.\(decimals)f", self) 45 | } 46 | 47 | // 返回指定格式的字符串 48 | func toString(format: String) -> String { 49 | return String(format: format, self) 50 | } 51 | } 52 | 53 | // Convert Double to String 54 | public extension Double { 55 | // 返回指定小数位数的字符串 56 | func toString(decimals: Int) -> String { 57 | return String(format: "%.\(decimals)f", self) 58 | } 59 | // 返回指定格式的字符串 60 | func toString(format: String) -> String { 61 | return String(format: format, self) 62 | } 63 | } 64 | 65 | public extension Int { 66 | 67 | // 返回指定格式的字符串 68 | func toString(format: String? = nil) -> String { 69 | if let format = format { 70 | return String(format: format, self) 71 | } else { 72 | return "\(self)" 73 | } 74 | } 75 | 76 | /// 返回当前值是否是偶数 77 | var isEven: Bool { 78 | return self % 2 == 0 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Comet/Extensions/String+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley on 2016/11/8. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - Bridge 12 | public extension String { 13 | 14 | /// 返回桥接版的 NSString 15 | var ns: NSString { 16 | return self as NSString 17 | } 18 | } 19 | 20 | // MARK: - Urils 21 | public extension String { 22 | /// 给空值提供默认替换值, 如果 string 为空字符串,则使用指定的默认值替换 23 | /// 24 | /// - Parameter value: 指定用来替换的值 25 | func emptyDefault(_ value: String) -> String { 26 | return self.isEmpty ? value : self 27 | } 28 | 29 | /// 判断 String 是否不为空 30 | var notEmpty: Bool { 31 | return !isEmpty 32 | } 33 | } 34 | 35 | // MARK: - 拼音 36 | 37 | public extension String { 38 | 39 | /// 拼音的类型 40 | enum PinyinType { 41 | case normal // 默认类型,不带声调 42 | case withTone // 带声调的拼音 43 | case firstLetter // 拼音首字母 44 | } 45 | 46 | func pinyin(_ type: PinyinType = .normal) -> String { 47 | switch type { 48 | case .normal: 49 | return normalPinyin() 50 | case .withTone: 51 | return pinyinWithTone() 52 | case .firstLetter: 53 | return pinyinFirstLetter() 54 | } 55 | } 56 | 57 | private func pinyinWithTone() -> String { 58 | //转换为带声调的拼音 59 | let nameRef = CFStringCreateMutableCopy(nil, 0, self as CFString) 60 | CFStringTransform(nameRef, nil, kCFStringTransformMandarinLatin, false) 61 | return nameRef! as String 62 | } 63 | 64 | private func normalPinyin() -> String { 65 | //去除声调 66 | let nameRef = CFStringCreateMutableCopy(nil, 0, self as CFString) 67 | CFStringTransform(nameRef, nil, kCFStringTransformMandarinLatin, false) 68 | CFStringTransform(nameRef, nil, kCFStringTransformStripDiacritics, false) 69 | return nameRef! as String 70 | } 71 | 72 | private func pinyinFirstLetter() -> String { 73 | let pinyinString = pinyin() as NSString 74 | return pinyinString.substring(to: 1) 75 | } 76 | } 77 | 78 | // MARK: - Base64 79 | public extension String { 80 | 81 | var base64Decode: String? { 82 | 83 | if let data = Data(base64Encoded: self) { 84 | return String(data: data, encoding: .utf8) 85 | } 86 | return nil 87 | } 88 | 89 | var base64Encode: String? { 90 | if let data = self.data(using: .utf8) { 91 | return data.base64EncodedString() 92 | } 93 | return nil 94 | } 95 | } 96 | 97 | // MARK: - RegEx 98 | public extension String { 99 | /// 常用正则表达式 100 | // 邮箱 101 | var regex_email: String { 102 | return "\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*" 103 | } 104 | 105 | // 电话号码 106 | var regex_phone: String { 107 | return "^(([+])\\d{1,4})*(\\d{3,4})*\\d{7,8}(\\d{1,4})*$" 108 | } 109 | 110 | // 手机号码 111 | var regex_mobile: String { 112 | return "^(([+])\\d{1,4})*1[0-9][0-9]\\d{8}$" 113 | } 114 | 115 | /// 判断是否匹配正则表达式 116 | func match(regex: String) -> Bool { 117 | let predicate = NSPredicate(format: "SELF MATCHES %@", regex) 118 | return predicate.evaluate(with: self) 119 | } 120 | 121 | /// 判断字符串是否是邮箱 122 | var isEmail: Bool { 123 | return self.match(regex: regex_email) 124 | } 125 | 126 | /// 判断是否是电话号码 127 | var isPhone: Bool { 128 | return self.match(regex: regex_phone) 129 | } 130 | 131 | /// 判断是否是手机号码 132 | var isMobile: Bool { 133 | return self.match(regex: regex_mobile) 134 | } 135 | 136 | /// 同时验证电话和手机 137 | var isPhoneOrMobile: Bool { 138 | return isPhone || isMobile 139 | } 140 | } 141 | 142 | // MARK: - Bounding Rect 143 | public extension String { 144 | 145 | /// 计算字符串在指定高度下的宽度值 146 | /// - Parameters: 147 | /// - height: 指定的字符串高度 148 | /// - font: 字符串使用的字体 149 | /// - options: 字符串绘制选项 150 | /// - attributes: 字符串富文本属性,如果 attributes 中指定了 font,则忽略前面的 font 参数 151 | func limitedWidth( 152 | to height: CGFloat, 153 | font: UIFont, 154 | options: NSStringDrawingOptions = .preset, 155 | attributes: [NSAttributedString.Key : Any]? = nil 156 | ) -> CGFloat { 157 | let size = CGSize(width: CGFloat.greatestFiniteMagnitude, height: height) 158 | return self.limitedSize(to: size, font: font, options: options, attributes: attributes).width 159 | } 160 | 161 | /// 计算字符串在指定宽度下的高度值 162 | /// - Parameters: 163 | /// - width: 指定的字符串宽度 164 | /// - font: 字符串使用的字体 165 | /// - options: 字符串绘制选项 166 | /// - attributes: 字符串富文本属性,如果 attributes 中指定了 font,则忽略前面的 font 参数 167 | func limitedHeight( 168 | to width: CGFloat, 169 | font: UIFont, 170 | options: NSStringDrawingOptions = .preset, 171 | attributes: [NSAttributedString.Key : Any]? = nil 172 | ) -> CGFloat { 173 | let size = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude) 174 | return self.limitedSize(to: size, font: font, options: options, attributes: attributes).height 175 | } 176 | 177 | // 字符串尺寸计算,封装了 boundingRect 方法 178 | private func limitedSize( 179 | to size: CGSize, 180 | font: UIFont, 181 | options: NSStringDrawingOptions, 182 | attributes: [NSAttributedString.Key : Any]? = nil 183 | ) -> CGSize { 184 | let string = self as NSString 185 | var attributes = attributes ?? [:] 186 | if attributes[.font] == nil { 187 | attributes[.font] = font 188 | } 189 | let rect = string.boundingRect( 190 | with: size, 191 | options: options, 192 | attributes: attributes, 193 | context: nil 194 | ) 195 | return rect.size 196 | } 197 | } 198 | 199 | public extension NSStringDrawingOptions { 200 | // 预设选项 201 | static let preset: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesDeviceMetrics, .usesFontLeading] 202 | } 203 | -------------------------------------------------------------------------------- /Comet/Extensions/Text+Localize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Text+Localize.swift 3 | // Comet 4 | // 5 | // Created by Harley.xk on 2017/5/8. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | /// 给常用控件扩展功能: 13 | /// 通过 localizedKey 属性,可以给常用控件在 IB 中直接设置多语言的 string key 14 | 15 | 16 | extension UILabel { 17 | @IBInspectable var localizedKey: String? { 18 | set { 19 | guard let newValue = newValue else { return } 20 | text = NSLocalizedString(newValue, comment: "") 21 | } 22 | get { return text } 23 | } 24 | } 25 | 26 | extension UIButton { 27 | @IBInspectable var localizedKey: String? { 28 | set { 29 | guard let newValue = newValue else { return } 30 | setTitle(NSLocalizedString(newValue, comment: ""), for: .normal) 31 | } 32 | get { return titleLabel?.text } 33 | } 34 | } 35 | 36 | extension UITextField { 37 | @IBInspectable var localizedPlaceholderKey: String? { 38 | set { 39 | guard let newValue = newValue else { return } 40 | placeholder = NSLocalizedString(newValue, comment: "") 41 | } 42 | get { return placeholder } 43 | } 44 | } 45 | 46 | extension UINavigationItem { 47 | @IBInspectable var localizedTitleKey: String? { 48 | set { 49 | guard let newValue = newValue else { return } 50 | title = NSLocalizedString(newValue, comment: "") 51 | } 52 | get { return title } 53 | } 54 | } 55 | 56 | extension UIBarItem { 57 | @IBInspectable var localizedTitleKey: String? { 58 | set { 59 | guard let newValue = newValue else { return } 60 | title = NSLocalizedString(newValue, comment: "") 61 | } 62 | get { return title } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Comet/Extensions/UIAlertController+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley on 2016/11/8. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIAlertController { 12 | 13 | func addAction(title: String?, style: UIAlertAction.Style, handler: ((UIAlertAction)->())? = nil) { 14 | let action = UIAlertAction(title: title, style: style, handler: handler) 15 | self.addAction(action) 16 | } 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /Comet/Extensions/UICollectionView+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley.xk on 2018/2/12. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension UICollectionView { 12 | 13 | /// 快速注册可复用的 cell,使用类型名称作为 ReuseIdentifier 14 | public func register(cell: Cell.Type) { 15 | let identifier = String(describing: Cell.self) 16 | register(cell, forCellWithReuseIdentifier: identifier) 17 | } 18 | 19 | /// 快速创建可重用的 cell,使用 cell 的类名作为 ReuseIdentifier 20 | public func dequeueReusableCell(for indexPath: IndexPath) -> Cell { 21 | let identifier = String(describing: Cell.self) 22 | return dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! Cell 23 | } 24 | 25 | /// 快速注册可复用的 SupplementaryView,使用类型名称作为 ReuseIdentifier 26 | public func registerSupplementaryView(_ type: V.Type = V.self, ofKind kind: String) { 27 | let identifier = String(describing: V.self) 28 | register(V.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: identifier) 29 | } 30 | 31 | /// 快速获取可复用的 SupplementaryView,使用类型名称作为 ReuseIdentifier 32 | public func dequeueSupplementaryView(_ type: V.Type = V.self, ofKind kind: String, for indexPath: IndexPath) -> V { 33 | let identifier = String(describing: V.self) 34 | return dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: identifier, for: indexPath) as! V 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Comet/Extensions/UIColor+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley on 2016/11/8. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIColor { 12 | 13 | /// 使用16进制字符串创建颜色 14 | /// 15 | /// - Parameter hexString: 16进制字符串,支持 0XFFFFFF/#FFFFFF/FFFFFF 三种格式 16 | /// - Attention 在代码中创建颜色时,首选推荐使用更可靠高效的 Xcode 新特性 - Color Literal 17 | convenience init(hex: String, alpha: CGFloat = 1) { 18 | 19 | let characterSet = CharacterSet.whitespacesAndNewlines 20 | var string = hex.trimmingCharacters(in: characterSet).uppercased() 21 | 22 | if string.count < 6 { 23 | fatalError("unsupported color format!") 24 | } 25 | 26 | if string.hasPrefix("0X") { 27 | let ns = string as NSString 28 | string = ns.substring(from: 2) 29 | } 30 | if string.hasPrefix("#") { 31 | let ns = string as NSString 32 | string = ns.substring(from: 1) 33 | } 34 | 35 | if string.count != 6 { 36 | fatalError("unsupported color format!") 37 | } 38 | 39 | let colorString = string as NSString 40 | var range = NSMakeRange(0, 2) 41 | 42 | let rString = colorString .substring(with: range) 43 | 44 | range.location += 2 45 | let gString = colorString.substring(with: range) 46 | 47 | range.location += 2 48 | let bString = colorString.substring(with: range) 49 | 50 | var r: UInt32 = 0 51 | var g: UInt32 = 0 52 | var b: UInt32 = 0 53 | Scanner(string: rString).scanHexInt32(&r) 54 | Scanner(string: gString).scanHexInt32(&g) 55 | Scanner(string: bString).scanHexInt32(&b) 56 | 57 | self.init(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: alpha) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Comet/Extensions/UIDevice+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDevice+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley.xk on 16/6/27. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | /// 定义一个空函数的别名,方便使用 13 | public typealias EmptyHandler = () -> () 14 | 15 | /** 16 | 扩展 UIDevice,以快速获取一些属性 17 | */ 18 | public extension UIDevice { 19 | 20 | /// 设备唯一识别码 21 | var uuid: String { 22 | return identifierForVendor?.uuidString ?? "" 23 | } 24 | 25 | /// 设备详细型号,例如:iPhone 1,2 etc... 26 | var modelName: String { 27 | var systemInfo = utsname() 28 | uname(&systemInfo) 29 | let machineMirror = Mirror(reflecting: systemInfo.machine) 30 | let identifier = machineMirror.children.reduce("") { identifier, element in 31 | guard let value = element.value as? Int8 , value != 0 else { return identifier } 32 | return identifier + String(UnicodeScalar(UInt8(value))) 33 | } 34 | return identifier 35 | } 36 | 37 | /// 发起电话呼叫 38 | /// 39 | /// - Parameters: 40 | /// - phone: 被叫方的电话号码 41 | /// - withConfirm: 是否弹出确认提示 42 | /// - throws: 不支持电话功能时抛出 CometError.phoneCallNotSupported 异常 43 | func makePhoneCall(to phone: String, withConfirm: Bool = true) throws { 44 | let typeString = withConfirm ? "tel" : "telprompt" 45 | guard let callURL = URL(string: typeString + "://" + phone), 46 | UIApplication.shared.canOpenURL(callURL) else { 47 | throw CometError.phoneCallNotSupported 48 | } 49 | UIApplication.shared.open(callURL) 50 | } 51 | 52 | /// 检测设备是否越狱 53 | /// - Note: 通过检测是否存在 cydia 应用以及超越沙盒的访问权限来判断 54 | func jailbroken() -> Bool { 55 | 56 | // 存在 cydia 应用 57 | if let cydiaURL = URL(string: "cydia://package/com.example.package"), 58 | UIApplication.shared.canOpenURL(cydiaURL) { 59 | return true 60 | } 61 | 62 | // 越狱后可能存在或者访问到的路径 63 | let jailbrokenAccessable = [ 64 | "/Applications/Cydia.app", 65 | "/Library/MobileSubstrate/MobileSubstrate.dylib", 66 | "/var/lib/cydia", 67 | "/User/Applications/", 68 | "/bin/bash", 69 | "/usr/sbin/sshd", 70 | "/etc/apt" 71 | ] 72 | 73 | for path in jailbrokenAccessable { 74 | if FileManager.default.fileExists(atPath: path) { 75 | return true 76 | } 77 | } 78 | 79 | return false 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Comet/Extensions/UINavigationBar+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationBar+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley on 2016/11/8. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UINavigationBar { 13 | 14 | /// 定义导航栏的全局颜色 15 | class func customizeAppearenceColorWith(barTint: UIColor, foreground: UIColor) { 16 | 17 | self.appearance().barTintColor = barTint 18 | self.appearance().tintColor = foreground 19 | self.appearance().textColor = foreground 20 | } 21 | 22 | /// 设置导航栏的文字颜色 23 | var textColor: UIColor? { 24 | get { 25 | return self.titleTextAttributes?[.foregroundColor] as? UIColor 26 | } 27 | set { 28 | var attributes = self.titleTextAttributes ?? [NSAttributedString.Key : Any]() 29 | attributes[.foregroundColor] = newValue 30 | self.titleTextAttributes = attributes 31 | } 32 | } 33 | 34 | /// 设置导航栏标题的字体 35 | var titleFont: UIFont? { 36 | get { 37 | return self.titleTextAttributes?[.font] as? UIFont 38 | } 39 | set { 40 | var attributes = self.titleTextAttributes ?? [NSAttributedString.Key : Any]() 41 | attributes[.font] = newValue 42 | self.titleTextAttributes = attributes 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Comet/Extensions/UINavigationController+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+Comet.swift 3 | // Pods 4 | // 5 | // Created by Harley on 2017/2/9. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UINavigationController { 13 | /// 替换当前导航控制器栈顶的视图 14 | open func replaceTop(with viewController: UIViewController, animated: Bool = true) { 15 | var vcs = self.viewControllers 16 | vcs.removeLast() 17 | vcs.append(viewController) 18 | setViewControllers(vcs, animated: animated) 19 | } 20 | 21 | /// 简化 push 函数 22 | open func push(_ viewController: UIViewController, animated: Bool = true) { 23 | pushViewController(viewController, animated: animated) 24 | } 25 | 26 | /// 简化 pop 函数,且不强制要求接收返回值 27 | @discardableResult 28 | open func pop(animated: Bool = true) -> UIViewController? { 29 | let poped = popViewController(animated: animated) 30 | return poped 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Comet/Extensions/UIResponder+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIResponder+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley on 2016/11/8. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UIResponder { 13 | 14 | /// 解除任何可能存在的第一响应者 15 | @discardableResult class func resignAnyFirstResponder() -> Bool { 16 | return UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 17 | } 18 | 19 | /// 用于在 IB 中指定移除第一响应者事件 20 | @IBAction func autoResignFirstResponder() { 21 | resignFirstResponder() 22 | } 23 | 24 | /// 用于在 IB 中指定第一响应者事件 25 | @IBAction func autoBecomFirstResponder() { 26 | becomeFirstResponder() 27 | } 28 | } 29 | 30 | extension UIViewController { 31 | 32 | /// 检查当前视图控制器是否包含第一响应者,会遍历响应链以及子视图检查 33 | public var containsFirstResponder: Bool { 34 | // 从子视图中找到第一响应者的概率较大,所以先检查子视图 35 | if view.containsFirstResponder { 36 | return true 37 | } 38 | // 检查响应链 39 | var responder: UIResponder? = self 40 | while responder != nil { 41 | if responder!.isFirstResponder { 42 | return true 43 | } 44 | responder = responder?.next 45 | } 46 | return false 47 | } 48 | } 49 | 50 | extension UIView { 51 | 52 | /// 检查当前视图是否包含第一响应者,会遍历所有子视图检查 53 | public var containsFirstResponder: Bool { 54 | if isFirstResponder { 55 | return true 56 | } 57 | for subView in subviews { 58 | let result = subView.isFirstResponder 59 | if result { 60 | return true 61 | } 62 | } 63 | return false 64 | } 65 | } 66 | 67 | 68 | -------------------------------------------------------------------------------- /Comet/Extensions/UIScrollView+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+Comet.swift 3 | // Pods 4 | // 5 | // Created by Harley on 2017/1/22. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UIScrollView { 13 | 14 | /// 滚动到 ScrollView 顶部 15 | /// 16 | /// - Parameter animated: 是否显示动画,默认显示 17 | func scrollToTop(animated: Bool = true) { 18 | let x = contentOffset.x + contentInset.left + contentInset.right 19 | let destination = CGRect(x: x, y: 0, width: 1, height: 1) 20 | scrollRectToVisible(destination, animated: animated) 21 | } 22 | 23 | /// 滚动到 ScrollView 底部 24 | /// 25 | /// - Parameter animated: 是否显示动画,默认显示 26 | func scrollToBottom(animated: Bool = true) { 27 | let x = contentOffset.x + contentInset.left + contentInset.right 28 | let destination = CGRect(x: x, y: contentSize.height - 1, width: 1, height: 1) 29 | scrollRectToVisible(destination, animated: animated) 30 | } 31 | 32 | /// 滚动到 ScrollView 左边 33 | /// 34 | /// - Parameter animated: 是否显示动画,默认显示 35 | func scrollToLeft(animated: Bool = true) { 36 | let y = contentOffset.y + contentInset.top + contentInset.bottom 37 | let destination = CGRect(x: 0, y: y, width: 1, height: 1) 38 | scrollRectToVisible(destination, animated: animated) 39 | } 40 | 41 | /// 滚动到 ScrollView 底部 42 | /// 43 | /// - Parameter animated: 是否显示动画,默认显示 44 | func scrollToRight(animated: Bool = true) { 45 | let y = contentOffset.y + contentInset.top + contentInset.bottom 46 | let destination = CGRect(x: contentSize.width - 1, y: y, width: 1, height: 1) 47 | scrollRectToVisible(destination, animated: animated) 48 | } 49 | 50 | 51 | /// 快速设置 ContentInset 52 | func addContentInset(left: CGFloat = 0, top: CGFloat = 0, right: CGFloat = 0, bottom: CGFloat = 0) { 53 | var inset = contentInset 54 | inset.left += left 55 | inset.top += top 56 | inset.right += right 57 | inset.bottom += bottom 58 | contentInset = inset 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /Comet/Extensions/UITableView+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley.xk on 2018/2/12. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension UITableView { 12 | 13 | /// 快速注册可重用的 cell,使用 cell 的类名作为 ReuseIdentifier 14 | public func register(cell classType: Cell.Type) { 15 | let identifier = String(describing: Cell.self) 16 | register(classType, forCellReuseIdentifier: identifier) 17 | } 18 | 19 | /// 快速创建可重用的 cell,使用 cell 的类名作为 ReuseIdentifier 20 | public func dequeueReusableCell(for indexPath: IndexPath) -> Cell { 21 | let identifier = String(describing: Cell.self) 22 | return dequeueReusableCell(withIdentifier: identifier, for: indexPath) as! Cell 23 | } 24 | 25 | /// 快速注册可重用的 HeaderFooterView,使用 HeaderFooterView 的类名作为 ReuseIdentifier 26 | public func register(headerFooterView classType: View.Type) { 27 | let identifier = String(describing: View.self) 28 | register(classType, forHeaderFooterViewReuseIdentifier: identifier) 29 | } 30 | 31 | /// 快速创建可重用的 HeaderFooterView,使用 HeaderFooterView 的类名作为 ReuseIdentifier 32 | public func dequeueReusableHeaderFooterView() -> View { 33 | let identifier = String(describing: View.self) 34 | return dequeueReusableHeaderFooterView(withIdentifier: identifier) as! View 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Comet/Extensions/UITextField+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley on 2016/11/8. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UITextField { 13 | 14 | /// 设置 UITextField 的 placeholder 颜色 15 | func setPlaceholderColor(_ color: UIColor) { 16 | 17 | if let string = self.placeholder { 18 | if !string.isEmpty { 19 | let attributedString = NSAttributedString(string: string, attributes: [.foregroundColor : color]) 20 | self.attributedPlaceholder = attributedString 21 | } 22 | } else if let string = self.attributedPlaceholder { 23 | if string.length > 0 { 24 | let attributedString = NSMutableAttributedString(attributedString: string) 25 | attributedString.addAttribute(.foregroundColor, value: color, range: NSMakeRange(0, string.length)) 26 | self.attributedPlaceholder = attributedString 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Comet/Extensions/UIVIew+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIVIew+Comet.swift 3 | // Comet 4 | // 5 | // Created by Harley on 2016/11/8. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UIView { 13 | /// 快速扩展,获得在 IB 中直接编辑圆角的能力 14 | @IBInspectable var cornerRadius: CGFloat { 15 | get { 16 | return self.layer.cornerRadius 17 | } 18 | set { 19 | self.layer.cornerRadius = newValue 20 | } 21 | } 22 | 23 | /// 快速扩展,获得在 IB 中直接编辑边框宽度的能力 24 | @IBInspectable var borderWidth: CGFloat { 25 | get { 26 | return self.layer.borderWidth 27 | } 28 | set { 29 | self.layer.borderWidth = newValue 30 | } 31 | } 32 | 33 | /// 快速扩展,获得在 IB 中直接编辑边框颜色的能力 34 | @IBInspectable var borderColor: UIColor? { 35 | get { 36 | if let color = self.layer.borderColor { 37 | return UIColor(cgColor: color) 38 | } 39 | return nil 40 | } 41 | set { 42 | self.layer.borderColor = newValue?.cgColor 43 | } 44 | } 45 | } 46 | 47 | 48 | public extension Bundle { 49 | /// 从 xib 文件创建视图 50 | /// 51 | /// - Parameters: 52 | /// - name: xib 文件名,默认为指定视图类名 53 | func createView(_ name: String? = nil, owner: Any? = nil, options: [UINib.OptionsKey : Any]? = nil) -> T { 54 | let nibName = name ?? T.typeName 55 | let view = loadNibNamed(nibName, owner: owner, options: options)![0] as! T 56 | return view 57 | } 58 | } 59 | 60 | public extension UIView { 61 | /// 从 xib 文件创建视图 62 | /// 63 | /// - Parameters: 64 | /// - nibName: xib 文件名,默认为指定视图类名 65 | /// - bundle: xib 所在的 bundle,默认为 main bundle 66 | class func createFromXib(_ nibName: String? = nil, owner: Any? = nil, options: [UINib.OptionsKey : Any]? = nil, in bundle: Bundle = Bundle.main) -> Self { 67 | return bundle.createView(nibName ?? typeName, owner: owner, options: options) 68 | } 69 | } 70 | 71 | 72 | public extension UIView { 73 | 74 | /// 获取视图快照并转换为图片 75 | /// 76 | /// - Attention: 77 | /// - 常规截图方式无法截取到特殊层级的图像数据,比如 AVSampleBufferDisplayLayer 78 | func snapshotImage(afterScreenUpdates: Bool = false) -> UIImage? { 79 | let size = bounds.size 80 | let frame = CGRect(origin: .zero, size: size) 81 | if #available(iOS 10.0, *) { 82 | return UIGraphicsImageRenderer(size: size).image { (context) in 83 | drawHierarchy(in: frame, afterScreenUpdates: afterScreenUpdates) 84 | } 85 | } else { 86 | UIGraphicsBeginImageContextWithOptions(size, true, contentScaleFactor) 87 | drawHierarchy(in: frame, afterScreenUpdates: afterScreenUpdates) 88 | let image = UIGraphicsGetImageFromCurrentImageContext() 89 | UIGraphicsEndImageContext() 90 | return image 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Comet/Extensions/UIViewController+Comet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Comet.wwift 3 | // Comet 4 | // 5 | // Created by Harley on 2016/11/8. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UIStoryboard { 13 | 14 | /// 获取 Main Storyboard 15 | class var main: UIStoryboard { 16 | return UIStoryboard(name: "Main", bundle: nil) 17 | } 18 | 19 | /// 根据名称从 MainBundle 中创建 Storyboard 20 | convenience init(_ name: String) { 21 | self.init(name: name, bundle: nil) 22 | } 23 | 24 | /// 从 sb 创建视图控制器 25 | /// identifier 为空时默认使用类名 26 | func create(identifier: String? = nil) -> T { 27 | let id = identifier ?? T.typeName 28 | return self.instantiateViewController(withIdentifier: id) as! T 29 | } 30 | 31 | /// 创建当前 sb 入口视图控制器的实例 32 | func createInitial() -> T { 33 | return instantiateInitialViewController() as! T 34 | } 35 | } 36 | 37 | public extension UIViewController { 38 | 39 | /// 从 Storyboard 实例化视图控制器 40 | /// 41 | /// - Parameters: 42 | /// - storyboard: 视图控制器所在的故事版,默认为 main 43 | /// - identifier: 视图控制器在 Storyboard 中的 identifier,不传默认为类名 44 | /// - Description: 45 | /// 建议对 UIStoryboard 扩展来获得常用的故事版对象,参见 UIStoryboard.main 的实现 46 | class func createFromStoryboard(_ storyboard: UIStoryboard = .main, identifier: String? = nil) -> Self { 47 | return storyboard.create(identifier: identifier ?? typeName) 48 | } 49 | 50 | /// 从 Storyboard 实例化入口视图控制器 51 | /// 52 | /// - Parameters: 53 | /// - storyboard: 视图控制器所在的故事版,默认为 main 54 | class func createInitial(from storyboard: UIStoryboard = .main) -> Self { 55 | return storyboard.createInitial() 56 | } 57 | } 58 | 59 | 60 | public extension UIViewController { 61 | 62 | /// 从 Xib 文件创建视图控制器,nibName 为空时默认使用类名 63 | class func createFromXib(_ nibName: String? = nil, bundle: Bundle? = nil) -> Self { 64 | let name = nibName ?? typeName 65 | return self.init(nibName: name, bundle: bundle) 66 | } 67 | } 68 | 69 | public extension NSObject { 70 | /// 获取类名,不包含完整的模块名称 71 | class var typeName: String { 72 | return String(describing: self) 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /Example/Comet.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Comet.xcodeproj/xcshareddata/xcschemes/Comet-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 67 | 69 | 75 | 76 | 77 | 78 | 84 | 86 | 92 | 93 | 94 | 95 | 97 | 98 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /Example/Comet.xcodeproj/xcuserdata/Harley-xk.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Comet-Example.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 607FACCF1AFB9204008FA782 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Comet.xcodeproj/xcuserdata/Harley.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SuppressBuildableAutocreation 6 | 7 | 607FACCF1AFB9204008FA782 8 | 9 | primary 10 | 11 | 12 | 607FACE41AFB9204008FA782 13 | 14 | primary 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Example/Comet.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Comet.xcworkspace/xcuserdata/Harley-xk.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Example/Comet.xcworkspace/xcuserdata/Harley.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Example/Comet/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Comet 4 | // 5 | // Created by Harley.xk on 11/07/2016. 6 | // Copyright (c) 2016 Harley.xk. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /Example/Comet/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/Comet/CoreSample.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 49 | 55 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/Comet/Exttensions.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 35 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 36 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 37 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 38 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 39 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 40 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 41 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 42 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 43 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Example/Comet/GrouperSampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GrouperSampleViewController.swift 3 | // Comet_Example 4 | // 5 | // Created by Harley-xk on 2019/4/11. 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Comet 11 | import SwiftRandom 12 | 13 | let Rooms = ["101", "102", "103", "104", "201", "202", "301", "302"] 14 | 15 | struct Student { 16 | var name: String 17 | var grade: Int 18 | var room: String 19 | var age: Int 20 | 21 | static func random() -> Student { 22 | let student = Student(name: Randoms.randomFakeNameAndEnglishHonorific(), 23 | grade: Int.random(in: 0 ... 100), 24 | room: Rooms.randomElement()!, 25 | age: Int.random(in: 1 ... 100)) 26 | return student 27 | } 28 | } 29 | 30 | 31 | class GrouperSampleViewController: UIViewController { 32 | 33 | @IBOutlet weak var tableView: UITableView! 34 | 35 | var students: [Student] = [] 36 | 37 | var studentGroups: CollectionGroups<[Student], String>? 38 | 39 | override func viewDidLoad() { 40 | super.viewDidLoad() 41 | 42 | // Do any additional setup after loading the view. 43 | for _ in 0 ..< 50 { 44 | students.append(.random()) 45 | } 46 | groupByRoom() 47 | } 48 | 49 | @IBAction func changeGroupType(_ sender: UISegmentedControl) { 50 | 51 | let index = sender.selectedSegmentIndex 52 | 53 | switch index { 54 | case 0: groupByRoom() 55 | case 1: groupByAge() 56 | case 2: groupByGrade() 57 | default: break 58 | } 59 | 60 | tableView.reloadData() 61 | } 62 | 63 | private func groupByRoom() { 64 | studentGroups = CollectionGroups(group: students, by: \.room, sorted: {$0.room < $1.room}) 65 | } 66 | 67 | private func groupByAge() { 68 | studentGroups = CollectionGroups(group: students, by: { "\($0.age)岁" }, sorted: {$0.age < $1.age}) 69 | } 70 | 71 | private func groupByGrade() { 72 | studentGroups = CollectionGroups(group: students, by: { (student) -> String in 73 | if student.grade < 60 { 74 | return "60分以下 - 不及格" 75 | } else if student.grade < 70 { 76 | return "60~69分 - 差" 77 | } else if student.grade < 80 { 78 | return "70~79分 - 中等" 79 | } else if student.grade < 90 { 80 | return "80~89分 - 良好" 81 | } else if student.grade <= 100 { 82 | return "90分以上 - 优秀" 83 | } else { 84 | return "作弊" 85 | } 86 | }, sorted: {$0.grade < $1.grade}) 87 | } 88 | 89 | var groups: [Group] { 90 | return studentGroups?.sortedGroups ?? [] 91 | } 92 | } 93 | 94 | extension GrouperSampleViewController: UITableViewDataSource { 95 | 96 | func numberOfSections(in tableView: UITableView) -> Int { 97 | return groups.count 98 | } 99 | 100 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 101 | return groups[section].index 102 | } 103 | 104 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 105 | return groups[section].elements.count 106 | } 107 | 108 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 109 | let student = groups[indexPath.section].elements[indexPath.row] 110 | let cell = tableView.dequeueReusableCell(for: indexPath) as StudentInfoCell 111 | cell.nameLabel.text = student.name 112 | cell.ageLabel.text = "年龄: \(student.age)" 113 | cell.gradeLabel.text = "成绩: \(student.grade)" 114 | cell.roomLabel.text = "宿舍: \(student.room)" 115 | return cell 116 | } 117 | } 118 | 119 | class StudentInfoCell: UITableViewCell { 120 | 121 | @IBOutlet weak var nameLabel: UILabel! 122 | @IBOutlet weak var ageLabel: UILabel! 123 | @IBOutlet weak var roomLabel: UILabel! 124 | @IBOutlet weak var gradeLabel: UILabel! 125 | } 126 | -------------------------------------------------------------------------------- /Example/Comet/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Example/Comet/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/Comet/Playground.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import UIKit 4 | import Comet 5 | 6 | var str = "Hello, playground" 7 | 8 | let version = Utils.appVersion 9 | Utils.appBuild 10 | 11 | let model = Utils.deviceUUID 12 | 13 | Utils.deviceModel 14 | 15 | let path = Path.applicationSupport().resource("data.Sqlite").string 16 | 17 | let obj = NSObject() 18 | 19 | class Task: TaskProtocol { 20 | 21 | func cancel() { 22 | 23 | } 24 | } 25 | 26 | let task = Task() 27 | obj.record(task: task) 28 | 29 | let color = UIColor(hex: "0xaaddff") 30 | 31 | let action = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert) 32 | 33 | let now = Date() 34 | 35 | TimeZone(identifier: "Asia/Shanghai") 36 | 37 | now.add(.day(1)).weekday 38 | 39 | let date2 = now.set(.hour(0)) 40 | date2.string() 41 | date2.unit(.era) 42 | 43 | now.withoutTime.string() 44 | date2.withoutTime 45 | 46 | now.weekday 47 | 48 | let nextMonth = Date() + .month(1) 49 | let yestoday = now - .day(1) 50 | 51 | let duration = DateInterval(start: date2, end: now).duration 52 | 53 | let even = (-4).isEven 54 | print() 55 | 56 | 57 | //Utils.call("17768061343") 58 | -------------------------------------------------------------------------------- /Example/Comet/Playground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Example/Comet/ScrollViewExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewExtension.swift 3 | // Comet 4 | // 5 | // Created by Harley on 2017/1/22. 6 | // Copyright © 2017年 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ScrollViewExtension: UIViewController { 12 | 13 | @IBOutlet weak var scrollView: UIScrollView! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | // Do any additional setup after loading the view. 19 | } 20 | 21 | override func didReceiveMemoryWarning() { 22 | super.didReceiveMemoryWarning() 23 | // Dispose of any resources that can be recreated. 24 | } 25 | 26 | 27 | @IBAction func leftAction(_ sender: Any) { 28 | scrollView.scrollToLeft() 29 | } 30 | 31 | @IBAction func rightAction(_ sender: Any) { 32 | scrollView.scrollToRight() 33 | } 34 | 35 | @IBAction func topAction(_ sender: Any) { 36 | scrollView.scrollToTop() 37 | } 38 | 39 | @IBAction func bottomAction(_ sender: Any) { 40 | scrollView.scrollToBottom() 41 | } 42 | /* 43 | // MARK: - Navigation 44 | 45 | // In a storyboard-based application, you will often want to do a little preparation before navigation 46 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 47 | // Get the new view controller using segue.destinationViewController. 48 | // Pass the selected object to the new view controller. 49 | } 50 | */ 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Example/Comet/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Comet 4 | // 5 | // Created by Harley.xk on 11/07/2016. 6 | // Copyright (c) 2016 Harley.xk. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Comet 11 | 12 | class CustomView: UIView { 13 | 14 | } 15 | 16 | class ViewController: UITableViewController { 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | // Do any additional setup after loading the view, typically from a nib. 21 | 22 | let _ = UIStoryboard.main.create() as ViewController 23 | 24 | let vc = ViewController.createFromStoryboard(.main) 25 | print(vc) 26 | 27 | DispatchQueue.global().asyncAfter(delay: 2, execute:{ 28 | print("延迟两秒执行") 29 | let date = Date(string: "2013-10-24 12:24:56", timeZone: .utc)! 30 | let houtian = date.add(.day(2)) 31 | print(houtian) 32 | 33 | let nextMonth = Date() + .month(1) 34 | print(nextMonth.dateString()) 35 | }) 36 | } 37 | 38 | override func didReceiveMemoryWarning() { 39 | super.didReceiveMemoryWarning() 40 | // Dispose of any resources that can be recreated. 41 | } 42 | 43 | // MARK: - Samples 44 | func snapshotSample() { 45 | let image = navigationController?.view.snapshotImage() 46 | let imageView = UIImageView(image: image) 47 | view.addSubview(imageView) 48 | imageView.borderColor = .black 49 | imageView.borderWidth = 1 50 | imageView.transform = CGAffineTransform(scaleX: 0.85, y: 0.85) 51 | 52 | UIView.animate(withDuration: 1, delay: 1, animations: { 53 | imageView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) 54 | }) { (_) in 55 | imageView.removeFromSuperview() 56 | } 57 | } 58 | 59 | } 60 | 61 | extension ViewController { 62 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 63 | if indexPath.section == 1, indexPath.row == 3 { 64 | snapshotSample() 65 | } 66 | tableView.deselectRow(at: indexPath, animated: true) 67 | } 68 | } 69 | 70 | class LoginViewController: UIViewController { 71 | 72 | } 73 | 74 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | platform :ios, '10.0' 3 | 4 | target 'Comet_Example' do 5 | pod 'Comet', :path => '../' 6 | 7 | pod 'SwiftRandom', :git => 'https://github.com/thellimist/SwiftRandom.git' 8 | 9 | end 10 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Comet (2.0.0) 3 | - SwiftRandom (1.0.0) 4 | 5 | DEPENDENCIES: 6 | - Comet (from `../`) 7 | - SwiftRandom (from `https://github.com/thellimist/SwiftRandom.git`) 8 | 9 | EXTERNAL SOURCES: 10 | Comet: 11 | :path: "../" 12 | SwiftRandom: 13 | :git: https://github.com/thellimist/SwiftRandom.git 14 | 15 | CHECKOUT OPTIONS: 16 | SwiftRandom: 17 | :commit: 6d82b5edbdbefdd1f801ef6ed7e5397f20cc6216 18 | :git: https://github.com/thellimist/SwiftRandom.git 19 | 20 | SPEC CHECKSUMS: 21 | Comet: 244cf0f6a5620b30dcb3a7bbf194eaeb1fbc1648 22 | SwiftRandom: 437275c63500ddf5a565cbd56996fb23f12cfc3d 23 | 24 | PODFILE CHECKSUM: 2d6064e85fa381db6da8c3cfe1734833d792dd7a 25 | 26 | COCOAPODS: 1.9.3 27 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/Comet.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Comet", 3 | "version": "2.0.0", 4 | "summary": "iOS 项目的 Swift 基础库,提供一些常用组件、便利方法等。", 5 | "description": "iOS 项目的 Swift 基础库,提供一些常用组件、便利方法等。支持 Swift 5.3", 6 | "homepage": "https://github.com/Harley-xk/Comet", 7 | "license": { 8 | "type": "MIT", 9 | "file": "LICENSE" 10 | }, 11 | "authors": { 12 | "Harley.xk": "harley.gb@foxmail.com" 13 | }, 14 | "source": { 15 | "git": "https://github.com/Harley-xk/Comet.git", 16 | "tag": "2.0.0" 17 | }, 18 | "platforms": { 19 | "ios": "10.0" 20 | }, 21 | "source_files": "Comet/**/*" 22 | } 23 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/SwiftRandom.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SwiftRandom", 3 | "version": "1.0.0", 4 | "summary": "A tiny generator of random data for swift", 5 | "description": "A tiny generator of fake/random data for swift", 6 | "homepage": "https://github.com/thellimist/SwiftRandom", 7 | "license": "MIT", 8 | "authors": { 9 | "thellimist": "mufuyil@gmail.com" 10 | }, 11 | "source": { 12 | "git": "https://github.com/thellimist/SwiftRandom.git", 13 | "tag": "1.0.0" 14 | }, 15 | "platforms": { 16 | "ios": "8.0" 17 | }, 18 | "requires_arc": true, 19 | "source_files": "SwiftRandom/Randoms.swift" 20 | } 21 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Comet (2.0.0) 3 | - SwiftRandom (1.0.0) 4 | 5 | DEPENDENCIES: 6 | - Comet (from `../`) 7 | - SwiftRandom (from `https://github.com/thellimist/SwiftRandom.git`) 8 | 9 | EXTERNAL SOURCES: 10 | Comet: 11 | :path: "../" 12 | SwiftRandom: 13 | :git: https://github.com/thellimist/SwiftRandom.git 14 | 15 | CHECKOUT OPTIONS: 16 | SwiftRandom: 17 | :commit: 6d82b5edbdbefdd1f801ef6ed7e5397f20cc6216 18 | :git: https://github.com/thellimist/SwiftRandom.git 19 | 20 | SPEC CHECKSUMS: 21 | Comet: 244cf0f6a5620b30dcb3a7bbf194eaeb1fbc1648 22 | SwiftRandom: 437275c63500ddf5a565cbd56996fb23f12cfc3d 23 | 24 | PODFILE CHECKSUM: 2d6064e85fa381db6da8c3cfe1734833d792dd7a 25 | 26 | COCOAPODS: 1.9.3 27 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/Harley-xk.xcuserdatad/xcschemes/Comet.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/Harley-xk.xcuserdatad/xcschemes/Pods-Comet_Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/Harley-xk.xcuserdatad/xcschemes/SwiftRandom.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/Harley-xk.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Comet.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 1 13 | 14 | Pods-Comet_Example.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 2 20 | 21 | SwiftRandom.xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 3 27 | 28 | 29 | SuppressBuildableAutocreation 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/Harley.xcuserdatad/xcschemes/Comet.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 66 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/Harley.xcuserdatad/xcschemes/Pods-Comet_Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 66 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/Harley.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Comet.xcscheme 8 | 9 | isShown 10 | 11 | 12 | Pods-Comet_Example.xcscheme 13 | 14 | isShown 15 | 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | D6F864A2D331382193C7ABDD75639BCB 21 | 22 | primary 23 | 24 | 25 | F616BF41154754BE261116E98B3AAB96 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Example/Pods/SwiftRandom/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Furkan Yilmaz 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 | 23 | -------------------------------------------------------------------------------- /Example/Pods/SwiftRandom/README.md: -------------------------------------------------------------------------------- 1 | SwiftRandom 2 | ============== 3 | 4 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/SwiftRandom.svg)](https://img.shields.io/cocoapods/v/SwiftRandom.svg) 6 | 7 | SwiftRandom is a tiny help suite for generating random data such as 8 | * Random human stuff like: names, gender, titles, tags, conversations 9 | * Random data types like: Int, CGFloat, Bool, UIColor, NSDate, NSURL, element in Array 10 | 11 | ## Usage 12 | 13 | ```swift 14 | func asExtension() { 15 | Int.random(2, 77) // Random between 2-77 16 | Int.random(2...77) 17 | Int32.random(13, 37) // Random between 13-37 18 | Int32.random(13...37) 19 | Double.random() 20 | Float.random(3.2, 4.5) // Random between 3.2-4.5 21 | CGFloat.random() 22 | NSDate.random() 23 | NSDate.randomWithinDaysBeforeToday(7) 24 | NSDate.random(between: Date().addingTimeInterval(TimeInterval(-5*24*60*60)), and:Date()) // Random date between now and 5 days ago 25 | UIColor.random() 26 | NSURL.random() 27 | 28 | // Array Extensions 29 | var someArray = ["hello", "world"] 30 | someArray[0..= 10 and <= 20 44 | // Example Output: 13 45 | Randoms.randomInt(10,20) 46 | 47 | // Return random Int32 >= 10 and <= 20 48 | // Example Output: 13 49 | Randoms.randomInt32(10,20) 50 | 51 | // Returns a random 16 character long string containing alphanumeric characters 52 | Randoms.randomString(ofLength: 16) 53 | 54 | // Returns a random string containing alphanumeric characters ranging in length from 16 to 32. 55 | Randoms.randomString(minimumLength: 16, maximumLength: 32) 56 | 57 | // Returns a random 16 character long string containing the specified characters 58 | Randoms.randomString(withCharactersInString: "abc", ofLength: 16) 59 | 60 | // Returns a random string of length between 16 and 32 containing the specified characters 61 | Randoms.randomString(withCharactersInString: "abc", minimumLength: 16, maximumLength: 32) 62 | 63 | // Return random Double >= 10 and <= 20 64 | // Example Output: 11.511219042938 65 | Randoms.randomDouble(10,20) 66 | 67 | // Return random Float >= 10 and <= 20 68 | // Example Output: 17.0361 69 | Randoms.randomFloat(10,20) 70 | 71 | // Return random CGFloat between 1 >= and >= 0 72 | // Example Output: 0.622616 73 | Randoms.randomCGFloat() 74 | 75 | // Return true 30%, false %70 76 | // Example Output: false 77 | Randoms.randomPercentageisOver(70) 78 | 79 | // Return true or false 80 | // Example Output: false 81 | Randoms.randomBool() 82 | 83 | // Return random NSDate today > and > today - 7. 84 | // Example Output: 2015-10-08 03:55:09 +0000 85 | Randoms.randomDateWithinDaysBeforeToday(7) 86 | 87 | // Random Date since 1970 88 | // Example Output: 1997-02-01 15:27:08 +0000 89 | Randoms.randomDate() 90 | 91 | // Return UIColor. Alpha channel always 1. 92 | // Example Output: UIDeviceRGBColorSpace 0.645737 0.126625 0.52535 1 93 | Randoms.randomColor() 94 | 95 | // Return random NSURL 96 | // Example Output: http://leagueoflegends.com/ 97 | Randoms.randomNSURL() 98 | 99 | // ==================== Fake Generators for Fake Datasources ==================== // 100 | 101 | // Return random name 102 | // Example Output: "Megan Freeman" 103 | Randoms.randomFakeName() 104 | 105 | // Return random first name 106 | // Example Output: "Megan" 107 | Randoms.randomFakeFirstName() 108 | 109 | // Return random last name 110 | // Example Output: "Freeman" 111 | Randoms.randomFakeLastName() 112 | 113 | // Return random fake name prefixed by English honorific 114 | // Example Output: "Dr. Megan Freeman" 115 | Randoms.randomFakeNameAndEnglishHonorific() 116 | 117 | // Return "Male" or "Female" as String 118 | // Example Output: "Female" 119 | Randoms.randomFakeGender() 120 | 121 | // Return random conversation 122 | // Example Output: "No! I'm tired of doing what you say." 123 | Randoms.randomFakeConversation() 124 | 125 | // Return random title 126 | // Example Output: "B2 Pilot @ USAF" 127 | Randoms.randomFakeTitle() 128 | 129 | // Return random tag as string 130 | // Example Output: "question" 131 | Randoms.randomFakeTag() 132 | 133 | // Return random currency as String 134 | // Example Output: "EUR" 135 | Randoms.randomCurrency() 136 | 137 | // Return random (non-existing) gravatar as UIImage? 138 | // The image is optional in case of network issues 139 | Randoms.randomGravatar { (image, error) -> Void in 140 | // Handle the image/error 141 | } 142 | 143 | // For consistance behaviour you can create custom Gravatar 144 | Randoms.createGravatar(Randoms.GravatarStyle.Retro) { (image, error) -> Void in 145 | // Handle the image/error 146 | } 147 | } 148 | 149 | ``` 150 | 151 | ### Requirements 152 | 153 | - Swift version 2.0 154 | 155 | 156 | ## Installation 157 | 158 | ### Install via Carthage 159 | 160 | * Create a `Cartfile` with the following specification and run `carthage update`. 161 | 162 | ``` 163 | github "thellimist/SwiftRandom" >= 1.0.0 164 | ``` 165 | 166 | * Follow the [instructions](https://github.com/Carthage/Carthage#if-youre-building-for-ios) to add the framework to an iOS project. 167 | 168 | ### Install via CocoaPods 169 | 170 | You can use [CocoaPods](http://cocoapods.org/) to install `SwiftRandom` by adding it to your `Podfile`: 171 | ```ruby 172 | platform :ios, '8.0' 173 | use_frameworks! 174 | pod 'SwiftRandom' #Stable release for Swift 3.0 175 | ``` 176 | 177 | To download older version you can use 178 | ``` 179 | pod 'SwiftRandom', :git => 'https://github.com/thellimist/SwiftRandom.git' #Latest release for Swift 3.0 180 | pod 'SwiftRandom', :git => 'https://github.com/thellimist/SwiftRandom.git', :branch => '2.x' #For Swift 2.3 181 | 182 | ``` 183 | 184 | ### Install Manually 185 | 186 | - Download and drop 'Randoms.swift' in your project. 187 | 188 | 189 | ### Improvement 190 | - Feel free adding your own random data functions and sending pull requests. 191 | 192 | ##### Possible features: 193 | - Random wildlife pictures (Should not include the image inside project, should load it from web when needed) 194 | - Make OSX compatiable and add here: https://github.com/AndrewSB/awesome-osx 195 | - Random JSON 196 | - Random Gifs (Should not include gifs inside the project, should load it from web when needed) 197 | - Random Videos (Should not include videos inside the project, should load it from web when needed) 198 | - Implement `SwiftRandom` as a protocol any class can conform to (`Post.random()` would give you a random post) 199 | 200 | ### License 201 | - SwiftRandom is available under the MIT license. See the [LICENSE file](https://github.com/thellimist/SwiftRandom/blob/master/LICENSE). 202 | 203 | ## Keywords 204 | random, swift, data, generator, faker, fake, gravatar 205 | -------------------------------------------------------------------------------- /Example/Pods/SwiftRandom/SwiftRandom/Randoms.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftRandom.swift 3 | // 4 | // Created by Furkan Yilmaz on 7/10/15. 5 | // Copyright (c) 2015 Furkan Yilmaz. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | // each type has its own random 11 | 12 | public extension Bool { 13 | /// SwiftRandom extension 14 | static func random() -> Bool { 15 | return Int.random() % 2 == 0 16 | } 17 | } 18 | 19 | public extension Int { 20 | /// SwiftRandom extension 21 | static func random(_ lower: Int = 0, _ upper: Int = 100) -> Int { 22 | return Int.random(in: lower...upper) 23 | } 24 | } 25 | 26 | public extension Int32 { 27 | /// SwiftRandom extension 28 | /// 29 | /// - note: Using `Int` as parameter type as we usually just want to write `Int32.random(13, 37)` and not `Int32.random(Int32(13), Int32(37))` 30 | static func random(_ lower: Int = 0, _ upper: Int = 100) -> Int32 { 31 | return Int32.random(in: Int32(lower)...Int32(upper)) 32 | } 33 | } 34 | 35 | public extension String { 36 | /// SwiftRandom extension 37 | static func random(ofLength length: Int) -> String { 38 | return random(minimumLength: length, maximumLength: length) 39 | } 40 | 41 | /// SwiftRandom extension 42 | static func random(minimumLength min: Int, maximumLength max: Int) -> String { 43 | return random( 44 | withCharactersInString: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 45 | minimumLength: min, 46 | maximumLength: max 47 | ) 48 | } 49 | 50 | /// SwiftRandom extension 51 | static func random(withCharactersInString string: String, ofLength length: Int) -> String { 52 | return random( 53 | withCharactersInString: string, 54 | minimumLength: length, 55 | maximumLength: length 56 | ) 57 | } 58 | 59 | /// SwiftRandom extension 60 | static func random(withCharactersInString string: String, minimumLength min: Int, maximumLength max: Int) -> String { 61 | guard min > 0 && max >= min else { 62 | return "" 63 | } 64 | 65 | let length: Int = (min < max) ? .random(in: min...max) : max 66 | var randomString = "" 67 | 68 | (1...length).forEach { _ in 69 | let randomIndex: Int = .random(in: 0.. Double { 81 | return Double.random(in: lower...upper) 82 | } 83 | } 84 | 85 | public extension Float { 86 | /// SwiftRandom extension 87 | static func random(_ lower: Float = 0, _ upper: Float = 100) -> Float { 88 | return Float.random(in: lower...upper) 89 | } 90 | } 91 | 92 | public extension CGFloat { 93 | /// SwiftRandom extension 94 | static func random(_ lower: CGFloat = 0, _ upper: CGFloat = 1) -> CGFloat { 95 | return CGFloat.random(in: lower...upper) 96 | } 97 | } 98 | 99 | public extension Date { 100 | /// SwiftRandom extension 101 | static func randomWithinDaysBeforeToday(_ days: Int) -> Date { 102 | let today = Date() 103 | let earliest = today.addingTimeInterval(TimeInterval(-days*24*60*60)) 104 | 105 | return Date.random(between: earliest, and: today) 106 | } 107 | 108 | /// SwiftRandom extension 109 | static func random() -> Date { 110 | let randomTime = TimeInterval(arc4random_uniform(UInt32.max)) 111 | return Date(timeIntervalSince1970: randomTime) 112 | } 113 | 114 | static func random(between initial: Date, and final:Date) -> Date { 115 | let interval = final.timeIntervalSince(initial) 116 | let randomInterval = TimeInterval(arc4random_uniform(UInt32(interval))) 117 | return initial.addingTimeInterval(randomInterval) 118 | } 119 | 120 | } 121 | 122 | public extension UIColor { 123 | /// SwiftRandom extension 124 | static func random(_ randomAlpha: Bool = false) -> UIColor { 125 | let randomRed = CGFloat.random() 126 | let randomGreen = CGFloat.random() 127 | let randomBlue = CGFloat.random() 128 | let alpha = randomAlpha ? CGFloat.random() : 1.0 129 | return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: alpha) 130 | } 131 | } 132 | 133 | public extension URL { 134 | /// SwiftRandom extension 135 | static func random() -> URL { 136 | let urlList = ["http://www.google.com", "http://leagueoflegends.com/", "https://github.com/", "http://stackoverflow.com/", "https://medium.com/", "http://9gag.com/gag/6715049", "http://imgur.com/gallery/s9zoqs9", "https://www.youtube.com/watch?v=uelHwf8o7_U"] 137 | return URL(string: urlList.randomElement()!)! 138 | } 139 | } 140 | 141 | 142 | public struct Randoms { 143 | 144 | //========================================================================================================== 145 | // MARK: - Object randoms 146 | //========================================================================================================== 147 | 148 | public static func randomBool() -> Bool { 149 | return Bool.random() 150 | } 151 | 152 | public static func randomInt(_ range: Range) -> Int { 153 | return Int.random(in: range) 154 | } 155 | 156 | public static func randomInt(_ lower: Int = 0, _ upper: Int = 100) -> Int { 157 | return Int.random(lower, upper) 158 | } 159 | 160 | public static func randomInt32(_ range: Range) -> Int32 { 161 | return Int32.random(in: range) 162 | } 163 | 164 | public static func randomInt32(_ lower: Int = 0, _ upper: Int = 100) -> Int32 { 165 | return Int32.random(lower, upper) 166 | } 167 | 168 | public static func randomString(ofLength length: Int) -> String { 169 | return String.random(ofLength: length) 170 | } 171 | 172 | public static func randomString(minimumLength min: Int, maximumLength max: Int) -> String { 173 | return String.random(minimumLength: min, maximumLength: max) 174 | } 175 | 176 | public static func randomString(withCharactersInString string: String, ofLength length: Int) -> String { 177 | return String.random(withCharactersInString: string, ofLength: length) 178 | } 179 | 180 | public static func randomString(withCharactersInString string: String, minimumLength min: Int, maximumLength max: Int) -> String { 181 | return String.random(withCharactersInString: string, minimumLength: min, maximumLength: max) 182 | } 183 | 184 | public static func randomPercentageisOver(_ percentage: Int) -> Bool { 185 | return Int.random() >= percentage 186 | } 187 | 188 | public static func randomDouble(_ lower: Double = 0, _ upper: Double = 100) -> Double { 189 | return Double.random(lower, upper) 190 | } 191 | 192 | public static func randomFloat(_ lower: Float = 0, _ upper: Float = 100) -> Float { 193 | return Float.random(lower, upper) 194 | } 195 | 196 | public static func randomCGFloat(_ lower: CGFloat = 0, _ upper: CGFloat = 1) -> CGFloat { 197 | return CGFloat.random(lower, upper) 198 | } 199 | 200 | public static func randomDateWithinDaysBeforeToday(_ days: Int) -> Date { 201 | return Date.randomWithinDaysBeforeToday(days) 202 | } 203 | 204 | public static func randomDate() -> Date { 205 | return Date.random() 206 | } 207 | 208 | public static func randomColor(_ randomAlpha: Bool = false) -> UIColor { 209 | return UIColor.random(randomAlpha) 210 | } 211 | 212 | public static func randomNSURL() -> URL { 213 | return URL.random() 214 | } 215 | 216 | //========================================================================================================== 217 | // MARK: - Fake random data generators 218 | //========================================================================================================== 219 | 220 | public static func randomFakeName() -> String { 221 | return randomFakeFirstName() + " " + randomFakeLastName() 222 | } 223 | 224 | public static func randomFakeFirstName() -> String { 225 | let firstNameList = ["Henry", "William", "Geoffrey", "Jim", "Yvonne", "Jamie", "Leticia", "Priscilla", "Sidney", "Nancy", "Edmund", "Bill", "Megan"] 226 | return firstNameList.randomElement()! 227 | } 228 | 229 | public static func randomFakeLastName() -> String { 230 | let lastNameList = ["Pearson", "Adams", "Cole", "Francis", "Andrews", "Casey", "Gross", "Lane", "Thomas", "Patrick", "Strickland", "Nicolas", "Freeman"] 231 | return lastNameList.randomElement()! 232 | } 233 | 234 | public static func randomFakeGender() -> String { 235 | return Bool.random() ? "Male" : "Female" 236 | } 237 | 238 | public static func randomFakeConversation() -> String { 239 | let convoList = ["You embarrassed me this evening.", "You don't think that was just lemonade in your glass, do you?", "Do you ever think we should just stop doing this?", "Why didn't he come and talk to me himself?", "Promise me you'll look after your mother.", "If you get me his phone, I might reconsider.", "I think the room is bugged.", "No! I'm tired of doing what you say.", "For some reason, I'm attracted to you."] 240 | return convoList.randomElement()! 241 | } 242 | 243 | public static func randomFakeTitle() -> String { 244 | let titleList = ["CEO of Google", "CEO of Facebook", "VP of Marketing @Uber", "Business Developer at IBM", "Jungler @ Fanatic", "B2 Pilot @ USAF", "Student at Stanford", "Student at Harvard", "Mayor of Raccoon City", "CTO @ Umbrella Corporation", "Professor at Pallet Town University"] 245 | return titleList.randomElement()! 246 | } 247 | 248 | public static func randomFakeTag() -> String { 249 | let tagList = ["meta", "forum", "troll", "meme", "question", "important", "like4like", "f4f"] 250 | return tagList.randomElement()! 251 | } 252 | 253 | fileprivate static func randomEnglishHonorific() -> String { 254 | let englishHonorificsList = ["Mr.", "Ms.", "Dr.", "Mrs.", "Mz.", "Mx.", "Prof."] 255 | return englishHonorificsList.randomElement()! 256 | } 257 | 258 | public static func randomFakeNameAndEnglishHonorific() -> String { 259 | let englishHonorific = randomEnglishHonorific() 260 | let name = randomFakeName() 261 | return englishHonorific + " " + name 262 | } 263 | 264 | public static func randomFakeCity() -> String { 265 | let cityPrefixes = ["North", "East", "West", "South", "New", "Lake", "Port"] 266 | let citySuffixes = ["town", "ton", "land", "ville", "berg", "burgh", "borough", "bury", "view", "port", "mouth", "stad", "furt", "chester", "mouth", "fort", "haven", "side", "shire"] 267 | return cityPrefixes.randomElement()! + citySuffixes.randomElement()! 268 | } 269 | 270 | public static func randomCurrency() -> String { 271 | let currencyList = ["USD", "EUR", "GBP", "JPY", "AUD", "CAD", "ZAR", "NZD", "INR", "BRP", "CNY", "EGP", "KRW", "MXN", "SAR", "SGD",] 272 | 273 | return currencyList.randomElement()! 274 | } 275 | 276 | public enum GravatarStyle: String { 277 | case Standard 278 | case MM 279 | case Identicon 280 | case MonsterID 281 | case Wavatar 282 | case Retro 283 | 284 | static let allValues = [Standard, MM, Identicon, MonsterID, Wavatar, Retro] 285 | } 286 | 287 | public static func createGravatar(_ style: Randoms.GravatarStyle = .Standard, size: Int = 80, completion: ((_ image: UIImage?, _ error: Error?) -> Void)?) { 288 | var url = "https://secure.gravatar.com/avatar/thisimagewillnotbefound?s=\(size)" 289 | if style != .Standard { 290 | url += "&d=\(style.rawValue.lowercased())" 291 | } 292 | 293 | let request = URLRequest(url: URL(string: url)! as URL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 5.0) 294 | let session = URLSession.shared 295 | 296 | session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) in 297 | DispatchQueue.main.async { 298 | if error == nil { 299 | completion?(UIImage(data: data!), nil) 300 | } else { 301 | completion?(nil, error) 302 | } 303 | } 304 | }).resume() 305 | } 306 | 307 | public static func randomGravatar(_ size: Int = 80, completion: ((_ image: UIImage?, _ error: Error?) -> Void)?) { 308 | let options = Randoms.GravatarStyle.allValues 309 | Randoms.createGravatar(options.randomElement()!, size: size, completion: completion) 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Comet/Comet-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Comet/Comet-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Comet : NSObject 3 | @end 4 | @implementation PodsDummy_Comet 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Comet/Comet-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Comet/Comet-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double CometVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char CometVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Comet/Comet.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Comet 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 11 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Comet/Comet.modulemap: -------------------------------------------------------------------------------- 1 | framework module Comet { 2 | umbrella header "Comet-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Comet/Comet.release.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Comet 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 11 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Comet/Comet.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Comet 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 11 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Comet/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.3.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Pods-Comet_Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Pods-Comet_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Comet 5 | 6 | MIT License 7 | 8 | Copyright (c) 2016 Harley.xk 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | 29 | ## SwiftRandom 30 | 31 | The MIT License (MIT) 32 | 33 | Copyright (c) 2015 Furkan Yilmaz 34 | 35 | Permission is hereby granted, free of charge, to any person obtaining a copy 36 | of this software and associated documentation files (the "Software"), to deal 37 | in the Software without restriction, including without limitation the rights 38 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 39 | copies of the Software, and to permit persons to whom the Software is 40 | furnished to do so, subject to the following conditions: 41 | 42 | The above copyright notice and this permission notice shall be included in all 43 | copies or substantial portions of the Software. 44 | 45 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 46 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 47 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 48 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 49 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 50 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 51 | SOFTWARE. 52 | 53 | 54 | Generated by CocoaPods - https://cocoapods.org 55 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Pods-Comet_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | MIT License 18 | 19 | Copyright (c) 2016 Harley.xk 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | License 40 | MIT 41 | Title 42 | Comet 43 | Type 44 | PSGroupSpecifier 45 | 46 | 47 | FooterText 48 | The MIT License (MIT) 49 | 50 | Copyright (c) 2015 Furkan Yilmaz 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the "Software"), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in all 60 | copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 68 | SOFTWARE. 69 | 70 | 71 | License 72 | MIT 73 | Title 74 | SwiftRandom 75 | Type 76 | PSGroupSpecifier 77 | 78 | 79 | FooterText 80 | Generated by CocoaPods - https://cocoapods.org 81 | Title 82 | 83 | Type 84 | PSGroupSpecifier 85 | 86 | 87 | StringsTable 88 | Acknowledgements 89 | Title 90 | Acknowledgements 91 | 92 | 93 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Pods-Comet_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_Comet_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_Comet_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Pods-Comet_Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | 23 | # Used as a return value for each invocation of `strip_invalid_archs` function. 24 | STRIP_BINARY_RETVAL=0 25 | 26 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 27 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 28 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 29 | 30 | # Copies and strips a vendored framework 31 | install_framework() 32 | { 33 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 34 | local source="${BUILT_PRODUCTS_DIR}/$1" 35 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 36 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 37 | elif [ -r "$1" ]; then 38 | local source="$1" 39 | fi 40 | 41 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 42 | 43 | if [ -L "${source}" ]; then 44 | echo "Symlinked..." 45 | source="$(readlink "${source}")" 46 | fi 47 | 48 | # Use filter instead of exclude so missing patterns don't throw errors. 49 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 50 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 51 | 52 | local basename 53 | basename="$(basename -s .framework "$1")" 54 | binary="${destination}/${basename}.framework/${basename}" 55 | 56 | if ! [ -r "$binary" ]; then 57 | binary="${destination}/${basename}" 58 | elif [ -L "${binary}" ]; then 59 | echo "Destination binary is symlinked..." 60 | dirname="$(dirname "${binary}")" 61 | binary="${dirname}/$(readlink "${binary}")" 62 | fi 63 | 64 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 65 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 66 | strip_invalid_archs "$binary" 67 | fi 68 | 69 | # Resign the code if required by the build settings to avoid unstable apps 70 | code_sign_if_enabled "${destination}/$(basename "$1")" 71 | 72 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 73 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 74 | local swift_runtime_libs 75 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 76 | for lib in $swift_runtime_libs; do 77 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 78 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 79 | code_sign_if_enabled "${destination}/${lib}" 80 | done 81 | fi 82 | } 83 | 84 | # Copies and strips a vendored dSYM 85 | install_dsym() { 86 | local source="$1" 87 | warn_missing_arch=${2:-true} 88 | if [ -r "$source" ]; then 89 | # Copy the dSYM into the targets temp dir. 90 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 91 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 92 | 93 | local basename 94 | basename="$(basename -s .dSYM "$source")" 95 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 96 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 97 | 98 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 99 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 100 | strip_invalid_archs "$binary" "$warn_missing_arch" 101 | fi 102 | 103 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then 104 | # Move the stripped file into its final destination. 105 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 106 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 107 | else 108 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 109 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 110 | fi 111 | fi 112 | } 113 | 114 | # Copies the bcsymbolmap files of a vendored framework 115 | install_bcsymbolmap() { 116 | local bcsymbolmap_path="$1" 117 | local destination="${BUILT_PRODUCTS_DIR}" 118 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 119 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 120 | } 121 | 122 | # Signs a framework with the provided identity 123 | code_sign_if_enabled() { 124 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 125 | # Use the current code_sign_identity 126 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 127 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 128 | 129 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 130 | code_sign_cmd="$code_sign_cmd &" 131 | fi 132 | echo "$code_sign_cmd" 133 | eval "$code_sign_cmd" 134 | fi 135 | } 136 | 137 | # Strip invalid architectures 138 | strip_invalid_archs() { 139 | binary="$1" 140 | warn_missing_arch=${2:-true} 141 | # Get architectures for current target binary 142 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 143 | # Intersect them with the architectures we are building for 144 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 145 | # If there are no archs supported by this binary then warn the user 146 | if [[ -z "$intersected_archs" ]]; then 147 | if [[ "$warn_missing_arch" == "true" ]]; then 148 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 149 | fi 150 | STRIP_BINARY_RETVAL=0 151 | return 152 | fi 153 | stripped="" 154 | for arch in $binary_archs; do 155 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 156 | # Strip non-valid architectures in-place 157 | lipo -remove "$arch" -output "$binary" "$binary" 158 | stripped="$stripped $arch" 159 | fi 160 | done 161 | if [[ "$stripped" ]]; then 162 | echo "Stripped $binary of architectures:$stripped" 163 | fi 164 | STRIP_BINARY_RETVAL=1 165 | } 166 | 167 | install_artifact() { 168 | artifact="$1" 169 | base="$(basename "$artifact")" 170 | case $base in 171 | *.framework) 172 | install_framework "$artifact" 173 | ;; 174 | *.dSYM) 175 | # Suppress arch warnings since XCFrameworks will include many dSYM files 176 | install_dsym "$artifact" "false" 177 | ;; 178 | *.bcsymbolmap) 179 | install_bcsymbolmap "$artifact" 180 | ;; 181 | *) 182 | echo "error: Unrecognized artifact "$artifact"" 183 | ;; 184 | esac 185 | } 186 | 187 | copy_artifacts() { 188 | file_list="$1" 189 | while read artifact; do 190 | install_artifact "$artifact" 191 | done <$file_list 192 | } 193 | 194 | ARTIFACT_LIST_FILE="${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt" 195 | if [ -r "${ARTIFACT_LIST_FILE}" ]; then 196 | copy_artifacts "${ARTIFACT_LIST_FILE}" 197 | fi 198 | 199 | if [[ "$CONFIGURATION" == "Debug" ]]; then 200 | install_framework "${BUILT_PRODUCTS_DIR}/Comet/Comet.framework" 201 | install_framework "${BUILT_PRODUCTS_DIR}/SwiftRandom/SwiftRandom.framework" 202 | fi 203 | if [[ "$CONFIGURATION" == "Release" ]]; then 204 | install_framework "${BUILT_PRODUCTS_DIR}/Comet/Comet.framework" 205 | install_framework "${BUILT_PRODUCTS_DIR}/SwiftRandom/SwiftRandom.framework" 206 | fi 207 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 208 | wait 209 | fi 210 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Pods-Comet_Example-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | case "${TARGETED_DEVICE_FAMILY}" in 12 | 1,2) 13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 14 | ;; 15 | 1) 16 | TARGET_DEVICE_ARGS="--target-device iphone" 17 | ;; 18 | 2) 19 | TARGET_DEVICE_ARGS="--target-device ipad" 20 | ;; 21 | 3) 22 | TARGET_DEVICE_ARGS="--target-device tv" 23 | ;; 24 | *) 25 | TARGET_DEVICE_ARGS="--target-device mac" 26 | ;; 27 | esac 28 | 29 | install_resource() 30 | { 31 | if [[ "$1" = /* ]] ; then 32 | RESOURCE_PATH="$1" 33 | else 34 | RESOURCE_PATH="${PODS_ROOT}/$1" 35 | fi 36 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 37 | cat << EOM 38 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 39 | EOM 40 | exit 1 41 | fi 42 | case $RESOURCE_PATH in 43 | *.storyboard) 44 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 45 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 46 | ;; 47 | *.xib) 48 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" 49 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 50 | ;; 51 | *.framework) 52 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 53 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 54 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 55 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 56 | ;; 57 | *.xcdatamodel) 58 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" 59 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 60 | ;; 61 | *.xcdatamodeld) 62 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" 63 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 64 | ;; 65 | *.xcmappingmodel) 66 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" 67 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 68 | ;; 69 | *.xcassets) 70 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 71 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 72 | ;; 73 | *) 74 | echo "$RESOURCE_PATH" 75 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 76 | ;; 77 | esac 78 | } 79 | 80 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 81 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 82 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 83 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 85 | fi 86 | rm -f "$RESOURCES_TO_COPY" 87 | 88 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 89 | then 90 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 91 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 92 | while read line; do 93 | if [[ $line != "${PODS_ROOT}*" ]]; then 94 | XCASSET_FILES+=("$line") 95 | fi 96 | done <<<"$OTHER_XCASSETS" 97 | 98 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 99 | fi 100 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Pods-Comet_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_Comet_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_Comet_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Pods-Comet_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Comet" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftRandom" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Comet/Comet.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftRandom/SwiftRandom.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "Comet" -framework "SwiftRandom" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Pods-Comet_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_Comet_Example { 2 | umbrella header "Pods-Comet_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-Comet_Example/Pods-Comet_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Comet" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftRandom" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Comet/Comet.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftRandom/SwiftRandom.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "Comet" -framework "SwiftRandom" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/SwiftRandom/SwiftRandom-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/SwiftRandom/SwiftRandom-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_SwiftRandom : NSObject 3 | @end 4 | @implementation PodsDummy_SwiftRandom 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/SwiftRandom/SwiftRandom-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/SwiftRandom/SwiftRandom-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double SwiftRandomVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char SwiftRandomVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/SwiftRandom/SwiftRandom.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftRandom 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftRandom 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 11 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/SwiftRandom/SwiftRandom.modulemap: -------------------------------------------------------------------------------- 1 | framework module SwiftRandom { 2 | umbrella header "SwiftRandom-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/SwiftRandom/SwiftRandom.release.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftRandom 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftRandom 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 11 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/SwiftRandom/SwiftRandom.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftRandom 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftRandom 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 11 | -------------------------------------------------------------------------------- /Images/img_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harley-xk/Comet/3c579fdd47767c463dc072f0840786b0aa30f95b/Images/img_0.png -------------------------------------------------------------------------------- /Images/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harley-xk/Comet/3c579fdd47767c463dc072f0840786b0aa30f95b/Images/img_1.png -------------------------------------------------------------------------------- /Images/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harley-xk/Comet/3c579fdd47767c463dc072f0840786b0aa30f95b/Images/img_2.png -------------------------------------------------------------------------------- /Images/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harley-xk/Comet/3c579fdd47767c463dc072f0840786b0aa30f95b/Images/img_3.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Harley.xk 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 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: "Comet", 8 | platforms: [ 9 | .iOS(.v10) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "Comet", 15 | targets: ["Comet"]) 16 | ], 17 | dependencies: [ 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 22 | .target( 23 | name: "Comet", 24 | dependencies: [], path: "Comet") 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Comet](Images/img_0.png) Comet 2 | 3 | [![CI Status](http://img.shields.io/travis/Harley-xk/Comet.svg?style=flat)](https://travis-ci.org/Harley-xk/Comet) 4 | [![Version](https://img.shields.io/cocoapods/v/Comet.svg?style=flat)](http://cocoapods.org/pods/Comet) 5 | [![Language](https://img.shields.io/badge/language-Swift%204-orange.svg)](https://swift.org) 6 | [![codebeat badge](https://codebeat.co/badges/f5e29594-b882-4f7f-af50-c36407efab7f)](https://codebeat.co/projects/github-com-harley-xk-comet-master) 7 | [![License](https://img.shields.io/cocoapods/l/Comet.svg?style=flat)](http://cocoapods.org/pods/Comet) 8 | [![Platform](https://img.shields.io/cocoapods/p/Comet.svg?style=flat)](http://cocoapods.org/pods/Comet) 9 | 10 | iOS 项目的 Swift 基础库,提供大量常用组件、便利方法等。支持 **Swift 3.0+**。 11 | 1.0.0 起支持 Swift 4.x,需要支持 Swift 3.x 请使用 0.7.5 版本。 12 | 1.5.0 起支持 Swift 4.2, 需要支持 Swift 4.0/4.1 请指定 1.4.1 版本。 13 | 14 | ## 安装 15 | 16 | ### 支持 CocoaPods 安装: 17 | 18 | ```ruby 19 | # for swift 4.2 20 | pod 'Comet' 21 | # for swift 4.0/4.1 22 | pod 'Comet', :git => 'https://github.com/Harley-xk/Comet.git, :tag=>1.4.1' 23 | # for swift 3.1/3.2 24 | pod 'Comet', :git => 'https://github.com/Harley-xk/Comet.git, :tag=>0.7.5' 25 | ``` 26 | 27 | ### 支持 Swift Package Manager 28 | 29 | 将 Comet 添加到 Package.swift 的 dependencies 中: 30 | 31 | ```swift 32 | dependencies: [ 33 | // ... 34 | .package(url: "https://github.com/Harley-xk/Comet.git", .upToNextMinor(from: "1.6.0")), 35 | // ... 36 | ], 37 | ``` 38 | 39 | ## API 清单 40 | 41 | ### 工具类 42 | 43 | #### 1. KeyboardPlacehoder —— 键盘占位符 44 | 45 | 键盘输入是几乎每个 App 都要涉及到的内容,当输入框获得焦点,虚拟键盘弹出时,需要动态调整界面 UI 布局以适应新的界面尺寸。一般做法是通过在视图控制器中监听键盘的弹出隐藏等事件通知,根据不同的状态进行 UI 调整处理。当有多个甚至大量的界面需要处理内容输入时,需要在每个视图控制器中实现几乎相同的代码逻辑,繁琐又耗时。**键盘占位符**就是专门用来应对这个问题的。 46 | 47 | 原理: 48 | 49 | **键盘占位符**就是键盘弹出后在实际试图中的映射。开发时只需要将占位符添加到任意视图中,设置其他视图的相对位置;当键盘弹出或者收起(任意键盘尺寸、位置发生改变)时,占位符将会自动调整自身的高度,保证实际尺寸和位置与键盘在占位符父视图中的投影相一致。 50 | 51 | _*键盘占位符相比于原来的键盘管理器更加方便使用,推荐使用新的占位符方式处理键盘事件,键盘管理器后期将会被废除*_ 52 | 53 | #### ~~2. HairLine —— 极细的线?(已废除)~~ 54 | 55 | #### 3. Path —— 路径 56 | 57 | 文件读写是大多数 App 或多或少需要涉及的内容,路径类主要用于快速获取设备的各种文件及文件夹路径。 **Path** 类的本质是对路径字符串的封装,在此基础上提供额外的简便操作方法。详细可以查看 **Path** 类的方法注释 58 | 59 | #### 4. CollectionGrouper 60 | 61 | 1.6 新增 `CollectionGrouper` 集合分组工具类,可以对集合进行指定元素属性进行分组,也可以通过自定义分组规则实现自定义分组,详细参考 “Grouper Sample” 实例。 62 | 63 | #### ~~4. PinyinIndexer —— 拼音索引器(即将废除)~~ 64 | 65 | **_即将废除,新增的 `CollectionGrouper` 可以实现拼音分组需求以及更多其他自定义需求,专门的拼音索引器会在将来移除_** 66 | 67 | 遇到列表类需求时(比如联系人列表),往往需要将列表的内容按照拼音首字母进行索引排序,这是一个简单但又繁琐的工作,因此**拼音索引器**诞生了。 68 | 69 | 用法: 70 | 71 | 1. 创建需要进行索引的对象数组。~~因为索引器在获取属性时使用了 **_KVC_** 的方式来获取对象对应属性的值,因此要求数据对象必须是 NSObject 的子类。~~ 72 | 73 | 0.5 及以上版本更新使用协议来实现,不再要求继承 NSObject,参见第 5 点。 74 | 75 | 2. 创建拼音索引器,构造函数需要两个参数:**_对象数组_** 和索引所依据的 **_属性键值_**。 76 | 3. 索引器创建时会直接进行索引任务,对大量数据进行索引时考虑到性能问题,不建议在主线程处理。 77 | 4. 索引器创建完成后,可以通过 **_indexedObjects_** 和 **_indexedTitles_** 属性获得索引的结果 78 | 79 | - **_indexedObjects_** 是一个二维数组,其中是根据索引顺序排序好的对象数组 80 | - **_indexedTitles_** 是索引后的拼音首字母的数组 81 | 82 | 5. 0.5.0 更新:使用协议的方式代替 KVC,解除了数据对象必须为 NSObject 子类的限制。使用时,声明数据类实现`PinyinIndexable`协议,然后通过`var valueToPinyin: String { get }`这个协议方法,返回需要转换为拼音的属性即可。 83 | 84 | #### 5. TaskRecorder —— 任务记录器 85 | 86 | App 的基本功能就是执行各种任务,比如网络任务。正常情况下,发起的任务都能执行完毕并返回结果。在某些情况下,任务并不能或者没有必要执行完毕。比如在一个视图控制器中发起了一个网络请求来获取数据,以显示在当前界面上;但是在请求执行完毕之前,用户操作退出了该界面,此时往往没有必要再继续执行这个请求,因此需要程序作出处理,取消这个网络任务的执行。当一个界面中的网络任务较多时,手动处理这些逻辑就会变得繁琐且容易出错。 87 | 通过任务记录器可以将发起的任务关联到某个对象,并且在这个对象被销毁时,任务纪录器会将所有已纪录并且还没有执行完毕的任务都取消并销毁。 88 | 89 | 用法: 90 | 91 | 1. 需要被纪录的任务都必须实现 _TaskProtocol_ 协议,只需要实现一个简单的 _cancel_ 方法 92 | 2. 对需要关联的对象调用 _record(task:)_ 方法,并将需要纪录的任务作为参数传入,就会自动创建一个记录器并关联到该对象 93 | 3. 对象销毁时记录器会自动执行逻辑,对未完成的任务调用 _cancel_ 方法并将其销毁 94 | 95 | #### 6. Utils —— 通用工具类 96 | 97 | 主要提供一些设备相关的工具方法,例如获取设备型号、系统版本、拨打电话等。详情参见 **Utils** 类的方法注释 98 | 99 | ### 扩展 100 | 101 | #### 1. Date —— 日期类扩展 102 | 103 | 日期类扩展提供快速操作日期的一些方法: 104 | 105 | 1. **通过日期字符串创建日期对象** 106 | 107 | ```swift 108 | public init?(string: String, format: String = "yyyy-MM-dd HH:mm:ss", timeZone: TimeZone = TimeZone.current) 109 | ``` 110 | 111 | _string_ - 日期字符串 112 | 113 | _format_ - 日期的格式,默认为"yyyy-MM-dd HH:mm:ss" 114 | 115 | _timeZone_ - 时区,默认为设备当前设置的时区 116 | 117 | _将 local 参数替换为 timeZone_ 118 | 119 | 2) **将日期转换为指定格式的字符串** 120 | 121 | ```swift 122 | public func string(format: String = "yyyy-MM-dd HH:mm:ss", timeZone: TimeZone = TimeZone.current) -> String 123 | ``` 124 | 125 | _format_ - 指定的字符串格式 126 | 127 | _timeZone_ - 时区,默认为设备当前设置的时区 128 | 129 | _将 local 参数替换为 timeZone_ 130 | 131 | 3) **日期计算** 132 | 133 | ```swift 134 | public func add(_ value: Int, _ unit: DateUnit) -> Date 135 | ``` 136 | 137 | 返回当前日期加上指定单位值之后的日期,会自动进位或减位 138 | 139 | 例如:10 月 30 日加上两天后会变成 11 月 1 日 140 | 141 | _value_ - 对应单位的值 142 | 143 | _unit_ - 计算的单位 144 | 145 | 4) **日期设定** 146 | 147 | ```swift 148 | public func set(_ unit: DateUnit, to value: Int) -> Date 149 | ``` 150 | 151 | 将指定单位设置为指定的值,返回修改后的新日期 152 | 153 | 如果设置的值大于当前单位的最大值或者小于最小值,会自动进位或减位 154 | 155 | _unit_ - 设置的单位 156 | 157 | _value_ - 设置的值 158 | 159 | 5) **忽略精确时间(时/分/秒)的日期** 160 | 161 | ```swift 162 | public var withoutTime: Date 163 | ``` 164 | 165 | 有时候进行日期计算需要以天为最小单位,忽略具体的时间。该属性可以获取该日期当天零点的时间对象 166 | 167 | 6) **获取指定日期组件的值** 168 | 169 | ```swift 170 | public func unit(_ unit: DateUnit) -> Int 171 | ``` 172 | 173 | 通过设置单位,可以获取某个日期的年、月、日等单个单位的值 174 | 175 | 7) **一周中的时间** 176 | 177 | ```swift 178 | public var weekday: Int 179 | ``` 180 | 181 | 获取某个日期是一周中的第几天,即周几 182 | 183 | **注:周日为一周的第一天,从 0 开始,周一为 1,依此类推** 184 | 185 | #### 2. String —— 字符串扩展 186 | 187 | 1. **拼音** 188 | 189 | ```swift 190 | public func pinyin(_ type: PinyinType = .normal) -> String 191 | ``` 192 | 193 | 获取指定类型的拼音 194 | 195 | - normal - 默认不带声调的全拼 196 | 197 | - withTone - 带声调的全拼 198 | 199 | - firstLetter - 拼音首字母 200 | 201 | 2. **Base64 编码/解码** 202 | `swift public var base64Decode: String? public var base64Encode: String?` 203 | 204 | 3. **RegEx 正则表达式** 205 | 206 | ```swift 207 | /// 常用正则表达式 208 | // 邮箱 209 | public var regex_email: String 210 | // 电话号码 211 | public var regex_phone: String 212 | // 手机号码 213 | public var regex_mobile: String 214 | 215 | /// 判断是否匹配正则表达式 216 | public func match(regex: String) -> Bool 217 | /// 判断是否是邮箱 218 | public var isEmail: Bool 219 | /// 判断是否是电话号码 220 | public var isPhone: Bool 221 | /// 判断是否是手机号码 222 | public var isMobile: Bool 223 | /// 同时验证电话和手机 224 | public var isPhoneOrMobile: Bool 225 | ``` 226 | 227 | 4. **URL** 228 | 229 | ```swift 230 | // URL 编码 231 | public var URLEncode: String? 232 | // URL 解码 233 | 234 | public var URLDecode: String? 235 | 236 | ``` 237 | 238 | ``` 239 | 240 | 5. **计算大小** 241 | 242 | ```swift 243 | public func width(limitToHeight height: CGFloat, font: UIFont) -> CGFloat 244 | public func height(limitToWidth width: CGFloat, font: UIFont) -> CGFloat 245 | public func size(limitToSize size: CGSize, font: UIFont) -> CGSize 246 | ``` 247 | 248 | 根据限定的高或者宽度,计算另一项的值 249 | 250 | #### 3. UIColor 251 | 252 | 1. **16 进制颜色** 253 | 254 | ```swift 255 | public convenience init?(hex: String, alpha: CGFloat = 1) 256 | ``` 257 | 258 | 用 16 进制颜色代码创建 UIColor 对象,字符串可以是 0xaaaaaa、#aaaaaa、aaaaaa 三种格式中的任何一种 259 | 260 | #### 4. UIResponder 261 | 262 | 1. **解除任何第一响应者** 263 | 264 | ```swift 265 | 266 | public class func resignAnyFirstResponder() 267 | 268 | ``` 269 | 通过该方法可以不需要指定任何对象,直接将当前任何处于第一响应者状态的控件解除该状态 270 | 271 | ``` 272 | 273 | 2. **在 IB 中设置 - 解除第一响应者** 274 | 275 | ```swift 276 | 277 | @IBAction public func autoResignFirstResponder() 278 | 279 | ``` 280 | 在 IB 中,将特定事件指派到 FirstResponder 上的 _autoResignFirstResponder_ 方法,可以在事件触发后解除当前第一响应者状态的操作,如图: 281 | 282 | 283 | ``` 284 | 285 | 3) **在 IB 中设置 - 指定第一响应者** 286 | 287 | ```swift 288 | 289 | @IBAction public func autoBecomFirstResponder() 290 | 291 | ``` 292 | 在 IB 中,将特定事件指派到输入框的 _autoBecomFirstResponder_ 方法,可以在事件触发后使指定控件成为第一响应者,如图: 293 | 294 | ``` 295 | 296 | #### 5. UIView 297 | 298 | 1. **在 IB 中快速设置属性** 299 | 300 | ```swift 301 | 302 | @IBInspectable var cornerRadius: CGFloat // 边角半径 303 | @IBInspectable var borderWidth: CGFloat // 边框宽度 304 | @IBInspectable var borderColor: UIColor? // 边框颜色 305 | 306 | ``` 307 | 这些声明实现了直接在 IB 中设置 UIView 相关属性的功能: 308 | 309 | 310 | ``` 311 | 312 | #### 6. UIStoryboard 313 | 314 | 1. **获取 Storyboard** 315 | `swift // 获取创建项目时自动创建的 Main Stroyboard public class var main: UIStoryboard // 根据名称从 MainBundle 中创建 Storyboard public convenience init(_ name: String = "Main") {` 316 | 2. **创建视图控制器** 317 | 318 | ```swift 319 | public func create(identifier: String? = nil) -> T 320 | ``` 321 | 322 | 该方法可以从 Storyboard 创建指定的视图控制器实例,_identifier_ 为 IB 中设置的视图控制器 ID。 323 | 324 | _identifier_ 可以省略,此时要求 IB 中设置的 ID 为 视图控制器的类名,此时写法如下: 325 | 326 | ```swift 327 | let controller = UIStoryboard("Auth").create() as LoginViewController 328 | ``` 329 | 330 | 3. **入口视图控制器** 331 | 332 | ```swift 333 | public var initial: UIViewController? 334 | ` 335 | 例 336 | 337 | ``` 338 | 339 | #### 7. GCD 扩展 340 | 341 | 扩展几个 GCD 方法以更方便地调用 GCD 的延迟函数 342 | 343 | ```swift 344 | public func asyncAfter(delay: DispatchTimeInterval, execute work: @escaping @convention(block) () -> Swift.Void) 345 | 346 | public func asyncAfter(delay seconds: TimeInterval, execute work: @escaping @convention(block) () -> Swift.Void) 347 | 348 | public func asyncAfter(delay: DispatchTimeInterval, execute: DispatchWorkItem) 349 | 350 | public func asyncAfter(delay seconds: TimeInterval, execute: DispatchWorkItem) 351 | ``` 352 | 353 | 示例: 354 | 355 | ```swift 356 | DispatchQueue.global().asyncAfter(delay: 2) { 357 | print("延迟两秒执行") 358 | } 359 | 360 | DispatchQueue.global().asyncAfter(delay: .nanoseconds(2)) { 361 | print("延迟两纳秒执行") 362 | } 363 | ``` 364 | 365 | #### 8. KVO & 闭包 366 | 367 | KVO 是 Foundation 框架强大的功能之一,但是由于不支持闭包,导致实现起来比较繁琐。注册和实际处理的代码需要写在不同的地方,对于一些轻量级的逻辑来说并不十分友好。 368 | 369 | 通过 NSObject+KVOHandler 扩展,可以在注册 KVO 观察者时直接提供一个闭包来实现了。比如下面的代码实现了观察 ScrollView 的 contentOffset 的变化,可以比较一下原来的实现方式和闭包形式的实现方式。 370 | 371 | 原来的实现: 372 | 373 | ```swift 374 | override func viewDidLoad() { 375 | super.viewDidLoad() 376 | 377 | scrollView.addObserver(self, forKeyPath: "contentOffset", context: nil) 378 | } 379 | 380 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 381 | if keyPath == "contentOffset" { 382 | // Do something 383 | } 384 | } 385 | ``` 386 | 387 | 使用闭包实现: 388 | 389 | ```swift 390 | override func viewDidLoad() { 391 | super.viewDidLoad() 392 | 393 | scrollView.addObserver(for: "contentOffset") { (_, _, _) in 394 | // Do something 395 | } 396 | } 397 | ``` 398 | 399 | ### 移除 400 | 401 | 1. 移除 MD5 编码、RC4 加密等相关内容。推荐使用更加成熟的加密框架: [CryptoSwift](https://github.com/krzyzanowskim/CryptoSwift),支持更广泛的加密协议。 402 | 2. 移除 HKUserDefaults。RC4 属于已过时的加密方式,随着 RC4 加密的移除将 KUserDefaults 一并移除了,有加密需求推荐使用更成熟的第三方加密框架。 403 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harley-xk/Comet/3c579fdd47767c463dc072f0840786b0aa30f95b/icon.png --------------------------------------------------------------------------------