├── .gitignore ├── Arale.podspec ├── Arale.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── zulwiyoza.putra.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Arale ├── Arale.h ├── AraleHeaderView.swift ├── AraleHeaderViewDelegate.swift └── Info.plist ├── AraleTests ├── AraleTests.swift └── Info.plist ├── LICENSE └── README.md /.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 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 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 | # Add this line if you want to avoid checking in source code from the Xcode workspace 52 | # *.xcworkspace 53 | 54 | # Carthage 55 | # 56 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 57 | # Carthage/Checkouts 58 | 59 | Carthage/Build 60 | 61 | # fastlane 62 | # 63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 64 | # screenshots whenever they are needed. 65 | # For more information about the recommended setup visit: 66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 67 | 68 | fastlane/report.xml 69 | fastlane/Preview.html 70 | fastlane/screenshots/**/*.png 71 | fastlane/test_output 72 | 73 | # Code Injection 74 | # 75 | # After new code Injection tools there's a generated folder /iOSInjectionProject 76 | # https://github.com/johnno1962/injectionforxcode 77 | 78 | iOSInjectionProject/ 79 | -------------------------------------------------------------------------------- /Arale.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 4 | 5 | s.name = "Arale" 6 | s.version = "0.0.4" 7 | s.summary = "A stretchy big header for UITableView, UICollectionView, or any UIScrollView subclasses." 8 | s.description = "Arale is a completely customizeble UI component that can be used in any UIScrollView and UIScrollView subclasses to give a stretchy header effect." 9 | s.homepage = "https://github.com/ZulwiyozaPutra/Arale" 10 | 11 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | 13 | s.license = { :type => "MIT", :file => "LICENSE" } 14 | 15 | 16 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 17 | 18 | s.author = { "Zulwiyoza Putra" => "zulwiyozaputra@gmail.com" } 19 | s.social_media_url = "http://twitter.com/ZulwiyozaPutra" 20 | 21 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 22 | 23 | s.platform = :ios, "10.0" 24 | 25 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 26 | 27 | s.source = { :git => "https://github.com/ZulwiyozaPutra/Arale.git", :tag => "#{s.version}" } 28 | 29 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 30 | 31 | s.source_files = "Arale", "Arale/**/*.{h,m}" 32 | s.exclude_files = "Arale/Exclude" 33 | 34 | 35 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 36 | 37 | s.swift_version = "4.2" 38 | 39 | end 40 | -------------------------------------------------------------------------------- /Arale.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 81B100BA219AB78E0065A3B4 /* Arale.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81B100B0219AB78E0065A3B4 /* Arale.framework */; }; 11 | 81B100BF219AB78E0065A3B4 /* AraleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B100BE219AB78E0065A3B4 /* AraleTests.swift */; }; 12 | 81B100C1219AB78E0065A3B4 /* Arale.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B100B3219AB78E0065A3B4 /* Arale.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 81B100CB219ABA6A0065A3B4 /* AraleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B100CA219ABA6A0065A3B4 /* AraleHeaderView.swift */; }; 14 | 81B100CD219ABB440065A3B4 /* AraleHeaderViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B100CC219ABB440065A3B4 /* AraleHeaderViewDelegate.swift */; }; 15 | 81B100CE219ABB440065A3B4 /* AraleHeaderViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B100CC219ABB440065A3B4 /* AraleHeaderViewDelegate.swift */; }; 16 | 81B100D2219AE7EF0065A3B4 /* AraleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B100CA219ABA6A0065A3B4 /* AraleHeaderView.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 81B100BB219AB78E0065A3B4 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 81B100A7219AB78E0065A3B4 /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 81B100AF219AB78E0065A3B4; 25 | remoteInfo = Arale; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 81B100B0219AB78E0065A3B4 /* Arale.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Arale.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 81B100B3219AB78E0065A3B4 /* Arale.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Arale.h; sourceTree = ""; }; 32 | 81B100B4219AB78E0065A3B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | 81B100B9219AB78E0065A3B4 /* AraleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AraleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 81B100BE219AB78E0065A3B4 /* AraleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AraleTests.swift; sourceTree = ""; }; 35 | 81B100C0219AB78E0065A3B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 81B100CA219ABA6A0065A3B4 /* AraleHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AraleHeaderView.swift; sourceTree = ""; }; 37 | 81B100CC219ABB440065A3B4 /* AraleHeaderViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AraleHeaderViewDelegate.swift; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | 81B100AD219AB78E0065A3B4 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | 81B100B6219AB78E0065A3B4 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | 81B100BA219AB78E0065A3B4 /* Arale.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | 81B100A6219AB78E0065A3B4 = { 60 | isa = PBXGroup; 61 | children = ( 62 | 81B100B2219AB78E0065A3B4 /* Arale */, 63 | 81B100BD219AB78E0065A3B4 /* AraleTests */, 64 | 81B100B1219AB78E0065A3B4 /* Products */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | 81B100B1219AB78E0065A3B4 /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 81B100B0219AB78E0065A3B4 /* Arale.framework */, 72 | 81B100B9219AB78E0065A3B4 /* AraleTests.xctest */, 73 | ); 74 | name = Products; 75 | sourceTree = ""; 76 | }; 77 | 81B100B2219AB78E0065A3B4 /* Arale */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 81B100B3219AB78E0065A3B4 /* Arale.h */, 81 | 81B100CC219ABB440065A3B4 /* AraleHeaderViewDelegate.swift */, 82 | 81B100CA219ABA6A0065A3B4 /* AraleHeaderView.swift */, 83 | 81B100B4219AB78E0065A3B4 /* Info.plist */, 84 | ); 85 | path = Arale; 86 | sourceTree = ""; 87 | }; 88 | 81B100BD219AB78E0065A3B4 /* AraleTests */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 81B100BE219AB78E0065A3B4 /* AraleTests.swift */, 92 | 81B100C0219AB78E0065A3B4 /* Info.plist */, 93 | ); 94 | path = AraleTests; 95 | sourceTree = ""; 96 | }; 97 | /* End PBXGroup section */ 98 | 99 | /* Begin PBXHeadersBuildPhase section */ 100 | 81B100AB219AB78E0065A3B4 /* Headers */ = { 101 | isa = PBXHeadersBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | 81B100C1219AB78E0065A3B4 /* Arale.h in Headers */, 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | /* End PBXHeadersBuildPhase section */ 109 | 110 | /* Begin PBXNativeTarget section */ 111 | 81B100AF219AB78E0065A3B4 /* Arale */ = { 112 | isa = PBXNativeTarget; 113 | buildConfigurationList = 81B100C4219AB78E0065A3B4 /* Build configuration list for PBXNativeTarget "Arale" */; 114 | buildPhases = ( 115 | 81B100AB219AB78E0065A3B4 /* Headers */, 116 | 81B100AC219AB78E0065A3B4 /* Sources */, 117 | 81B100AD219AB78E0065A3B4 /* Frameworks */, 118 | 81B100AE219AB78E0065A3B4 /* Resources */, 119 | ); 120 | buildRules = ( 121 | ); 122 | dependencies = ( 123 | ); 124 | name = Arale; 125 | productName = Arale; 126 | productReference = 81B100B0219AB78E0065A3B4 /* Arale.framework */; 127 | productType = "com.apple.product-type.framework"; 128 | }; 129 | 81B100B8219AB78E0065A3B4 /* AraleTests */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = 81B100C7219AB78E0065A3B4 /* Build configuration list for PBXNativeTarget "AraleTests" */; 132 | buildPhases = ( 133 | 81B100B5219AB78E0065A3B4 /* Sources */, 134 | 81B100B6219AB78E0065A3B4 /* Frameworks */, 135 | 81B100B7219AB78E0065A3B4 /* Resources */, 136 | ); 137 | buildRules = ( 138 | ); 139 | dependencies = ( 140 | 81B100BC219AB78E0065A3B4 /* PBXTargetDependency */, 141 | ); 142 | name = AraleTests; 143 | productName = AraleTests; 144 | productReference = 81B100B9219AB78E0065A3B4 /* AraleTests.xctest */; 145 | productType = "com.apple.product-type.bundle.unit-test"; 146 | }; 147 | /* End PBXNativeTarget section */ 148 | 149 | /* Begin PBXProject section */ 150 | 81B100A7219AB78E0065A3B4 /* Project object */ = { 151 | isa = PBXProject; 152 | attributes = { 153 | LastSwiftUpdateCheck = 1010; 154 | LastUpgradeCheck = 1010; 155 | ORGANIZATIONNAME = "Zulwiyoza Putra"; 156 | TargetAttributes = { 157 | 81B100AF219AB78E0065A3B4 = { 158 | CreatedOnToolsVersion = 10.1; 159 | LastSwiftMigration = 1140; 160 | }; 161 | 81B100B8219AB78E0065A3B4 = { 162 | CreatedOnToolsVersion = 10.1; 163 | LastSwiftMigration = 1140; 164 | }; 165 | }; 166 | }; 167 | buildConfigurationList = 81B100AA219AB78E0065A3B4 /* Build configuration list for PBXProject "Arale" */; 168 | compatibilityVersion = "Xcode 9.3"; 169 | developmentRegion = en; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | en, 173 | ); 174 | mainGroup = 81B100A6219AB78E0065A3B4; 175 | productRefGroup = 81B100B1219AB78E0065A3B4 /* Products */; 176 | projectDirPath = ""; 177 | projectRoot = ""; 178 | targets = ( 179 | 81B100AF219AB78E0065A3B4 /* Arale */, 180 | 81B100B8219AB78E0065A3B4 /* AraleTests */, 181 | ); 182 | }; 183 | /* End PBXProject section */ 184 | 185 | /* Begin PBXResourcesBuildPhase section */ 186 | 81B100AE219AB78E0065A3B4 /* Resources */ = { 187 | isa = PBXResourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | runOnlyForDeploymentPostprocessing = 0; 192 | }; 193 | 81B100B7219AB78E0065A3B4 /* Resources */ = { 194 | isa = PBXResourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | /* End PBXResourcesBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 81B100AC219AB78E0065A3B4 /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 81B100CD219ABB440065A3B4 /* AraleHeaderViewDelegate.swift in Sources */, 208 | 81B100CB219ABA6A0065A3B4 /* AraleHeaderView.swift in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | 81B100B5219AB78E0065A3B4 /* Sources */ = { 213 | isa = PBXSourcesBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | 81B100CE219ABB440065A3B4 /* AraleHeaderViewDelegate.swift in Sources */, 217 | 81B100D2219AE7EF0065A3B4 /* AraleHeaderView.swift in Sources */, 218 | 81B100BF219AB78E0065A3B4 /* AraleTests.swift in Sources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXSourcesBuildPhase section */ 223 | 224 | /* Begin PBXTargetDependency section */ 225 | 81B100BC219AB78E0065A3B4 /* PBXTargetDependency */ = { 226 | isa = PBXTargetDependency; 227 | target = 81B100AF219AB78E0065A3B4 /* Arale */; 228 | targetProxy = 81B100BB219AB78E0065A3B4 /* PBXContainerItemProxy */; 229 | }; 230 | /* End PBXTargetDependency section */ 231 | 232 | /* Begin XCBuildConfiguration section */ 233 | 81B100C2219AB78E0065A3B4 /* Debug */ = { 234 | isa = XCBuildConfiguration; 235 | buildSettings = { 236 | ALWAYS_SEARCH_USER_PATHS = NO; 237 | CLANG_ANALYZER_NONNULL = YES; 238 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_ENABLE_OBJC_WEAK = YES; 244 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 245 | CLANG_WARN_BOOL_CONVERSION = YES; 246 | CLANG_WARN_COMMA = YES; 247 | CLANG_WARN_CONSTANT_CONVERSION = YES; 248 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 250 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 251 | CLANG_WARN_EMPTY_BODY = YES; 252 | CLANG_WARN_ENUM_CONVERSION = YES; 253 | CLANG_WARN_INFINITE_RECURSION = YES; 254 | CLANG_WARN_INT_CONVERSION = YES; 255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 260 | CLANG_WARN_STRICT_PROTOTYPES = YES; 261 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 262 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | CODE_SIGN_IDENTITY = "iPhone Developer"; 266 | COPY_PHASE_STRIP = NO; 267 | CURRENT_PROJECT_VERSION = 1; 268 | DEBUG_INFORMATION_FORMAT = dwarf; 269 | ENABLE_STRICT_OBJC_MSGSEND = YES; 270 | ENABLE_TESTABILITY = YES; 271 | GCC_C_LANGUAGE_STANDARD = gnu11; 272 | GCC_DYNAMIC_NO_PIC = NO; 273 | GCC_NO_COMMON_BLOCKS = YES; 274 | GCC_OPTIMIZATION_LEVEL = 0; 275 | GCC_PREPROCESSOR_DEFINITIONS = ( 276 | "DEBUG=1", 277 | "$(inherited)", 278 | ); 279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_VARIABLE = YES; 285 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 286 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 287 | MTL_FAST_MATH = YES; 288 | ONLY_ACTIVE_ARCH = YES; 289 | SDKROOT = iphoneos; 290 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 291 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 292 | VERSIONING_SYSTEM = "apple-generic"; 293 | VERSION_INFO_PREFIX = ""; 294 | }; 295 | name = Debug; 296 | }; 297 | 81B100C3219AB78E0065A3B4 /* Release */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ALWAYS_SEARCH_USER_PATHS = NO; 301 | CLANG_ANALYZER_NONNULL = YES; 302 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 303 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 304 | CLANG_CXX_LIBRARY = "libc++"; 305 | CLANG_ENABLE_MODULES = YES; 306 | CLANG_ENABLE_OBJC_ARC = YES; 307 | CLANG_ENABLE_OBJC_WEAK = YES; 308 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 309 | CLANG_WARN_BOOL_CONVERSION = YES; 310 | CLANG_WARN_COMMA = YES; 311 | CLANG_WARN_CONSTANT_CONVERSION = YES; 312 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 313 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 314 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 315 | CLANG_WARN_EMPTY_BODY = YES; 316 | CLANG_WARN_ENUM_CONVERSION = YES; 317 | CLANG_WARN_INFINITE_RECURSION = YES; 318 | CLANG_WARN_INT_CONVERSION = YES; 319 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 321 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 323 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 324 | CLANG_WARN_STRICT_PROTOTYPES = YES; 325 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 326 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 327 | CLANG_WARN_UNREACHABLE_CODE = YES; 328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 329 | CODE_SIGN_IDENTITY = "iPhone Developer"; 330 | COPY_PHASE_STRIP = NO; 331 | CURRENT_PROJECT_VERSION = 1; 332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 333 | ENABLE_NS_ASSERTIONS = NO; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu11; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 344 | MTL_ENABLE_DEBUG_INFO = NO; 345 | MTL_FAST_MATH = YES; 346 | SDKROOT = iphoneos; 347 | SWIFT_COMPILATION_MODE = wholemodule; 348 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 349 | VALIDATE_PRODUCT = YES; 350 | VERSIONING_SYSTEM = "apple-generic"; 351 | VERSION_INFO_PREFIX = ""; 352 | }; 353 | name = Release; 354 | }; 355 | 81B100C5219AB78E0065A3B4 /* Debug */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | CLANG_ENABLE_MODULES = YES; 359 | CODE_SIGN_IDENTITY = ""; 360 | CODE_SIGN_STYLE = Automatic; 361 | DEFINES_MODULE = YES; 362 | DEVELOPMENT_TEAM = RU5UXF462E; 363 | DYLIB_COMPATIBILITY_VERSION = 1; 364 | DYLIB_CURRENT_VERSION = 1; 365 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 366 | INFOPLIST_FILE = Arale/Info.plist; 367 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 368 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 369 | LD_RUNPATH_SEARCH_PATHS = ( 370 | "$(inherited)", 371 | "@executable_path/Frameworks", 372 | "@loader_path/Frameworks", 373 | ); 374 | PRODUCT_BUNDLE_IDENTIFIER = com.wiyoza.Arale; 375 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 376 | SKIP_INSTALL = YES; 377 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 378 | SWIFT_VERSION = 5.0; 379 | TARGETED_DEVICE_FAMILY = "1,2"; 380 | }; 381 | name = Debug; 382 | }; 383 | 81B100C6219AB78E0065A3B4 /* Release */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | CLANG_ENABLE_MODULES = YES; 387 | CODE_SIGN_IDENTITY = ""; 388 | CODE_SIGN_STYLE = Automatic; 389 | DEFINES_MODULE = YES; 390 | DEVELOPMENT_TEAM = RU5UXF462E; 391 | DYLIB_COMPATIBILITY_VERSION = 1; 392 | DYLIB_CURRENT_VERSION = 1; 393 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 394 | INFOPLIST_FILE = Arale/Info.plist; 395 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 396 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 397 | LD_RUNPATH_SEARCH_PATHS = ( 398 | "$(inherited)", 399 | "@executable_path/Frameworks", 400 | "@loader_path/Frameworks", 401 | ); 402 | PRODUCT_BUNDLE_IDENTIFIER = com.wiyoza.Arale; 403 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 404 | SKIP_INSTALL = YES; 405 | SWIFT_VERSION = 5.0; 406 | TARGETED_DEVICE_FAMILY = "1,2"; 407 | }; 408 | name = Release; 409 | }; 410 | 81B100C8219AB78E0065A3B4 /* Debug */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 414 | CODE_SIGN_STYLE = Automatic; 415 | DEVELOPMENT_TEAM = RU5UXF462E; 416 | INFOPLIST_FILE = AraleTests/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = ( 418 | "$(inherited)", 419 | "@executable_path/Frameworks", 420 | "@loader_path/Frameworks", 421 | ); 422 | PRODUCT_BUNDLE_IDENTIFIER = com.wiyoza.AraleTests; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | SWIFT_VERSION = 5.0; 425 | TARGETED_DEVICE_FAMILY = "1,2"; 426 | }; 427 | name = Debug; 428 | }; 429 | 81B100C9219AB78E0065A3B4 /* Release */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 433 | CODE_SIGN_STYLE = Automatic; 434 | DEVELOPMENT_TEAM = RU5UXF462E; 435 | INFOPLIST_FILE = AraleTests/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = ( 437 | "$(inherited)", 438 | "@executable_path/Frameworks", 439 | "@loader_path/Frameworks", 440 | ); 441 | PRODUCT_BUNDLE_IDENTIFIER = com.wiyoza.AraleTests; 442 | PRODUCT_NAME = "$(TARGET_NAME)"; 443 | SWIFT_VERSION = 5.0; 444 | TARGETED_DEVICE_FAMILY = "1,2"; 445 | }; 446 | name = Release; 447 | }; 448 | /* End XCBuildConfiguration section */ 449 | 450 | /* Begin XCConfigurationList section */ 451 | 81B100AA219AB78E0065A3B4 /* Build configuration list for PBXProject "Arale" */ = { 452 | isa = XCConfigurationList; 453 | buildConfigurations = ( 454 | 81B100C2219AB78E0065A3B4 /* Debug */, 455 | 81B100C3219AB78E0065A3B4 /* Release */, 456 | ); 457 | defaultConfigurationIsVisible = 0; 458 | defaultConfigurationName = Release; 459 | }; 460 | 81B100C4219AB78E0065A3B4 /* Build configuration list for PBXNativeTarget "Arale" */ = { 461 | isa = XCConfigurationList; 462 | buildConfigurations = ( 463 | 81B100C5219AB78E0065A3B4 /* Debug */, 464 | 81B100C6219AB78E0065A3B4 /* Release */, 465 | ); 466 | defaultConfigurationIsVisible = 0; 467 | defaultConfigurationName = Release; 468 | }; 469 | 81B100C7219AB78E0065A3B4 /* Build configuration list for PBXNativeTarget "AraleTests" */ = { 470 | isa = XCConfigurationList; 471 | buildConfigurations = ( 472 | 81B100C8219AB78E0065A3B4 /* Debug */, 473 | 81B100C9219AB78E0065A3B4 /* Release */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | defaultConfigurationName = Release; 477 | }; 478 | /* End XCConfigurationList section */ 479 | }; 480 | rootObject = 81B100A7219AB78E0065A3B4 /* Project object */; 481 | } 482 | -------------------------------------------------------------------------------- /Arale.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Arale.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Arale.xcodeproj/xcuserdata/zulwiyoza.putra.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Arale.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Arale/Arale.h: -------------------------------------------------------------------------------- 1 | // 2 | // Arale.h 3 | // Arale 4 | // 5 | // Created by Zulwiyoza Putra on 13/11/18. 6 | // Copyright © 2018 Zulwiyoza Putra. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Arale. 12 | FOUNDATION_EXPORT double AraleVersionNumber; 13 | 14 | //! Project version string for Arale. 15 | FOUNDATION_EXPORT const unsigned char AraleVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Arale/AraleHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AraleHeaderView.swift 3 | // Arale 4 | // 5 | // Created by Zulwiyoza Putra on 13/11/18. 6 | // Copyright © 2018 Zulwiyoza Putra. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | open class AraleHeaderView: UIView { 13 | /** 14 | The refreshControl associated with HeaderView 15 | Controlled by its scrollView 16 | */ 17 | open var activityIndicatorView: UIActivityIndicatorView? 18 | 19 | /** 20 | Gettable image for HeaderView 21 | Settable from init method 22 | @return background image 23 | */ 24 | open private(set) var backgroundImage: UIImage? 25 | 26 | /** 27 | Gettable image for HeaderView 28 | Settable from init method 29 | @return the view that is displayed on top of the image view. 30 | */ 31 | open private(set) var contentView: UIView = { 32 | let contentView = UIView() 33 | contentView.backgroundColor = .clear 34 | return contentView 35 | }() 36 | 37 | /** 38 | RefreshControl will automatically endRefreshing after reaching refreshTimoutLimit 39 | The default is nil will animate infinitely 40 | @return refresh timeout limit if refreshControl is not nil 41 | */ 42 | open var refreshTimeoutLimit: Double? 43 | 44 | /** 45 | Gettable minimum Height for HeaderView 46 | Settable from init method 47 | @return initial Height 48 | */ 49 | open private(set) var minHeight: CGFloat 50 | 51 | /** 52 | Gettable maximum Height for HeaderView 53 | Constant to 125% of minHeight 54 | @return maximum Height to invoke activityIndicator 55 | */ 56 | open private(set) var maxHeight: CGFloat 57 | 58 | /** 59 | Gettable height for margin with top scrollView 60 | Settable from init method 61 | @return height for bottomMargin 62 | */ 63 | open private(set) var bottomMargin: CGFloat 64 | 65 | /** 66 | Optional delegate 67 | @return delegate for HeaderView event 68 | */ 69 | open weak var delegate: AraleHeaderViewDelegate? 70 | 71 | private weak var scrollView: UIScrollView? 72 | private weak var adjustableHeightAnchor: NSLayoutConstraint? 73 | 74 | private var isReachedMaxHeight: Bool 75 | 76 | /** 77 | Getter to calculate top safeAreaInsets 78 | @return 0 if window doesn't have safeAreaInstets 79 | */ 80 | @available(iOS 11.0, *) 81 | private var topSafeAreaHeight: CGFloat { 82 | get { 83 | guard UIApplication.shared.windows.count != 0 else { 84 | return 0 85 | } 86 | let keyWindow = UIApplication.shared.windows[0] 87 | return keyWindow.safeAreaInsets.top 88 | } 89 | } 90 | 91 | private override init(frame: CGRect) { 92 | self.minHeight = 0 93 | self.maxHeight = 0 94 | self.bottomMargin = 0 95 | self.isReachedMaxHeight = false 96 | super.init(frame: frame) 97 | } 98 | 99 | convenience public init(withMinHeight minHeight: CGFloat, bottomMargin: CGFloat = 0, backgroundImage: UIImage? = nil) { 100 | self.init(frame: CGRect.zero) 101 | self.minHeight = minHeight 102 | self.maxHeight = minHeight * 1.25 103 | self.bottomMargin = bottomMargin 104 | self.backgroundImage = backgroundImage 105 | } 106 | 107 | required public init?(coder aDecoder: NSCoder) { 108 | fatalError("init(coder:) has not been implemented") 109 | } 110 | 111 | deinit { 112 | guard let scrollView = self.scrollView else { 113 | return 114 | } 115 | scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset), context: nil) 116 | } 117 | 118 | open override func didMoveToSuperview() { 119 | super.didMoveToSuperview() 120 | guard let superview = self.superview else { 121 | return 122 | } 123 | 124 | guard let scrollView = superview as? UIScrollView else { 125 | fatalError("HeaderView can be only sit as a subview of UIScrollView instance") 126 | } 127 | 128 | self.scrollView = scrollView 129 | 130 | setImageView() 131 | setContentView() 132 | setScrollViewContentInset() 133 | setLayoutConstraints() 134 | setSubviewLayoutConstraints() 135 | observeScrollViewContentOffset() 136 | layoutIfNeeded() 137 | } 138 | 139 | private func resizeFrameOnScroll(withContentOffset contentOffset: CGPoint) { 140 | if self.window == nil { 141 | return 142 | } 143 | 144 | if contentOffset.y < -(minHeight + bottomMargin) { 145 | if let delegate = self.delegate { 146 | delegate.headerViewWillResizeFrame(self) 147 | } 148 | 149 | guard let adjustableHeightAnchor = self.adjustableHeightAnchor else { 150 | return 151 | } 152 | 153 | adjustableHeightAnchor.constant = -contentOffset.y - bottomMargin 154 | 155 | if let delegate = self.delegate { 156 | delegate.headerViewDidResizeFrame(self) 157 | } 158 | 159 | if contentOffset.y <= -(maxHeight + bottomMargin) { 160 | if isReachedMaxHeight { 161 | return 162 | } 163 | isReachedMaxHeight = true 164 | viewDidReachMaxHeight() 165 | } else { 166 | isReachedMaxHeight = false 167 | } 168 | } 169 | } 170 | 171 | private func handleActivityIndicatorAlpha(withContentOffset contentOffset: CGPoint) { 172 | if self.window == nil { 173 | return 174 | } 175 | 176 | guard let activityIndicatorView = self.activityIndicatorView else { 177 | return 178 | } 179 | 180 | if (contentOffset.y < -(minHeight + bottomMargin)) && (contentOffset.y >= -(maxHeight + bottomMargin)) { 181 | activityIndicatorView.isOpaque = true 182 | 183 | var denominator = (-contentOffset.y - (minHeight + bottomMargin)) 184 | var nominator = (maxHeight + bottomMargin - minHeight) 185 | if #available(iOS 11.0, *) { 186 | denominator = -contentOffset.y - (minHeight + bottomMargin + topSafeAreaHeight) 187 | nominator = maxHeight + bottomMargin - minHeight - topSafeAreaHeight 188 | } 189 | 190 | activityIndicatorView.alpha = denominator/nominator 191 | } 192 | } 193 | 194 | private func viewDidReachMaxHeight() { 195 | if let delegate = self.delegate { 196 | delegate.headerViewDidReachMaxHeight(self) 197 | } 198 | 199 | guard let activityIndicatorView = self.activityIndicatorView else { 200 | return 201 | } 202 | 203 | if !activityIndicatorView.isAnimating { 204 | activityIndicatorView.startAnimating() 205 | 206 | if #available(iOS 10.0, *) { 207 | let feedbackGenerator = UIImpactFeedbackGenerator(style: .medium) 208 | feedbackGenerator.impactOccurred() 209 | } 210 | 211 | if let timoutLimit = refreshTimeoutLimit { 212 | DispatchQueue.main.asyncAfter(deadline: .now() + timoutLimit) { 213 | activityIndicatorView.stopAnimating() 214 | guard let scrollView = self.scrollView else { 215 | return 216 | } 217 | self.handleActivityIndicatorAlpha(withContentOffset: scrollView.contentOffset) 218 | } 219 | } 220 | 221 | } 222 | } 223 | 224 | private func observeScrollViewContentOffset() { 225 | guard let scrollView = self.scrollView else { 226 | return 227 | } 228 | scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset), options: [.new], context: nil) 229 | } 230 | 231 | override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 232 | guard let scrollView = self.scrollView else { 233 | return 234 | } 235 | 236 | guard let keyPath = keyPath, keyPath == #keyPath(UIScrollView.contentOffset) else { 237 | return 238 | } 239 | 240 | guard let object = object as? UIScrollView, object == scrollView else { 241 | return 242 | } 243 | 244 | guard let change = change, let contentOffset = change[NSKeyValueChangeKey.newKey] as? CGPoint else { 245 | return 246 | } 247 | 248 | resizeFrameOnScroll(withContentOffset: contentOffset) 249 | 250 | guard let activityIndicatorView = self.activityIndicatorView else { 251 | return 252 | } 253 | 254 | if !activityIndicatorView.isAnimating { 255 | handleActivityIndicatorAlpha(withContentOffset: contentOffset) 256 | } 257 | } 258 | } 259 | 260 | extension AraleHeaderView { 261 | private func setImageView() { 262 | guard let image = backgroundImage else { 263 | return 264 | } 265 | 266 | let imageView = UIImageView(image: image) 267 | self.addSubview(imageView) 268 | imageView.clipsToBounds = true 269 | imageView.translatesAutoresizingMaskIntoConstraints = false 270 | imageView.contentMode = .scaleAspectFill 271 | imageView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true 272 | imageView.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true 273 | imageView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true 274 | imageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true 275 | } 276 | 277 | private func setContentView() { 278 | self.addSubview(contentView) 279 | contentView.clipsToBounds = true 280 | contentView.translatesAutoresizingMaskIntoConstraints = false 281 | contentView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true 282 | contentView.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true 283 | contentView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true 284 | contentView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true 285 | } 286 | 287 | private func setScrollViewContentInset() { 288 | guard let scrollView = self.scrollView else { 289 | return 290 | } 291 | 292 | var topContentInset = minHeight + bottomMargin 293 | 294 | if #available(iOS 11.0, *) { 295 | topContentInset -= topSafeAreaHeight 296 | } 297 | 298 | scrollView.contentInset.top = topContentInset 299 | } 300 | 301 | private func setLayoutConstraints() { 302 | guard let scrollView = self.scrollView, scrollView == superview else { 303 | return 304 | } 305 | 306 | translatesAutoresizingMaskIntoConstraints = false 307 | 308 | let adjustableHeightAnchor = self.heightAnchor.constraint(equalToConstant: self.minHeight) 309 | adjustableHeightAnchor.isActive = true 310 | self.adjustableHeightAnchor = adjustableHeightAnchor 311 | 312 | self.bottomAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true 313 | self.bottomAnchor.constraint(equalTo: scrollView.topAnchor, constant: -self.bottomMargin).isActive = true 314 | self.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true 315 | self.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true 316 | } 317 | 318 | private func setSubviewLayoutConstraints() { 319 | guard let activityIndicatorView = self.activityIndicatorView else { 320 | return 321 | } 322 | 323 | activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false 324 | activityIndicatorView.stopAnimating() 325 | activityIndicatorView.hidesWhenStopped = false 326 | self.addSubview(activityIndicatorView) 327 | 328 | let constraints = [ 329 | activityIndicatorView.leadingAnchor.constraint(equalTo: leadingAnchor), 330 | activityIndicatorView.trailingAnchor.constraint(equalTo: trailingAnchor), 331 | activityIndicatorView.topAnchor.constraint(equalTo: topAnchor), 332 | activityIndicatorView.bottomAnchor.constraint(equalTo: bottomAnchor) 333 | ] 334 | 335 | NSLayoutConstraint.activate(constraints) 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /Arale/AraleHeaderViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AraleHeaderViewDelegate.swift 3 | // Arale 4 | // 5 | // Created by Zulwiyoza Putra on 13/11/18. 6 | // Copyright © 2018 Zulwiyoza Putra. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol AraleHeaderViewDelegate: NSObjectProtocol { 12 | func headerViewWillResizeFrame(_ headerView: AraleHeaderView) 13 | func headerViewDidResizeFrame(_ headerView: AraleHeaderView) 14 | func headerViewDidReachMaxHeight(_ headerView: AraleHeaderView) 15 | } 16 | 17 | public extension AraleHeaderView { 18 | func headerViewWillResizeFrame(_ headerView: AraleHeaderView) { return } 19 | func headerViewDidResizeFrame(_ headerView: AraleHeaderView) { return } 20 | func headerViewDidReachMaxHeight(_headerView: AraleHeaderView) { return } 21 | } 22 | -------------------------------------------------------------------------------- /Arale/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /AraleTests/AraleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AraleTests.swift 3 | // AraleTests 4 | // 5 | // Created by Zulwiyoza Putra on 13/11/18. 6 | // Copyright © 2018 Zulwiyoza Putra. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Arale 11 | 12 | class AraleTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /AraleTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Zulwiyoza Putra 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](https://img.shields.io/cocoapods/l/Arale.svg?style=flat)](http://cocoapods.org/pods/Arale) 2 | [![Platform](https://img.shields.io/cocoapods/p/Arale.svg?style=flat)](http://cocoapods.org/pods/Arale) 3 | [![Version](https://img.shields.io/cocoapods/v/Arale.svg?style=flat)](http://cocoapods.org/pods/Arale) 4 | 5 | # Arale 6 | A custom stretchable header view for `UIScrollView` or any its subclasses with `UIActivityIndicatorView` support for reloading your content. Built for iOS 10 and later. 7 | 8 | # Demo 9 | ![Example 1](https://media.giphy.com/media/1qbl6sAB2EJh0fi9p7/giphy.gif) 10 | 11 | # Arale 12 | 13 | - Compatible with `UITableView`, `UICollectionView`, or any `UIScrollView` subclasses. 14 | - Data source and delegate independency: can be added to an existing view controller without interfering with your existing `delegate` or `dataSource`. 15 | - No need to subclass a custom view controller or to use a custom `UICollectionViewLayout`. 16 | 17 | 18 | If you are using this library in your project, I would be more than glad to [know about it!](mailto:zulwiyozaputra@gmail.com) 19 | 20 | ## Usage 21 | 22 | To add a stretchy header to your table or collection view, you just have to do this: 23 | 24 | ```swift 25 | 26 | import Arale 27 | ``` 28 | 29 | `init` with optional `backgroundImage` 30 | ```swift 31 | let araleHeaderView = AraleHeaderView(minHeight: 256.0, backgroundImage: myBackgroundImage) 32 | self.tableView.addSubview(araleHeaderView) 33 | ``` 34 | 35 | In case you want to add a `UIActivityIndicatorView`, to handle action if the `AraleHeaderView` did resize to `maxHeight` you can implement a `AraleHeaderViewDelegate` conformed `UIViewController` 36 | ```swift 37 | araleHeaderView.delegate = self 38 | ``` 39 | 40 | You can implement `headerViewDidReachMaxHeight` method to get event when the `araleHeaderView` did reach the maximum height 41 | ```swift 42 | func headerViewDidReachMaxHeight(_ headerView: AraleHeaderView) { 43 | NSLog("%@", "Start Refreshing") 44 | headerView.activityIndicatorView.stopAnimating() 45 | } 46 | ``` 47 | `AraleHeaderViewDelegate` comes with three optional delegate method 48 | ```swift 49 | 50 | func headerViewWillResizeFrame(_ headerView: AraleHeaderView) 51 | func headerViewDidResizeFrame(_ headerView: AraleHeaderView) 52 | func headerViewDidReachMaxHeight(_ headerView: AraleHeaderView) 53 | ``` 54 | 55 | ## Configuration 56 | 57 | You can add an optional `UIViewActivityIndicatorView` in your stretchy header view: 58 | ```swift 59 | let myActivityIndicatorview = UIActivityIndicatorView(style: .white) 60 | araleHeadeView.activityIndicatorView = myActivityIndicatorView 61 | ``` 62 | 63 | the `activityIndicatorView` will not be rendered if remain `nil` in case you don't need an activityIndicator. 64 | 65 | 66 | 67 | ## Installation 68 | 69 | Arale is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile, you can check the Example Podfile to see how it looks like: 70 | 71 | ```ruby 72 | pod "Arale" 73 | ``` 74 | 75 | ## Contributions 76 | 77 | Contributions are more than welcome! If you find a solution for a bug or have an improvement, don't hesitate to [open a pull request](https://github.com/ZulwiyozaPutra/Arale/compare)! 78 | 79 | ## License 80 | 81 | `Arale` is available under the MIT license. See the LICENSE file for more info. 82 | 83 | If your app uses `Arale`, I'd be glad if you reach me via [Twitter](https://twitter.com/zulwiyozaputra) or via email. 84 | --------------------------------------------------------------------------------