├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── .travis.yml ├── DEMO ├── .swiftlint.yml ├── KRPullLoaderDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── KRPullLoaderDemo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── KRPullLoaderDemo │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── Main.storyboard │ ├── CollectionViewSampleVC.swift │ ├── HorizontalCollectionViewSampleVC.swift │ ├── HorizontalPullLoadView.swift │ ├── Info.plist │ ├── LaunchScreen.storyboard │ ├── TableViewSampleVC.swift │ └── UIColorExtension.swift └── Podfile ├── KRPullLoader.podspec ├── KRPullLoader.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── KRPullLoader.xcscheme ├── KRPullLoader ├── Classes │ ├── KRPullLoadView.swift │ ├── KRPullLoader.swift │ ├── NSLayoutConstraintExtensions.swift │ └── UIScrollViewExtensions.swift ├── Info.plist └── KRPullLoader.h ├── KRPullLoaderTests ├── Info.plist └── KRPullLoaderTests.swift ├── LICENSE ├── README.md └── README_Ja.md /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # KRPullLoader 23 | Carthage 24 | Pods 25 | DEMO/Podfile.lock 26 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0.1 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - line_length 3 | - file_length 4 | included: 5 | - KRPullLoader 6 | - KRPullLoaderTests 7 | excluded: 8 | - Pods 9 | type_body_length: 10 | - 400 # warning 11 | - 500 # error 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10.2 3 | xcode_sdk: iphonesimulator12.2 4 | install: 5 | - gem install xcpretty 6 | script: 7 | - set -o pipefail 8 | - travis_retry xcodebuild -project KRPullLoader.xcodeproj -scheme KRPullLoader -destination "platform=iOS Simulator,name=iPhone Xs" build-for-testing test | xcpretty 9 | notifications: 10 | email: false 11 | -------------------------------------------------------------------------------- /DEMO/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - line_length 3 | - file_length 4 | included: 5 | - KRPullLoaderDemo 6 | type_body_length: 7 | - 400 # warning 8 | - 500 # error 9 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6B0E3F2E1EE4EE08001053BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B0E3F281EE4EE08001053BF /* AppDelegate.swift */; }; 11 | 6B0E3F2F1EE4EE08001053BF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6B0E3F291EE4EE08001053BF /* Main.storyboard */; }; 12 | 6B0E3F311EE4EE08001053BF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6B0E3F2C1EE4EE08001053BF /* LaunchScreen.storyboard */; }; 13 | 6B0E3F321EE4EE08001053BF /* TableViewSampleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B0E3F2D1EE4EE08001053BF /* TableViewSampleVC.swift */; }; 14 | 6B803B471EE95A5A00E0A784 /* HorizontalPullLoadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B803B461EE95A5A00E0A784 /* HorizontalPullLoadView.swift */; }; 15 | 6B803B491EEA32E600E0A784 /* CollectionViewSampleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B803B481EEA32E600E0A784 /* CollectionViewSampleVC.swift */; }; 16 | 6B803B4C1EEA40C100E0A784 /* HorizontalCollectionViewSampleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B803B4B1EEA40C100E0A784 /* HorizontalCollectionViewSampleVC.swift */; }; 17 | 6B803B521EEA451900E0A784 /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B803B511EEA451900E0A784 /* UIColorExtension.swift */; }; 18 | A0D3AAA248C2969B8987D4F7 /* Pods_KRPullLoaderDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0A26CCCA68595BDDA031797 /* Pods_KRPullLoaderDemo.framework */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 3A91C976D96A88AEDC538D0E /* Pods-KRPullLoaderDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KRPullLoaderDemo.debug.xcconfig"; path = "Target Support Files/Pods-KRPullLoaderDemo/Pods-KRPullLoaderDemo.debug.xcconfig"; sourceTree = ""; }; 23 | 6B0587FC1EE0FA38004763D4 /* KRPullLoaderDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KRPullLoaderDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 6B0E3F281EE4EE08001053BF /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | 6B0E3F2A1EE4EE08001053BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 6B0E3F2B1EE4EE08001053BF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | 6B0E3F2C1EE4EE08001053BF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 28 | 6B0E3F2D1EE4EE08001053BF /* TableViewSampleVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewSampleVC.swift; sourceTree = ""; }; 29 | 6B803B461EE95A5A00E0A784 /* HorizontalPullLoadView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalPullLoadView.swift; sourceTree = ""; }; 30 | 6B803B481EEA32E600E0A784 /* CollectionViewSampleVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewSampleVC.swift; sourceTree = ""; }; 31 | 6B803B4B1EEA40C100E0A784 /* HorizontalCollectionViewSampleVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCollectionViewSampleVC.swift; sourceTree = ""; }; 32 | 6B803B511EEA451900E0A784 /* UIColorExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColorExtension.swift; sourceTree = ""; }; 33 | A0A26CCCA68595BDDA031797 /* Pods_KRPullLoaderDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KRPullLoaderDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | E4FDBEE956B8CBB710ACD23F /* Pods-KRPullLoaderDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KRPullLoaderDemo.release.xcconfig"; path = "Target Support Files/Pods-KRPullLoaderDemo/Pods-KRPullLoaderDemo.release.xcconfig"; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 6B0587F91EE0FA38004763D4 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | A0D3AAA248C2969B8987D4F7 /* Pods_KRPullLoaderDemo.framework in Frameworks */, 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | 5D129E5C90FC4890C2431E2B /* Pods */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 3A91C976D96A88AEDC538D0E /* Pods-KRPullLoaderDemo.debug.xcconfig */, 53 | E4FDBEE956B8CBB710ACD23F /* Pods-KRPullLoaderDemo.release.xcconfig */, 54 | ); 55 | path = Pods; 56 | sourceTree = ""; 57 | }; 58 | 6B0587F31EE0FA38004763D4 = { 59 | isa = PBXGroup; 60 | children = ( 61 | 6B0E3F271EE4EE08001053BF /* KRPullLoaderDemo */, 62 | 6B0587FD1EE0FA38004763D4 /* Products */, 63 | 5D129E5C90FC4890C2431E2B /* Pods */, 64 | F5EEA70B7302F4A6BCEAA155 /* Frameworks */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | 6B0587FD1EE0FA38004763D4 /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 6B0587FC1EE0FA38004763D4 /* KRPullLoaderDemo.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | 6B0E3F271EE4EE08001053BF /* KRPullLoaderDemo */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 6B0E3F281EE4EE08001053BF /* AppDelegate.swift */, 80 | 6B803B511EEA451900E0A784 /* UIColorExtension.swift */, 81 | 6B0E3F2D1EE4EE08001053BF /* TableViewSampleVC.swift */, 82 | 6B803B481EEA32E600E0A784 /* CollectionViewSampleVC.swift */, 83 | 6B803B4B1EEA40C100E0A784 /* HorizontalCollectionViewSampleVC.swift */, 84 | 6B803B4E1EEA40C800E0A784 /* CustomLoader */, 85 | 6B0E3F291EE4EE08001053BF /* Main.storyboard */, 86 | 6B0E3F2C1EE4EE08001053BF /* LaunchScreen.storyboard */, 87 | 6B0E3F2B1EE4EE08001053BF /* Info.plist */, 88 | ); 89 | path = KRPullLoaderDemo; 90 | sourceTree = ""; 91 | }; 92 | 6B803B4E1EEA40C800E0A784 /* CustomLoader */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 6B803B461EE95A5A00E0A784 /* HorizontalPullLoadView.swift */, 96 | ); 97 | name = CustomLoader; 98 | sourceTree = ""; 99 | }; 100 | F5EEA70B7302F4A6BCEAA155 /* Frameworks */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | A0A26CCCA68595BDDA031797 /* Pods_KRPullLoaderDemo.framework */, 104 | ); 105 | name = Frameworks; 106 | sourceTree = ""; 107 | }; 108 | /* End PBXGroup section */ 109 | 110 | /* Begin PBXNativeTarget section */ 111 | 6B0587FB1EE0FA38004763D4 /* KRPullLoaderDemo */ = { 112 | isa = PBXNativeTarget; 113 | buildConfigurationList = 6B05880E1EE0FA38004763D4 /* Build configuration list for PBXNativeTarget "KRPullLoaderDemo" */; 114 | buildPhases = ( 115 | 448B3D95FA82A1E8297F9EF0 /* [CP] Check Pods Manifest.lock */, 116 | 6B0587F81EE0FA38004763D4 /* Sources */, 117 | 6B0587F91EE0FA38004763D4 /* Frameworks */, 118 | 6B0587FA1EE0FA38004763D4 /* Resources */, 119 | 6B2F501E1EE0FACC0060197B /* Swift Lint */, 120 | C43E7E37CB01727CC4E912C3 /* [CP] Embed Pods Frameworks */, 121 | ); 122 | buildRules = ( 123 | ); 124 | dependencies = ( 125 | ); 126 | name = KRPullLoaderDemo; 127 | productName = KRPullLoaderDemo; 128 | productReference = 6B0587FC1EE0FA38004763D4 /* KRPullLoaderDemo.app */; 129 | productType = "com.apple.product-type.application"; 130 | }; 131 | /* End PBXNativeTarget section */ 132 | 133 | /* Begin PBXProject section */ 134 | 6B0587F41EE0FA38004763D4 /* Project object */ = { 135 | isa = PBXProject; 136 | attributes = { 137 | LastSwiftUpdateCheck = 0830; 138 | LastUpgradeCheck = 1020; 139 | ORGANIZATIONNAME = Krimpedance; 140 | TargetAttributes = { 141 | 6B0587FB1EE0FA38004763D4 = { 142 | CreatedOnToolsVersion = 8.3.2; 143 | LastSwiftMigration = 1000; 144 | ProvisioningStyle = Automatic; 145 | }; 146 | }; 147 | }; 148 | buildConfigurationList = 6B0587F71EE0FA38004763D4 /* Build configuration list for PBXProject "KRPullLoaderDemo" */; 149 | compatibilityVersion = "Xcode 10.0"; 150 | developmentRegion = English; 151 | hasScannedForEncodings = 0; 152 | knownRegions = ( 153 | English, 154 | en, 155 | Base, 156 | ); 157 | mainGroup = 6B0587F31EE0FA38004763D4; 158 | productRefGroup = 6B0587FD1EE0FA38004763D4 /* Products */; 159 | projectDirPath = ""; 160 | projectRoot = ""; 161 | targets = ( 162 | 6B0587FB1EE0FA38004763D4 /* KRPullLoaderDemo */, 163 | ); 164 | }; 165 | /* End PBXProject section */ 166 | 167 | /* Begin PBXResourcesBuildPhase section */ 168 | 6B0587FA1EE0FA38004763D4 /* Resources */ = { 169 | isa = PBXResourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | 6B0E3F311EE4EE08001053BF /* LaunchScreen.storyboard in Resources */, 173 | 6B0E3F2F1EE4EE08001053BF /* Main.storyboard in Resources */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | /* End PBXResourcesBuildPhase section */ 178 | 179 | /* Begin PBXShellScriptBuildPhase section */ 180 | 448B3D95FA82A1E8297F9EF0 /* [CP] Check Pods Manifest.lock */ = { 181 | isa = PBXShellScriptBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | ); 185 | inputFileListPaths = ( 186 | ); 187 | inputPaths = ( 188 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 189 | "${PODS_ROOT}/Manifest.lock", 190 | ); 191 | name = "[CP] Check Pods Manifest.lock"; 192 | outputFileListPaths = ( 193 | ); 194 | outputPaths = ( 195 | "$(DERIVED_FILE_DIR)/Pods-KRPullLoaderDemo-checkManifestLockResult.txt", 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | shellPath = /bin/sh; 199 | 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"; 200 | showEnvVarsInLog = 0; 201 | }; 202 | 6B2F501E1EE0FACC0060197B /* Swift Lint */ = { 203 | isa = PBXShellScriptBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | ); 207 | inputPaths = ( 208 | ); 209 | name = "Swift Lint"; 210 | outputPaths = ( 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | shellPath = /bin/sh; 214 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"SwiftLint does not exist, download from https://github.com/realm/SwiftLint\"\nfi"; 215 | }; 216 | C43E7E37CB01727CC4E912C3 /* [CP] Embed Pods Frameworks */ = { 217 | isa = PBXShellScriptBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | ); 221 | inputFileListPaths = ( 222 | ); 223 | inputPaths = ( 224 | "${PODS_ROOT}/Target Support Files/Pods-KRPullLoaderDemo/Pods-KRPullLoaderDemo-frameworks.sh", 225 | "${BUILT_PRODUCTS_DIR}/KRPullLoader/KRPullLoader.framework", 226 | ); 227 | name = "[CP] Embed Pods Frameworks"; 228 | outputFileListPaths = ( 229 | ); 230 | outputPaths = ( 231 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KRPullLoader.framework", 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | shellPath = /bin/sh; 235 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-KRPullLoaderDemo/Pods-KRPullLoaderDemo-frameworks.sh\"\n"; 236 | showEnvVarsInLog = 0; 237 | }; 238 | /* End PBXShellScriptBuildPhase section */ 239 | 240 | /* Begin PBXSourcesBuildPhase section */ 241 | 6B0587F81EE0FA38004763D4 /* Sources */ = { 242 | isa = PBXSourcesBuildPhase; 243 | buildActionMask = 2147483647; 244 | files = ( 245 | 6B0E3F321EE4EE08001053BF /* TableViewSampleVC.swift in Sources */, 246 | 6B803B521EEA451900E0A784 /* UIColorExtension.swift in Sources */, 247 | 6B803B491EEA32E600E0A784 /* CollectionViewSampleVC.swift in Sources */, 248 | 6B803B471EE95A5A00E0A784 /* HorizontalPullLoadView.swift in Sources */, 249 | 6B803B4C1EEA40C100E0A784 /* HorizontalCollectionViewSampleVC.swift in Sources */, 250 | 6B0E3F2E1EE4EE08001053BF /* AppDelegate.swift in Sources */, 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | }; 254 | /* End PBXSourcesBuildPhase section */ 255 | 256 | /* Begin PBXVariantGroup section */ 257 | 6B0E3F291EE4EE08001053BF /* Main.storyboard */ = { 258 | isa = PBXVariantGroup; 259 | children = ( 260 | 6B0E3F2A1EE4EE08001053BF /* Base */, 261 | ); 262 | name = Main.storyboard; 263 | sourceTree = ""; 264 | }; 265 | /* End PBXVariantGroup section */ 266 | 267 | /* Begin XCBuildConfiguration section */ 268 | 6B05880C1EE0FA38004763D4 /* Debug */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | ALWAYS_SEARCH_USER_PATHS = NO; 272 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 273 | CLANG_ANALYZER_NONNULL = YES; 274 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 276 | CLANG_CXX_LIBRARY = "libc++"; 277 | CLANG_ENABLE_MODULES = YES; 278 | CLANG_ENABLE_OBJC_ARC = YES; 279 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 280 | CLANG_WARN_BOOL_CONVERSION = YES; 281 | CLANG_WARN_COMMA = YES; 282 | CLANG_WARN_CONSTANT_CONVERSION = YES; 283 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 284 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 285 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 286 | CLANG_WARN_EMPTY_BODY = YES; 287 | CLANG_WARN_ENUM_CONVERSION = YES; 288 | CLANG_WARN_INFINITE_RECURSION = YES; 289 | CLANG_WARN_INT_CONVERSION = YES; 290 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 291 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 292 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 293 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 294 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 295 | CLANG_WARN_STRICT_PROTOTYPES = YES; 296 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 297 | CLANG_WARN_UNREACHABLE_CODE = YES; 298 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 299 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 300 | COPY_PHASE_STRIP = NO; 301 | DEBUG_INFORMATION_FORMAT = dwarf; 302 | ENABLE_STRICT_OBJC_MSGSEND = YES; 303 | ENABLE_TESTABILITY = YES; 304 | GCC_C_LANGUAGE_STANDARD = gnu99; 305 | GCC_DYNAMIC_NO_PIC = NO; 306 | GCC_NO_COMMON_BLOCKS = YES; 307 | GCC_OPTIMIZATION_LEVEL = 0; 308 | GCC_PREPROCESSOR_DEFINITIONS = ( 309 | "DEBUG=1", 310 | "$(inherited)", 311 | ); 312 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 313 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 314 | GCC_WARN_UNDECLARED_SELECTOR = YES; 315 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 316 | GCC_WARN_UNUSED_FUNCTION = YES; 317 | GCC_WARN_UNUSED_VARIABLE = YES; 318 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 319 | MTL_ENABLE_DEBUG_INFO = YES; 320 | ONLY_ACTIVE_ARCH = YES; 321 | SDKROOT = iphoneos; 322 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 323 | SWIFT_COMPILATION_MODE = singlefile; 324 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 325 | SWIFT_VERSION = 5.0; 326 | }; 327 | name = Debug; 328 | }; 329 | 6B05880D1EE0FA38004763D4 /* Release */ = { 330 | isa = XCBuildConfiguration; 331 | buildSettings = { 332 | ALWAYS_SEARCH_USER_PATHS = NO; 333 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 334 | CLANG_ANALYZER_NONNULL = YES; 335 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 336 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 337 | CLANG_CXX_LIBRARY = "libc++"; 338 | CLANG_ENABLE_MODULES = YES; 339 | CLANG_ENABLE_OBJC_ARC = YES; 340 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 341 | CLANG_WARN_BOOL_CONVERSION = YES; 342 | CLANG_WARN_COMMA = YES; 343 | CLANG_WARN_CONSTANT_CONVERSION = YES; 344 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 345 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 346 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 347 | CLANG_WARN_EMPTY_BODY = YES; 348 | CLANG_WARN_ENUM_CONVERSION = YES; 349 | CLANG_WARN_INFINITE_RECURSION = YES; 350 | CLANG_WARN_INT_CONVERSION = YES; 351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 353 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 354 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 355 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 356 | CLANG_WARN_STRICT_PROTOTYPES = YES; 357 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 363 | ENABLE_NS_ASSERTIONS = NO; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu99; 366 | GCC_NO_COMMON_BLOCKS = YES; 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 374 | MTL_ENABLE_DEBUG_INFO = NO; 375 | SDKROOT = iphoneos; 376 | SWIFT_COMPILATION_MODE = wholemodule; 377 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 378 | SWIFT_VERSION = 5.0; 379 | VALIDATE_PRODUCT = YES; 380 | }; 381 | name = Release; 382 | }; 383 | 6B05880F1EE0FA38004763D4 /* Debug */ = { 384 | isa = XCBuildConfiguration; 385 | baseConfigurationReference = 3A91C976D96A88AEDC538D0E /* Pods-KRPullLoaderDemo.debug.xcconfig */; 386 | buildSettings = { 387 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 388 | DEVELOPMENT_TEAM = ""; 389 | INFOPLIST_FILE = "$(SRCROOT)/KRPullLoaderDemo/Info.plist"; 390 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 391 | LD_RUNPATH_SEARCH_PATHS = ( 392 | "$(inherited)", 393 | "@executable_path/Frameworks", 394 | ); 395 | PRODUCT_BUNDLE_IDENTIFIER = com.krimpedance.KRPullLoaderDemo; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | PROVISIONING_PROFILE_SPECIFIER = ""; 398 | SWIFT_VERSION = 5.0; 399 | }; 400 | name = Debug; 401 | }; 402 | 6B0588101EE0FA38004763D4 /* Release */ = { 403 | isa = XCBuildConfiguration; 404 | baseConfigurationReference = E4FDBEE956B8CBB710ACD23F /* Pods-KRPullLoaderDemo.release.xcconfig */; 405 | buildSettings = { 406 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 407 | DEVELOPMENT_TEAM = ""; 408 | INFOPLIST_FILE = "$(SRCROOT)/KRPullLoaderDemo/Info.plist"; 409 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 410 | LD_RUNPATH_SEARCH_PATHS = ( 411 | "$(inherited)", 412 | "@executable_path/Frameworks", 413 | ); 414 | PRODUCT_BUNDLE_IDENTIFIER = com.krimpedance.KRPullLoaderDemo; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | PROVISIONING_PROFILE_SPECIFIER = ""; 417 | SWIFT_VERSION = 5.0; 418 | }; 419 | name = Release; 420 | }; 421 | /* End XCBuildConfiguration section */ 422 | 423 | /* Begin XCConfigurationList section */ 424 | 6B0587F71EE0FA38004763D4 /* Build configuration list for PBXProject "KRPullLoaderDemo" */ = { 425 | isa = XCConfigurationList; 426 | buildConfigurations = ( 427 | 6B05880C1EE0FA38004763D4 /* Debug */, 428 | 6B05880D1EE0FA38004763D4 /* Release */, 429 | ); 430 | defaultConfigurationIsVisible = 0; 431 | defaultConfigurationName = Release; 432 | }; 433 | 6B05880E1EE0FA38004763D4 /* Build configuration list for PBXNativeTarget "KRPullLoaderDemo" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | 6B05880F1EE0FA38004763D4 /* Debug */, 437 | 6B0588101EE0FA38004763D4 /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | /* End XCConfigurationList section */ 443 | }; 444 | rootObject = 6B0587F41EE0FA38004763D4 /* Project object */; 445 | } 446 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // KRPullLoaderDemo 4 | // 5 | // Created by Krimpedance on 2017/06/02. 6 | // Copyright © 2017年 Krimpedance. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo/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 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo/CollectionViewSampleVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewSampleVC.swift 3 | // KRPullLoaderDemo 4 | // 5 | // Copyright © 2017年 Krimpedance. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import KRPullLoader 10 | 11 | class CollectionViewSampleVC: UIViewController { 12 | 13 | @IBOutlet weak var collectionView: UICollectionView! 14 | var index = 0 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | collectionView.dataSource = self 20 | let refreshView = KRPullLoadView() 21 | refreshView.delegate = self 22 | collectionView.addPullLoadableView(refreshView, type: .refresh) 23 | let loadMoreView = KRPullLoadView() 24 | loadMoreView.delegate = self 25 | collectionView.addPullLoadableView(loadMoreView, type: .loadMore) 26 | } 27 | } 28 | 29 | // MARK: - UICollectionView data source ------------------- 30 | 31 | extension CollectionViewSampleVC: UICollectionViewDataSource { 32 | func numberOfSections(in collectionView: UICollectionView) -> Int { 33 | return 1 34 | } 35 | 36 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 37 | self.index += 1 38 | return 10 * index 39 | } 40 | 41 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 42 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) 43 | cell.backgroundColor = .getColor(with: indexPath.row) 44 | return cell 45 | } 46 | } 47 | 48 | // MARK: - KRPullLoadView delegate ------------------- 49 | 50 | extension CollectionViewSampleVC: KRPullLoadViewDelegate { 51 | func pullLoadView(_ pullLoadView: KRPullLoadView, didChangeState state: KRPullLoaderState, viewType type: KRPullLoaderType) { 52 | if type == .loadMore { 53 | switch state { 54 | case let .loading(completionHandler): 55 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+1) { 56 | completionHandler() 57 | self.collectionView.reloadData() 58 | } 59 | default: break 60 | } 61 | return 62 | } 63 | 64 | switch state { 65 | case .none: 66 | pullLoadView.messageLabel.text = "" 67 | 68 | case let .pulling(offset, threshould): 69 | if offset.y > threshould { 70 | pullLoadView.messageLabel.text = "Pull more. offset: \(Int(offset.y)), threshould: \(Int(threshould)))" 71 | } else { 72 | pullLoadView.messageLabel.text = "Release to refresh. offset: \(Int(offset.y)), threshould: \(Int(threshould)))" 73 | } 74 | 75 | case let .loading(completionHandler): 76 | pullLoadView.messageLabel.text = "Updating..." 77 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+1) { 78 | completionHandler() 79 | self.collectionView.reloadData() 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo/HorizontalCollectionViewSampleVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalHorizontalCollectionViewSampleVC.swift 3 | // KRPullLoaderDemo 4 | // 5 | // Copyright © 2017年 Krimpedance. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | import UIKit 11 | import KRPullLoader 12 | 13 | class HorizontalCollectionViewSampleVC: UIViewController { 14 | 15 | @IBOutlet weak var collectionView: UICollectionView! 16 | var index = 0 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | collectionView.dataSource = self 22 | collectionView.addPullLoadableView(HorizontalPullLoadView(), type: .refresh) 23 | collectionView.addPullLoadableView(HorizontalPullLoadView(), type: .loadMore) 24 | 25 | collectionView.contentInset.left = 50 26 | collectionView.contentInset.right = 50 27 | } 28 | } 29 | 30 | // MARK: - UICollectionView data source ------------------- 31 | 32 | extension HorizontalCollectionViewSampleVC: UICollectionViewDataSource { 33 | func numberOfSections(in collectionView: UICollectionView) -> Int { 34 | return 1 35 | } 36 | 37 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 38 | index += 1 39 | return 10 * index 40 | } 41 | 42 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 43 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) 44 | cell.backgroundColor = .getColor(with: indexPath.row) 45 | return cell 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo/HorizontalPullLoadView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalPullLoadView.swift 3 | // KRPullLoaderDemo 4 | // 5 | // Copyright © 2017年 Krimpedance. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import KRPullLoader 10 | 11 | class HorizontalPullLoadView: UIView { 12 | 13 | public let activityIndicator = UIActivityIndicatorView() 14 | 15 | var shouldSetConstraints = true 16 | 17 | open override func layoutSubviews() { 18 | super.layoutSubviews() 19 | if shouldSetConstraints { setUp() } 20 | shouldSetConstraints = false 21 | } 22 | } 23 | 24 | // MARK: - Actions ------------------- 25 | 26 | extension HorizontalPullLoadView { 27 | func setUp() { 28 | backgroundColor = .clear 29 | 30 | activityIndicator.style = .gray 31 | activityIndicator.hidesWhenStopped = false 32 | activityIndicator.translatesAutoresizingMaskIntoConstraints = false 33 | addSubview(activityIndicator) 34 | 35 | addConstraints([ 36 | NSLayoutConstraint(item: activityIndicator, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 15.0), 37 | NSLayoutConstraint(item: activityIndicator, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0.0) 38 | ]) 39 | 40 | addConstraint( 41 | NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1.0, constant: 50) 42 | ) 43 | } 44 | } 45 | 46 | // MARK: - KRPullLoadable actions ------------------- 47 | 48 | extension HorizontalPullLoadView: KRPullLoadable { 49 | func didChangeState(_ state: KRPullLoaderState, viewType type: KRPullLoaderType) { 50 | switch state { 51 | case .none: 52 | activityIndicator.stopAnimating() 53 | 54 | case .pulling: 55 | break 56 | 57 | case let .loading(completionHandler): 58 | activityIndicator.startAnimating() 59 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+1) { 60 | completionHandler() 61 | if let collectionView = self.superview?.superview as? UICollectionView { 62 | collectionView.reloadData() 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.3.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo/TableViewSampleVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewSampleVC.swift 3 | // KRPullLoaderDemo 4 | // 5 | // Copyright © 2017年 Krimpedance. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import KRPullLoader 10 | 11 | class TableViewSampleVC: UIViewController { 12 | 13 | @IBOutlet weak var tableView: UITableView! 14 | var index = 1 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | tableView.dataSource = self 20 | 21 | let refreshView = KRPullLoadView() 22 | refreshView.delegate = self 23 | tableView.addPullLoadableView(refreshView, type: .refresh) 24 | let loadMoreView = KRPullLoadView() 25 | loadMoreView.delegate = self 26 | tableView.addPullLoadableView(loadMoreView, type: .loadMore) 27 | 28 | tableView.contentInset.top = 50 29 | tableView.contentInset.bottom = 50 30 | } 31 | } 32 | 33 | // MARK: - UITableView data source ------------------- 34 | 35 | extension TableViewSampleVC: UITableViewDataSource { 36 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 37 | return 10 * index 38 | } 39 | 40 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 41 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! 42 | cell.backgroundColor = .getColor(with: indexPath.row) 43 | return cell 44 | } 45 | } 46 | 47 | // MARK: - KRPullLoadView delegate ------------------- 48 | 49 | extension TableViewSampleVC: KRPullLoadViewDelegate { 50 | func pullLoadView(_ pullLoadView: KRPullLoadView, didChangeState state: KRPullLoaderState, viewType type: KRPullLoaderType) { 51 | if type == .loadMore { 52 | switch state { 53 | case let .loading(completionHandler): 54 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+1) { 55 | completionHandler() 56 | self.index += 1 57 | self.tableView.reloadData() 58 | } 59 | default: break 60 | } 61 | return 62 | } 63 | 64 | switch state { 65 | case .none: 66 | pullLoadView.messageLabel.text = "" 67 | 68 | case let .pulling(offset, threshould): 69 | if offset.y > threshould { 70 | pullLoadView.messageLabel.text = "Pull more. offset: \(Int(offset.y)), threshould: \(Int(threshould)))" 71 | } else { 72 | pullLoadView.messageLabel.text = "Release to refresh. offset: \(Int(offset.y)), threshould: \(Int(threshould)))" 73 | } 74 | 75 | case let .loading(completionHandler): 76 | pullLoadView.messageLabel.text = "Updating..." 77 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+1) { 78 | completionHandler() 79 | self.index += 1 80 | self.tableView.reloadData() 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /DEMO/KRPullLoaderDemo/UIColorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColorExtension.swift 3 | // KRPullLoaderDemo 4 | // 5 | // Copyright © 2017年 Krimpedance. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIColor { 11 | class func getColor(with row: Int) -> UIColor { 12 | return [.red, .blue, .green, .orange, .yellow][(row / 10) % 5] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /DEMO/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '12.0' 2 | 3 | target 'KRPullLoaderDemo' do 4 | use_frameworks! 5 | 6 | pod 'KRPullLoader', path: '../KRPullLoader.podspec' 7 | end 8 | -------------------------------------------------------------------------------- /KRPullLoader.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "KRPullLoader" 3 | s.version = "1.3.0" 4 | s.summary = "A 'pull to load' control for UIScrollView(, UITableView, UICollectionView, ...)." 5 | s.description = "KRPullLoader is a 'pull to load' control for UIScrollView(, UITableView, UICollectionView, ...) on iOS." 6 | s.homepage = "https://github.com/krimpedance/KRPullLoader" 7 | s.license = { :type => "MIT", :file => "LICENSE" } 8 | 9 | s.author = { "krimpedance" => "info@krimpedance.com" } 10 | s.requires_arc = true 11 | s.platform = :ios, '8.0' 12 | s.ios.deployment_target = '8.0' 13 | 14 | s.source = { :git => "https://github.com/krimpedance/KRPullLoader.git", :tag => s.version.to_s } 15 | s.source_files = "KRPullLoader/**/*.swift" 16 | end 17 | -------------------------------------------------------------------------------- /KRPullLoader.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6B296A9E1EE63BE80024A516 /* KRPullLoadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B296A9D1EE63BE80024A516 /* KRPullLoadView.swift */; }; 11 | 6B2F50201EE0FCE60060197B /* KRPullLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B2F501F1EE0FCE60060197B /* KRPullLoader.swift */; }; 12 | 6B2F50281EE102A50060197B /* KRPullLoader.swift in Headers */ = {isa = PBXBuildFile; fileRef = 6B2F501F1EE0FCE60060197B /* KRPullLoader.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 6B803B541EEA8D6300E0A784 /* UIScrollViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B803B531EEA8D6300E0A784 /* UIScrollViewExtensions.swift */; }; 14 | 6BD9970C1EE0F99100C7C273 /* KRPullLoader.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BD997021EE0F99000C7C273 /* KRPullLoader.framework */; }; 15 | 6BD997111EE0F99100C7C273 /* KRPullLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BD997101EE0F99100C7C273 /* KRPullLoaderTests.swift */; }; 16 | 6BD997131EE0F99100C7C273 /* KRPullLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BD997051EE0F99000C7C273 /* KRPullLoader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | 6BF179631F8DAB6300B58E8E /* KRPullLoadView.swift in Headers */ = {isa = PBXBuildFile; fileRef = 6B296A9D1EE63BE80024A516 /* KRPullLoadView.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 18 | 6BF179641F8DAB6300B58E8E /* UIScrollViewExtensions.swift in Headers */ = {isa = PBXBuildFile; fileRef = 6B803B531EEA8D6300E0A784 /* UIScrollViewExtensions.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | 6BFA23A220444D22009E934B /* NSLayoutConstraintExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BFA23A120444D22009E934B /* NSLayoutConstraintExtensions.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 6BD9970D1EE0F99100C7C273 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 6BD996F91EE0F99000C7C273 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 6BD997011EE0F99000C7C273; 28 | remoteInfo = KRPullLoader; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 6B296A9D1EE63BE80024A516 /* KRPullLoadView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KRPullLoadView.swift; sourceTree = ""; }; 34 | 6B2F501F1EE0FCE60060197B /* KRPullLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KRPullLoader.swift; sourceTree = ""; }; 35 | 6B803B531EEA8D6300E0A784 /* UIScrollViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIScrollViewExtensions.swift; sourceTree = ""; }; 36 | 6BD997021EE0F99000C7C273 /* KRPullLoader.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KRPullLoader.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 6BD997051EE0F99000C7C273 /* KRPullLoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KRPullLoader.h; sourceTree = ""; }; 38 | 6BD997061EE0F99000C7C273 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 6BD9970B1EE0F99100C7C273 /* KRPullLoaderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KRPullLoaderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 6BD997101EE0F99100C7C273 /* KRPullLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KRPullLoaderTests.swift; sourceTree = ""; }; 41 | 6BD997121EE0F99100C7C273 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 6BFA23A120444D22009E934B /* NSLayoutConstraintExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraintExtensions.swift; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | 6BD996FE1EE0F99000C7C273 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | 6BD997081EE0F99100C7C273 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 6BD9970C1EE0F99100C7C273 /* KRPullLoader.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 6B0587F21EE0FA14004763D4 /* Classes */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 6B2F501F1EE0FCE60060197B /* KRPullLoader.swift */, 68 | 6B296A9D1EE63BE80024A516 /* KRPullLoadView.swift */, 69 | 6B803B531EEA8D6300E0A784 /* UIScrollViewExtensions.swift */, 70 | 6BFA23A120444D22009E934B /* NSLayoutConstraintExtensions.swift */, 71 | ); 72 | path = Classes; 73 | sourceTree = ""; 74 | }; 75 | 6BD996F81EE0F99000C7C273 = { 76 | isa = PBXGroup; 77 | children = ( 78 | 6BD997041EE0F99000C7C273 /* KRPullLoader */, 79 | 6BD9970F1EE0F99100C7C273 /* KRPullLoaderTests */, 80 | 6BD997031EE0F99000C7C273 /* Products */, 81 | ); 82 | sourceTree = ""; 83 | }; 84 | 6BD997031EE0F99000C7C273 /* Products */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 6BD997021EE0F99000C7C273 /* KRPullLoader.framework */, 88 | 6BD9970B1EE0F99100C7C273 /* KRPullLoaderTests.xctest */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | 6BD997041EE0F99000C7C273 /* KRPullLoader */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 6BD997051EE0F99000C7C273 /* KRPullLoader.h */, 97 | 6BD997061EE0F99000C7C273 /* Info.plist */, 98 | 6B0587F21EE0FA14004763D4 /* Classes */, 99 | ); 100 | path = KRPullLoader; 101 | sourceTree = ""; 102 | }; 103 | 6BD9970F1EE0F99100C7C273 /* KRPullLoaderTests */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 6BD997101EE0F99100C7C273 /* KRPullLoaderTests.swift */, 107 | 6BD997121EE0F99100C7C273 /* Info.plist */, 108 | ); 109 | path = KRPullLoaderTests; 110 | sourceTree = ""; 111 | }; 112 | /* End PBXGroup section */ 113 | 114 | /* Begin PBXHeadersBuildPhase section */ 115 | 6BD996FF1EE0F99000C7C273 /* Headers */ = { 116 | isa = PBXHeadersBuildPhase; 117 | buildActionMask = 2147483647; 118 | files = ( 119 | 6BD997131EE0F99100C7C273 /* KRPullLoader.h in Headers */, 120 | 6B2F50281EE102A50060197B /* KRPullLoader.swift in Headers */, 121 | 6BF179631F8DAB6300B58E8E /* KRPullLoadView.swift in Headers */, 122 | 6BF179641F8DAB6300B58E8E /* UIScrollViewExtensions.swift in Headers */, 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | /* End PBXHeadersBuildPhase section */ 127 | 128 | /* Begin PBXNativeTarget section */ 129 | 6BD997011EE0F99000C7C273 /* KRPullLoader */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = 6BD997161EE0F99100C7C273 /* Build configuration list for PBXNativeTarget "KRPullLoader" */; 132 | buildPhases = ( 133 | 6BD996FD1EE0F99000C7C273 /* Sources */, 134 | 6BD996FE1EE0F99000C7C273 /* Frameworks */, 135 | 6BD996FF1EE0F99000C7C273 /* Headers */, 136 | 6BD997001EE0F99000C7C273 /* Resources */, 137 | 6B2F501D1EE0FAA40060197B /* Swift Lint */, 138 | ); 139 | buildRules = ( 140 | ); 141 | dependencies = ( 142 | ); 143 | name = KRPullLoader; 144 | productName = KRPullLoader; 145 | productReference = 6BD997021EE0F99000C7C273 /* KRPullLoader.framework */; 146 | productType = "com.apple.product-type.framework"; 147 | }; 148 | 6BD9970A1EE0F99100C7C273 /* KRPullLoaderTests */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = 6BD997191EE0F99100C7C273 /* Build configuration list for PBXNativeTarget "KRPullLoaderTests" */; 151 | buildPhases = ( 152 | 6BD997071EE0F99100C7C273 /* Sources */, 153 | 6BD997081EE0F99100C7C273 /* Frameworks */, 154 | 6BD997091EE0F99100C7C273 /* Resources */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | 6BD9970E1EE0F99100C7C273 /* PBXTargetDependency */, 160 | ); 161 | name = KRPullLoaderTests; 162 | productName = KRPullLoaderTests; 163 | productReference = 6BD9970B1EE0F99100C7C273 /* KRPullLoaderTests.xctest */; 164 | productType = "com.apple.product-type.bundle.unit-test"; 165 | }; 166 | /* End PBXNativeTarget section */ 167 | 168 | /* Begin PBXProject section */ 169 | 6BD996F91EE0F99000C7C273 /* Project object */ = { 170 | isa = PBXProject; 171 | attributes = { 172 | LastSwiftUpdateCheck = 0830; 173 | LastUpgradeCheck = 1020; 174 | ORGANIZATIONNAME = Krimpedance; 175 | TargetAttributes = { 176 | 6BD997011EE0F99000C7C273 = { 177 | CreatedOnToolsVersion = 8.3.2; 178 | LastSwiftMigration = 1000; 179 | ProvisioningStyle = Automatic; 180 | }; 181 | 6BD9970A1EE0F99100C7C273 = { 182 | CreatedOnToolsVersion = 8.3.2; 183 | LastSwiftMigration = 1000; 184 | ProvisioningStyle = Automatic; 185 | }; 186 | }; 187 | }; 188 | buildConfigurationList = 6BD996FC1EE0F99000C7C273 /* Build configuration list for PBXProject "KRPullLoader" */; 189 | compatibilityVersion = "Xcode 10.0"; 190 | developmentRegion = en; 191 | hasScannedForEncodings = 0; 192 | knownRegions = ( 193 | en, 194 | Base, 195 | ); 196 | mainGroup = 6BD996F81EE0F99000C7C273; 197 | productRefGroup = 6BD997031EE0F99000C7C273 /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | 6BD997011EE0F99000C7C273 /* KRPullLoader */, 202 | 6BD9970A1EE0F99100C7C273 /* KRPullLoaderTests */, 203 | ); 204 | }; 205 | /* End PBXProject section */ 206 | 207 | /* Begin PBXResourcesBuildPhase section */ 208 | 6BD997001EE0F99000C7C273 /* Resources */ = { 209 | isa = PBXResourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | 6BD997091EE0F99100C7C273 /* Resources */ = { 216 | isa = PBXResourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXShellScriptBuildPhase section */ 225 | 6B2F501D1EE0FAA40060197B /* Swift Lint */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | ); 230 | inputPaths = ( 231 | ); 232 | name = "Swift Lint"; 233 | outputPaths = ( 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | shellPath = /bin/sh; 237 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"SwiftLint does not exist, download from https://github.com/realm/SwiftLint\"\nfi\n"; 238 | }; 239 | /* End PBXShellScriptBuildPhase section */ 240 | 241 | /* Begin PBXSourcesBuildPhase section */ 242 | 6BD996FD1EE0F99000C7C273 /* Sources */ = { 243 | isa = PBXSourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | 6B2F50201EE0FCE60060197B /* KRPullLoader.swift in Sources */, 247 | 6BFA23A220444D22009E934B /* NSLayoutConstraintExtensions.swift in Sources */, 248 | 6B296A9E1EE63BE80024A516 /* KRPullLoadView.swift in Sources */, 249 | 6B803B541EEA8D6300E0A784 /* UIScrollViewExtensions.swift in Sources */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | 6BD997071EE0F99100C7C273 /* Sources */ = { 254 | isa = PBXSourcesBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | 6BD997111EE0F99100C7C273 /* KRPullLoaderTests.swift in Sources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | /* End PBXSourcesBuildPhase section */ 262 | 263 | /* Begin PBXTargetDependency section */ 264 | 6BD9970E1EE0F99100C7C273 /* PBXTargetDependency */ = { 265 | isa = PBXTargetDependency; 266 | target = 6BD997011EE0F99000C7C273 /* KRPullLoader */; 267 | targetProxy = 6BD9970D1EE0F99100C7C273 /* PBXContainerItemProxy */; 268 | }; 269 | /* End PBXTargetDependency section */ 270 | 271 | /* Begin XCBuildConfiguration section */ 272 | 6BD997141EE0F99100C7C273 /* Debug */ = { 273 | isa = XCBuildConfiguration; 274 | buildSettings = { 275 | ALWAYS_SEARCH_USER_PATHS = NO; 276 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 277 | CLANG_ANALYZER_NONNULL = YES; 278 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 279 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 280 | CLANG_CXX_LIBRARY = "libc++"; 281 | CLANG_ENABLE_MODULES = YES; 282 | CLANG_ENABLE_OBJC_ARC = YES; 283 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 284 | CLANG_WARN_BOOL_CONVERSION = YES; 285 | CLANG_WARN_COMMA = YES; 286 | CLANG_WARN_CONSTANT_CONVERSION = YES; 287 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 288 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 289 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 290 | CLANG_WARN_EMPTY_BODY = YES; 291 | CLANG_WARN_ENUM_CONVERSION = YES; 292 | CLANG_WARN_INFINITE_RECURSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 295 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 296 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 297 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 298 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 299 | CLANG_WARN_STRICT_PROTOTYPES = YES; 300 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 301 | CLANG_WARN_UNREACHABLE_CODE = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 304 | COPY_PHASE_STRIP = NO; 305 | CURRENT_PROJECT_VERSION = 1; 306 | DEBUG_INFORMATION_FORMAT = dwarf; 307 | ENABLE_STRICT_OBJC_MSGSEND = YES; 308 | ENABLE_TESTABILITY = YES; 309 | GCC_C_LANGUAGE_STANDARD = gnu99; 310 | GCC_DYNAMIC_NO_PIC = NO; 311 | GCC_NO_COMMON_BLOCKS = YES; 312 | GCC_OPTIMIZATION_LEVEL = 0; 313 | GCC_PREPROCESSOR_DEFINITIONS = ( 314 | "DEBUG=1", 315 | "$(inherited)", 316 | ); 317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 319 | GCC_WARN_UNDECLARED_SELECTOR = YES; 320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 321 | GCC_WARN_UNUSED_FUNCTION = YES; 322 | GCC_WARN_UNUSED_VARIABLE = YES; 323 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 324 | MTL_ENABLE_DEBUG_INFO = YES; 325 | ONLY_ACTIVE_ARCH = YES; 326 | SDKROOT = iphoneos; 327 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 328 | SWIFT_COMPILATION_MODE = singlefile; 329 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 330 | SWIFT_VERSION = 5.0; 331 | TARGETED_DEVICE_FAMILY = "1,2"; 332 | VERSIONING_SYSTEM = "apple-generic"; 333 | VERSION_INFO_PREFIX = ""; 334 | }; 335 | name = Debug; 336 | }; 337 | 6BD997151EE0F99100C7C273 /* Release */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ALWAYS_SEARCH_USER_PATHS = NO; 341 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 342 | CLANG_ANALYZER_NONNULL = YES; 343 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 344 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 345 | CLANG_CXX_LIBRARY = "libc++"; 346 | CLANG_ENABLE_MODULES = YES; 347 | CLANG_ENABLE_OBJC_ARC = YES; 348 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 349 | CLANG_WARN_BOOL_CONVERSION = YES; 350 | CLANG_WARN_COMMA = YES; 351 | CLANG_WARN_CONSTANT_CONVERSION = YES; 352 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 353 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 354 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 355 | CLANG_WARN_EMPTY_BODY = YES; 356 | CLANG_WARN_ENUM_CONVERSION = YES; 357 | CLANG_WARN_INFINITE_RECURSION = YES; 358 | CLANG_WARN_INT_CONVERSION = YES; 359 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 361 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 362 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 363 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 364 | CLANG_WARN_STRICT_PROTOTYPES = YES; 365 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 366 | CLANG_WARN_UNREACHABLE_CODE = YES; 367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 368 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 369 | COPY_PHASE_STRIP = NO; 370 | CURRENT_PROJECT_VERSION = 1; 371 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 372 | ENABLE_NS_ASSERTIONS = NO; 373 | ENABLE_STRICT_OBJC_MSGSEND = YES; 374 | GCC_C_LANGUAGE_STANDARD = gnu99; 375 | GCC_NO_COMMON_BLOCKS = YES; 376 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 378 | GCC_WARN_UNDECLARED_SELECTOR = YES; 379 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 380 | GCC_WARN_UNUSED_FUNCTION = YES; 381 | GCC_WARN_UNUSED_VARIABLE = YES; 382 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 383 | MTL_ENABLE_DEBUG_INFO = NO; 384 | SDKROOT = iphoneos; 385 | SWIFT_COMPILATION_MODE = wholemodule; 386 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 387 | SWIFT_VERSION = 5.0; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | VALIDATE_PRODUCT = YES; 390 | VERSIONING_SYSTEM = "apple-generic"; 391 | VERSION_INFO_PREFIX = ""; 392 | }; 393 | name = Release; 394 | }; 395 | 6BD997171EE0F99100C7C273 /* Debug */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | CLANG_ENABLE_MODULES = YES; 399 | CODE_SIGN_IDENTITY = ""; 400 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 401 | DEFINES_MODULE = YES; 402 | DEVELOPMENT_TEAM = ""; 403 | DYLIB_COMPATIBILITY_VERSION = 1; 404 | DYLIB_CURRENT_VERSION = 1; 405 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 406 | INFOPLIST_FILE = KRPullLoader/Info.plist; 407 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 408 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 409 | LD_RUNPATH_SEARCH_PATHS = ( 410 | "$(inherited)", 411 | "@executable_path/Frameworks", 412 | "@loader_path/Frameworks", 413 | ); 414 | PRODUCT_BUNDLE_IDENTIFIER = com.krimpedance.KRPullLoader; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | PROVISIONING_PROFILE_SPECIFIER = ""; 417 | SKIP_INSTALL = YES; 418 | SWIFT_COMPILATION_MODE = singlefile; 419 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 420 | SWIFT_VERSION = 5.0; 421 | }; 422 | name = Debug; 423 | }; 424 | 6BD997181EE0F99100C7C273 /* Release */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | CLANG_ENABLE_MODULES = YES; 428 | CODE_SIGN_IDENTITY = ""; 429 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 430 | DEFINES_MODULE = YES; 431 | DEVELOPMENT_TEAM = ""; 432 | DYLIB_COMPATIBILITY_VERSION = 1; 433 | DYLIB_CURRENT_VERSION = 1; 434 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 435 | INFOPLIST_FILE = KRPullLoader/Info.plist; 436 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 437 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 438 | LD_RUNPATH_SEARCH_PATHS = ( 439 | "$(inherited)", 440 | "@executable_path/Frameworks", 441 | "@loader_path/Frameworks", 442 | ); 443 | PRODUCT_BUNDLE_IDENTIFIER = com.krimpedance.KRPullLoader; 444 | PRODUCT_NAME = "$(TARGET_NAME)"; 445 | PROVISIONING_PROFILE_SPECIFIER = ""; 446 | SKIP_INSTALL = YES; 447 | SWIFT_VERSION = 5.0; 448 | }; 449 | name = Release; 450 | }; 451 | 6BD9971A1EE0F99100C7C273 /* Debug */ = { 452 | isa = XCBuildConfiguration; 453 | buildSettings = { 454 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 455 | DEVELOPMENT_TEAM = ""; 456 | INFOPLIST_FILE = KRPullLoaderTests/Info.plist; 457 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 458 | LD_RUNPATH_SEARCH_PATHS = ( 459 | "$(inherited)", 460 | "@executable_path/Frameworks", 461 | "@loader_path/Frameworks", 462 | ); 463 | PRODUCT_BUNDLE_IDENTIFIER = com.krimpedance.KRPullLoaderTests; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | SWIFT_VERSION = 5.0; 466 | }; 467 | name = Debug; 468 | }; 469 | 6BD9971B1EE0F99100C7C273 /* Release */ = { 470 | isa = XCBuildConfiguration; 471 | buildSettings = { 472 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 473 | DEVELOPMENT_TEAM = ""; 474 | INFOPLIST_FILE = KRPullLoaderTests/Info.plist; 475 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 476 | LD_RUNPATH_SEARCH_PATHS = ( 477 | "$(inherited)", 478 | "@executable_path/Frameworks", 479 | "@loader_path/Frameworks", 480 | ); 481 | PRODUCT_BUNDLE_IDENTIFIER = com.krimpedance.KRPullLoaderTests; 482 | PRODUCT_NAME = "$(TARGET_NAME)"; 483 | SWIFT_VERSION = 5.0; 484 | }; 485 | name = Release; 486 | }; 487 | /* End XCBuildConfiguration section */ 488 | 489 | /* Begin XCConfigurationList section */ 490 | 6BD996FC1EE0F99000C7C273 /* Build configuration list for PBXProject "KRPullLoader" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | 6BD997141EE0F99100C7C273 /* Debug */, 494 | 6BD997151EE0F99100C7C273 /* Release */, 495 | ); 496 | defaultConfigurationIsVisible = 0; 497 | defaultConfigurationName = Release; 498 | }; 499 | 6BD997161EE0F99100C7C273 /* Build configuration list for PBXNativeTarget "KRPullLoader" */ = { 500 | isa = XCConfigurationList; 501 | buildConfigurations = ( 502 | 6BD997171EE0F99100C7C273 /* Debug */, 503 | 6BD997181EE0F99100C7C273 /* Release */, 504 | ); 505 | defaultConfigurationIsVisible = 0; 506 | defaultConfigurationName = Release; 507 | }; 508 | 6BD997191EE0F99100C7C273 /* Build configuration list for PBXNativeTarget "KRPullLoaderTests" */ = { 509 | isa = XCConfigurationList; 510 | buildConfigurations = ( 511 | 6BD9971A1EE0F99100C7C273 /* Debug */, 512 | 6BD9971B1EE0F99100C7C273 /* Release */, 513 | ); 514 | defaultConfigurationIsVisible = 0; 515 | defaultConfigurationName = Release; 516 | }; 517 | /* End XCConfigurationList section */ 518 | }; 519 | rootObject = 6BD996F91EE0F99000C7C273 /* Project object */; 520 | } 521 | -------------------------------------------------------------------------------- /KRPullLoader.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KRPullLoader.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /KRPullLoader.xcodeproj/xcshareddata/xcschemes/KRPullLoader.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /KRPullLoader/Classes/KRPullLoadView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KRPullLoadView.swift 3 | // KRPullLoader 4 | // 5 | // Copyright © 2017 Krimpedance. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | /// Delegate for KRPullLoadView. 11 | public protocol KRPullLoadViewDelegate: class { 12 | /// Handler when KRPullLoaderState value changed. 13 | /// 14 | /// - Parameters: 15 | /// - pullLoadView: KRPullLoadView. 16 | /// - state: New state. 17 | /// - type: KRPullLoaderType. 18 | func pullLoadView(_ pullLoadView: KRPullLoadView, didChangeState state: KRPullLoaderState, viewType type: KRPullLoaderType) 19 | } 20 | 21 | /// Simple view which inherited KRPullLoadable protocol. 22 | /// This has only activity indicator and message label. 23 | open class KRPullLoadView: UIView, KRPullLoadable { 24 | 25 | private lazy var oneTimeSetUp: Void = { self.setUp() }() 26 | 27 | public let activityIndicator = UIActivityIndicatorView() 28 | public let messageLabel = UILabel() 29 | 30 | open weak var delegate: KRPullLoadViewDelegate? 31 | 32 | open override func layoutSubviews() { 33 | super.layoutSubviews() 34 | _ = oneTimeSetUp 35 | } 36 | 37 | // Set up ------------ 38 | 39 | open func setUp() { 40 | backgroundColor = .clear 41 | 42 | activityIndicator.style = .gray 43 | activityIndicator.hidesWhenStopped = false 44 | activityIndicator.translatesAutoresizingMaskIntoConstraints = false 45 | addSubview(activityIndicator) 46 | 47 | messageLabel.font = .systemFont(ofSize: 10) 48 | messageLabel.textAlignment = .center 49 | messageLabel.textColor = .gray 50 | messageLabel.translatesAutoresizingMaskIntoConstraints = false 51 | addSubview(messageLabel) 52 | 53 | addConstraints([ 54 | NSLayoutConstraint(item: activityIndicator, attribute: .top, toItem: self, constant: 15.0), 55 | NSLayoutConstraint(item: activityIndicator, attribute: .centerX, toItem: self), 56 | NSLayoutConstraint(item: messageLabel, attribute: .top, toItem: self, constant: 40.0), 57 | NSLayoutConstraint(item: messageLabel, attribute: .centerX, toItem: self), 58 | NSLayoutConstraint(item: messageLabel, attribute: .bottom, toItem: self, constant: -15.0) 59 | ]) 60 | 61 | messageLabel.addConstraint( 62 | NSLayoutConstraint(item: messageLabel, attribute: .width, relatedBy: .lessThanOrEqual, constant: 300) 63 | ) 64 | } 65 | 66 | // KRPullLoadable ------------ 67 | 68 | open func didChangeState(_ state: KRPullLoaderState, viewType type: KRPullLoaderType) { 69 | switch state { 70 | case .none: 71 | activityIndicator.stopAnimating() 72 | 73 | case .pulling: 74 | break 75 | 76 | case .loading: 77 | activityIndicator.startAnimating() 78 | } 79 | delegate?.pullLoadView(self, didChangeState: state, viewType: type) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /KRPullLoader/Classes/KRPullLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KRPullLoader.swift 3 | // KRPullLoader 4 | // 5 | // Copyright © 2017 Krimpedance. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | /// Type of KRPullLoader's position. 11 | /// 12 | /// - refresh: At the head of UIScrollView's scroll direction 13 | /// - loadMore: At the tail of UIScrollView's scroll direction 14 | public enum KRPullLoaderType { 15 | case refresh, loadMore 16 | } 17 | 18 | /// State of KRPullLoader 19 | /// 20 | /// - none: hides the view. 21 | /// - pulling: pulling. `offset` is pull offset (always <= 0). 22 | /// - loading: Shows the view. You should call `completionHandler` when some actions have been completed. 23 | public enum KRPullLoaderState: Equatable { 24 | case none 25 | case pulling(offset: CGPoint, threshold: CGFloat) 26 | case loading(completionHandler: ()->Void) 27 | 28 | public static func == (lhs: KRPullLoaderState, rhs: KRPullLoaderState) -> Bool { 29 | switch (lhs, rhs) { 30 | case (.none, .none), (.loading, .loading): return true 31 | case let (.pulling(lOffset, lThreshold), .pulling(rOffset, rThreshold)): 32 | return lOffset == rOffset && lThreshold == rThreshold 33 | default: return false 34 | } 35 | } 36 | } 37 | 38 | /// KRPullLoadable is a protocol for views added to UIScrollView. 39 | public protocol KRPullLoadable: UIView { 40 | /// Handler when KRPullLoaderState value changed. 41 | /// 42 | /// - Parameters: 43 | /// - state: New state. 44 | /// - type: KRPullLoaderType. 45 | func didChangeState(_ state: KRPullLoaderState, viewType type: KRPullLoaderType) 46 | } 47 | 48 | class KRPullLoader: UIView { 49 | 50 | private lazy var setUpLayoutConstraints: Void = { self.adjustLayoutConstraints() }() 51 | 52 | private var observations = [NSKeyValueObservation]() 53 | private var defaultInset = UIEdgeInsets() 54 | private var scrollDirectionPositionConstraint: NSLayoutConstraint? 55 | 56 | let loadView: KRPullLoadable 57 | let type: KRPullLoaderType 58 | 59 | var scrollView: UIScrollView? { return superview as? UIScrollView } 60 | 61 | var scrollDirection: UICollectionView.ScrollDirection { 62 | return ((superview as? UICollectionView)?.collectionViewLayout as? UICollectionViewFlowLayout)?.scrollDirection ?? .vertical 63 | } 64 | 65 | var state = KRPullLoaderState.none { 66 | didSet { 67 | loadView.didChangeState(state, viewType: type) 68 | } 69 | } 70 | 71 | init(loadView: KRPullLoadable, type: KRPullLoaderType) { 72 | self.loadView = loadView 73 | self.type = type 74 | super.init(frame: loadView.bounds) 75 | addSubview(loadView) 76 | } 77 | 78 | required init?(coder aDecoder: NSCoder) { fatalError() } 79 | 80 | override func layoutSubviews() { 81 | super.layoutSubviews() 82 | _ = setUpLayoutConstraints 83 | } 84 | 85 | override func willMove(toSuperview newSuperview: UIView?) { 86 | super.willMove(toSuperview: newSuperview) 87 | if newSuperview == nil { observations = [] } 88 | } 89 | } 90 | 91 | // MARK: - Private actions ------------ 92 | 93 | private extension KRPullLoader { 94 | func addObservers() { 95 | guard let scrollView = self.scrollView else { return } 96 | 97 | let contentOffsetObservation = scrollView.observe(\.contentOffset) { [weak self] _, _ in 98 | guard let wSelf = self else { return } 99 | if case .loading = wSelf.state { return } 100 | if wSelf.isHidden { return } 101 | wSelf.updateState() 102 | } 103 | 104 | let contentSizeObservation = scrollView.observe(\.contentSize) { [weak self] _, _ in 105 | guard let wSelf = self else { return } 106 | if case .loading = wSelf.state { return } 107 | wSelf.checkScrollViewContentSize() 108 | } 109 | 110 | observations = [contentOffsetObservation, contentSizeObservation] 111 | } 112 | 113 | func updateState() { 114 | guard let scrollView = scrollView else { return } 115 | 116 | let offset = (type == .refresh) ? scrollView.distanceOffset : scrollView.distanceEndOffset 117 | let offsetValue = (scrollDirection == .vertical) ? offset.y : offset.x 118 | let threshold = (scrollDirection == .vertical) ? bounds.height : bounds.width 119 | 120 | if scrollView.isDecelerating && offsetValue < -threshold { 121 | state = .loading { [weak self] in self?.endLoading() } 122 | startLoading() 123 | } else if offsetValue < 0 { 124 | state = .pulling(offset: offset, threshold: -(threshold + 12)) 125 | } else if state != .none { 126 | state = .none 127 | } 128 | } 129 | } 130 | 131 | // MARK: - Layouts ------------ 132 | 133 | private extension KRPullLoader { 134 | func checkScrollViewContentSize() { 135 | if type == .refresh { return } 136 | guard let scrollView = scrollView, let constraint = scrollDirectionPositionConstraint else { return } 137 | self.isHidden = scrollView.bounds.height > (scrollView.contentSize.height + scrollView.contentInset.top + scrollView.contentInset.bottom) 138 | constraint.constant = (scrollDirection == .vertical) ? 139 | scrollView.contentSize.height + scrollView.contentInset.bottom : 140 | scrollView.contentSize.width + scrollView.contentInset.right 141 | } 142 | 143 | func adjustLayoutConstraints() { 144 | clipsToBounds = true 145 | translatesAutoresizingMaskIntoConstraints = false 146 | loadView.translatesAutoresizingMaskIntoConstraints = false 147 | 148 | let attributes: [NSLayoutConstraint.Attribute] = [.top, .left, .right, .bottom] 149 | addConstraints(attributes.map { NSLayoutConstraint(item: loadView, attribute: $0, toItem: self) }) 150 | 151 | scrollDirection == .vertical ? adjustVerticalScrollLayoutConstraints() : adjustHorizontalScrollLayoutConstraints() 152 | 153 | checkScrollViewContentSize() 154 | } 155 | 156 | func adjustVerticalScrollLayoutConstraints() { 157 | guard let scrollView = scrollView else { return } 158 | 159 | switch type { 160 | case .refresh: 161 | scrollDirectionPositionConstraint = NSLayoutConstraint(item: self, attribute: .bottom, toItem: scrollView, attribute: .top, constant: -scrollView.contentInset.top) 162 | case .loadMore: 163 | let constant = scrollView.contentSize.height + scrollView.contentInset.bottom 164 | scrollDirectionPositionConstraint = NSLayoutConstraint(item: self, attribute: .top, toItem: scrollView, attribute: .top, constant: constant) 165 | } 166 | 167 | scrollView.addConstraints([ 168 | scrollDirectionPositionConstraint!, 169 | NSLayoutConstraint(item: self, attribute: .centerX, toItem: scrollView), 170 | NSLayoutConstraint(item: self, attribute: .width, toItem: scrollView) 171 | ]) 172 | } 173 | 174 | func adjustHorizontalScrollLayoutConstraints() { 175 | guard let scrollView = scrollView else { return } 176 | 177 | switch type { 178 | case .refresh: 179 | let constant = -scrollView.contentInset.left 180 | scrollDirectionPositionConstraint = NSLayoutConstraint(item: self, attribute: .right, toItem: scrollView, attribute: .left, constant: constant) 181 | case .loadMore: 182 | let constant = scrollView.contentSize.width + scrollView.contentInset.right 183 | scrollDirectionPositionConstraint = NSLayoutConstraint(item: self, attribute: .left, toItem: scrollView, attribute: .left, constant: constant) 184 | } 185 | 186 | scrollView.addConstraints([ 187 | scrollDirectionPositionConstraint!, 188 | NSLayoutConstraint(item: self, attribute: .centerY, toItem: scrollView), 189 | NSLayoutConstraint(item: self, attribute: .height, toItem: scrollView) 190 | ]) 191 | } 192 | } 193 | 194 | // MARK: - Loading actions ------------ 195 | 196 | private extension KRPullLoader { 197 | func endLoading() { 198 | state = .none 199 | animateScrollViewInset(isShow: false) 200 | } 201 | 202 | func animateScrollViewInset(isShow: Bool) { 203 | guard let scrollView = self.scrollView else { return } 204 | 205 | UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: { [unowned self] in 206 | switch (self.scrollDirection, self.type) { 207 | case (.vertical, .refresh): 208 | scrollView.contentInset.top = self.defaultInset.top + (isShow ? self.bounds.height : 0) 209 | case (.vertical, .loadMore): 210 | scrollView.contentInset.bottom = self.defaultInset.bottom + (isShow ? self.bounds.height : 0) 211 | case (.horizontal, .refresh): 212 | scrollView.contentInset.left = self.defaultInset.left + (isShow ? self.bounds.width : 0) 213 | case (.horizontal, .loadMore): 214 | scrollView.contentInset.right = self.defaultInset.right + (isShow ? self.bounds.width : 0) 215 | case (_, _): 216 | break 217 | } 218 | }, completion: nil) 219 | } 220 | } 221 | 222 | // MARK: - Actions ------------ 223 | 224 | extension KRPullLoader { 225 | func setUp() { 226 | checkScrollViewContentSize() 227 | addObservers() 228 | } 229 | 230 | func startLoading(force: Bool = false) { 231 | if force { 232 | if case .loading = state { return } 233 | if type == .loadMore { return } 234 | state = .loading { [weak self] in self?.endLoading() } 235 | } 236 | guard case .loading = state, let scrollView = self.scrollView else { return } 237 | layoutIfNeeded() // adjust bounds.size 238 | defaultInset = scrollView.contentInset 239 | animateScrollViewInset(isShow: true) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /KRPullLoader/Classes/NSLayoutConstraintExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSLayoutConstraintExtensions.swift 3 | // KRPullLoader 4 | // 5 | // Copyright © 2018 Krimpedance. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | extension NSLayoutConstraint { 11 | convenience init(item view1: Any, attribute attr1: NSLayoutConstraint.Attribute, relatedBy relation: NSLayoutConstraint.Relation = .equal, toItem view2: Any? = nil, attribute attr2: NSLayoutConstraint.Attribute? = nil, constant: CGFloat = 0) { 12 | self.init(item: view1, attribute: attr1, relatedBy: relation, toItem: view2, attribute: attr2 ?? attr1, multiplier: 1.0, constant: constant) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /KRPullLoader/Classes/UIScrollViewExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollViewExtensions.swift 3 | // KRPullLoader 4 | // 5 | // Copyright © 2017 Krimpedance. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIScrollView { 11 | var distanceOffset: CGPoint { 12 | get { 13 | return CGPoint( 14 | x: contentOffset.x + contentInset.left, 15 | y: contentOffset.y + contentInset.top 16 | ) 17 | } 18 | set { 19 | let point = CGPoint( 20 | x: newValue.x - contentInset.left, 21 | y: newValue.y - contentInset.top 22 | ) 23 | setContentOffset(point, animated: true) 24 | } 25 | } 26 | 27 | var distanceEndOffset: CGPoint { 28 | get { 29 | return CGPoint( 30 | x: (contentSize.width + contentInset.right) - (contentOffset.x + bounds.width), 31 | y: (contentSize.height + contentInset.bottom) - (contentOffset.y + bounds.height) 32 | ) 33 | } 34 | set { 35 | contentOffset = CGPoint( 36 | x: newValue.x - (bounds.width - (contentSize.width + contentInset.right)), 37 | y: newValue.y - (bounds.height - (contentSize.height + contentInset.bottom)) 38 | ) 39 | } 40 | } 41 | } 42 | 43 | // MARK: - Public UIScrollView extensions ------------ 44 | 45 | public extension UIScrollView { 46 | /// Adds the PullLoadableView. 47 | /// 48 | /// - Parameters: 49 | /// - loadView: view that contain KRPullLoadable. 50 | /// - type: KRPullLoaderType. Default type is `.refresh`. 51 | func addPullLoadableView(_ loadView: KRPullLoadable, type: KRPullLoaderType = .refresh) { 52 | let loader = KRPullLoader(loadView: loadView, type: type) 53 | insertSubview(loader, at: 0) 54 | loader.setUp() 55 | } 56 | 57 | /// Remove the PullLoadableView. 58 | /// 59 | /// - Parameter type: KRPullLoaderType. 60 | func removePullLoadableView(type: KRPullLoaderType) { 61 | guard let loader = subviews.first(where: { ($0 as? KRPullLoader)?.type == type }) else { return } 62 | loader.removeFromSuperview() 63 | } 64 | 65 | /// Pull to refresh programmatically 66 | func pullToRefresh() { 67 | guard let loader = subviews.first(where: { ($0 as? KRPullLoader)?.type == .refresh }) as? KRPullLoader else { return } 68 | loader.startLoading(force: true) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /KRPullLoader/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.3.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /KRPullLoader/KRPullLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // KRPullLoader.h 3 | // KRPullLoader 4 | // 5 | // Created by Krimpedance on 2017/06/02. 6 | // Copyright © 2017年 Krimpedance. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for KRPullLoader. 12 | FOUNDATION_EXPORT double KRPullLoaderVersionNumber; 13 | 14 | //! Project version string for KRPullLoader. 15 | FOUNDATION_EXPORT const unsigned char KRPullLoaderVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /KRPullLoaderTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /KRPullLoaderTests/KRPullLoaderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KRPullLoaderTests.swift 3 | // KRPullLoaderTests 4 | // 5 | // Copyright © 2017年 Krimpedance. All rights reserved. 6 | // 7 | 8 | import XCTest 9 | @testable import KRPullLoader 10 | 11 | class KRPullLoaderTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | } 16 | 17 | override func tearDown() { 18 | super.tearDown() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017- Krimpedance 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [日本語](./README_Ja.md) 2 | 3 | # KRPullLoader 4 | 5 | [![Version](https://img.shields.io/cocoapods/v/KRPullLoader.svg?style=flat)](http://cocoapods.org/pods/KRPullLoader) 6 | [![License](https://img.shields.io/cocoapods/l/KRPullLoader.svg?style=flat)](http://cocoapods.org/pods/KRPullLoader) 7 | [![Platform](https://img.shields.io/cocoapods/p/KRPullLoader.svg?style=flat)](http://cocoapods.org/pods/KRPullLoader) 8 | [![Download](https://img.shields.io/cocoapods/dt/KRPullLoader.svg?style=flat)](http://cocoapods.org/pods/KRPullLoader) 9 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 10 | [![CI Status](http://img.shields.io/travis/krimpedance/KRPullLoader.svg?style=flat)](https://travis-ci.org/krimpedance/KRPullLoader) 11 | 12 | 13 | 14 | ## Features 15 | - Easy-to-use 16 | - Possibles both of `pull refresh` and `load more`. 17 | 18 | ## Requirements 19 | - iOS 8.0+ 20 | - Xcode 10.2+ 21 | - Swift 5.0+ 22 | 23 | ## DEMO 24 | To run the example project, clone the repo, and open `KRPullLoaderDemo.xcodeproj` from the DEMO directory. 25 | 26 | or [appetize.io](https://appetize.io/app/d17hjrvt0fm9mfg2crmqbu4qx4) 27 | 28 | ## Installation 29 | KRPullLoader is available through [CocoaPods](http://cocoapods.org) and [Carthage](https://github.com/Carthage/Carthage). 30 | To install it, simply add the following line to your Podfile or Cartfile: 31 | 32 | ```ruby 33 | # CocoaPods 34 | pod "KRPullLoader" 35 | ``` 36 | 37 | ```ruby 38 | # Carthage 39 | github "Krimpedance/KRPullLoader" 40 | ``` 41 | 42 | ## Usage 43 | (see sample Xcode project in /Demo) 44 | 45 | #### Add views 46 | 47 | The simplest way: 48 | 49 | ```swift 50 | let refreshView = KRPullLoadView() 51 | refreshView.delegate = self 52 | tableView.addPullLoadableView(refreshView, type: .refresh) 53 | ``` 54 | 55 | `KRPullLoadView` is a simple loading view which consists of `UIActivityIndicatorView` and `UILabel`. 56 | 57 | The change in the state can be watched by the delegate method. 58 | 59 | `type` has `.refresh` and `.loadMore` and can add either of UIScrollView's top and bottom. 60 | 61 | #### Add custom views 62 | 63 | You can design loading views freely by making custom UIView in succession to `KRPullLoadable` protocol 64 | 65 | Please refer to [KRPullLoadView.swift](./KRPullLoader/Classes/KRPullLoadView.swift) or [HorizontalPullLoadView.swift](./DEMO/KRPullLoaderDemo/HorizontalPullLoadView.swift). 66 | 67 | #### KRPullLoadable 68 | 69 | ```swift 70 | /** 71 | Handler when KRPullLoaderState value changed. 72 | 73 | - parameter state: New state. 74 | - parameter type: KRPullLoaderType. 75 | */ 76 | func didChangeState(_ state: KRPullLoaderState, viewType type: KRPullLoaderType) 77 | ``` 78 | 79 | #### KRPullLoaderState 80 | 81 | This is enum which shows the state of the scrolling. 82 | 83 | ```swift 84 | .none 85 | // hides the view. 86 | .pulling(offset: CGPoint, threshold: CGFloat) 87 | // Pulling. 88 | // `offset` is pull offset (always <= 0). 89 | // This state changes to `loading` when `offset` exceeded `threshold`. 90 | .loading(completionHandler: ()->Void) 91 | // Shows the view. 92 | // You should call `completionHandler` when some actions have been completed. 93 | ``` 94 | 95 | ## Contributing to this project 96 | I'm seeking bug reports and feature requests. 97 | 98 | ## Release Note 99 | + 1.3.0 100 | - Compatible with Swift 5.0. 101 | - Add method that pull to refresh programmatically (Only `KRPullLoaderType.refresh`) 102 | 103 | + 1.2.0 104 | - Compatible with Swift 4.2. 105 | 106 | ## License 107 | KRPullLoader is available under the MIT license. See the LICENSE file for more info. 108 | -------------------------------------------------------------------------------- /README_Ja.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) 2 | 3 | # KRPullLoader 4 | 5 | [![Version](https://img.shields.io/cocoapods/v/KRPullLoader.svg?style=flat)](http://cocoapods.org/pods/KRPullLoader) 6 | [![License](https://img.shields.io/cocoapods/l/KRPullLoader.svg?style=flat)](http://cocoapods.org/pods/KRPullLoader) 7 | [![Platform](https://img.shields.io/cocoapods/p/KRPullLoader.svg?style=flat)](http://cocoapods.org/pods/KRPullLoader) 8 | [![Download](https://img.shields.io/cocoapods/dt/KRPullLoader.svg?style=flat)](http://cocoapods.org/pods/KRPullLoader) 9 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 10 | [![CI Status](http://img.shields.io/travis/krimpedance/KRPullLoader.svg?style=flat)](https://travis-ci.org/krimpedance/KRPullLoader) 11 | 12 | 13 | 14 | ## 特徴 15 | - 使いやすさ 16 | - `引っ張って更新`, `もっと読み込む`の両アクションに対応 17 | 18 | ## 必要環境 19 | - iOS 8.0+ 20 | - Xcode 10.2+ 21 | - Swift 5.0+ 22 | 23 | ## デモ 24 | `DEMO/`以下にあるサンプルプロジェクトから確認してください. 25 | 26 | または,[Appetize.io](https://appetize.io/app/d17hjrvt0fm9mfg2crmqbu4qx4)にてシュミレートしてください. 27 | 28 | ## インストール 29 | KRPullLoaderは[CocoaPods](http://cocoapods.org)と[Carthage](https://github.com/Carthage/Carthage)で 30 | インストールすることができます. 31 | 32 | ```ruby 33 | # Podfile 34 | pod "KRPullLoader" 35 | ``` 36 | 37 | ```ruby 38 | # Cartfile 39 | github "Krimpedance/KRPullLoader" 40 | ``` 41 | 42 | ## 使い方 43 | (`/Demo`以下のサンプルを見てみてください) 44 | 45 | #### ビューの追加 46 | 47 | 最もシンプルな方法: 48 | 49 | ```swift 50 | let refreshView = KRPullLoadView() 51 | refreshView.delegate = self 52 | tableView.addPullLoadableView(refreshView, type: .refresh) 53 | ``` 54 | 55 | `KRPullLoadView`は`UIActivityIndicatorView`と`UILabel`からなる, 最もシンプルなローディングビューです. 56 | 57 | デリゲートメソッドで状態の変更を監視できます. 58 | 59 | `type`は`.refresh`と`.loadMore`があり, 上部/下部のどちらにでも追加できます. 60 | 61 | #### カスタムビューの追加 62 | 63 | ローディングビューは, `KRPullLoadable`プロトコルを継承したUIViewを作成することで, 64 | 自由にデザインすることができます. 65 | 66 | [KRPullLoadView.swift](./KRPullLoader/Classes/KRPullLoadView.swift)や[HorizontalPullLoadView.swift](./DEMO/KRPullLoaderDemo/HorizontalPullLoadView.swift)を参考にしてください. 67 | 68 | #### KRPullLoadable 69 | 70 | このプロトコルは以下のメソッドのみを持っています. 71 | 72 | ```swift 73 | /** 74 | KRPullLoaderStateが変わる時に呼ばれる関数 75 | 76 | - parameter state: 新しい状態 77 | - parameter type: 自身のタイプ. 78 | */ 79 | func didChangeState(_ state: KRPullLoaderState, viewType type: KRPullLoaderType) 80 | ``` 81 | 82 | #### KRPullLoaderState 83 | 84 | スクロールの状態を表すEnumです. 85 | 86 | ```swift 87 | .none 88 | // ローディングビューが隠れている状態(タップなし, または通常のスクロール中) 89 | .pulling(offset: CGPoint, threshold: CGFloat) 90 | // ScrollViewのContentSizeを超えて画面を引っ張っている状態 91 | // offset: オーバースクロール量 92 | // threshold: タップをやめた時に`.loading`へ移行する閾値 93 | .loading(completionHandler: ()->Void) 94 | // ローディング中 95 | // `completionHandler`を呼ぶとローディングを終了します. 96 | ``` 97 | 98 | ## ライブラリに関する質問等 99 | バグや機能のリクエストがありましたら,気軽にコメントしてください. 100 | 101 | ## リリースノート 102 | + 1.3.0 103 | - Swift 5.0 に対応. 104 | - コードからローディングビューを表示できるように変更(上部のみ) 105 | 106 | + 1.2.0 107 | - Swift 4.2 に対応. 108 | 109 | ## ライセンス 110 | KRPullLoaderはMITライセンスに準拠しています. 111 | 112 | 詳しくは`LICENSE`ファイルをみてください. 113 | --------------------------------------------------------------------------------