├── .gitignore ├── .history ├── CWUtils_20191024163238.podspec ├── CWUtils_20191210115354.podspec ├── CWUtils_20191210115402.podspec ├── CWUtils_20191210185735.podspec ├── README_20190416152425.md ├── README_20191210115328.md ├── README_20191210190702.md ├── README_20191210191136.md └── README_20191210191153.md ├── CWUtils.podspec ├── CWUtilsTest.podspec ├── Docs └── spm.png ├── Example ├── Example.xcodeproj │ └── project.pbxproj ├── Example.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── trust_black.imageset │ │ │ ├── Contents.json │ │ │ └── trust me.png │ │ └── trust_white.imageset │ │ │ ├── Contents.json │ │ │ └── trust me2.png │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── ExampleTests │ ├── ExampleTests.swift │ └── Info.plist ├── Gemfile ├── Gemfile.lock ├── Makefile └── Podfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── Sources ├── CWUtils │ ├── CWUtils.swift │ ├── Core │ │ ├── Date │ │ │ ├── Date+Ago.swift │ │ │ ├── Date+Format.swift │ │ │ └── Date+Milli.swift │ │ ├── Foundation │ │ │ ├── Array+Equtable.swift │ │ │ ├── Codable+Dictionary.swift │ │ │ ├── Number+Format.swift │ │ │ └── URL+Convenient.swift │ │ ├── Functions │ │ │ ├── AppVersion.swift │ │ │ ├── DLog.swift │ │ │ ├── Device.swift │ │ │ ├── Executor.swift │ │ │ ├── Functions.swift │ │ │ └── Schedulers.swift │ │ ├── RxSwift │ │ │ └── RxSwift+RetryInterval.swift │ │ └── String │ │ │ ├── AttributeString+Style.swift │ │ │ ├── String+Measure.swift │ │ │ ├── String+Trans.swift │ │ │ └── String+Validation.swift │ └── UI │ │ ├── Button+Tap.swift │ │ ├── CWViewController.swift │ │ ├── Color+hex.swift │ │ ├── DarkMode.swift │ │ ├── Image+Resize.swift │ │ ├── Image+Rx.swift │ │ ├── ImagePicker.swift │ │ ├── Keyboard.swift │ │ ├── PandaExtension.swift │ │ ├── Popup.swift │ │ ├── Reusable+Rx.swift │ │ ├── ScrollView+More.swift │ │ ├── StackView+Remove.swift │ │ ├── TableView+Reload.swift │ │ ├── UIColorExtensions.swift │ │ ├── UIControl+Rx.swift │ │ ├── View+GestureRecognizer.swift │ │ ├── View+IB.swift │ │ ├── View+Measure.swift │ │ ├── View+Subview.swift │ │ └── ViewCreator.swift └── CWUtilsTest │ └── Test │ └── Await.swift └── Tests ├── CWUtilsTests ├── CWUtilsTests.swift └── XCTestManifests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | index/ 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata/ 20 | 21 | ## Other 22 | *.moved-aside 23 | *.xccheckout 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | *.dSYM.zip 30 | *.dSYM 31 | 32 | ## Playgrounds 33 | timeline.xctimeline 34 | playground.xcworkspace 35 | 36 | # Swift Package Manager 37 | # 38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 39 | # Packages/ 40 | # Package.pins 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots 68 | fastlane/test_output 69 | 70 | Example/Podfile\.lock 71 | -------------------------------------------------------------------------------- /.history/CWUtils_20191024163238.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CWUtils" 3 | s.version = "0.15.2" 4 | s.summary = "iOS 어플리케이션 개발용 유틸리티 모음" 5 | s.description = "RxSwift 를 기반으로 하고 있음. (Dependancy : RxSwift, RxCocoa, RxOptional)" 6 | 7 | s.homepage = "https://github.com/iamchiwon/CWUtils" 8 | s.license = { :type => "MIT", :file => "LICENSE" } 9 | s.author = { "iamchiwon" => "iamchiwon@gmail.com" } 10 | 11 | s.swift_versions = '5' 12 | s.ios.deployment_target = '10.0' 13 | s.source = { :git => "https://github.com/iamchiwon/CWUtils.git", :tag => s.version.to_s } 14 | s.source_files = "CWUtils/Classes/**/*" 15 | 16 | # s.resource_bundles = { 17 | # 'CWUtils' => ['CWUtils/Assets/*.png'] 18 | # } 19 | 20 | s.dependency "RxSwift" 21 | s.dependency "RxCocoa" 22 | s.dependency "RxSwiftExt" 23 | s.dependency "RxOptional" 24 | s.dependency "RxViewController" 25 | s.dependency "SnapKit" 26 | s.dependency "Reusable" 27 | s.dependency "Then" 28 | s.dependency "Kingfisher" 29 | 30 | end 31 | -------------------------------------------------------------------------------- /.history/CWUtils_20191210115354.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CWUtils" 3 | s.version = "0.15.2" 4 | s.summary = "iOS 어플리케이션 개발용 유틸리티 모음" 5 | s.description = "RxSwift 를 기반으로 하고 있음. (Dependancy : RxSwift, RxCocoa, RxOptional)" 6 | 7 | s.homepage = "https://github.com/iamchiwon/CWUtils" 8 | s.license = { :type => "MIT", :file => "LICENSE" } 9 | s.author = { "iamchiwon" => "iamchiwon@gmail.com" } 10 | 11 | s.swift_versions = '5' 12 | s.ios.deployment_target = '10.0' 13 | s.source = { :git => "https://github.com/iamchiwon/CWUtils.git", :tag => s.version.to_s } 14 | s.source_files = "CWUtils/Classes/**/*" 15 | 16 | # s.resource_bundles = { 17 | # 'CWUtils' => ['CWUtils/Assets/*.png'] 18 | # } 19 | 20 | s.dependency "RxSwift" 21 | s.dependency "RxCocoa" 22 | s.dependency "RxSwiftExt" 23 | s.dependency "RxOptional" 24 | s.dependency "RxViewController" 25 | s.dependency "SnapKit" 26 | s.dependency "Panda" 27 | s.dependency "Reusable" 28 | s.dependency "Then" 29 | s.dependency "Kingfisher" 30 | 31 | end 32 | -------------------------------------------------------------------------------- /.history/CWUtils_20191210115402.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CWUtils" 3 | s.version = "0.16.0" 4 | s.summary = "iOS 어플리케이션 개발용 유틸리티 모음" 5 | s.description = "RxSwift 를 기반으로 하고 있음. (Dependancy : RxSwift, RxCocoa, RxOptional)" 6 | 7 | s.homepage = "https://github.com/iamchiwon/CWUtils" 8 | s.license = { :type => "MIT", :file => "LICENSE" } 9 | s.author = { "iamchiwon" => "iamchiwon@gmail.com" } 10 | 11 | s.swift_versions = '5' 12 | s.ios.deployment_target = '10.0' 13 | s.source = { :git => "https://github.com/iamchiwon/CWUtils.git", :tag => s.version.to_s } 14 | s.source_files = "CWUtils/Classes/**/*" 15 | 16 | # s.resource_bundles = { 17 | # 'CWUtils' => ['CWUtils/Assets/*.png'] 18 | # } 19 | 20 | s.dependency "RxSwift" 21 | s.dependency "RxCocoa" 22 | s.dependency "RxSwiftExt" 23 | s.dependency "RxOptional" 24 | s.dependency "RxViewController" 25 | s.dependency "SnapKit" 26 | s.dependency "Panda" 27 | s.dependency "Reusable" 28 | s.dependency "Then" 29 | s.dependency "Kingfisher" 30 | 31 | end 32 | -------------------------------------------------------------------------------- /.history/CWUtils_20191210185735.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CWUtils" 3 | s.version = "0.16.1" 4 | s.summary = "iOS 어플리케이션 개발용 유틸리티 모음" 5 | s.description = "RxSwift 를 기반으로 하고 있음. (Dependancy : RxSwift, RxCocoa, RxOptional)" 6 | 7 | s.homepage = "https://github.com/iamchiwon/CWUtils" 8 | s.license = { :type => "MIT", :file => "LICENSE" } 9 | s.author = { "iamchiwon" => "iamchiwon@gmail.com" } 10 | 11 | s.swift_versions = '5' 12 | s.ios.deployment_target = '10.0' 13 | s.source = { :git => "https://github.com/iamchiwon/CWUtils.git", :tag => s.version.to_s } 14 | s.source_files = "CWUtils/Classes/**/*" 15 | 16 | # s.resource_bundles = { 17 | # 'CWUtils' => ['CWUtils/Assets/*.png'] 18 | # } 19 | 20 | s.dependency "RxSwift" 21 | s.dependency "RxCocoa" 22 | s.dependency "RxSwiftExt" 23 | s.dependency "RxOptional" 24 | s.dependency "RxViewController" 25 | s.dependency "SnapKit" 26 | s.dependency "Panda" 27 | s.dependency "Reusable" 28 | s.dependency "Then" 29 | s.dependency "Kingfisher" 30 | 31 | end 32 | -------------------------------------------------------------------------------- /.history/README_20190416152425.md: -------------------------------------------------------------------------------- 1 | # CWUtils 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 4 | [![License](https://img.shields.io/cocoapods/l/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 5 | [![Platform](https://img.shields.io/cocoapods/p/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 6 | 7 | ## Updated 8 | - 0.14.0 : Swift 5, target iOS 10.0 9 | 10 | ## Installation 11 | 12 | CWUtils is available through [CocoaPods](http://cocoapods.org). To install 13 | it, simply add the following line to your Podfile: 14 | 15 | ```ruby 16 | pod 'CWUtils' 17 | ``` 18 | 19 | ## Dependancies 20 | 21 | - [RxSwift](https://github.com/ReactiveX/RxSwift) 22 | - [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa) 23 | - [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt) 24 | - [RxOptional](https://github.com/RxSwiftCommunity/RxOptional) 25 | - [RxViewController](https://github.com/devxoul/RxViewController) 26 | - [SnapKit](https://github.com/SnapKit/SnapKit) 27 | - [Reusable](https://github.com/AliSoftware/Reusable) 28 | - [Then](https://github.com/devxoul/Then) 29 | - [Kingfisher](https://github.com/onevcat/Kingfisher) 30 | 31 | ## Sample Code 32 | 33 | ### Utilities 34 | 35 | #### Logs 36 | 37 | ```swift 38 | DLog("log for debug build only") 39 | ELog("error log for all build") 40 | ``` 41 | 42 | #### Device 43 | 44 | ```swift 45 | runOnSimulatorOnly { 46 | print("running on simulator") 47 | } 48 | runOnDeviceOnly { 49 | print("running on device") 50 | } 51 | ``` 52 | 53 | #### Version Check 54 | 55 | ```swift 56 | let currentVersion: String = currentApplicationVersion() 57 | let needUpdate: Bool = isUpdateAvailable() 58 | ``` 59 | 60 | #### Validataion 61 | 62 | ```swift 63 | let isEmail = "nobody@email.com".isValid(withType: .email) 64 | let isEmail2 = "nobody@email.com".isValid(withRegEx: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}") 65 | ``` 66 | 67 | ### Extensions 68 | 69 | #### Strings 70 | 71 | ```swift 72 | let url = "www.apple.com".url() 73 | let base64 = "hello world".base64() 74 | let trimmed = " hello world \n \n ".trim() 75 | let localized = "Hello World".localized() 76 | ``` 77 | 78 | #### Date 79 | 80 | ```swift 81 | let serverDateParser = Date.fromFormat("yyyy-MM-dd HH:mm:ss") 82 | let date = serverDateParser("2018-02-17 17:00:00") 83 | ``` 84 | 85 | #### Array 86 | ```swift 87 | struct Data : Equatable {} 88 | var array: [Data] 89 | //-------- 90 | let data = Data() 91 | let exists: Bool = array.exists(data) 92 | let index: Int = array.indexOf(data) 93 | let array2 = array.removed(data) 94 | array.remove(item: data) //mutating 95 | ``` 96 | 97 | #### Codable 98 | ```swift 99 | struct Data : Codable {} 100 | //-------- 101 | let data = Data() 102 | let dictionary: [String:Any] = data.dictionary() 103 | let jsonString: String = data.json() 104 | let jsonString2: String = dictionary.json() 105 | let data2: Data = dictionary.decode() 106 | ``` 107 | 108 | ### UI 109 | 110 | #### UIColor 111 | 112 | ```swift 113 | let red = UIColor(red:255, green:0, blue:0) 114 | let blue = UIColor(0x00FF00) 115 | let green = UIColor("#0000FF") 116 | ``` 117 | 118 | #### subview 119 | 120 | ```swift 121 | //view with tag 122 | view.label(100)?.text = "Hello World" 123 | //image with tag 124 | view.imageView(100)?image = nil 125 | ``` 126 | 127 | #### popup 128 | 129 | ```swift 130 | if needUpdate { 131 | popupOkCancel(on: self, title: "업데이트", message: "업데이트가 필요합니다.", onOk: { 132 | let urlString = "itms-apps://itunes.apple.com/app/id0000000000" 133 | UIApplication.shared.openURL(URL(string: urlString)!) 134 | }) 135 | } else { 136 | popupOK(on: self, title: "업데이트", message: "최신버전입니다.") 137 | } 138 | ``` 139 | 140 | #### Image Resize 141 | 142 | ```swift 143 | let original = UIImage(named: "big_image") 144 | let resized = original?.resized(maxSize: CGSize(width: 100, height: 100)) 145 | let resizedForScreen = original?.resizedToScreen() 146 | ``` 147 | 148 | #### View Createor (with SnapKit) 149 | 150 | ```swift 151 | let rect = createView(UIView(), parent: self.view, setting: { v in 152 | v.backgroundColor = .red 153 | }, constraint: { m in 154 | m.top.left.equalToSuperview() 155 | m.width.height.equalTo(100) 156 | }) 157 | ``` 158 | 159 | ### RxSwift 160 | 161 | #### GestureRecognizer 162 | 163 | ```swift 164 | view.addTapGestureRecognizer() 165 | .subscribe(onNext: { (tapGusture : UITapGestureRecognizer) in 166 | }) 167 | 168 | view.addSwipeGestureRecognizer() 169 | .subscribe(onNext: { (tapGusture : UISwipeGestureRecognizer) in 170 | }) 171 | ``` 172 | 173 | #### Button 174 | 175 | ```swift 176 | //prevent double touched 177 | view.button(100)?.whenTouchUpInside(thro) 178 | .subscribe(onNext: { button in 179 | }) 180 | ``` 181 | 182 | #### Keyboard Notification 183 | 184 | ```swift 185 | whenKeyboardShowNotification() 186 | .subscribe(onNext: { (keyboardHeight: CGFloat, duration: TimeInterval) in 187 | }) 188 | .disposed(by: disposeBag) 189 | 190 | whenKeyboardHideNotification() 191 | .subscribe(onNext: { (duration: TimeInterval) in 192 | }) 193 | .disposed(by: disposeBag) 194 | ``` 195 | 196 | #### ScrollView 197 | 198 | ```swift 199 | collectionView.needsMore() 200 | .filter { !self.isLoading } 201 | .subscribe(onNext: { self.loadMore() }) 202 | .disposed(by: disposeBag) 203 | ``` 204 | 205 | #### Image Picker 206 | ```swift 207 | pickImageFromAlbum(on: self, withEdit: true) 208 | .subscribe(onNext: { self.imageView.image = $0 }) 209 | ``` 210 | 211 | ## History 212 | 213 | - 0.9.2 : Support Swift 4.2 214 | 215 | ## Author 216 | 217 | iamchiwon, iamchiwon@gmail.com 218 | 219 | ## License 220 | 221 | CWUtils is available under the MIT license. See the LICENSE file for more info. 222 | -------------------------------------------------------------------------------- /.history/README_20191210115328.md: -------------------------------------------------------------------------------- 1 | # CWUtils 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 4 | [![License](https://img.shields.io/cocoapods/l/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 5 | [![Platform](https://img.shields.io/cocoapods/p/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 6 | 7 | ## Updated 8 | 9 | - 0.14.0 : Swift 5, target iOS 10.0 10 | 11 | ## Installation 12 | 13 | CWUtils is available through [CocoaPods](http://cocoapods.org). To install 14 | it, simply add the following line to your Podfile: 15 | 16 | ```ruby 17 | pod 'CWUtils' 18 | ``` 19 | 20 | ## Dependancies 21 | 22 | - [RxSwift](https://github.com/ReactiveX/RxSwift) 23 | - [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa) 24 | - [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt) 25 | - [RxOptional](https://github.com/RxSwiftCommunity/RxOptional) 26 | - [RxViewController](https://github.com/devxoul/RxViewController) 27 | - [SnapKit](https://github.com/SnapKit/SnapKit) 28 | - [Reusable](https://github.com/AliSoftware/Reusable) 29 | - [Then](https://github.com/devxoul/Then) 30 | - [Kingfisher](https://github.com/onevcat/Kingfisher) 31 | 32 | ## Sample Code 33 | 34 | ### Utilities 35 | 36 | #### Logs 37 | 38 | ```swift 39 | DLog("log for debug build only") 40 | ELog("error log for all build") 41 | ``` 42 | 43 | #### Device 44 | 45 | ```swift 46 | runOnSimulatorOnly { 47 | print("running on simulator") 48 | } 49 | runOnDeviceOnly { 50 | print("running on device") 51 | } 52 | ``` 53 | 54 | #### Version Check 55 | 56 | ```swift 57 | let currentVersion: String = currentApplicationVersion() 58 | let needUpdate: Bool = isUpdateAvailable() 59 | ``` 60 | 61 | #### Validataion 62 | 63 | ```swift 64 | let isEmail = "nobody@email.com".isValid(withType: .email) 65 | let isEmail2 = "nobody@email.com".isValid(withRegEx: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}") 66 | ``` 67 | 68 | ### Extensions 69 | 70 | #### Strings 71 | 72 | ```swift 73 | let url = "www.apple.com".url() 74 | let base64 = "hello world".base64() 75 | let trimmed = " hello world \n \n ".trim() 76 | let localized = "Hello World".localized() 77 | ``` 78 | 79 | #### Date 80 | 81 | ```swift 82 | let serverDateParser = Date.fromFormat("yyyy-MM-dd HH:mm:ss") 83 | let date = serverDateParser("2018-02-17 17:00:00") 84 | ``` 85 | 86 | #### Array 87 | 88 | ```swift 89 | struct Data : Equatable {} 90 | var array: [Data] 91 | //-------- 92 | let data = Data() 93 | let exists: Bool = array.exists(data) 94 | let index: Int = array.indexOf(data) 95 | let array2 = array.removed(data) 96 | array.remove(item: data) //mutating 97 | ``` 98 | 99 | #### Codable 100 | 101 | ```swift 102 | struct Data : Codable {} 103 | //-------- 104 | let data = Data() 105 | let dictionary: [String:Any] = data.dictionary() 106 | let jsonString: String = data.json() 107 | let jsonString2: String = dictionary.json() 108 | let data2: Data = dictionary.decode() 109 | ``` 110 | 111 | ### UI 112 | 113 | #### UIColor 114 | 115 | ```swift 116 | let red = UIColor(red:255, green:0, blue:0) 117 | let blue = UIColor(0x00FF00) 118 | let green = UIColor("#0000FF") 119 | ``` 120 | 121 | #### subview 122 | 123 | ```swift 124 | //view with tag 125 | view.label(100)?.text = "Hello World" 126 | //image with tag 127 | view.imageView(100)?image = nil 128 | ``` 129 | 130 | #### popup 131 | 132 | ```swift 133 | if needUpdate { 134 | popupOkCancel(on: self, title: "업데이트", message: "업데이트가 필요합니다.", onOk: { 135 | let urlString = "itms-apps://itunes.apple.com/app/id0000000000" 136 | UIApplication.shared.openURL(URL(string: urlString)!) 137 | }) 138 | } else { 139 | popupOK(on: self, title: "업데이트", message: "최신버전입니다.") 140 | } 141 | ``` 142 | 143 | #### Image Resize 144 | 145 | ```swift 146 | let original = UIImage(named: "big_image") 147 | let resized = original?.resized(maxSize: CGSize(width: 100, height: 100)) 148 | let resizedForScreen = original?.resizedToScreen() 149 | ``` 150 | 151 | #### View Createor (with SnapKit) 152 | 153 | ```swift 154 | let rect = createView(UIView(), parent: self.view, setting: { v in 155 | v.backgroundColor = .red 156 | }, constraint: { m in 157 | m.top.left.equalToSuperview() 158 | m.width.height.equalTo(100) 159 | }) 160 | ``` 161 | 162 | ### RxSwift 163 | 164 | #### GestureRecognizer 165 | 166 | ```swift 167 | view.addTapGestureRecognizer() 168 | .subscribe(onNext: { (tapGusture : UITapGestureRecognizer) in 169 | }) 170 | 171 | view.addSwipeGestureRecognizer() 172 | .subscribe(onNext: { (tapGusture : UISwipeGestureRecognizer) in 173 | }) 174 | ``` 175 | 176 | #### Button 177 | 178 | ```swift 179 | //prevent double touched 180 | view.button(100)?.whenTouchUpInside(thro) 181 | .subscribe(onNext: { button in 182 | }) 183 | ``` 184 | 185 | #### Keyboard Notification 186 | 187 | ```swift 188 | whenKeyboardShowNotification() 189 | .subscribe(onNext: { (keyboardHeight: CGFloat, duration: TimeInterval) in 190 | }) 191 | .disposed(by: disposeBag) 192 | 193 | whenKeyboardHideNotification() 194 | .subscribe(onNext: { (duration: TimeInterval) in 195 | }) 196 | .disposed(by: disposeBag) 197 | ``` 198 | 199 | #### ScrollView 200 | 201 | ```swift 202 | collectionView.needsMore() 203 | .filter { !self.isLoading } 204 | .subscribe(onNext: { self.loadMore() }) 205 | .disposed(by: disposeBag) 206 | ``` 207 | 208 | #### Image Picker 209 | 210 | ```swift 211 | pickImageFromAlbum(on: self, withEdit: true) 212 | .subscribe(onNext: { self.imageView.image = $0 }) 213 | ``` 214 | 215 | ## History 216 | 217 | - 0.9.2 : Support Swift 4.2 218 | 219 | ## Author 220 | 221 | iamchiwon, iamchiwon@gmail.com 222 | 223 | ## License 224 | 225 | CWUtils is available under the MIT license. See the LICENSE file for more info. 226 | -------------------------------------------------------------------------------- /.history/README_20191210190702.md: -------------------------------------------------------------------------------- 1 | # CWUtils 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 4 | [![License](https://img.shields.io/cocoapods/l/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 5 | [![Platform](https://img.shields.io/cocoapods/p/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 6 | 7 | ## Updated 8 | 9 | - 0.14.0 : Swift 5, target iOS 10.0 10 | 11 | ## Installation 12 | 13 | CWUtils is available through [CocoaPods](http://cocoapods.org). To install 14 | it, simply add the following line to your Podfile: 15 | 16 | ```ruby 17 | pod 'CWUtils' 18 | ``` 19 | 20 | ## Dependancies 21 | 22 | - [RxSwift](https://github.com/ReactiveX/RxSwift) 23 | - [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa) 24 | - [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt) 25 | - [RxOptional](https://github.com/RxSwiftCommunity/RxOptional) 26 | - [RxViewController](https://github.com/devxoul/RxViewController) 27 | - [SnapKit](https://github.com/SnapKit/SnapKit) 28 | - [Panda](https://github.com/wordlessj/Panda) 29 | - [Reusable](https://github.com/AliSoftware/Reusable) 30 | - [Then](https://github.com/devxoul/Then) 31 | - [Kingfisher](https://github.com/onevcat/Kingfisher) 32 | 33 | ## Sample Code 34 | 35 | ### Utilities 36 | 37 | #### Logs 38 | 39 | ```swift 40 | DLog("log for debug build only") 41 | ELog("error log for all build") 42 | ``` 43 | 44 | #### Device 45 | 46 | ```swift 47 | runOnSimulatorOnly { 48 | print("running on simulator") 49 | } 50 | runOnDeviceOnly { 51 | print("running on device") 52 | } 53 | ``` 54 | 55 | #### Version Check 56 | 57 | ```swift 58 | let currentVersion: String = currentApplicationVersion() 59 | let needUpdate: Bool = isUpdateAvailable() 60 | ``` 61 | 62 | #### Validataion 63 | 64 | ```swift 65 | let isEmail = "nobody@email.com".isValid(withType: .email) 66 | let isEmail2 = "nobody@email.com".isValid(withRegEx: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}") 67 | ``` 68 | 69 | ### Extensions 70 | 71 | #### Strings 72 | 73 | ```swift 74 | let url = "www.apple.com".url() 75 | let base64 = "hello world".base64() 76 | let trimmed = " hello world \n \n ".trim() 77 | let localized = "Hello World".localized() 78 | ``` 79 | 80 | #### Date 81 | 82 | ```swift 83 | let serverDateParser = Date.fromFormat("yyyy-MM-dd HH:mm:ss") 84 | let date = serverDateParser("2018-02-17 17:00:00") 85 | ``` 86 | 87 | #### Array 88 | 89 | ```swift 90 | struct Data : Equatable {} 91 | var array: [Data] 92 | //-------- 93 | let data = Data() 94 | let exists: Bool = array.exists(data) 95 | let index: Int = array.indexOf(data) 96 | let array2 = array.removed(data) 97 | array.remove(item: data) //mutating 98 | ``` 99 | 100 | #### Codable 101 | 102 | ```swift 103 | struct Data : Codable {} 104 | //-------- 105 | let data = Data() 106 | let dictionary: [String:Any] = data.dictionary() 107 | let jsonString: String = data.json() 108 | let jsonString2: String = dictionary.json() 109 | let data2: Data = dictionary.decode() 110 | ``` 111 | 112 | ### UI 113 | 114 | #### UIColor 115 | 116 | ```swift 117 | let red = UIColor(red:255, green:0, blue:0) 118 | let blue = UIColor(0x00FF00) 119 | let green = UIColor("#0000FF") 120 | ``` 121 | 122 | #### subview 123 | 124 | ```swift 125 | //view with tag 126 | view.label(100)?.text = "Hello World" 127 | //image with tag 128 | view.imageView(100)?image = nil 129 | ``` 130 | 131 | #### popup 132 | 133 | ```swift 134 | if needUpdate { 135 | popupOkCancel(on: self, title: "업데이트", message: "업데이트가 필요합니다.", onOk: { 136 | let urlString = "itms-apps://itunes.apple.com/app/id0000000000" 137 | UIApplication.shared.openURL(URL(string: urlString)!) 138 | }) 139 | } else { 140 | popupOK(on: self, title: "업데이트", message: "최신버전입니다.") 141 | } 142 | ``` 143 | 144 | #### Image Resize 145 | 146 | ```swift 147 | let original = UIImage(named: "big_image") 148 | let resized = original?.resized(maxSize: CGSize(width: 100, height: 100)) 149 | let resizedForScreen = original?.resizedToScreen() 150 | ``` 151 | 152 | #### View Createor (with SnapKit) 153 | 154 | ```swift 155 | let rect = createView(UIView(), parent: self.view, setting: { v in 156 | v.backgroundColor = .red 157 | }, constraint: { m in 158 | m.top.left.equalToSuperview() 159 | m.width.height.equalTo(100) 160 | }) 161 | ``` 162 | 163 | ### RxSwift 164 | 165 | #### GestureRecognizer 166 | 167 | ```swift 168 | view.addTapGestureRecognizer() 169 | .subscribe(onNext: { (tapGusture : UITapGestureRecognizer) in 170 | }) 171 | 172 | view.addSwipeGestureRecognizer() 173 | .subscribe(onNext: { (tapGusture : UISwipeGestureRecognizer) in 174 | }) 175 | ``` 176 | 177 | #### Button 178 | 179 | ```swift 180 | //prevent double touched 181 | view.button(100)?.whenTouchUpInside(thro) 182 | .subscribe(onNext: { button in 183 | }) 184 | ``` 185 | 186 | #### Keyboard Notification 187 | 188 | ```swift 189 | whenKeyboardShowNotification() 190 | .subscribe(onNext: { (keyboardHeight: CGFloat, duration: TimeInterval) in 191 | }) 192 | .disposed(by: disposeBag) 193 | 194 | whenKeyboardHideNotification() 195 | .subscribe(onNext: { (duration: TimeInterval) in 196 | }) 197 | .disposed(by: disposeBag) 198 | ``` 199 | 200 | #### ScrollView 201 | 202 | ```swift 203 | collectionView.needsMore() 204 | .filter { !self.isLoading } 205 | .subscribe(onNext: { self.loadMore() }) 206 | .disposed(by: disposeBag) 207 | ``` 208 | 209 | #### Image Picker 210 | 211 | ```swift 212 | pickImageFromAlbum(on: self, withEdit: true) 213 | .subscribe(onNext: { self.imageView.image = $0 }) 214 | ``` 215 | 216 | ## History 217 | 218 | - 0.9.2 : Support Swift 4.2 219 | 220 | ## Author 221 | 222 | iamchiwon, iamchiwon@gmail.com 223 | 224 | ## License 225 | 226 | CWUtils is available under the MIT license. See the LICENSE file for more info. 227 | -------------------------------------------------------------------------------- /.history/README_20191210191136.md: -------------------------------------------------------------------------------- 1 | # CWUtils 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 4 | [![License](https://img.shields.io/cocoapods/l/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 5 | [![Platform](https://img.shields.io/cocoapods/p/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 6 | 7 | ## Installation 8 | 9 | CWUtils is available through [CocoaPods](http://cocoapods.org). To install 10 | it, simply add the following line to your Podfile: 11 | 12 | ```ruby 13 | pod 'CWUtils' 14 | ``` 15 | 16 | ## Dependancies 17 | 18 | - [RxSwift](https://github.com/ReactiveX/RxSwift) 19 | - [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa) 20 | - [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt) 21 | - [RxOptional](https://github.com/RxSwiftCommunity/RxOptional) 22 | - [RxViewController](https://github.com/devxoul/RxViewController) 23 | - [SnapKit](https://github.com/SnapKit/SnapKit) 24 | - [Panda](https://github.com/wordlessj/Panda) 25 | - [Reusable](https://github.com/AliSoftware/Reusable) 26 | - [Then](https://github.com/devxoul/Then) 27 | - [Kingfisher](https://github.com/onevcat/Kingfisher) 28 | 29 | ## Sample Code 30 | 31 | ### Utilities 32 | 33 | #### Logs 34 | 35 | ```swift 36 | DLog("log for debug build only") 37 | ELog("error log for all build") 38 | ``` 39 | 40 | #### Device 41 | 42 | ```swift 43 | runOnSimulatorOnly { 44 | print("running on simulator") 45 | } 46 | runOnDeviceOnly { 47 | print("running on device") 48 | } 49 | ``` 50 | 51 | #### Version Check 52 | 53 | ```swift 54 | let currentVersion: String = currentApplicationVersion() 55 | let needUpdate: Bool = isUpdateAvailable() 56 | ``` 57 | 58 | #### Validataion 59 | 60 | ```swift 61 | let isEmail = "nobody@email.com".isValid(withType: .email) 62 | let isEmail2 = "nobody@email.com".isValid(withRegEx: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}") 63 | ``` 64 | 65 | ### Extensions 66 | 67 | #### Strings 68 | 69 | ```swift 70 | let url = "www.apple.com".url() 71 | let base64 = "hello world".base64() 72 | let trimmed = " hello world \n \n ".trim() 73 | let localized = "Hello World".localized() 74 | ``` 75 | 76 | #### Date 77 | 78 | ```swift 79 | let serverDateParser = Date.fromFormat("yyyy-MM-dd HH:mm:ss") 80 | let date = serverDateParser("2018-02-17 17:00:00") 81 | ``` 82 | 83 | #### Array 84 | 85 | ```swift 86 | struct Data : Equatable {} 87 | var array: [Data] 88 | //-------- 89 | let data = Data() 90 | let exists: Bool = array.exists(data) 91 | let index: Int = array.indexOf(data) 92 | let array2 = array.removed(data) 93 | array.remove(item: data) //mutating 94 | ``` 95 | 96 | #### Codable 97 | 98 | ```swift 99 | struct Data : Codable {} 100 | //-------- 101 | let data = Data() 102 | let dictionary: [String:Any] = data.dictionary() 103 | let jsonString: String = data.json() 104 | let jsonString2: String = dictionary.json() 105 | let data2: Data = dictionary.decode() 106 | ``` 107 | 108 | ### UI 109 | 110 | #### UIColor 111 | 112 | ```swift 113 | let red = UIColor(red:255, green:0, blue:0) 114 | let blue = UIColor(0x00FF00) 115 | let green = UIColor("#0000FF") 116 | ``` 117 | 118 | #### subview 119 | 120 | ```swift 121 | //view with tag 122 | view.label(100)?.text = "Hello World" 123 | //image with tag 124 | view.imageView(100)?image = nil 125 | ``` 126 | 127 | #### popup 128 | 129 | ```swift 130 | if needUpdate { 131 | popupOkCancel(on: self, title: "업데이트", message: "업데이트가 필요합니다.", onOk: { 132 | let urlString = "itms-apps://itunes.apple.com/app/id0000000000" 133 | UIApplication.shared.openURL(URL(string: urlString)!) 134 | }) 135 | } else { 136 | popupOK(on: self, title: "업데이트", message: "최신버전입니다.") 137 | } 138 | ``` 139 | 140 | #### Image Resize 141 | 142 | ```swift 143 | let original = UIImage(named: "big_image") 144 | let resized = original?.resized(maxSize: CGSize(width: 100, height: 100)) 145 | let resizedForScreen = original?.resizedToScreen() 146 | ``` 147 | 148 | #### View Createor (with SnapKit) 149 | 150 | ```swift 151 | let rect = createView(UIView(), parent: self.view, setting: { v in 152 | v.backgroundColor = .red 153 | }, constraint: { m in 154 | m.top.left.equalToSuperview() 155 | m.width.height.equalTo(100) 156 | }) 157 | ``` 158 | 159 | ### RxSwift 160 | 161 | #### GestureRecognizer 162 | 163 | ```swift 164 | view.addTapGestureRecognizer() 165 | .subscribe(onNext: { (tapGusture : UITapGestureRecognizer) in 166 | }) 167 | 168 | view.addSwipeGestureRecognizer() 169 | .subscribe(onNext: { (tapGusture : UISwipeGestureRecognizer) in 170 | }) 171 | ``` 172 | 173 | #### Button 174 | 175 | ```swift 176 | //prevent double touched 177 | view.button(100)?.whenTouchUpInside(thro) 178 | .subscribe(onNext: { button in 179 | }) 180 | ``` 181 | 182 | #### Keyboard Notification 183 | 184 | ```swift 185 | whenKeyboardShowNotification() 186 | .subscribe(onNext: { (keyboardHeight: CGFloat, duration: TimeInterval) in 187 | }) 188 | .disposed(by: disposeBag) 189 | 190 | whenKeyboardHideNotification() 191 | .subscribe(onNext: { (duration: TimeInterval) in 192 | }) 193 | .disposed(by: disposeBag) 194 | ``` 195 | 196 | #### ScrollView 197 | 198 | ```swift 199 | collectionView.needsMore() 200 | .filter { !self.isLoading } 201 | .subscribe(onNext: { self.loadMore() }) 202 | .disposed(by: disposeBag) 203 | ``` 204 | 205 | #### Image Picker 206 | 207 | ```swift 208 | pickImageFromAlbum(on: self, withEdit: true) 209 | .subscribe(onNext: { self.imageView.image = $0 }) 210 | ``` 211 | 212 | ## History 213 | 214 | - 0.16.0 : Panda and extensions, darkmode 215 | - 0.15.0 : image load/save w/ Rx 216 | - 0.14.0 : Swift 5, target iOS 10.0 217 | - 0.13.0 : executors 218 | - 0.12.0 : Extensions (String, Number, URL), Functions (currying) 219 | - ... 220 | 221 | ## Author 222 | 223 | iamchiwon, iamchiwon@gmail.com 224 | 225 | ## License 226 | 227 | CWUtils is available under the MIT license. See the LICENSE file for more info. 228 | -------------------------------------------------------------------------------- /.history/README_20191210191153.md: -------------------------------------------------------------------------------- 1 | # CWUtils 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 4 | [![License](https://img.shields.io/cocoapods/l/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 5 | [![Platform](https://img.shields.io/cocoapods/p/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 6 | 7 | ## Installation 8 | 9 | CWUtils is available through [CocoaPods](http://cocoapods.org). To install 10 | it, simply add the following line to your Podfile: 11 | 12 | ```ruby 13 | pod 'CWUtils' 14 | ``` 15 | 16 | ## Dependancies 17 | 18 | - [RxSwift](https://github.com/ReactiveX/RxSwift) 19 | - [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa) 20 | - [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt) 21 | - [RxOptional](https://github.com/RxSwiftCommunity/RxOptional) 22 | - [RxViewController](https://github.com/devxoul/RxViewController) 23 | - [SnapKit](https://github.com/SnapKit/SnapKit) 24 | - [Panda](https://github.com/wordlessj/Panda) 25 | - [Reusable](https://github.com/AliSoftware/Reusable) 26 | - [Then](https://github.com/devxoul/Then) 27 | - [Kingfisher](https://github.com/onevcat/Kingfisher) 28 | 29 | ## Sample Code 30 | 31 | ### Utilities 32 | 33 | #### Logs 34 | 35 | ```swift 36 | DLog("log for debug build only") 37 | ELog("error log for all build") 38 | ``` 39 | 40 | #### Device 41 | 42 | ```swift 43 | runOnSimulatorOnly { 44 | print("running on simulator") 45 | } 46 | runOnDeviceOnly { 47 | print("running on device") 48 | } 49 | ``` 50 | 51 | #### Version Check 52 | 53 | ```swift 54 | let currentVersion: String = currentApplicationVersion() 55 | let needUpdate: Bool = isUpdateAvailable() 56 | ``` 57 | 58 | #### Validataion 59 | 60 | ```swift 61 | let isEmail = "nobody@email.com".isValid(withType: .email) 62 | let isEmail2 = "nobody@email.com".isValid(withRegEx: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}") 63 | ``` 64 | 65 | ### Extensions 66 | 67 | #### Strings 68 | 69 | ```swift 70 | let url = "www.apple.com".url() 71 | let base64 = "hello world".base64() 72 | let trimmed = " hello world \n \n ".trim() 73 | let localized = "Hello World".localized() 74 | ``` 75 | 76 | #### Date 77 | 78 | ```swift 79 | let serverDateParser = Date.fromFormat("yyyy-MM-dd HH:mm:ss") 80 | let date = serverDateParser("2018-02-17 17:00:00") 81 | ``` 82 | 83 | #### Array 84 | 85 | ```swift 86 | struct Data : Equatable {} 87 | var array: [Data] 88 | //-------- 89 | let data = Data() 90 | let exists: Bool = array.exists(data) 91 | let index: Int = array.indexOf(data) 92 | let array2 = array.removed(data) 93 | array.remove(item: data) //mutating 94 | ``` 95 | 96 | #### Codable 97 | 98 | ```swift 99 | struct Data : Codable {} 100 | //-------- 101 | let data = Data() 102 | let dictionary: [String:Any] = data.dictionary() 103 | let jsonString: String = data.json() 104 | let jsonString2: String = dictionary.json() 105 | let data2: Data = dictionary.decode() 106 | ``` 107 | 108 | ### UI 109 | 110 | #### UIColor 111 | 112 | ```swift 113 | let red = UIColor(red:255, green:0, blue:0) 114 | let blue = UIColor(0x00FF00) 115 | let green = UIColor("#0000FF") 116 | ``` 117 | 118 | #### subview 119 | 120 | ```swift 121 | //view with tag 122 | view.label(100)?.text = "Hello World" 123 | //image with tag 124 | view.imageView(100)?image = nil 125 | ``` 126 | 127 | #### popup 128 | 129 | ```swift 130 | if needUpdate { 131 | popupOkCancel(on: self, title: "업데이트", message: "업데이트가 필요합니다.", onOk: { 132 | let urlString = "itms-apps://itunes.apple.com/app/id0000000000" 133 | UIApplication.shared.openURL(URL(string: urlString)!) 134 | }) 135 | } else { 136 | popupOK(on: self, title: "업데이트", message: "최신버전입니다.") 137 | } 138 | ``` 139 | 140 | #### Image Resize 141 | 142 | ```swift 143 | let original = UIImage(named: "big_image") 144 | let resized = original?.resized(maxSize: CGSize(width: 100, height: 100)) 145 | let resizedForScreen = original?.resizedToScreen() 146 | ``` 147 | 148 | #### View Createor (with SnapKit) 149 | 150 | ```swift 151 | let rect = createView(UIView(), parent: self.view, setting: { v in 152 | v.backgroundColor = .red 153 | }, constraint: { m in 154 | m.top.left.equalToSuperview() 155 | m.width.height.equalTo(100) 156 | }) 157 | ``` 158 | 159 | ### RxSwift 160 | 161 | #### GestureRecognizer 162 | 163 | ```swift 164 | view.addTapGestureRecognizer() 165 | .subscribe(onNext: { (tapGusture : UITapGestureRecognizer) in 166 | }) 167 | 168 | view.addSwipeGestureRecognizer() 169 | .subscribe(onNext: { (tapGusture : UISwipeGestureRecognizer) in 170 | }) 171 | ``` 172 | 173 | #### Button 174 | 175 | ```swift 176 | //prevent double touched 177 | view.button(100)?.whenTouchUpInside(thro) 178 | .subscribe(onNext: { button in 179 | }) 180 | ``` 181 | 182 | #### Keyboard Notification 183 | 184 | ```swift 185 | whenKeyboardShowNotification() 186 | .subscribe(onNext: { (keyboardHeight: CGFloat, duration: TimeInterval) in 187 | }) 188 | .disposed(by: disposeBag) 189 | 190 | whenKeyboardHideNotification() 191 | .subscribe(onNext: { (duration: TimeInterval) in 192 | }) 193 | .disposed(by: disposeBag) 194 | ``` 195 | 196 | #### ScrollView 197 | 198 | ```swift 199 | collectionView.needsMore() 200 | .filter { !self.isLoading } 201 | .subscribe(onNext: { self.loadMore() }) 202 | .disposed(by: disposeBag) 203 | ``` 204 | 205 | #### Image Picker 206 | 207 | ```swift 208 | pickImageFromAlbum(on: self, withEdit: true) 209 | .subscribe(onNext: { self.imageView.image = $0 }) 210 | ``` 211 | 212 | ## History 213 | 214 | - 0.16.0 : Panda and extensions, darkmode 215 | - 0.15.0 : image load/save w/ Rx 216 | - 0.14.0 : Swift 5, target iOS 10.0 217 | - 0.13.0 : executors 218 | - 0.12.0 : Extensions (String, Number, URL), Functions (currying) 219 | - ... 220 | 221 | ## Author 222 | 223 | iamchiwon, iamchiwon@gmail.com 224 | 225 | ## License 226 | 227 | CWUtils is available under the MIT license. See the LICENSE file for more info. 228 | -------------------------------------------------------------------------------- /CWUtils.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CWUtils" 3 | s.version = "1.1.1" 4 | s.summary = "iOS 어플리케이션 개발용 유틸리티 모음" 5 | s.description = "RxSwift 를 기반으로 하고 있음" 6 | 7 | s.homepage = "https://github.com/iamchiwon/CWUtils" 8 | s.license = { :type => "MIT", :file => "LICENSE" } 9 | s.author = { "iamchiwon" => "iamchiwon@gmail.com" } 10 | 11 | s.swift_versions = '5' 12 | s.ios.deployment_target = '10.0' 13 | s.source = { :git => "https://github.com/iamchiwon/CWUtils.git", :tag => s.version.to_s } 14 | s.source_files = "Sources/CWUtils/**/*" 15 | 16 | # CORE 17 | s.dependency "RxSwift" 18 | s.dependency "RxCocoa" 19 | s.dependency "RxSwiftExt" 20 | s.dependency "RxOptional" 21 | s.dependency "Then" 22 | # UI 23 | s.dependency "RxViewController" 24 | s.dependency "SnapKit" 25 | s.dependency "Panda" 26 | s.dependency "Reusable" 27 | s.dependency "Kingfisher" 28 | 29 | end 30 | -------------------------------------------------------------------------------- /CWUtilsTest.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CWUtilsTest" 3 | s.version = "1.1.1" 4 | s.summary = "iOS 어플리케이션 개발용 유틸리티 모음" 5 | s.description = "테스트용 유틸리티 편" 6 | 7 | s.homepage = "https://github.com/iamchiwon/CWUtils" 8 | s.license = { :type => "MIT", :file => "LICENSE" } 9 | s.author = { "iamchiwon" => "iamchiwon@gmail.com" } 10 | 11 | s.swift_versions = '5' 12 | s.ios.deployment_target = '10.0' 13 | s.source = { :git => "https://github.com/iamchiwon/CWUtils.git", :tag => s.version.to_s } 14 | s.source_files = "Sources/CWUtilsTest/**/*" 15 | s.framework = 'XCTest' 16 | 17 | end 18 | -------------------------------------------------------------------------------- /Docs/spm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchiwon/CWUtils/747a0130d249a66fa4f0d96596e304f6155c02e5/Docs/spm.png -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8DB54467BF2F29AF9FC40002 /* Pods_ExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6AA2D02B262B81549B702B0 /* Pods_ExampleTests.framework */; }; 11 | A100E1D723A3EC1900741F36 /* ExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A100E1D623A3EC1900741F36 /* ExampleTests.swift */; }; 12 | A1BDB4021FED0DF50014D8B2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1BDB4011FED0DF50014D8B2 /* AppDelegate.swift */; }; 13 | A1BDB4041FED0DF50014D8B2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1BDB4031FED0DF50014D8B2 /* ViewController.swift */; }; 14 | A1BDB4071FED0DF50014D8B2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A1BDB4051FED0DF50014D8B2 /* Main.storyboard */; }; 15 | A1BDB4091FED0DF60014D8B2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1BDB4081FED0DF60014D8B2 /* Assets.xcassets */; }; 16 | A1BDB40C1FED0DF60014D8B2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A1BDB40A1FED0DF60014D8B2 /* LaunchScreen.storyboard */; }; 17 | FC9FCA0046D9B407F6392F86 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D74A909B74F0DB7D3932DBA /* Pods_Example.framework */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | A100E1D923A3EC1900741F36 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = A1BDB3F61FED0DF50014D8B2 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = A1BDB3FD1FED0DF50014D8B2; 26 | remoteInfo = Example; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 03E0C7653A95F254B22EAC8D /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; 32 | 280E80BAD44234C632484CBE /* Pods-ExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.release.xcconfig"; sourceTree = ""; }; 33 | 2DCB4101FA07DAAC9E7ADAD5 /* Pods-ExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.debug.xcconfig"; sourceTree = ""; }; 34 | 4D74A909B74F0DB7D3932DBA /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 746FF9D5541AF4A8803133E6 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; 36 | A100E1D423A3EC1900741F36 /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | A100E1D623A3EC1900741F36 /* ExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleTests.swift; sourceTree = ""; }; 38 | A100E1D823A3EC1900741F36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | A1BDB3FE1FED0DF50014D8B2 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | A1BDB4011FED0DF50014D8B2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | A1BDB4031FED0DF50014D8B2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 42 | A1BDB4061FED0DF50014D8B2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43 | A1BDB4081FED0DF60014D8B2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 44 | A1BDB40B1FED0DF60014D8B2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 45 | A1BDB40D1FED0DF60014D8B2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | D6AA2D02B262B81549B702B0 /* Pods_ExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | A100E1D123A3EC1900741F36 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | 8DB54467BF2F29AF9FC40002 /* Pods_ExampleTests.framework in Frameworks */, 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | A1BDB3FB1FED0DF50014D8B2 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | FC9FCA0046D9B407F6392F86 /* Pods_Example.framework in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 9242040BFD30A85072FA02BF /* Frameworks */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 4D74A909B74F0DB7D3932DBA /* Pods_Example.framework */, 73 | D6AA2D02B262B81549B702B0 /* Pods_ExampleTests.framework */, 74 | ); 75 | name = Frameworks; 76 | sourceTree = ""; 77 | }; 78 | A100E1D523A3EC1900741F36 /* ExampleTests */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | A100E1D623A3EC1900741F36 /* ExampleTests.swift */, 82 | A100E1D823A3EC1900741F36 /* Info.plist */, 83 | ); 84 | path = ExampleTests; 85 | sourceTree = ""; 86 | }; 87 | A1BDB3F51FED0DF50014D8B2 = { 88 | isa = PBXGroup; 89 | children = ( 90 | A1BDB4001FED0DF50014D8B2 /* Example */, 91 | A100E1D523A3EC1900741F36 /* ExampleTests */, 92 | A1BDB3FF1FED0DF50014D8B2 /* Products */, 93 | ADF0677AC9D16523B5A3BF96 /* Pods */, 94 | 9242040BFD30A85072FA02BF /* Frameworks */, 95 | ); 96 | sourceTree = ""; 97 | }; 98 | A1BDB3FF1FED0DF50014D8B2 /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | A1BDB3FE1FED0DF50014D8B2 /* Example.app */, 102 | A100E1D423A3EC1900741F36 /* ExampleTests.xctest */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | A1BDB4001FED0DF50014D8B2 /* Example */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | A1BDB4011FED0DF50014D8B2 /* AppDelegate.swift */, 111 | A1BDB4031FED0DF50014D8B2 /* ViewController.swift */, 112 | A1BDB4051FED0DF50014D8B2 /* Main.storyboard */, 113 | A1BDB4081FED0DF60014D8B2 /* Assets.xcassets */, 114 | A1BDB40A1FED0DF60014D8B2 /* LaunchScreen.storyboard */, 115 | A1BDB40D1FED0DF60014D8B2 /* Info.plist */, 116 | ); 117 | path = Example; 118 | sourceTree = ""; 119 | }; 120 | ADF0677AC9D16523B5A3BF96 /* Pods */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 03E0C7653A95F254B22EAC8D /* Pods-Example.debug.xcconfig */, 124 | 746FF9D5541AF4A8803133E6 /* Pods-Example.release.xcconfig */, 125 | 2DCB4101FA07DAAC9E7ADAD5 /* Pods-ExampleTests.debug.xcconfig */, 126 | 280E80BAD44234C632484CBE /* Pods-ExampleTests.release.xcconfig */, 127 | ); 128 | name = Pods; 129 | sourceTree = ""; 130 | }; 131 | /* End PBXGroup section */ 132 | 133 | /* Begin PBXNativeTarget section */ 134 | A100E1D323A3EC1900741F36 /* ExampleTests */ = { 135 | isa = PBXNativeTarget; 136 | buildConfigurationList = A100E1DD23A3EC1900741F36 /* Build configuration list for PBXNativeTarget "ExampleTests" */; 137 | buildPhases = ( 138 | CAD26AC3A70EEF3CF6EED0A5 /* [CP] Check Pods Manifest.lock */, 139 | A100E1D023A3EC1900741F36 /* Sources */, 140 | A100E1D123A3EC1900741F36 /* Frameworks */, 141 | A100E1D223A3EC1900741F36 /* Resources */, 142 | B4E3490B0E0BE81FA14EF599 /* [CP] Embed Pods Frameworks */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | A100E1DA23A3EC1900741F36 /* PBXTargetDependency */, 148 | ); 149 | name = ExampleTests; 150 | productName = ExampleTests; 151 | productReference = A100E1D423A3EC1900741F36 /* ExampleTests.xctest */; 152 | productType = "com.apple.product-type.bundle.unit-test"; 153 | }; 154 | A1BDB3FD1FED0DF50014D8B2 /* Example */ = { 155 | isa = PBXNativeTarget; 156 | buildConfigurationList = A1BDB4101FED0DF60014D8B2 /* Build configuration list for PBXNativeTarget "Example" */; 157 | buildPhases = ( 158 | 5BFFF3DB7B3CDD91BD67FBEB /* [CP] Check Pods Manifest.lock */, 159 | A1BDB3FA1FED0DF50014D8B2 /* Sources */, 160 | A1BDB3FB1FED0DF50014D8B2 /* Frameworks */, 161 | A1BDB3FC1FED0DF50014D8B2 /* Resources */, 162 | D17EAF84841118BACFD0A2DC /* [CP] Embed Pods Frameworks */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | ); 168 | name = Example; 169 | productName = Example; 170 | productReference = A1BDB3FE1FED0DF50014D8B2 /* Example.app */; 171 | productType = "com.apple.product-type.application"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | A1BDB3F61FED0DF50014D8B2 /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastSwiftUpdateCheck = 1130; 180 | LastUpgradeCheck = 1000; 181 | ORGANIZATIONNAME = makecube; 182 | TargetAttributes = { 183 | A100E1D323A3EC1900741F36 = { 184 | CreatedOnToolsVersion = 11.3; 185 | ProvisioningStyle = Automatic; 186 | TestTargetID = A1BDB3FD1FED0DF50014D8B2; 187 | }; 188 | A1BDB3FD1FED0DF50014D8B2 = { 189 | CreatedOnToolsVersion = 9.2; 190 | ProvisioningStyle = Automatic; 191 | }; 192 | }; 193 | }; 194 | buildConfigurationList = A1BDB3F91FED0DF50014D8B2 /* Build configuration list for PBXProject "Example" */; 195 | compatibilityVersion = "Xcode 8.0"; 196 | developmentRegion = en; 197 | hasScannedForEncodings = 0; 198 | knownRegions = ( 199 | en, 200 | Base, 201 | ); 202 | mainGroup = A1BDB3F51FED0DF50014D8B2; 203 | productRefGroup = A1BDB3FF1FED0DF50014D8B2 /* Products */; 204 | projectDirPath = ""; 205 | projectRoot = ""; 206 | targets = ( 207 | A1BDB3FD1FED0DF50014D8B2 /* Example */, 208 | A100E1D323A3EC1900741F36 /* ExampleTests */, 209 | ); 210 | }; 211 | /* End PBXProject section */ 212 | 213 | /* Begin PBXResourcesBuildPhase section */ 214 | A100E1D223A3EC1900741F36 /* Resources */ = { 215 | isa = PBXResourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | }; 221 | A1BDB3FC1FED0DF50014D8B2 /* Resources */ = { 222 | isa = PBXResourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | A1BDB40C1FED0DF60014D8B2 /* LaunchScreen.storyboard in Resources */, 226 | A1BDB4091FED0DF60014D8B2 /* Assets.xcassets in Resources */, 227 | A1BDB4071FED0DF50014D8B2 /* Main.storyboard in Resources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | /* End PBXResourcesBuildPhase section */ 232 | 233 | /* Begin PBXShellScriptBuildPhase section */ 234 | 5BFFF3DB7B3CDD91BD67FBEB /* [CP] Check Pods Manifest.lock */ = { 235 | isa = PBXShellScriptBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | ); 239 | inputPaths = ( 240 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 241 | "${PODS_ROOT}/Manifest.lock", 242 | ); 243 | name = "[CP] Check Pods Manifest.lock"; 244 | outputPaths = ( 245 | "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt", 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | shellPath = /bin/sh; 249 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 250 | showEnvVarsInLog = 0; 251 | }; 252 | B4E3490B0E0BE81FA14EF599 /* [CP] Embed Pods Frameworks */ = { 253 | isa = PBXShellScriptBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | ); 257 | inputPaths = ( 258 | "${PODS_ROOT}/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-frameworks.sh", 259 | "${BUILT_PRODUCTS_DIR}/CWUtilsTest/CWUtilsTest.framework", 260 | ); 261 | name = "[CP] Embed Pods Frameworks"; 262 | outputPaths = ( 263 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CWUtilsTest.framework", 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | shellPath = /bin/sh; 267 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-frameworks.sh\"\n"; 268 | showEnvVarsInLog = 0; 269 | }; 270 | CAD26AC3A70EEF3CF6EED0A5 /* [CP] Check Pods Manifest.lock */ = { 271 | isa = PBXShellScriptBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | ); 275 | inputFileListPaths = ( 276 | ); 277 | inputPaths = ( 278 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 279 | "${PODS_ROOT}/Manifest.lock", 280 | ); 281 | name = "[CP] Check Pods Manifest.lock"; 282 | outputFileListPaths = ( 283 | ); 284 | outputPaths = ( 285 | "$(DERIVED_FILE_DIR)/Pods-ExampleTests-checkManifestLockResult.txt", 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | shellPath = /bin/sh; 289 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 290 | showEnvVarsInLog = 0; 291 | }; 292 | D17EAF84841118BACFD0A2DC /* [CP] Embed Pods Frameworks */ = { 293 | isa = PBXShellScriptBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | ); 297 | inputPaths = ( 298 | "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh", 299 | "${BUILT_PRODUCTS_DIR}/CWUtils/CWUtils.framework", 300 | "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework", 301 | "${BUILT_PRODUCTS_DIR}/Panda/Panda.framework", 302 | "${BUILT_PRODUCTS_DIR}/Reusable/Reusable.framework", 303 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", 304 | "${BUILT_PRODUCTS_DIR}/RxOptional/RxOptional.framework", 305 | "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework", 306 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 307 | "${BUILT_PRODUCTS_DIR}/RxSwiftExt/RxSwiftExt.framework", 308 | "${BUILT_PRODUCTS_DIR}/RxViewController/RxViewController.framework", 309 | "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", 310 | "${BUILT_PRODUCTS_DIR}/Then/Then.framework", 311 | ); 312 | name = "[CP] Embed Pods Frameworks"; 313 | outputPaths = ( 314 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CWUtils.framework", 315 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework", 316 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Panda.framework", 317 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reusable.framework", 318 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", 319 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxOptional.framework", 320 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRelay.framework", 321 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 322 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwiftExt.framework", 323 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxViewController.framework", 324 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", 325 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Then.framework", 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | shellPath = /bin/sh; 329 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n"; 330 | showEnvVarsInLog = 0; 331 | }; 332 | /* End PBXShellScriptBuildPhase section */ 333 | 334 | /* Begin PBXSourcesBuildPhase section */ 335 | A100E1D023A3EC1900741F36 /* Sources */ = { 336 | isa = PBXSourcesBuildPhase; 337 | buildActionMask = 2147483647; 338 | files = ( 339 | A100E1D723A3EC1900741F36 /* ExampleTests.swift in Sources */, 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | }; 343 | A1BDB3FA1FED0DF50014D8B2 /* Sources */ = { 344 | isa = PBXSourcesBuildPhase; 345 | buildActionMask = 2147483647; 346 | files = ( 347 | A1BDB4041FED0DF50014D8B2 /* ViewController.swift in Sources */, 348 | A1BDB4021FED0DF50014D8B2 /* AppDelegate.swift in Sources */, 349 | ); 350 | runOnlyForDeploymentPostprocessing = 0; 351 | }; 352 | /* End PBXSourcesBuildPhase section */ 353 | 354 | /* Begin PBXTargetDependency section */ 355 | A100E1DA23A3EC1900741F36 /* PBXTargetDependency */ = { 356 | isa = PBXTargetDependency; 357 | target = A1BDB3FD1FED0DF50014D8B2 /* Example */; 358 | targetProxy = A100E1D923A3EC1900741F36 /* PBXContainerItemProxy */; 359 | }; 360 | /* End PBXTargetDependency section */ 361 | 362 | /* Begin PBXVariantGroup section */ 363 | A1BDB4051FED0DF50014D8B2 /* Main.storyboard */ = { 364 | isa = PBXVariantGroup; 365 | children = ( 366 | A1BDB4061FED0DF50014D8B2 /* Base */, 367 | ); 368 | name = Main.storyboard; 369 | sourceTree = ""; 370 | }; 371 | A1BDB40A1FED0DF60014D8B2 /* LaunchScreen.storyboard */ = { 372 | isa = PBXVariantGroup; 373 | children = ( 374 | A1BDB40B1FED0DF60014D8B2 /* Base */, 375 | ); 376 | name = LaunchScreen.storyboard; 377 | sourceTree = ""; 378 | }; 379 | /* End PBXVariantGroup section */ 380 | 381 | /* Begin XCBuildConfiguration section */ 382 | A100E1DB23A3EC1900741F36 /* Debug */ = { 383 | isa = XCBuildConfiguration; 384 | baseConfigurationReference = 2DCB4101FA07DAAC9E7ADAD5 /* Pods-ExampleTests.debug.xcconfig */; 385 | buildSettings = { 386 | BUNDLE_LOADER = "$(TEST_HOST)"; 387 | CLANG_ENABLE_OBJC_WEAK = YES; 388 | CODE_SIGN_STYLE = Automatic; 389 | DEVELOPMENT_TEAM = 77YZ9248DM; 390 | INFOPLIST_FILE = ExampleTests/Info.plist; 391 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 392 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 393 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 394 | MTL_FAST_MATH = YES; 395 | PRODUCT_BUNDLE_IDENTIFIER = in.makecube.ExampleTests; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | SWIFT_VERSION = 5.0; 398 | TARGETED_DEVICE_FAMILY = "1,2"; 399 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 400 | }; 401 | name = Debug; 402 | }; 403 | A100E1DC23A3EC1900741F36 /* Release */ = { 404 | isa = XCBuildConfiguration; 405 | baseConfigurationReference = 280E80BAD44234C632484CBE /* Pods-ExampleTests.release.xcconfig */; 406 | buildSettings = { 407 | BUNDLE_LOADER = "$(TEST_HOST)"; 408 | CLANG_ENABLE_OBJC_WEAK = YES; 409 | CODE_SIGN_STYLE = Automatic; 410 | DEVELOPMENT_TEAM = 77YZ9248DM; 411 | INFOPLIST_FILE = ExampleTests/Info.plist; 412 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 414 | MTL_FAST_MATH = YES; 415 | PRODUCT_BUNDLE_IDENTIFIER = in.makecube.ExampleTests; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | SWIFT_VERSION = 5.0; 418 | TARGETED_DEVICE_FAMILY = "1,2"; 419 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 420 | }; 421 | name = Release; 422 | }; 423 | A1BDB40E1FED0DF60014D8B2 /* Debug */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | ALWAYS_SEARCH_USER_PATHS = NO; 427 | CLANG_ANALYZER_NONNULL = YES; 428 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 429 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 430 | CLANG_CXX_LIBRARY = "libc++"; 431 | CLANG_ENABLE_MODULES = YES; 432 | CLANG_ENABLE_OBJC_ARC = YES; 433 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 434 | CLANG_WARN_BOOL_CONVERSION = YES; 435 | CLANG_WARN_COMMA = YES; 436 | CLANG_WARN_CONSTANT_CONVERSION = YES; 437 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 438 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 439 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 440 | CLANG_WARN_EMPTY_BODY = YES; 441 | CLANG_WARN_ENUM_CONVERSION = YES; 442 | CLANG_WARN_INFINITE_RECURSION = YES; 443 | CLANG_WARN_INT_CONVERSION = YES; 444 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 445 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 446 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 447 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 448 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 449 | CLANG_WARN_STRICT_PROTOTYPES = YES; 450 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 451 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 452 | CLANG_WARN_UNREACHABLE_CODE = YES; 453 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 454 | CODE_SIGN_IDENTITY = "iPhone Developer"; 455 | COPY_PHASE_STRIP = NO; 456 | DEBUG_INFORMATION_FORMAT = dwarf; 457 | ENABLE_STRICT_OBJC_MSGSEND = YES; 458 | ENABLE_TESTABILITY = YES; 459 | GCC_C_LANGUAGE_STANDARD = gnu11; 460 | GCC_DYNAMIC_NO_PIC = NO; 461 | GCC_NO_COMMON_BLOCKS = YES; 462 | GCC_OPTIMIZATION_LEVEL = 0; 463 | GCC_PREPROCESSOR_DEFINITIONS = ( 464 | "DEBUG=1", 465 | "$(inherited)", 466 | ); 467 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 468 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 469 | GCC_WARN_UNDECLARED_SELECTOR = YES; 470 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 471 | GCC_WARN_UNUSED_FUNCTION = YES; 472 | GCC_WARN_UNUSED_VARIABLE = YES; 473 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 474 | MTL_ENABLE_DEBUG_INFO = YES; 475 | ONLY_ACTIVE_ARCH = YES; 476 | SDKROOT = iphoneos; 477 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 478 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 479 | SWIFT_VERSION = 5.0; 480 | }; 481 | name = Debug; 482 | }; 483 | A1BDB40F1FED0DF60014D8B2 /* Release */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | ALWAYS_SEARCH_USER_PATHS = NO; 487 | CLANG_ANALYZER_NONNULL = YES; 488 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 489 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 490 | CLANG_CXX_LIBRARY = "libc++"; 491 | CLANG_ENABLE_MODULES = YES; 492 | CLANG_ENABLE_OBJC_ARC = YES; 493 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 494 | CLANG_WARN_BOOL_CONVERSION = YES; 495 | CLANG_WARN_COMMA = YES; 496 | CLANG_WARN_CONSTANT_CONVERSION = YES; 497 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 498 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 499 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 500 | CLANG_WARN_EMPTY_BODY = YES; 501 | CLANG_WARN_ENUM_CONVERSION = YES; 502 | CLANG_WARN_INFINITE_RECURSION = YES; 503 | CLANG_WARN_INT_CONVERSION = YES; 504 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 505 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 506 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 507 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 508 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 509 | CLANG_WARN_STRICT_PROTOTYPES = YES; 510 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 511 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 512 | CLANG_WARN_UNREACHABLE_CODE = YES; 513 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 514 | CODE_SIGN_IDENTITY = "iPhone Developer"; 515 | COPY_PHASE_STRIP = NO; 516 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 517 | ENABLE_NS_ASSERTIONS = NO; 518 | ENABLE_STRICT_OBJC_MSGSEND = YES; 519 | GCC_C_LANGUAGE_STANDARD = gnu11; 520 | GCC_NO_COMMON_BLOCKS = YES; 521 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 522 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 523 | GCC_WARN_UNDECLARED_SELECTOR = YES; 524 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 525 | GCC_WARN_UNUSED_FUNCTION = YES; 526 | GCC_WARN_UNUSED_VARIABLE = YES; 527 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 528 | MTL_ENABLE_DEBUG_INFO = NO; 529 | SDKROOT = iphoneos; 530 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 531 | SWIFT_VERSION = 5.0; 532 | VALIDATE_PRODUCT = YES; 533 | }; 534 | name = Release; 535 | }; 536 | A1BDB4111FED0DF60014D8B2 /* Debug */ = { 537 | isa = XCBuildConfiguration; 538 | baseConfigurationReference = 03E0C7653A95F254B22EAC8D /* Pods-Example.debug.xcconfig */; 539 | buildSettings = { 540 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 541 | CODE_SIGN_STYLE = Automatic; 542 | DEVELOPMENT_TEAM = 77YZ9248DM; 543 | INFOPLIST_FILE = Example/Info.plist; 544 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 545 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 546 | PRODUCT_BUNDLE_IDENTIFIER = in.makecube.Example; 547 | PRODUCT_NAME = "$(TARGET_NAME)"; 548 | SWIFT_VERSION = 5.0; 549 | TARGETED_DEVICE_FAMILY = "1,2"; 550 | }; 551 | name = Debug; 552 | }; 553 | A1BDB4121FED0DF60014D8B2 /* Release */ = { 554 | isa = XCBuildConfiguration; 555 | baseConfigurationReference = 746FF9D5541AF4A8803133E6 /* Pods-Example.release.xcconfig */; 556 | buildSettings = { 557 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 558 | CODE_SIGN_STYLE = Automatic; 559 | DEVELOPMENT_TEAM = 77YZ9248DM; 560 | INFOPLIST_FILE = Example/Info.plist; 561 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 562 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 563 | PRODUCT_BUNDLE_IDENTIFIER = in.makecube.Example; 564 | PRODUCT_NAME = "$(TARGET_NAME)"; 565 | SWIFT_VERSION = 5.0; 566 | TARGETED_DEVICE_FAMILY = "1,2"; 567 | }; 568 | name = Release; 569 | }; 570 | /* End XCBuildConfiguration section */ 571 | 572 | /* Begin XCConfigurationList section */ 573 | A100E1DD23A3EC1900741F36 /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { 574 | isa = XCConfigurationList; 575 | buildConfigurations = ( 576 | A100E1DB23A3EC1900741F36 /* Debug */, 577 | A100E1DC23A3EC1900741F36 /* Release */, 578 | ); 579 | defaultConfigurationIsVisible = 0; 580 | defaultConfigurationName = Release; 581 | }; 582 | A1BDB3F91FED0DF50014D8B2 /* Build configuration list for PBXProject "Example" */ = { 583 | isa = XCConfigurationList; 584 | buildConfigurations = ( 585 | A1BDB40E1FED0DF60014D8B2 /* Debug */, 586 | A1BDB40F1FED0DF60014D8B2 /* Release */, 587 | ); 588 | defaultConfigurationIsVisible = 0; 589 | defaultConfigurationName = Release; 590 | }; 591 | A1BDB4101FED0DF60014D8B2 /* Build configuration list for PBXNativeTarget "Example" */ = { 592 | isa = XCConfigurationList; 593 | buildConfigurations = ( 594 | A1BDB4111FED0DF60014D8B2 /* Debug */, 595 | A1BDB4121FED0DF60014D8B2 /* Release */, 596 | ); 597 | defaultConfigurationIsVisible = 0; 598 | defaultConfigurationName = Release; 599 | }; 600 | /* End XCConfigurationList section */ 601 | }; 602 | rootObject = A1BDB3F61FED0DF50014D8B2 /* Project object */; 603 | } 604 | -------------------------------------------------------------------------------- /Example/Example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Example/Example/Assets.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" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/trust_black.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "trust me.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/trust_black.imageset/trust me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchiwon/CWUtils/747a0130d249a66fa4f0d96596e304f6155c02e5/Example/Example/Assets.xcassets/trust_black.imageset/trust me.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/trust_white.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "trust me2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/trust_white.imageset/trust me2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamchiwon/CWUtils/747a0130d249a66fa4f0d96596e304f6155c02e5/Example/Example/Assets.xcassets/trust_white.imageset/trust me2.png -------------------------------------------------------------------------------- /Example/Example/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /Example/Example/Base.lproj/Main.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 | 54 | 60 | 66 | 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 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | $(PRODUCT_NAME) camera use 7 | NSPhotoLibraryUsageDescription 8 | $(PRODUCT_NAME) photo use 9 | CFBundleDevelopmentRegion 10 | $(DEVELOPMENT_LANGUAGE) 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import CWUtils 10 | import Panda 11 | import RxOptional 12 | import RxSwift 13 | import SnapKit 14 | import UIKit 15 | 16 | struct Book: Codable, Equatable { 17 | let ibsn: String 18 | let title: String 19 | let author: String 20 | 21 | static func == (left: Book, right: Book) -> Bool { 22 | return left.ibsn == right.ibsn 23 | } 24 | } 25 | 26 | class ViewController: CWViewController { 27 | var books: [Book] = [] 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | buildBookData() 33 | examTime() 34 | examList() 35 | examCodable() 36 | 37 | /** VERSION CHECK **/ 38 | checkVersion() 39 | } 40 | 41 | func buildBookData() { 42 | books.append(Book(ibsn: "ibsn0001", title: "Book1", author: "Auther1")) 43 | books.append(Book(ibsn: "ibsn0002", title: "Book2", author: "Auther2")) 44 | books.append(Book(ibsn: "ibsn0003", title: "Book3", author: "Auther3")) 45 | books.append(Book(ibsn: "ibsn0004", title: "Book4", author: "Auther4")) 46 | } 47 | 48 | func examTime() { 49 | let currentTimInMilli: Int = Date.currentTimeInMilli() 50 | print(currentTimInMilli) 51 | 52 | let timeInMillie: Int = Date().timeInMilli() 53 | print(timeInMillie) 54 | } 55 | 56 | func examList() { 57 | if let indexOfBook3 = books.indexOf(Book(ibsn: "ibsn0003", title: "Book3", author: "Auther3")) { 58 | print("\(indexOfBook3)") 59 | } 60 | _ = books.remove(item: Book(ibsn: "ibsn0002", title: "Book2", author: "Auther2")) 61 | print("\(books)") 62 | } 63 | 64 | func examCodable() { 65 | let book5 = Book(ibsn: "ibsn0005", title: "Book5", author: "Auther5") 66 | let dictionary = book5.dictionary() 67 | print("\(dictionary)") 68 | 69 | let json = dictionary.json() 70 | print("\(json)") 71 | 72 | let decoded: Book = dictionary.decode()! 73 | print("\(decoded)") 74 | } 75 | 76 | func checkVersion() { 77 | let needUpdate: Bool = isUpdateAvailable() 78 | if needUpdate { 79 | popupOkCancel(on: self, title: "업데이트", message: "업데이트가 필요합니다.", onOk: { 80 | let urlString = "itms-apps://itunes.apple.com/app/id0000000000" 81 | UIApplication.shared.open(urlString.url(), options: [:], completionHandler: nil) 82 | }) 83 | } else { 84 | popupOK(on: self, title: "업데이트", message: "최신버전입니다.") 85 | } 86 | } 87 | } 88 | 89 | extension ViewController { 90 | func setupBinds() { 91 | if let imageView = view.imageView(100) { 92 | imageView.addTapGestureRecognizer() 93 | .flatMap { _ in 94 | UIImagePickerController.pickImage(on: self, allowsEditing: true) 95 | } 96 | .map { original -> UIImage in 97 | let resized = original.resized(maxSize: CGSize(width: 100, height: 100)) 98 | print("resized(100,100) : \(resized)") 99 | 100 | let resizedForScreen = original.resizedToScreen() 101 | print("resizedToScreen : \(resizedForScreen)") 102 | 103 | return resized 104 | } 105 | .bind(to: imageView.rx.image) 106 | .disposed(by: disposeBag) 107 | } 108 | 109 | view.button(102)?.whenTouchUpInside() 110 | .map { [weak self] _ in self?.view.textfield(101)?.text ?? "" } 111 | .subscribe(onNext: { text in 112 | if text.isValid(withType: .email) { 113 | popupOK(on: self, title: "이메일", message: text) 114 | } else { 115 | popupOK(on: self, title: "메시지", message: text) 116 | } 117 | }) 118 | .disposed(by: disposeBag) 119 | 120 | view.textfield(101)?.rx.controlEvent(event: .editingDidEndOnExit) 121 | .map({ $0.sender.text?.trim() }) 122 | .filterNil() 123 | .filter(isNotEmpty) 124 | .subscribe(onNext: { print($0) }) 125 | .disposed(by: disposeBag) 126 | 127 | view.textfield(101)?.rx.text 128 | .asObservable() 129 | .bind(to: view.label(201)!.rx.text) 130 | .disposed(by: disposeBag) 131 | 132 | /** KEYBOARD EVENT **/ 133 | 134 | whenKeyboardShowNotification() 135 | .subscribe(onNext: { (_: CGFloat, _: TimeInterval) in 136 | print("keyboard showing") 137 | }) 138 | .disposed(by: disposeBag) 139 | 140 | whenKeyboardHideNotification() 141 | .subscribe(onNext: { (_: TimeInterval) in 142 | print("keyboard hide") 143 | }) 144 | .disposed(by: disposeBag) 145 | } 146 | } 147 | 148 | extension ViewController { 149 | func setupViews() { 150 | setAppInfo() 151 | 152 | view.button(102)?.backgroundColor = 153 | UIColor.darkableColor(.systemBlue, darkmode: .orange) 154 | 155 | /** Building UI **/ 156 | 157 | if let bottomView = view.label(201) { 158 | let stack = VStack(spacing: 16).add(to: view) 159 | .constraint { m in 160 | m.top.equalTo(bottomView.snp.bottom).offset(20) 161 | m.left.right.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)) 162 | } 163 | 164 | Image().add(to: stack) 165 | .image(UIImage.darkableImage(#imageLiteral(resourceName: "trust_black"), darkmode: #imageLiteral(resourceName: "trust_white"))) 166 | .size(100) 167 | 168 | books.map(createBookView).forEach { stack.add($0) } 169 | } 170 | } 171 | 172 | private func createBookView(_ book: Book) -> UIView { 173 | return HStack(alignment: .center, spacing: 16, distribution: .fillProportionally) 174 | .add( 175 | VStack(alignment: .leading, spacing: 8) 176 | .add( 177 | Text(book.title) 178 | .font(size: 15, weight: .bold) 179 | .textColor(UIColor.darkableColor(.black, darkmode: .white)).view, 180 | 181 | Text(book.ibsn) 182 | .font(size: 9, weight: .light) 183 | .textColor(.gray).view 184 | ).view, 185 | Text(book.author) 186 | .font(size: 12, weight: .light) 187 | .textColor(.gray).view 188 | ).view 189 | } 190 | 191 | private func setAppInfo() { 192 | let today = Date().formatted("YYYY-MM-dd") 193 | 194 | var deviceInfo = "" 195 | runOnSimulatorOnly { 196 | deviceInfo = "Simulator " 197 | } 198 | 199 | runOnDeviceOnly { 200 | deviceInfo = "Device " 201 | } 202 | 203 | let versionInfo = currentApplicationVersion() 204 | 205 | view.label(200)?.text = "\(today) / \(deviceInfo) / \(versionInfo)" 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Example/ExampleTests/ExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleTests.swift 3 | // ExampleTests 4 | // 5 | // Created by iamchiwon on 2019/12/14. 6 | // Copyright © 2019 makecube. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CWUtilsTest 11 | 12 | class ExampleTests: XCTestCase { 13 | func testSync() { 14 | // given 15 | var a = 0 16 | // when 17 | a += 1 18 | // then 19 | XCTAssertEqual(a, 1) 20 | } 21 | 22 | func testAsync() { 23 | // given 24 | var a = 0 25 | 26 | // when 27 | await("after 1 second") { done in 28 | DispatchQueue.main.async { 29 | sleep(1) 30 | a = 1 31 | 32 | // then 33 | XCTAssertEqual(a, 1) 34 | done() 35 | } 36 | } 37 | 38 | // after 39 | XCTAssertEqual(a, 1) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Example/ExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Gemfile: -------------------------------------------------------------------------------- 1 | gem "cocoapods", "~> 1.8" 2 | -------------------------------------------------------------------------------- /Example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | specs: 3 | CFPropertyList (3.0.2) 4 | activesupport (4.2.11.1) 5 | i18n (~> 0.7) 6 | minitest (~> 5.1) 7 | thread_safe (~> 0.3, >= 0.3.4) 8 | tzinfo (~> 1.1) 9 | algoliasearch (1.27.1) 10 | httpclient (~> 2.8, >= 2.8.3) 11 | json (>= 1.5.1) 12 | atomos (0.1.3) 13 | claide (1.0.3) 14 | cocoapods (1.8.4) 15 | activesupport (>= 4.0.2, < 5) 16 | claide (>= 1.0.2, < 2.0) 17 | cocoapods-core (= 1.8.4) 18 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 19 | cocoapods-downloader (>= 1.2.2, < 2.0) 20 | cocoapods-plugins (>= 1.0.0, < 2.0) 21 | cocoapods-search (>= 1.0.0, < 2.0) 22 | cocoapods-stats (>= 1.0.0, < 2.0) 23 | cocoapods-trunk (>= 1.4.0, < 2.0) 24 | cocoapods-try (>= 1.1.0, < 2.0) 25 | colored2 (~> 3.1) 26 | escape (~> 0.0.4) 27 | fourflusher (>= 2.3.0, < 3.0) 28 | gh_inspector (~> 1.0) 29 | molinillo (~> 0.6.6) 30 | nap (~> 1.0) 31 | ruby-macho (~> 1.4) 32 | xcodeproj (>= 1.11.1, < 2.0) 33 | cocoapods-core (1.8.4) 34 | activesupport (>= 4.0.2, < 6) 35 | algoliasearch (~> 1.0) 36 | concurrent-ruby (~> 1.1) 37 | fuzzy_match (~> 2.0.4) 38 | nap (~> 1.0) 39 | cocoapods-deintegrate (1.0.4) 40 | cocoapods-downloader (1.3.0) 41 | cocoapods-plugins (1.0.0) 42 | nap 43 | cocoapods-search (1.0.0) 44 | cocoapods-stats (1.1.0) 45 | cocoapods-trunk (1.4.1) 46 | nap (>= 0.8, < 2.0) 47 | netrc (~> 0.11) 48 | cocoapods-try (1.1.0) 49 | colored2 (3.1.2) 50 | concurrent-ruby (1.1.5) 51 | escape (0.0.4) 52 | fourflusher (2.3.1) 53 | fuzzy_match (2.0.4) 54 | gh_inspector (1.1.3) 55 | httpclient (2.8.3) 56 | i18n (0.9.5) 57 | concurrent-ruby (~> 1.0) 58 | json (2.3.0) 59 | minitest (5.13.0) 60 | molinillo (0.6.6) 61 | nanaimo (0.2.6) 62 | nap (1.1.0) 63 | netrc (0.11.0) 64 | ruby-macho (1.4.0) 65 | thread_safe (0.3.6) 66 | tzinfo (1.2.6) 67 | thread_safe (~> 0.1) 68 | xcodeproj (1.14.0) 69 | CFPropertyList (>= 2.3.3, < 4.0) 70 | atomos (~> 0.1.3) 71 | claide (>= 1.0.2, < 2.0) 72 | colored2 (~> 3.1) 73 | nanaimo (~> 0.2.6) 74 | 75 | PLATFORMS 76 | ruby 77 | 78 | DEPENDENCIES 79 | cocoapods (~> 1.8) 80 | 81 | BUNDLED WITH 82 | 2.0.2 83 | -------------------------------------------------------------------------------- /Example/Makefile: -------------------------------------------------------------------------------- 1 | all: update 2 | update: 3 | @bundle update 4 | @bundle exec pod update 5 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '10.0' 3 | 4 | target 'Example' do 5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for Example 9 | pod 'CWUtils', :path => '../' 10 | 11 | 12 | target 'ExampleTests' do 13 | inherit! :search_paths 14 | 15 | pod 'CWUtilsTest', :path => '../' 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | gem "cocoapods", "~> 1.8" 2 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | specs: 3 | CFPropertyList (3.0.2) 4 | activesupport (4.2.11.1) 5 | i18n (~> 0.7) 6 | minitest (~> 5.1) 7 | thread_safe (~> 0.3, >= 0.3.4) 8 | tzinfo (~> 1.1) 9 | algoliasearch (1.27.1) 10 | httpclient (~> 2.8, >= 2.8.3) 11 | json (>= 1.5.1) 12 | atomos (0.1.3) 13 | claide (1.0.3) 14 | cocoapods (1.8.4) 15 | activesupport (>= 4.0.2, < 5) 16 | claide (>= 1.0.2, < 2.0) 17 | cocoapods-core (= 1.8.4) 18 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 19 | cocoapods-downloader (>= 1.2.2, < 2.0) 20 | cocoapods-plugins (>= 1.0.0, < 2.0) 21 | cocoapods-search (>= 1.0.0, < 2.0) 22 | cocoapods-stats (>= 1.0.0, < 2.0) 23 | cocoapods-trunk (>= 1.4.0, < 2.0) 24 | cocoapods-try (>= 1.1.0, < 2.0) 25 | colored2 (~> 3.1) 26 | escape (~> 0.0.4) 27 | fourflusher (>= 2.3.0, < 3.0) 28 | gh_inspector (~> 1.0) 29 | molinillo (~> 0.6.6) 30 | nap (~> 1.0) 31 | ruby-macho (~> 1.4) 32 | xcodeproj (>= 1.11.1, < 2.0) 33 | cocoapods-core (1.8.4) 34 | activesupport (>= 4.0.2, < 6) 35 | algoliasearch (~> 1.0) 36 | concurrent-ruby (~> 1.1) 37 | fuzzy_match (~> 2.0.4) 38 | nap (~> 1.0) 39 | cocoapods-deintegrate (1.0.4) 40 | cocoapods-downloader (1.3.0) 41 | cocoapods-plugins (1.0.0) 42 | nap 43 | cocoapods-search (1.0.0) 44 | cocoapods-stats (1.1.0) 45 | cocoapods-trunk (1.4.1) 46 | nap (>= 0.8, < 2.0) 47 | netrc (~> 0.11) 48 | cocoapods-try (1.1.0) 49 | colored2 (3.1.2) 50 | concurrent-ruby (1.1.5) 51 | escape (0.0.4) 52 | fourflusher (2.3.1) 53 | fuzzy_match (2.0.4) 54 | gh_inspector (1.1.3) 55 | httpclient (2.8.3) 56 | i18n (0.9.5) 57 | concurrent-ruby (~> 1.0) 58 | json (2.3.0) 59 | minitest (5.13.0) 60 | molinillo (0.6.6) 61 | nanaimo (0.2.6) 62 | nap (1.1.0) 63 | netrc (0.11.0) 64 | ruby-macho (1.4.0) 65 | thread_safe (0.3.6) 66 | tzinfo (1.2.5) 67 | thread_safe (~> 0.1) 68 | xcodeproj (1.14.0) 69 | CFPropertyList (>= 2.3.3, < 4.0) 70 | atomos (~> 0.1.3) 71 | claide (>= 1.0.2, < 2.0) 72 | colored2 (~> 3.1) 73 | nanaimo (~> 0.2.6) 74 | 75 | PLATFORMS 76 | ruby 77 | 78 | DEPENDENCIES 79 | cocoapods (~> 1.8) 80 | 81 | BUNDLED WITH 82 | 2.0.2 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Song Chiwon 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: update lint trunk 2 | lint: 3 | @bundle exec pod spec lint CWUtils.podspec --allow-warnings 4 | @bundle exec pod spec lint CWUtilsTest.podspec --allow-warnings 5 | trunk: 6 | @bundle exec pod trunk push CWUtils.podspec --allow-warnings 7 | @bundle exec pod trunk push CWUtilsTest.podspec --allow-warnings 8 | update: 9 | @bundle update cocoapods 10 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "CWUtils", 7 | platforms: [ 8 | .iOS(.v10), 9 | ], 10 | products: [ 11 | .library(name: "CWUtils", targets: ["CWUtils"]), 12 | .library(name: "CWUtilsTest", targets: ["CWUtilsTest"]), 13 | ], 14 | dependencies: [ 15 | .package(url: "https://github.com/ReactiveX/RxSwift", .upToNextMajor(from: "5.0.0")), 16 | .package(url: "https://github.com/RxSwiftCommunity/RxSwiftExt", .upToNextMajor(from: "5.0.0")), 17 | .package(url: "https://github.com/RxSwiftCommunity/RxOptional", .upToNextMajor(from: "4.0.0")), 18 | .package(url: "https://github.com/devxoul/RxViewController", .upToNextMajor(from: "1.0.0")), 19 | .package(url: "https://github.com/SnapKit/SnapKit", .upToNextMajor(from: "5.0.0")), 20 | .package(url: "https://github.com/iamchiwon/Panda", .upToNextMajor(from: "2.0.0")), 21 | .package(url: "https://github.com/AliSoftware/Reusable", .branch("master")), 22 | .package(url: "https://github.com/devxoul/Then", .upToNextMajor(from: "2.6.0")), 23 | .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "5.12.0")), 24 | ], 25 | targets: [ 26 | .target(name: "CWUtils", dependencies: ["RxSwift", "RxCocoa", "RxSwiftExt", "Then", "Kingfisher", "SnapKit", "Panda", "Reusable"]), 27 | .target(name: "CWUtilsTest", dependencies: ["CWUtils"]), 28 | .testTarget(name: "CWUtilsTests", dependencies: ["CWUtils", "CWUtilsTest"]), 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CWUtils 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 4 | [![License](https://img.shields.io/cocoapods/l/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 5 | [![Platform](https://img.shields.io/cocoapods/p/CWUtils.svg?style=flat)](http://cocoapods.org/pods/CWUtils) 6 | 7 | ## Installation 8 | 9 | #### Cocoapods 10 | CWUtils is available through [CocoaPods](http://cocoapods.org). To install 11 | it, simply add the following line to your Podfile: 12 | 13 | ```ruby 14 | pod 'CWUtils' 15 | ``` 16 | 17 | #### Swift Package Manager 18 | Match target to right product 19 | ![](./Docs/spm.png) 20 | 21 | 22 | ## Dependancies 23 | 24 | - [RxSwift](https://github.com/ReactiveX/RxSwift) 25 | - [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa) 26 | - [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt) 27 | - [RxOptional](https://github.com/RxSwiftCommunity/RxOptional) 28 | - [RxViewController](https://github.com/devxoul/RxViewController) 29 | - [SnapKit](https://github.com/SnapKit/SnapKit) 30 | - [Panda](https://github.com/wordlessj/Panda) 31 | - [Reusable](https://github.com/AliSoftware/Reusable) 32 | - [Then](https://github.com/devxoul/Then) 33 | - [Kingfisher](https://github.com/onevcat/Kingfisher) 34 | 35 | ## Sample Code 36 | 37 | ### Utilities 38 | 39 | #### Logs 40 | 41 | ```swift 42 | DLog("log for debug build only") 43 | ELog("error log for all build") 44 | ``` 45 | 46 | #### Device 47 | 48 | ```swift 49 | runOnSimulatorOnly { 50 | print("running on simulator") 51 | } 52 | runOnDeviceOnly { 53 | print("running on device") 54 | } 55 | ``` 56 | 57 | #### Version Check 58 | 59 | ```swift 60 | let currentVersion: String = currentApplicationVersion() 61 | let needUpdate: Bool = isUpdateAvailable() 62 | ``` 63 | 64 | #### Validataion 65 | 66 | ```swift 67 | let isEmail = "nobody@email.com".isValid(withType: .email) 68 | let isEmail2 = "nobody@email.com".isValid(withRegEx: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}") 69 | ``` 70 | 71 | ### Extensions 72 | 73 | #### Strings 74 | 75 | ```swift 76 | let url = "www.apple.com".url() 77 | let base64 = "hello world".base64() 78 | let trimmed = " hello world \n \n ".trim() 79 | let localized = "Hello World".localized() 80 | ``` 81 | 82 | #### Date 83 | 84 | ```swift 85 | let serverDateParser = Date.fromFormat("yyyy-MM-dd HH:mm:ss") 86 | let date = serverDateParser("2018-02-17 17:00:00") 87 | ``` 88 | 89 | #### Array 90 | 91 | ```swift 92 | struct Data : Equatable {} 93 | var array: [Data] 94 | //-------- 95 | let data = Data() 96 | let exists: Bool = array.exists(data) 97 | let index: Int = array.indexOf(data) 98 | let array2 = array.removed(data) 99 | array.remove(item: data) //mutating 100 | ``` 101 | 102 | #### Codable 103 | 104 | ```swift 105 | struct Data : Codable {} 106 | //-------- 107 | let data = Data() 108 | let dictionary: [String:Any] = data.dictionary() 109 | let jsonString: String = data.json() 110 | let jsonString2: String = dictionary.json() 111 | let data2: Data = dictionary.decode() 112 | ``` 113 | 114 | ### UI 115 | 116 | #### UIColor 117 | 118 | ```swift 119 | let red = UIColor(red:255, green:0, blue:0) 120 | let blue = UIColor(0x00FF00) 121 | let green = UIColor("#0000FF") 122 | ``` 123 | 124 | #### subview 125 | 126 | ```swift 127 | //view with tag 128 | view.label(100)?.text = "Hello World" 129 | //image with tag 130 | view.imageView(100)?image = nil 131 | ``` 132 | 133 | #### popup 134 | 135 | ```swift 136 | if needUpdate { 137 | popupOkCancel(on: self, title: "업데이트", message: "업데이트가 필요합니다.", onOk: { 138 | let urlString = "itms-apps://itunes.apple.com/app/id0000000000" 139 | UIApplication.shared.openURL(URL(string: urlString)!) 140 | }) 141 | } else { 142 | popupOK(on: self, title: "업데이트", message: "최신버전입니다.") 143 | } 144 | ``` 145 | 146 | #### Image Resize 147 | 148 | ```swift 149 | let original = UIImage(named: "big_image") 150 | let resized = original?.resized(maxSize: CGSize(width: 100, height: 100)) 151 | let resizedForScreen = original?.resizedToScreen() 152 | ``` 153 | 154 | #### View Creator (with SnapKit) 155 | 156 | ```swift 157 | let rect = createView(UIView(), parent: self.view, setting: { v in 158 | v.backgroundColor = .red 159 | }, constraint: { m in 160 | m.top.left.equalToSuperview() 161 | m.width.height.equalTo(100) 162 | }) 163 | ``` 164 | 165 | ### RxSwift 166 | 167 | #### GestureRecognizer 168 | 169 | ```swift 170 | view.addTapGestureRecognizer() 171 | .subscribe(onNext: { (tapGusture : UITapGestureRecognizer) in 172 | }) 173 | 174 | view.addSwipeGestureRecognizer() 175 | .subscribe(onNext: { (tapGusture : UISwipeGestureRecognizer) in 176 | }) 177 | ``` 178 | 179 | #### Button 180 | 181 | ```swift 182 | //prevent double touched 183 | view.button(100)?.whenTouchUpInside(thro) 184 | .subscribe(onNext: { button in 185 | }) 186 | ``` 187 | 188 | #### Keyboard Notification 189 | 190 | ```swift 191 | whenKeyboardShowNotification() 192 | .subscribe(onNext: { (keyboardHeight: CGFloat, duration: TimeInterval) in 193 | }) 194 | .disposed(by: disposeBag) 195 | 196 | whenKeyboardHideNotification() 197 | .subscribe(onNext: { (duration: TimeInterval) in 198 | }) 199 | .disposed(by: disposeBag) 200 | ``` 201 | 202 | #### ScrollView 203 | 204 | ```swift 205 | collectionView.needsMore() 206 | .filter { !self.isLoading } 207 | .subscribe(onNext: { self.loadMore() }) 208 | .disposed(by: disposeBag) 209 | ``` 210 | 211 | #### Image Picker 212 | 213 | ```swift 214 | pickImageFromAlbum(on: self, withEdit: true) 215 | .subscribe(onNext: { self.imageView.image = $0 }) 216 | ``` 217 | 218 | ## Author 219 | 220 | iamchiwon, iamchiwon@gmail.com 221 | 222 | ## License 223 | 224 | CWUtils is available under the MIT license. See the LICENSE file for more info. 225 | -------------------------------------------------------------------------------- /Sources/CWUtils/CWUtils.swift: -------------------------------------------------------------------------------- 1 | struct CWUtils { 2 | var text = "Hello, World!" 3 | } 4 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Date/Date+Ago.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Ago.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 16/04/2019. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | public func ago() -> String { 12 | let seconds = timeIntervalSince1970 13 | let now = Date().timeIntervalSince1970 14 | let age = now - seconds 15 | if age < 10 { return "방금 전".localized() } 16 | if age < 60 { return "1분 전".localized() } 17 | if age < 60 * 60 { return "\(Int(age / 60))분 전".localized() } 18 | if age < 60 * 60 * 24 { return "\(Int(age / 60 / 60))시간 전".localized() } 19 | if age < 60 * 60 * 24 * 30 { return "\(Int(age / 60 / 60 / 24))일 전".localized() } 20 | if age < 60 * 60 * 24 * 30 * 12 { return "\(Int(age / 60 / 60 / 24 / 30))달 전".localized() } 21 | return "\(Int(age / 60 / 60 / 24 / 30 / 12))년 전".localized() 22 | } 23 | 24 | public func remain() -> String { 25 | let seconds = timeIntervalSince1970 26 | let now = Date().timeIntervalSince1970 27 | var remains = Int(seconds - now) 28 | if remains <= 0 { return "" } 29 | 30 | var dayString = "" 31 | 32 | let sec = remains % 60 33 | remains /= 60 34 | 35 | let minutes = remains % 60 36 | remains /= 60 37 | 38 | let hour = remains % 24 39 | remains /= 24 40 | 41 | let days = remains 42 | 43 | if days == 1 { 44 | dayString = "1Day" 45 | } else if days > 1 { 46 | dayString = "\(days)Days" 47 | } 48 | 49 | return String(format: "%@ %02d:%02d:%02d", dayString, hour, minutes, sec) 50 | } 51 | 52 | public func remainKr() -> String { 53 | let seconds = timeIntervalSince1970 54 | let now = Date().timeIntervalSince1970 55 | var remains = Int(seconds - now) 56 | if remains <= 0 { return "" } 57 | 58 | let sec = remains % 60 59 | remains /= 60 60 | 61 | let minutes = remains % 60 62 | remains /= 60 63 | 64 | let hour = remains % 24 65 | remains /= 24 66 | 67 | let days = remains 68 | 69 | return String(format: "%d일 %02d시간 %02d분 %02d초", days, hour, minutes, sec) 70 | } 71 | } -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Date/Date+Format.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Format.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | 12 | public static func fromFormat(_ format: String) -> (String) -> Date? { 13 | let df = DateFormatter() 14 | df.dateFormat = format 15 | df.locale = Locale(identifier: "en_US_POSIX") 16 | return { dateString in 17 | guard !dateString.isEmpty else { return nil } 18 | return df.date(from: dateString) 19 | } 20 | } 21 | 22 | public static func fromServerFormat() -> (String) -> Date? { 23 | return fromFormat("yyyy-MM-dd HH:mm:ss") 24 | } 25 | 26 | public func formatted(_ format: String) -> String { 27 | let df = DateFormatter() 28 | df.dateFormat = format 29 | df.locale = Locale(identifier: "en_US_POSIX") 30 | return df.string(from: self) 31 | } 32 | 33 | public func serverFormat() -> String { 34 | return self.formatted("yyyy-MM-dd HH:mm:ss") 35 | } 36 | 37 | public func shortDate() -> String { 38 | return self.formatted("yyyyMMdd") 39 | } 40 | 41 | public func longDate() -> String { 42 | return self.formatted("yyyy년 MM월 dd일") 43 | } 44 | 45 | public func timestamp() -> String { 46 | return self.formatted("yyyy/MM/dd HH:mm") 47 | } 48 | 49 | public func timestampDetail() -> String { 50 | return self.formatted("yyyy-MM-dd HH:mm:ss") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Date/Date+Milli.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+CWUtils.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Date { 12 | 13 | public static func currentTimeInMilli() -> Int { 14 | return Date().timeInMilli() 15 | } 16 | 17 | public func timeInMilli() -> Int { 18 | return Int(self.timeIntervalSince1970 / 1000.0) 19 | } 20 | 21 | init(milli: UInt64) { 22 | self.init(timeIntervalSince1970: (TimeInterval(milli) / 1000.0)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Foundation/Array+Equtable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Equtable.swift 3 | // d.code 4 | // 5 | // Created by iamchiwon on 2017. 12. 26.. 6 | // Copyright © 2017년 n.code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array where Element: Equatable { 12 | 13 | public func exists(_ item: Element) -> Bool { 14 | if let _ = self.indexOf(item) { 15 | return true 16 | } 17 | return false 18 | } 19 | 20 | public func indexOf(_ item: Element) -> Int? { 21 | return self.enumerated().filter({ $0.element == item }).map({ $0.offset }).first 22 | } 23 | 24 | public mutating func remove(item: Element) -> Element? { 25 | if let index = indexOf(item) { 26 | return self.remove(at: index) 27 | } 28 | return nil 29 | } 30 | 31 | public func removed(_ item: Element) -> [Element] { 32 | return self.filter({ $0 != item }) 33 | } 34 | 35 | public func findAndReplace(_ selector: (Element) -> Bool, 36 | replaceWith: (Element) -> Element) -> [Element] { 37 | return map { item in selector(item) ? replaceWith(item) : item } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Foundation/Codable+Dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Equtable.swift 3 | // d.code 4 | // 5 | // Created by iamchiwon on 2017. 12. 26.. 6 | // Copyright © 2017년 n.code. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Encodable { 12 | 13 | public func dictionary() -> [String: Any] { 14 | do { 15 | let data = try JSONEncoder().encode(self) 16 | let serialized = try JSONSerialization.jsonObject(with: data, options: .allowFragments) 17 | if let dictionary = serialized as? [String: Any] { 18 | return dictionary 19 | } 20 | return [:] 21 | } catch let e { 22 | ELog(e.localizedDescription) 23 | return [:] 24 | } 25 | } 26 | 27 | public func json() -> String { 28 | do { 29 | let data = try JSONEncoder().encode(self) 30 | if let json = String(data: data, encoding: .utf8) { 31 | return json 32 | } 33 | return "{}" 34 | } catch let e { 35 | ELog(e.localizedDescription) 36 | return "{}" 37 | } 38 | } 39 | } 40 | 41 | extension Dictionary { 42 | 43 | public func decode() -> T? where T: Decodable { 44 | do { 45 | return try JSONDecoder().decode(T.self, from: self.json().data(using: .utf8)!) 46 | } catch let e { 47 | ELog(e.localizedDescription) 48 | return nil 49 | } 50 | } 51 | 52 | public func json() -> String { 53 | do { 54 | let data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) 55 | if let json = String(data: data, encoding: .utf8) { 56 | return json 57 | } 58 | return "{}" 59 | } catch let e { 60 | ELog(e.localizedDescription) 61 | return "{}" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Foundation/Number+Format.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Number+Format.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Int { 12 | 13 | public func decimal() -> String { 14 | let numberFormatter = NumberFormatter() 15 | numberFormatter.numberStyle = NumberFormatter.Style.decimal 16 | return numberFormatter.string(from: NSNumber(value: self))! 17 | } 18 | 19 | public func currencyKRW() -> String { 20 | return "₩ " + decimal() 21 | } 22 | } 23 | 24 | extension FloatingPoint { 25 | 26 | public var degreesToRadians: Self { return self * .pi / 180 } 27 | public var radiansToDegrees: Self { return self * 180 / .pi } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Foundation/URL+Convenient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Convenient.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | 13 | public var queryItems: [String: String] { 14 | var params = [String: String]() 15 | return URLComponents(url: self, resolvingAgainstBaseURL: false)? 16 | .queryItems? 17 | .reduce([:], { (_, item) -> [String: String] in 18 | params[item.name] = item.value 19 | return params 20 | }) ?? [:] 21 | } 22 | 23 | public subscript(queryParam: String) -> String { 24 | guard let url = URLComponents(string: self.absoluteString) else { return "" } 25 | return url.queryItems?.first(where: { $0.name == queryParam })?.value ?? "" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Functions/AppVersion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppVersion.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public func isUpdateAvailable() -> Bool { 12 | 13 | guard let info = Bundle.main.infoDictionary, 14 | let currentVersion = info["CFBundleShortVersionString"] as? String, 15 | let identifier = info["CFBundleIdentifier"] as? String, 16 | let url = URL(string: "http://itunes.apple.com/lookup?bundleId=\(identifier)") else { 17 | //확인 불가 18 | return false 19 | } 20 | 21 | guard let data = try? Data(contentsOf: url) else { 22 | //확인 불가 23 | return false 24 | } 25 | 26 | guard let json = try? JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any] else { 27 | //확인 불가 28 | return false 29 | } 30 | 31 | if let result = (json["results"] as? [Any])?.first as? [String: Any], 32 | let storeVersion = result["version"] as? String { 33 | //확인 결과 34 | return (storeVersion.compare(currentVersion, options: .numeric) == .orderedDescending) 35 | } 36 | 37 | //확인 불가 38 | return false 39 | } 40 | 41 | public func currentApplicationVersion() -> String { 42 | if let info = Bundle.main.infoDictionary, 43 | let currentVersion = info["CFBundleShortVersionString"] as? String { 44 | return currentVersion 45 | } 46 | return "" 47 | } 48 | 49 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Functions/DLog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DLog.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private func filename(_ path: String) -> String { 12 | guard let filename = path.split(separator: "/").last else { return path } 13 | return String(filename.prefix(upTo: filename.index(filename.endIndex, offsetBy: -6))) 14 | } 15 | 16 | private func functionName(_ function: String) -> String { 17 | guard let functionName = function.split(separator: "(").first else { return function } 18 | return String(functionName) 19 | } 20 | 21 | public func VLog(_ message: T, file: String = #file, function: String = #function, line: Int = #line) { 22 | print("📢 [\(filename(file))]\(functionName(function))(\(line)): \(message)") 23 | } 24 | 25 | public func DLog(_ message: T, file: String = #file, function: String = #function, line: Int = #line) { 26 | print("🐞 [\(filename(file))]\(functionName(function))(\(line)): \(message)") 27 | } 28 | 29 | public func DLog(with m: () -> T, file: String = #file, function: String = #function, line: Int = #line) { 30 | print("🐞 [\(filename(file))]\(functionName(function))(\(line)): \(m())") 31 | } 32 | 33 | public func SLog(_ message: T, file: String = #file, function: String = #function, line: Int = #line) { 34 | print("📣 [\(filename(file))]\(functionName(function))(\(line)): \(message)") 35 | } 36 | 37 | public func WLog(_ message: T, file: String = #file, function: String = #function, line: Int = #line) { 38 | print("⚠️ [\(filename(file))]\(functionName(function))(\(line)): \(message)") 39 | } 40 | 41 | public func ELog(error: Error, file: String = #file, function: String = #function, line: Int = #line) { 42 | print("🚫 [\(filename(file))]\(functionName(function))(\(line)): \(error.localizedDescription)") 43 | } 44 | 45 | public func ELog(_ message: T, file: String = #file, function: String = #function, line: Int = #line) { 46 | print("🚫 [\(filename(file))]\(functionName(function))(\(line)): \(message)") 47 | } 48 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Functions/Device.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Device.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public func runOnSimulatorOnly(_ runable: () -> ()) { 12 | if TARGET_IPHONE_SIMULATOR == 1 { 13 | runable() 14 | } 15 | } 16 | 17 | public func runOnDeviceOnly(_ runable: () -> ()) { 18 | if TARGET_IPHONE_SIMULATOR != 1 { 19 | runable() 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Functions/Executor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Executor.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 09/02/2019. 6 | // 7 | 8 | import Foundation 9 | import Then 10 | 11 | public func executeMainAsync(_ job: @escaping () -> Void) { 12 | DispatchQueue.main.async { 13 | job() 14 | } 15 | } 16 | 17 | public func execute(after: TimeInterval, job: @escaping () -> Void) { 18 | DispatchQueue.main.asyncAfter(deadline: .now() + after) { 19 | job() 20 | } 21 | } 22 | 23 | public func execute(daily forKey: String, job: @escaping () -> Void) { 24 | UserDefaults.standard.do { it in 25 | let lastDay = it.string(forKey: forKey) 26 | let today = Date().shortDate() 27 | if lastDay == nil || lastDay! != today { 28 | it.set(today, forKey: forKey) 29 | job() 30 | } 31 | } 32 | } 33 | 34 | public func execute(once forKey: String, job: @escaping () -> Void) { 35 | UserDefaults.standard.do { it in 36 | let lastDay = it.string(forKey: forKey) 37 | if lastDay == nil { 38 | it.set("executed", forKey: forKey) 39 | job() 40 | } 41 | } 42 | } 43 | 44 | public func resetExecuted(once forKey: String) { 45 | UserDefaults.standard.do { it in 46 | it.set(nil, forKey: forKey) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Functions/Functions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Functions.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 2018. 9. 18.. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | public let i2s: (Int) -> String = { "\($0)" } 12 | 13 | public func bypass(_ a: A) -> A { 14 | return a 15 | } 16 | 17 | public func invert(_ a: Bool) -> Bool { 18 | return !a 19 | } 20 | 21 | public func isZero(_ a: Int) -> Bool { 22 | return a == 0 23 | } 24 | 25 | public func isNotZero(_ a: Int) -> Bool { 26 | return a != 0 27 | } 28 | 29 | public func isEmpty(_ a: String?) -> Bool { 30 | return a?.isEmpty ?? true 31 | } 32 | 33 | public func isNotEmpty(_ a: String?) -> Bool { 34 | return !isEmpty(a) 35 | } 36 | 37 | func curry(_ f: @escaping ((A, B) -> C)) -> (A) -> (B) -> C { 38 | return { a in 39 | { b in 40 | f(a, b) 41 | } 42 | } 43 | } 44 | 45 | func curry(_ f: @escaping ((A, B, C) -> D)) -> (A) -> (B) -> (C) -> D { 46 | return { a in 47 | { b in 48 | { c in 49 | f(a, b, c) 50 | } 51 | } 52 | } 53 | } 54 | 55 | func ratioMap(_ _min: CGFloat, _ _max: CGFloat) -> (CGFloat, CGFloat) -> (CGFloat) -> CGFloat { 56 | return { start, end in 57 | { value in 58 | let mininum = min(start, end) 59 | let maximum = max(start, end) 60 | let result = start + (value - _min) * (end - start) / (_max - _min) 61 | if result < mininum { return mininum } 62 | if result > maximum { return maximum } 63 | return result 64 | } 65 | } 66 | } 67 | 68 | func ratioMap(min: CGFloat, max: CGFloat, start: CGFloat, end: CGFloat) -> (CGFloat) -> CGFloat { 69 | return ratioMap(min, max)(start, end) 70 | } -------------------------------------------------------------------------------- /Sources/CWUtils/Core/Functions/Schedulers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Schedulers.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 09/02/2019. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | 11 | public struct Schedulers { 12 | public static let main = { 13 | MainScheduler.instance 14 | }() 15 | 16 | public static let background = { 17 | ConcurrentDispatchQueueScheduler(queue: DispatchQueue(label: "background", qos: .background)) 18 | }() 19 | 20 | public static let utility = { 21 | ConcurrentDispatchQueueScheduler(queue: DispatchQueue(label: "utility", qos: .utility)) 22 | }() 23 | 24 | public static let io = { 25 | ConcurrentDispatchQueueScheduler(queue: DispatchQueue(label: "io", qos: .default)) 26 | }() 27 | 28 | public static let `default` = { 29 | ConcurrentDispatchQueueScheduler(queue: DispatchQueue(label: "default", qos: .default)) 30 | }() 31 | 32 | public static let computation = { 33 | ConcurrentDispatchQueueScheduler(queue: DispatchQueue(label: "computation", qos: .userInitiated)) 34 | }() 35 | 36 | public static let userInitiated = { 37 | ConcurrentDispatchQueueScheduler(queue: DispatchQueue(label: "userInitiated", qos: .userInitiated)) 38 | }() 39 | 40 | public static let immediate = { 41 | ConcurrentDispatchQueueScheduler(queue: DispatchQueue(label: "immediate", qos: .userInteractive)) 42 | }() 43 | 44 | public static let userInteractive = { 45 | ConcurrentDispatchQueueScheduler(queue: DispatchQueue(label: "userInteractive", qos: .userInteractive)) 46 | }() 47 | } 48 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/RxSwift/RxSwift+RetryInterval.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxSwift+RetryInterval.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 16/04/2019. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxSwiftExt 11 | 12 | extension ObservableType { 13 | public func retryInterval(_ intervals: [RxTimeInterval], when f: @escaping (Error) -> Bool) -> Observable { 14 | return retryWhen { error -> Observable in 15 | error.flatMap { e -> Observable in 16 | guard f(e) else { return .error(e) } 17 | return .just(()) 18 | } 19 | .zip(with: Observable.from(intervals)) { $1 } 20 | .flatMap { Observable.timer($0, scheduler: Schedulers.background) } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/String/AttributeString+Style.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AttributeString+Style.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 09/02/2019. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension NSAttributedString { 12 | func setLineSpacing(lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) -> NSAttributedString { 13 | let paragraphStyle = NSMutableParagraphStyle() 14 | paragraphStyle.lineSpacing = lineSpacing 15 | paragraphStyle.lineHeightMultiple = lineHeightMultiple 16 | 17 | let attributedString = NSMutableAttributedString(attributedString: self) 18 | attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, 19 | value: paragraphStyle, 20 | range: NSRange(location: 0, length: attributedString.length)) 21 | 22 | return attributedString 23 | } 24 | 25 | public func striked(strikeColor: UIColor = UIColor.lightGray) -> NSAttributedString { 26 | let attributed = NSMutableAttributedString(attributedString: self) 27 | let stringRange = NSRange(location: 0, length: self.string.count) 28 | attributed.addAttribute(NSAttributedString.Key.baselineOffset, value: 0, range: stringRange) 29 | attributed.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 1, range: stringRange) 30 | attributed.addAttribute(NSAttributedString.Key.strikethroughColor, value: strikeColor, range: stringRange) 31 | return attributed 32 | } 33 | 34 | public func underlined() -> NSAttributedString { 35 | let attributedString = NSMutableAttributedString(attributedString: self) 36 | let stringRange = NSRange(location: 0, length: self.string.count) 37 | attributedString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: stringRange) 38 | return attributedString 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/String/String+Measure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Measure.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension String { 12 | 13 | public func calcHeight(forWidth: CGFloat, font: UIFont?) -> CGFloat { 14 | guard let font = font else { return 0 } 15 | let rect = CGSize(width: forWidth, height: .greatestFiniteMagnitude) 16 | let boundingRect = self.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil) 17 | return ceil(boundingRect.height) 18 | } 19 | 20 | public func calcWidth(forHeight: CGFloat, font: UIFont?) -> CGFloat { 21 | guard let font = font else { return 0 } 22 | let rect = CGSize(width: .greatestFiniteMagnitude, height: forHeight) 23 | let boundingRect = self.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil) 24 | return ceil(boundingRect.width) 25 | } 26 | } 27 | 28 | extension NSAttributedString { 29 | 30 | public func calcHeight(forWidth: CGFloat) -> CGFloat { 31 | let rect = CGSize(width: forWidth, height: .greatestFiniteMagnitude) 32 | let boundingRect = self.boundingRect(with: rect, options: .usesLineFragmentOrigin, context: nil) 33 | return ceil(boundingRect.height) 34 | } 35 | 36 | public func calcWidth(forHeight: CGFloat) -> CGFloat { 37 | let rect = CGSize(width: .greatestFiniteMagnitude, height: forHeight) 38 | let boundingRect = self.boundingRect(with: rect, options: .usesLineFragmentOrigin, context: nil) 39 | return ceil(boundingRect.width) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/String/String+Trans.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Trans.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | public func trim() -> String { 13 | return trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 14 | } 15 | 16 | public func url() -> URL { 17 | let urlText = trim() 18 | if let url = URL(string: urlText) { 19 | return url 20 | } 21 | if let urlString = urlText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), 22 | let url = URL(string: urlString) { 23 | return url 24 | } 25 | return NSURLComponents().url! // empty url 26 | } 27 | 28 | public func base64() -> String { 29 | if let data = self.data(using: .utf8) { 30 | return data.base64EncodedString() 31 | } 32 | return self 33 | } 34 | 35 | public func localized() -> String { 36 | return NSLocalizedString(self, comment: self) 37 | } 38 | 39 | public func replace(_ originalString: String, with newString: String) -> String { 40 | return replacingOccurrences(of: originalString, with: newString) 41 | } 42 | 43 | public static var uuid: String { 44 | return UUID().uuidString 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/CWUtils/Core/String/String+Validation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Validation.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum ValidationType: String { 12 | case email = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}" 13 | } 14 | 15 | extension String { 16 | 17 | public func isValid(withRegEx regex: String) -> Bool { 18 | return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: self) 19 | } 20 | 21 | public func isValid(withType type: ValidationType) -> Bool { 22 | return isValid(withRegEx: type.rawValue) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/Button+Tap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+IB.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | import UIKit 11 | 12 | public extension UIButton { 13 | func whenTouchUpInside(throttle dueTime: TimeInterval = 0.3) -> Observable { 14 | return rx.tap.asDriver() 15 | .throttle(.milliseconds(Int(dueTime * 1000)), latest: false) 16 | .map({ self }) 17 | .asObservable() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/CWViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CWViewController.swift 3 | // CWUtils 4 | // 5 | // Created by Chiwon Song on 2019/12/29. 6 | // 7 | 8 | import RxSwift 9 | import UIKit 10 | 11 | @objc public protocol CWViewControllerCustomizable { 12 | @objc optional func setupViews() 13 | @objc optional func setupBinds() 14 | } 15 | 16 | open class CWViewController: UIViewController, CWViewControllerCustomizable { 17 | open var disposeBag = DisposeBag() 18 | 19 | open override func viewDidLoad() { 20 | super.viewDidLoad() 21 | _setupViews() 22 | _setupBinds() 23 | } 24 | 25 | open override func willMove(toParent parent: UIViewController?) { 26 | if parent == nil { 27 | disposeBag = DisposeBag() 28 | } 29 | super.willMove(toParent: parent) 30 | } 31 | 32 | func push(to vc: UIViewController, animated: Bool = true) { 33 | if let navi = navigationController { 34 | navi.pushViewController(vc, animated: animated) 35 | return 36 | } else { 37 | present(vc, animated: animated, completion: nil) 38 | } 39 | } 40 | 41 | func pop(animated: Bool = true) { 42 | if let presenting = presentingViewController { 43 | presenting.dismiss(animated: animated, completion: nil) 44 | return 45 | } else if let navi = navigationController { 46 | navi.popViewController(animated: animated) 47 | } 48 | } 49 | 50 | private func _setupViews() { 51 | view.backgroundColor = UIColor.sysBackground 52 | (self as CWViewControllerCustomizable).setupViews?() 53 | } 54 | 55 | private func _setupBinds() { 56 | view.addTapGestureRecognizer() 57 | .subscribe(onNext: { $0.view?.endEditing(true) }) 58 | .disposed(by: disposeBag) 59 | (self as CWViewControllerCustomizable).setupBinds?() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/Color+hex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+hex.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | public convenience init(red: Int, green: Int, blue: Int, a: Int = 255) { 13 | assert(red >= 0 && red <= 255, "Invalid red component") 14 | assert(green >= 0 && green <= 255, "Invalid green component") 15 | assert(blue >= 0 && blue <= 255, "Invalid blue component") 16 | 17 | self.init(red: CGFloat(red) / 255.0, 18 | green: CGFloat(green) / 255.0, 19 | blue: CGFloat(blue) / 255.0, 20 | alpha: CGFloat(a) / 255.0) 21 | } 22 | 23 | public convenience init(rgb: Int) { 24 | self.init( 25 | red: (rgb >> 16) & 0xFF, 26 | green: (rgb >> 8) & 0xFF, 27 | blue: rgb & 0xFF 28 | ) 29 | } 30 | 31 | public convenience init(rgba: Int) { 32 | self.init( 33 | red: (rgba >> 24) & 0xFF, 34 | green: (rgba >> 16) & 0xFF, 35 | blue: (rgba >> 8) & 0xFF, 36 | a: rgba & 0xFF 37 | ) 38 | } 39 | 40 | public static func fromHexString(_ rgbHex: String) -> UIColor { 41 | return UIColor(rgb: Int.parseHex(rgbHex)) 42 | } 43 | } 44 | 45 | extension Int { 46 | public static func parseHex(_ hex: String) -> Int { 47 | let h2i = { "0123456789ABCDEF".firstIndex(of: $0)?.utf16Offset(in: "") ?? 0 } 48 | return hex.uppercased().map(h2i).reduce(0, { val, i in val * 16 + i }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/DarkMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+DarkMode.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 2019/12/10. 6 | // 7 | 8 | import UIKit 9 | 10 | public func isDarkMode() -> Bool { 11 | if #available(iOS 13.0, *) { 12 | return UIScreen.main.traitCollection.userInterfaceStyle == .dark 13 | } else { 14 | return false 15 | } 16 | } 17 | 18 | extension UIColor { 19 | public static func darkableColor(_ color: UIColor, darkmode: UIColor) -> UIColor { 20 | if #available(iOS 13.0, *) { 21 | return UIColor { (traits) -> UIColor in 22 | traits.userInterfaceStyle == .dark ? darkmode : color 23 | } 24 | } else { 25 | return color 26 | } 27 | } 28 | } 29 | 30 | extension UIImage { 31 | public static func darkableImage(_ image: UIImage, darkmode: UIImage) -> UIImage { 32 | if #available(iOS 13.0, *) { 33 | image.imageAsset?.register(darkmode, with: UITraitCollection(userInterfaceStyle: .dark)) 34 | return image 35 | } else { 36 | return image 37 | } 38 | } 39 | } 40 | 41 | extension UITraitCollection { 42 | func darkenized(_ builder: @autoclosure () -> T) -> T { 43 | var image = builder() 44 | if #available(iOS 13.0, *) { 45 | performAsCurrent { 46 | image = builder() 47 | } 48 | } 49 | return image 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/Image+Resize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image+Resize.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIImage { 12 | func resizedToScreen() -> UIImage { 13 | return resized(maxSize: UIScreen.main.bounds.size) 14 | } 15 | 16 | func resized(maxSize: CGSize) -> UIImage { 17 | let horizontalRatio = maxSize.width / size.width 18 | let verticalRatio = maxSize.height / size.height 19 | 20 | let ratio = max(horizontalRatio, verticalRatio) 21 | if ratio >= 1.0 { return self } 22 | 23 | let newSize = CGSize(width: size.width * ratio, height: size.height * ratio) 24 | UIGraphicsBeginImageContextWithOptions(newSize, true, 0) 25 | draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: newSize)) 26 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 27 | UIGraphicsEndImageContext() 28 | return newImage! 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/Image+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image+Rx.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 16/04/2019. 6 | // 7 | 8 | import Kingfisher 9 | import Photos 10 | import RxCocoa 11 | import RxSwift 12 | import UIKit 13 | 14 | public func rxLoadImage(_ url: URL) -> Observable { 15 | return Observable.create { emitter in 16 | KingfisherManager.shared.retrieveImage(with: url, 17 | completionHandler: { result in 18 | switch result { 19 | case let .success(imageData): 20 | emitter.onNext(imageData.image) 21 | emitter.onCompleted() 22 | case let .failure(error): 23 | emitter.onError(error) 24 | } 25 | }) 26 | return Disposables.create() 27 | } 28 | } 29 | 30 | public func rxSaveImage(_ image: UIImage) -> Observable { 31 | return PhotoWriter.save(image) 32 | } 33 | 34 | private class PhotoWriter: NSObject { 35 | typealias Callback = (PhotoWriter, NSError?) -> Void 36 | let disposeBag = DisposeBag() 37 | 38 | private var callback: Callback 39 | init(callback: @escaping Callback) { 40 | self.callback = callback 41 | } 42 | 43 | static func save(_ image: UIImage) -> Observable { 44 | return Observable.create { observer in 45 | let writer = PhotoWriter(callback: { writer, error in 46 | if let error = error { 47 | observer.onError(error) 48 | } else { 49 | observer.onNext(writer.latestAssetId()) 50 | observer.onCompleted() 51 | } 52 | }) 53 | 54 | UIImageWriteToSavedPhotosAlbum(image, writer, #selector(PhotoWriter.image(_: didFinishSavingWithError: contextInfo:)), nil) 55 | 56 | return Disposables.create() 57 | } 58 | } 59 | 60 | private func latestAssetId() -> String? { 61 | let options = PHFetchOptions() 62 | options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] 63 | let result = PHAsset.fetchAssets(with: .image, options: options) 64 | 65 | var assetToShare: PHAsset? 66 | result.enumerateObjects { asset, _, stop in 67 | assetToShare = asset 68 | stop.pointee = true 69 | } 70 | 71 | return assetToShare?.localIdentifier 72 | } 73 | 74 | @objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo info: UnsafeRawPointer) { 75 | callback(self, error) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/ImagePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePicker.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | import UIKit 11 | 12 | extension UIImagePickerController { 13 | public class func pickImage(on baseVC: UIViewController, 14 | allowsEditing: Bool = true) -> Observable { 15 | 16 | let subject = PublishSubject() 17 | 18 | class InnerDelegate: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 19 | weak var subject: PublishSubject? 20 | var allowsEditing = false 21 | 22 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { 23 | if allowsEditing, 24 | let editedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage { 25 | subject?.onNext(editedImage) 26 | 27 | } else if !allowsEditing, 28 | let originalImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { 29 | subject?.onNext(originalImage) 30 | } 31 | 32 | subject?.onCompleted() 33 | picker.dismiss(animated: true, completion: nil) // 5 34 | } 35 | 36 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 37 | subject?.onCompleted() 38 | picker.dismiss(animated: true, completion: nil) 39 | } 40 | } 41 | 42 | let innerDelegate = InnerDelegate() 43 | innerDelegate.subject = subject 44 | innerDelegate.allowsEditing = allowsEditing 45 | 46 | let picker = UIImagePickerController() 47 | picker.delegate = innerDelegate 48 | picker.allowsEditing = allowsEditing 49 | picker.sourceType = .photoLibrary 50 | picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)! 51 | 52 | baseVC.present(picker, animated: true, completion: nil) 53 | 54 | return Observable.zip(Observable.just(innerDelegate), subject.asObserver()) { $1 } 55 | .single() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/Keyboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keyboard.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | public func whenKeyboardShowNotification() -> Observable<(CGFloat, TimeInterval)> { 14 | return NotificationCenter.default.rx 15 | .notification(UIResponder.keyboardWillShowNotification) 16 | .map({ notification -> (height: CGFloat, duration: TimeInterval) in 17 | let keyboardSize = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue 18 | let durationNumber = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber 19 | return (height: keyboardSize.cgRectValue.size.height, duration: durationNumber.doubleValue) 20 | }) 21 | } 22 | 23 | public func whenKeyboardHideNotification() -> Observable { 24 | return NotificationCenter.default.rx 25 | .notification(UIResponder.keyboardWillHideNotification) 26 | .map({ notification -> TimeInterval in 27 | let durationNumber = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber 28 | return durationNumber.doubleValue 29 | }) 30 | } 31 | 32 | extension UIScrollView { 33 | public func scrollToCenterWhenFocusedTextFieldBeganEdit(_ field: UITextField, moveMore: CGFloat = 0) -> Disposable { 34 | return field.rx.controlEvent(event: UIControl.Event.editingDidBegin) 35 | .map { $0.sender.calcAbsoluteRectInScreen() } 36 | .map { bounds -> CGFloat in 37 | let screenCenterY = UIScreen.main.bounds.height / 2 38 | let fieldCenterY = bounds.centerY 39 | let diff = fieldCenterY - screenCenterY 40 | return max(diff, 0) 41 | } 42 | .subscribe(onNext: { [weak self] moveY in 43 | guard let self = self else { return } 44 | let current = self.contentOffset 45 | let moveTo = CGPoint(x: current.x, y: current.y + moveY) 46 | self.setContentOffset(moveTo, animated: true) 47 | }) 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/PandaExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PandaExtension.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 2019/12/10. 6 | // 7 | 8 | import Panda 9 | import SnapKit 10 | import UIKit 11 | 12 | extension PandaChain where Object: UIView { 13 | @discardableResult 14 | public func size(_ size: CGSize) -> PandaChain { 15 | object.widthAnchor.constraint(equalToConstant: size.width).isActive = true 16 | object.heightAnchor.constraint(equalToConstant: size.height).isActive = true 17 | return self 18 | } 19 | 20 | @discardableResult 21 | public func size(_ size: CGFloat) -> PandaChain { 22 | object.widthAnchor.constraint(equalToConstant: size).isActive = true 23 | object.heightAnchor.constraint(equalToConstant: size).isActive = true 24 | return self 25 | } 26 | 27 | @discardableResult 28 | public func size(_ width: CGFloat, _ height: CGFloat) -> PandaChain { 29 | object.widthAnchor.constraint(equalToConstant: width).isActive = true 30 | object.heightAnchor.constraint(equalToConstant: height).isActive = true 31 | return self 32 | } 33 | 34 | @discardableResult 35 | public func width(_ width: CGFloat) -> PandaChain { 36 | object.widthAnchor.constraint(equalToConstant: width).isActive = true 37 | return self 38 | } 39 | 40 | @discardableResult 41 | public func height(_ height: CGFloat) -> PandaChain { 42 | object.heightAnchor.constraint(equalToConstant: height).isActive = true 43 | return self 44 | } 45 | 46 | @discardableResult 47 | public func constraint(_ maker: (ConstraintMaker) -> Void) -> PandaChain { 48 | object.snp.makeConstraints(maker) 49 | return self 50 | } 51 | 52 | @discardableResult 53 | public func add(to parent: UIView?) -> PandaChain { 54 | switch parent { 55 | case let stack as UIStackView: 56 | stack.addArrangedSubview(object) 57 | case let collectionCell as UICollectionViewCell: 58 | collectionCell.contentView.addSubview(object) 59 | case let tableCell as UITableViewCell: 60 | tableCell.contentView.addSubview(object) 61 | case let scrollView as UIScrollView: 62 | if let refresh = object as? UIRefreshControl { 63 | scrollView.refreshControl = refresh 64 | } else { 65 | scrollView.addSubview(object) 66 | } 67 | default: 68 | parent?.addSubview(object) 69 | } 70 | return self 71 | } 72 | 73 | public func add(to parent: PandaChain) -> PandaChain where Parent: UIView { 74 | add(to: parent.view) 75 | return self 76 | } 77 | 78 | public var snp: ConstraintViewDSL { 79 | return object.snp 80 | } 81 | 82 | public var view: Object { 83 | return object 84 | } 85 | } 86 | 87 | public func VStack(alignment: UIStackView.Alignment = .leading, 88 | spacing: CGFloat = 0, 89 | distribution: UIStackView.Distribution = .equalSpacing) -> PandaChain { 90 | return UIStackView().pd.axis(.vertical) 91 | .alignment(alignment) 92 | .spacing(spacing) 93 | .distribution(distribution) 94 | } 95 | 96 | public func HStack(alignment: UIStackView.Alignment = .center, 97 | spacing: CGFloat = 0, 98 | distribution: UIStackView.Distribution = .equalSpacing) -> PandaChain { 99 | return UIStackView().pd.axis(.horizontal) 100 | .alignment(alignment) 101 | .spacing(spacing) 102 | .distribution(distribution) 103 | } 104 | 105 | public func Text(_ text: String = "") -> PandaChain { 106 | return UILabel().pd.text(text) 107 | } 108 | 109 | public func Image(_ image: UIImage? = nil) -> PandaChain { 110 | return UIImageView().pd.image(image) 111 | } 112 | 113 | public func VStackView(alignment: UIStackView.Alignment = .leading, 114 | spacing: CGFloat = 0, 115 | distribution: UIStackView.Distribution = .equalSpacing) -> UIStackView { 116 | return VStack(alignment: alignment, 117 | spacing: spacing, 118 | distribution: distribution).view 119 | } 120 | 121 | public func HStackView(alignment: UIStackView.Alignment = .leading, 122 | spacing: CGFloat = 0, 123 | distribution: UIStackView.Distribution = .equalSpacing) -> UIStackView { 124 | return HStack(alignment: alignment, 125 | spacing: spacing, 126 | distribution: distribution).view 127 | } 128 | 129 | public func TextView(_ text: String = "") -> UILabel { 130 | return Text(text).view 131 | } 132 | 133 | public func ImageView(_ image: UIImage? = nil) -> UIImageView { 134 | return Image(image).view 135 | } 136 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/Popup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Alert.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public func popupOK(on base: UIViewController, 12 | title: String, 13 | message: String?, 14 | buttonTitle: String = "OK", 15 | completion: (() -> Void)? = nil) { 16 | let alert = UIAlertController(title: title, 17 | message: message, 18 | preferredStyle: UIAlertController.Style.alert) 19 | 20 | alert.addAction(UIAlertAction(title: buttonTitle.localized(), 21 | style: UIAlertAction.Style.default, 22 | handler: { _ in 23 | if let completion = completion { 24 | completion() 25 | } 26 | })) 27 | base.present(alert, animated: true, completion: nil) 28 | } 29 | 30 | public func popupOkCancel(on base: UIViewController, 31 | title: String, 32 | message: String, 33 | okButtonTitle: String = "OK", 34 | cancelButtonTitle: String = "Cancel", 35 | onOk: @escaping () -> Void) { 36 | let alert = UIAlertController(title: title, 37 | message: message, 38 | preferredStyle: UIAlertController.Style.alert) 39 | 40 | alert.addAction(UIAlertAction(title: okButtonTitle.localized(), 41 | style: UIAlertAction.Style.default, 42 | handler: { _ in 43 | onOk() 44 | })) 45 | alert.addAction(UIAlertAction(title: cancelButtonTitle.localized(), 46 | style: UIAlertAction.Style.cancel, 47 | handler: nil)) 48 | base.present(alert, animated: true, completion: nil) 49 | } 50 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/Reusable+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Reusable+Rx.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 09/02/2019. 6 | // 7 | 8 | import Reusable 9 | import RxSwift 10 | import UIKit 11 | 12 | public extension Reactive where Base: UITableView { 13 | func items(cellType: T.Type) 14 | -> (_ source: O) 15 | -> (_ configureCell: @escaping (Int, S.Iterator.Element, T) -> Void) 16 | -> Disposable 17 | where O.Element == S, T: Reusable { 18 | return items(cellIdentifier: T.reuseIdentifier, cellType: cellType) 19 | } 20 | } 21 | 22 | extension Reactive where Base: UICollectionView { 23 | public func items(cellType: T.Type) 24 | -> (_ source: O) 25 | -> (_ configureCell: @escaping (Int, S.Iterator.Element, T) -> Void) 26 | -> Disposable 27 | where O.Element == S, T: Reusable { 28 | return items(cellIdentifier: T.reuseIdentifier, cellType: cellType) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/ScrollView+More.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollView+More.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 2018. 2. 14.. 6 | // 7 | 8 | import UIKit 9 | import RxSwift 10 | import RxCocoa 11 | 12 | public extension UIScrollView { 13 | 14 | func needsMore() -> Observable { 15 | return self.rx.didScroll.map { _ in self } 16 | .filter { 17 | let offsetY = $0.contentOffset.y 18 | let scrollerHeight = $0.bounds.size.height 19 | let scrollRemain = $0.contentSize.height - offsetY - scrollerHeight 20 | return scrollRemain < scrollerHeight 21 | } 22 | } 23 | 24 | func calcScrollViewEndingPosition() -> CGPoint { 25 | let W = self.contentSize.width 26 | let w = self.bounds.width 27 | let r = self.contentInset.right 28 | 29 | let H = self.contentSize.height 30 | let h = self.bounds.height 31 | let b = self.contentInset.bottom 32 | 33 | return CGPoint(x: W - w + r, y: H - h + b) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/StackView+Remove.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StackView+Remove.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIStackView { 12 | func removeAllArrangedSubview() { 13 | arrangedSubviews.forEach({ child in 14 | self.removeArrangedSubview(child) 15 | child.removeFromSuperview() 16 | }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/TableView+Reload.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableView+Reload.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UITableView { 12 | func reload(animation: UITableView.RowAnimation) { 13 | let range = NSMakeRange(0, numberOfSections) 14 | let sections = NSIndexSet(indexesIn: range) 15 | reloadSections(sections as IndexSet, with: animation) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/UIColorExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColorExtensions.swift 3 | // CWUtils 4 | // 5 | // Created by Chiwon Song on 2019/12/29. 6 | // 7 | 8 | import UIKit 9 | 10 | public extension UIColor { 11 | static var sysBackground: UIColor = { 12 | if #available(iOS 13.0, *) { 13 | return UIColor.systemBackground 14 | } else { 15 | return #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) 16 | } 17 | }() 18 | 19 | static var sysLabel: UIColor = { 20 | if #available(iOS 13.0, *) { 21 | return UIColor.label 22 | } else { 23 | return #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) 24 | } 25 | }() 26 | 27 | static var sysBlue: UIColor = { 28 | if #available(iOS 13.0, *) { 29 | return UIColor.systemBlue 30 | } else { 31 | return #colorLiteral(red: 0, green: 0.4793452024, blue: 0.9990863204, alpha: 1) 32 | } 33 | }() 34 | 35 | static var sysGreen: UIColor = { 36 | if #available(iOS 13.0, *) { 37 | return UIColor.systemGreen 38 | } else { 39 | return #colorLiteral(red: 0.2036584318, green: 0.7829266191, blue: 0.3481193781, alpha: 1) 40 | } 41 | }() 42 | 43 | static var sysIndigo: UIColor = { 44 | if #available(iOS 13.0, *) { 45 | return UIColor.systemIndigo 46 | } else { 47 | return #colorLiteral(red: 0.3459055424, green: 0.3397476971, blue: 0.8399652839, alpha: 1) 48 | } 49 | }() 50 | 51 | static var sysOrange: UIColor = { 52 | if #available(iOS 13.0, *) { 53 | return UIColor.systemOrange 54 | } else { 55 | return #colorLiteral(red: 1, green: 0.5865499377, blue: 0, alpha: 1) 56 | } 57 | }() 58 | 59 | static var sysPink: UIColor = { 60 | if #available(iOS 13.0, *) { 61 | return UIColor.systemPink 62 | } else { 63 | return #colorLiteral(red: 1, green: 0.1786963046, blue: 0.3346384168, alpha: 1) 64 | } 65 | }() 66 | 67 | static var sysPurple: UIColor = { 68 | if #available(iOS 13.0, *) { 69 | return UIColor.systemPurple 70 | } else { 71 | return #colorLiteral(red: 0.6871816516, green: 0.3226081729, blue: 0.8700770736, alpha: 1) 72 | } 73 | }() 74 | 75 | static var sysRed: UIColor = { 76 | if #available(iOS 13.0, *) { 77 | return UIColor.systemRed 78 | } else { 79 | return #colorLiteral(red: 0.9990618825, green: 0.2337017655, blue: 0.1887318194, alpha: 1) 80 | } 81 | }() 82 | 83 | static var sysTeal: UIColor = { 84 | if #available(iOS 13.0, *) { 85 | return UIColor.systemTeal 86 | } else { 87 | return #colorLiteral(red: 0.3548746705, green: 0.7854425311, blue: 0.9815476537, alpha: 1) 88 | } 89 | }() 90 | 91 | static var sysYellow: UIColor = { 92 | if #available(iOS 13.0, *) { 93 | return UIColor.systemYellow 94 | } else { 95 | return #colorLiteral(red: 1, green: 0.8014437556, blue: 0.004643389955, alpha: 1) 96 | } 97 | }() 98 | 99 | static var sysGray: UIColor = { 100 | if #available(iOS 13.0, *) { 101 | return UIColor.systemGray 102 | } else { 103 | return #colorLiteral(red: 0.556738019, green: 0.5565260053, blue: 0.577188611, alpha: 1) 104 | } 105 | }() 106 | 107 | static var sysGray2: UIColor = { 108 | if #available(iOS 13.0, *) { 109 | return UIColor.systemGray2 110 | } else { 111 | return #colorLiteral(red: 0.6822280884, green: 0.6821004748, blue: 0.6985904574, alpha: 1) 112 | } 113 | }() 114 | 115 | static var sysGray3: UIColor = { 116 | if #available(iOS 13.0, *) { 117 | return UIColor.systemGray3 118 | } else { 119 | return #colorLiteral(red: 0.7802448869, green: 0.7800709605, blue: 0.8006975055, alpha: 1) 120 | } 121 | }() 122 | 123 | static var sysGray4: UIColor = { 124 | if #available(iOS 13.0, *) { 125 | return UIColor.systemGray4 126 | } else { 127 | return #colorLiteral(red: 0.8194566369, green: 0.8192892671, blue: 0.8399093747, alpha: 1) 128 | } 129 | }() 130 | 131 | static var sysGray5: UIColor = { 132 | if #available(iOS 13.0, *) { 133 | return UIColor.systemGray5 134 | } else { 135 | return #colorLiteral(red: 0.8978799582, green: 0.8977256417, blue: 0.9183328748, alpha: 1) 136 | } 137 | }() 138 | 139 | static var sysGray6: UIColor = { 140 | if #available(iOS 13.0, *) { 141 | return UIColor.systemGray6 142 | } else { 143 | return #colorLiteral(red: 0.9488552213, green: 0.9487094283, blue: 0.9693081975, alpha: 1) 144 | } 145 | }() 146 | } 147 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/UIControl+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl+Rx.swift 3 | // CWUtils 4 | // 5 | // Created by iamchiwon on 09/02/2019. 6 | // 7 | 8 | import RxCocoa 9 | import RxSwift 10 | import UIKit 11 | 12 | public struct TargetedControlEvent { 13 | public var event: UIControl.Event 14 | public var sender: T 15 | } 16 | 17 | public extension Reactive where Base: UIControl { 18 | func controlEvent(event: UIControl.Event) -> Observable> { 19 | let targetedControlEvent = TargetedControlEvent(event: event, sender: base) 20 | return base.rx.controlEvent(event).map({ _ in targetedControlEvent }) 21 | } 22 | 23 | func controlEvent(events: [UIControl.Event]) -> Observable> { 24 | return Observable.merge(events.map({ controlEvent(event: $0) })) 25 | } 26 | 27 | var allTouchEvents: Observable> { 28 | let touchEvents: [UIControl.Event] = [ 29 | .touchDownRepeat, 30 | .touchDragInside, 31 | .touchDragOutside, 32 | .touchDragEnter, 33 | .touchDragExit, 34 | .touchUpInside, 35 | .touchUpOutside, 36 | .touchCancel, 37 | ] 38 | return controlEvent(events: touchEvents) 39 | } 40 | 41 | var allEditingEvents: Observable> { 42 | let editingEvents: [UIControl.Event] = [ 43 | .editingDidBegin, 44 | .editingChanged, 45 | .editingDidEnd, 46 | .editingDidEndOnExit, 47 | ] 48 | return controlEvent(events: editingEvents) 49 | } 50 | 51 | var allEvents: Observable> { 52 | let events: [UIControl.Event] = [ 53 | .valueChanged, 54 | .applicationReserved, 55 | .systemReserved, 56 | ] 57 | 58 | var eventObservables = events.map({ controlEvent(event: $0) }) 59 | eventObservables.append(contentsOf: [allTouchEvents, allEditingEvents]) 60 | 61 | return Observable.merge(eventObservables) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/View+GestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+Touch.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | import UIKit 11 | 12 | public extension UIView { 13 | func addTapGestureRecognizer(_ numberOfTapsRequired: Int = 1) -> Observable { 14 | let tapGesture = UITapGestureRecognizer() 15 | tapGesture.numberOfTapsRequired = numberOfTapsRequired 16 | isUserInteractionEnabled = true 17 | addGestureRecognizer(tapGesture) 18 | return tapGesture.rx.event.asObservable().observeOn(Schedulers.main) 19 | } 20 | 21 | func addSwipeGestureRecognizer() -> Observable { 22 | let swipeGesture = UISwipeGestureRecognizer() 23 | isUserInteractionEnabled = true 24 | addGestureRecognizer(swipeGesture) 25 | return swipeGesture.rx.event.asObservable().observeOn(Schedulers.main) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/View+IB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+IB.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | public extension UIView { 13 | @IBInspectable 14 | var borderColor: UIColor? { 15 | set { 16 | layer.borderColor = newValue!.cgColor 17 | } 18 | get { 19 | if let color = layer.borderColor { 20 | return UIColor(cgColor: color) 21 | } else { 22 | return nil 23 | } 24 | } 25 | } 26 | 27 | @IBInspectable 28 | var borderWidth: CGFloat { 29 | set { 30 | layer.borderWidth = newValue 31 | } 32 | get { 33 | return layer.borderWidth 34 | } 35 | } 36 | 37 | @IBInspectable 38 | var cornerRadius: CGFloat { 39 | set { 40 | layer.cornerRadius = newValue 41 | clipsToBounds = newValue > 0 42 | } 43 | get { 44 | return layer.cornerRadius 45 | } 46 | } 47 | 48 | @IBInspectable 49 | var shadowColor: UIColor? { 50 | set { 51 | layer.shadowColor = newValue!.cgColor 52 | } 53 | get { 54 | if let color = layer.shadowColor { 55 | return UIColor(cgColor: color) 56 | } else { 57 | return nil 58 | } 59 | } 60 | } 61 | 62 | @IBInspectable 63 | var shadowOpacity: Float { 64 | set { 65 | layer.shadowOpacity = newValue 66 | } 67 | get { 68 | return layer.shadowOpacity 69 | } 70 | } 71 | 72 | @IBInspectable 73 | var masksToBounds: Bool { 74 | set { 75 | layer.masksToBounds = newValue 76 | } 77 | get { 78 | return layer.masksToBounds 79 | } 80 | } 81 | 82 | @IBInspectable 83 | var shadowRadius: CGFloat { 84 | set { 85 | layer.shadowRadius = newValue 86 | } 87 | get { 88 | return layer.shadowRadius 89 | } 90 | } 91 | 92 | @IBInspectable 93 | var shadowOffset: CGSize { 94 | set { 95 | layer.shadowOffset = newValue 96 | } 97 | get { 98 | return layer.shadowOffset 99 | } 100 | } 101 | 102 | var isShown: Bool { 103 | get { 104 | return !isHidden 105 | } 106 | set { 107 | isHidden = !newValue 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/View+Measure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+Measure.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension CGRect { 12 | var x: CGFloat { return self.origin.x } 13 | var y: CGFloat { return self.origin.y } 14 | var centerX: CGFloat { return self.midX } 15 | var centerY: CGFloat { return self.midY } 16 | var center: CGPoint { return CGPoint(x: self.midX, y: self.midY) } 17 | } 18 | 19 | public extension UIView { 20 | var centerX: CGFloat { return self.center.x } 21 | var centerY: CGFloat { return self.center.y } 22 | } 23 | 24 | public extension UIView { 25 | func calcAbsoluteRectInScreen() -> CGRect { 26 | var rect = self.frame 27 | var superView = self.superview 28 | while(superView != nil) { 29 | let ssuperView = superView!.superview 30 | if ssuperView == nil { break } 31 | 32 | rect = superView!.convert(rect, to: ssuperView!) 33 | superView = ssuperView 34 | } 35 | return rect 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/View+Subview.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+Subview.swift 3 | // Example 4 | // 5 | // Created by iamchiwon on 2017. 12. 22.. 6 | // Copyright © 2017년 makecube. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIView { 12 | func viewWithIdentifier(_ identifier: String) -> UIView? { 13 | if identifier == accessibilityIdentifier { return self } 14 | for v in subviews { 15 | let sub = v.viewWithIdentifier(identifier) 16 | if sub != nil { return sub } 17 | } 18 | return nil 19 | } 20 | 21 | func constraintWithIdentifier(_ identifier: String) -> NSLayoutConstraint? { 22 | var searchView: UIView? = self 23 | while searchView != nil { 24 | for constraint in searchView!.constraints as [NSLayoutConstraint] { 25 | if constraint.identifier == identifier { 26 | return constraint 27 | } 28 | } 29 | searchView = searchView!.superview 30 | } 31 | return nil 32 | } 33 | 34 | var parentViewController: UIViewController? { 35 | var parentResponder: UIResponder? = self 36 | while parentResponder != nil { 37 | parentResponder = parentResponder!.next 38 | if let viewController = parentResponder as? UIViewController { 39 | return viewController 40 | } 41 | } 42 | return nil 43 | } 44 | 45 | func imageView(_ tag: Int) -> UIImageView? { 46 | return viewWithTag(tag) as? UIImageView 47 | } 48 | 49 | func label(_ tag: Int) -> UILabel? { 50 | return viewWithTag(tag) as? UILabel 51 | } 52 | 53 | func button(_ tag: Int) -> UIButton? { 54 | return viewWithTag(tag) as? UIButton 55 | } 56 | 57 | func textfield(_ tag: Int) -> UITextField? { 58 | return viewWithTag(tag) as? UITextField 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/CWUtils/UI/ViewCreator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewCreator.swift 3 | // d.code 4 | // 5 | // Created by iamchiwon on 2018. 3. 6.. 6 | // Copyright © 2018년 n.code. All rights reserved. 7 | // 8 | 9 | import SnapKit 10 | import UIKit 11 | 12 | @discardableResult 13 | public func createView(_ view: T, 14 | parent: UIView?, 15 | setting: ((T) -> Void)? = nil, 16 | constraint: ((ConstraintMaker) -> Void)? = nil) -> T where T: UIView { 17 | 18 | switch parent { 19 | case let stack as UIStackView: 20 | stack.addArrangedSubview(view) 21 | case let collectionCell as UICollectionViewCell: 22 | collectionCell.contentView.addSubview(view) 23 | case let tableCell as UITableViewCell: 24 | tableCell.contentView.addSubview(view) 25 | case let scrollView as UIScrollView: 26 | if let refresh = view as? UIRefreshControl { 27 | scrollView.refreshControl = refresh 28 | } else { 29 | scrollView.addSubview(view) 30 | } 31 | default: 32 | parent?.addSubview(view) 33 | } 34 | 35 | if let setting = setting { 36 | setting(view) 37 | } 38 | 39 | if let constraint = constraint { 40 | view.snp.makeConstraints(constraint) 41 | } 42 | return view 43 | } 44 | 45 | @discardableResult 46 | public func createVStack(parent: UIView?, 47 | spacing: CGFloat = 0, 48 | setting: ((UIStackView) -> Void)? = nil, 49 | constraint: ((ConstraintMaker) -> Void)? = nil) -> UIStackView { 50 | return createView(UIStackView(), parent: parent, setting: { v in 51 | v.axis = .vertical 52 | v.distribution = .fillProportionally 53 | v.alignment = .leading 54 | v.spacing = spacing 55 | setting?(v) 56 | }, constraint: constraint) 57 | } 58 | 59 | @discardableResult 60 | public func createHStack(parent: UIView?, 61 | spacing: CGFloat = 0, 62 | setting: ((UIStackView) -> Void)? = nil, 63 | constraint: ((ConstraintMaker) -> Void)? = nil) -> UIStackView { 64 | return createView(UIStackView(), parent: parent, setting: { v in 65 | v.axis = .horizontal 66 | v.distribution = .fillProportionally 67 | v.alignment = .center 68 | v.spacing = spacing 69 | setting?(v) 70 | }, constraint: constraint) 71 | } 72 | 73 | @discardableResult 74 | public func createSpacer(parent: UIView?, 75 | width: CGFloat? = nil, 76 | height: CGFloat? = nil) -> UIView { 77 | return createView(UIView(), parent: parent, setting: { v in 78 | v.backgroundColor = .clear 79 | }, constraint: { m in 80 | if let w = width { m.width.equalTo(w) } 81 | if let h = height { m.height.equalTo(h) } 82 | }) 83 | } 84 | 85 | extension ConstraintMaker { 86 | public func aspectRatio(_ x: Int, by y: Int, self instance: ConstraintView) { 87 | self.width.equalTo(instance.snp.height).multipliedBy(x / y) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/CWUtilsTest/Test/Await.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Await.swift 3 | // CWUtilsTest 4 | // 5 | // Created by iamchiwon on 14/12/2019. 6 | // 7 | 8 | import XCTest 9 | 10 | extension XCTestCase { 11 | public func await(_ description: String = "", timeout: TimeInterval = 3, testing: @escaping (@escaping () -> Void) -> Void) { 12 | let exp = expectation(description: description) 13 | let startTime = Date().timeIntervalSince1970 * 1000 14 | let done: () -> Void = { 15 | let diff = Date().timeIntervalSince1970 * 1000 - startTime 16 | 17 | let numberFormatter = NumberFormatter() 18 | numberFormatter.numberStyle = NumberFormatter.Style.decimal 19 | numberFormatter.maximumSignificantDigits = 2 20 | 21 | let diffStr = numberFormatter.string(from: NSNumber(value: diff)) ?? "N/A" 22 | print("[AWAIT] \(description) : \(diffStr) (ms)") 23 | 24 | exp.fulfill() 25 | } 26 | 27 | testing(done) 28 | 29 | waitForExpectations(timeout: timeout + 0.1) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/CWUtilsTests/CWUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CWUtils 3 | 4 | final class CWUtilsTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(CWUtils().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Tests/CWUtilsTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(CWUtilsTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import CWUtilsTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += CWUtilsTests.allTests() 7 | XCTMain(tests) 8 | --------------------------------------------------------------------------------