├── .swiftlint.yml ├── Documents ├── load_more.gif ├── custom_pull_to_refresh1.gif ├── custom_pull_to_refresh2.gif └── pull_to_refresh_load_more.gif ├── Pull To Refresh Demo ├── Assets.xcassets │ ├── Contents.json │ ├── arrow.imageset │ │ ├── arrow@3x.png │ │ └── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Customization │ ├── PullToMakeSoup │ │ ├── Resources │ │ │ ├── CookingRefresher.xcassets │ │ │ │ ├── pan.imageset │ │ │ │ │ ├── pan@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── pea.imageset │ │ │ │ │ ├── pea@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── cover.imageset │ │ │ │ │ ├── cover@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── water.imageset │ │ │ │ │ ├── water@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── carrot.imageset │ │ │ │ │ ├── carrot@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── circle.imageset │ │ │ │ │ ├── circle@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── potato.imageset │ │ │ │ │ ├── potato@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── shadow.imageset │ │ │ │ │ ├── shadow@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ └── flames │ │ │ │ │ ├── Flames0001.imageset │ │ │ │ │ ├── Flames0001.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0002.imageset │ │ │ │ │ ├── Flames0002.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0003.imageset │ │ │ │ │ ├── Flames0003.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0004.imageset │ │ │ │ │ ├── Flames0004.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0005.imageset │ │ │ │ │ ├── Flames0005.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0006.imageset │ │ │ │ │ ├── Flames0006.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0007.imageset │ │ │ │ │ ├── Flames0007.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0008.imageset │ │ │ │ │ ├── Flames0008.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0009.imageset │ │ │ │ │ ├── Flames0009.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0010.imageset │ │ │ │ │ ├── Flames0010.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0011.imageset │ │ │ │ │ ├── Flames0011.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0012.imageset │ │ │ │ │ ├── Flames0012.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0013.imageset │ │ │ │ │ ├── Flames0013.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0014.imageset │ │ │ │ │ ├── Flames0014.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0015.imageset │ │ │ │ │ ├── Flames0015.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0016.imageset │ │ │ │ │ ├── Flames0016.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0017.imageset │ │ │ │ │ ├── Flames0017.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0018.imageset │ │ │ │ │ ├── Flames0018.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0019.imageset │ │ │ │ │ ├── Flames0019.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0020.imageset │ │ │ │ │ ├── Flames0020.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0021.imageset │ │ │ │ │ ├── Flames0021.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0022.imageset │ │ │ │ │ ├── Flames0022.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0023.imageset │ │ │ │ │ ├── Flames0023.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0024.imageset │ │ │ │ │ ├── Flames0024.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0025.imageset │ │ │ │ │ ├── Flames0025.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0026.imageset │ │ │ │ │ ├── Flames0026.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0027.imageset │ │ │ │ │ ├── Flames0027.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0028.imageset │ │ │ │ │ ├── Flames0028.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0029.imageset │ │ │ │ │ ├── Flames0029.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0030.imageset │ │ │ │ │ ├── Flames0030.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0031.imageset │ │ │ │ │ ├── Flames0031.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0032.imageset │ │ │ │ │ ├── Flames0032.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0033.imageset │ │ │ │ │ ├── Flames0033.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0034.imageset │ │ │ │ │ ├── Flames0034.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0035.imageset │ │ │ │ │ ├── Flames0035.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0036.imageset │ │ │ │ │ ├── Flames0036.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0037.imageset │ │ │ │ │ ├── Flames0037.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0038.imageset │ │ │ │ │ ├── Flames0038.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0039.imageset │ │ │ │ │ ├── Flames0039.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0040.imageset │ │ │ │ │ ├── Flames0040.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0041.imageset │ │ │ │ │ ├── Flames0041.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0042.imageset │ │ │ │ │ ├── Flames0042.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0043.imageset │ │ │ │ │ ├── Flames0043.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0044.imageset │ │ │ │ │ ├── Flames0044.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0045.imageset │ │ │ │ │ ├── Flames0045.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0046.imageset │ │ │ │ │ ├── Flames0046.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0047.imageset │ │ │ │ │ ├── Flames0047.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0048.imageset │ │ │ │ │ ├── Flames0048.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0049.imageset │ │ │ │ │ ├── Flames0049.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0050.imageset │ │ │ │ │ ├── Flames0050.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0051.imageset │ │ │ │ │ ├── Flames0051.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0052.imageset │ │ │ │ │ ├── Flames0052.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0053.imageset │ │ │ │ │ ├── Flames0053.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0054.imageset │ │ │ │ │ ├── Flames0054.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0055.imageset │ │ │ │ │ ├── Flames0055.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0056.imageset │ │ │ │ │ ├── Flames0056.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0057.imageset │ │ │ │ │ ├── Flames0057.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0058.imageset │ │ │ │ │ ├── Flames0058.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0059.imageset │ │ │ │ │ ├── Flames0059.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0060.imageset │ │ │ │ │ ├── Flames0060.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0061.imageset │ │ │ │ │ ├── Flames0061.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0062.imageset │ │ │ │ │ ├── Flames0062.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0063.imageset │ │ │ │ │ ├── Flames0063.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0064.imageset │ │ │ │ │ ├── Flames0064.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0065.imageset │ │ │ │ │ ├── Flames0065.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0066.imageset │ │ │ │ │ ├── Flames0066.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0067.imageset │ │ │ │ │ ├── Flames0067.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0068.imageset │ │ │ │ │ ├── Flames0068.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0069.imageset │ │ │ │ │ ├── Flames0069.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0070.imageset │ │ │ │ │ ├── Flames0070.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0071.imageset │ │ │ │ │ ├── Flames0071.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0072.imageset │ │ │ │ │ ├── Flames0072.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0073.imageset │ │ │ │ │ ├── Flames0073.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0074.imageset │ │ │ │ │ ├── Flames0074.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0075.imageset │ │ │ │ │ ├── Flames0075.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0076.imageset │ │ │ │ │ ├── Flames0076.png │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Flames0077.imageset │ │ │ │ │ ├── Flames0077.png │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Flames0078.imageset │ │ │ │ │ ├── Flames0078.png │ │ │ │ │ └── Contents.json │ │ │ ├── cover-path-only.svg │ │ │ ├── pea-from-left-path-only.svg │ │ │ ├── potato-path-only.svg │ │ │ ├── carrot-path-only.svg │ │ │ ├── pea-from-right-path-only.svg │ │ │ └── SoupView.xib │ │ ├── PocketSVG │ │ │ ├── PocketSVG+Extension.swift │ │ │ └── PocketSVG.h │ │ └── CAKeyframeAnimation+Extensions.swift │ ├── ProgressPullToRefresh.swift │ ├── PreloadActivityIndicatorView.swift │ └── CustomPullToRefresh.swift ├── API │ ├── IQAPIClient+APIPath.swift │ ├── IQAPIClient+User.swift │ └── Sample Responses │ │ ├── Colors.json │ │ └── Users.json ├── Store │ └── UsersStore.swift ├── Cell │ ├── UserCell.swift │ ├── CollectionViewCell │ │ ├── UserCollectionViewCell.swift │ │ └── UserCollectionViewCell.xib │ └── UserCell.xib ├── Model │ └── User.swift ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── SceneDelegate.swift ├── HorizontalCollectionViewController.swift ├── UsersViewModelController.swift ├── ProgrammaticUsersViewModelController.swift ├── AppDelegate.swift └── UsersViewController.swift ├── Pull_To_Refresh_Demo-Bridging-Header.h ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Pull To Refresh Demo.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── Pull To Refresh Demo.xcscheme ├── Pull To Refresh Demo.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── IQPullToRefresh ├── PrivacyInfo.xcprivacy ├── MainActor+AssumeIsolated.swift ├── RefreshView │ ├── IQRefreshIndicatorView.swift │ ├── IQAnimatableRefresh.swift │ └── UIRefreshControl+IQAnimatableRefresh.swift ├── IQPullToRefresh+LoadMore.swift └── IQPullToRefresh+Refresh.swift ├── .github └── workflows │ └── semgrep.yml ├── Package.swift ├── .travis.yml ├── IQPullToRefresh.podspec.json ├── LICENSE ├── Podfile ├── Podfile.lock └── .gitignore /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - Pods 3 | -------------------------------------------------------------------------------- /Documents/load_more.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Documents/load_more.gif -------------------------------------------------------------------------------- /Documents/custom_pull_to_refresh1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Documents/custom_pull_to_refresh1.gif -------------------------------------------------------------------------------- /Documents/custom_pull_to_refresh2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Documents/custom_pull_to_refresh2.gif -------------------------------------------------------------------------------- /Pull To Refresh Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Documents/pull_to_refresh_load_more.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Documents/pull_to_refresh_load_more.gif -------------------------------------------------------------------------------- /Pull_To_Refresh_Demo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "PocketSVG.h" -------------------------------------------------------------------------------- /Pull To Refresh Demo/Assets.xcassets/arrow.imageset/arrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Assets.xcassets/arrow.imageset/arrow@3x.png -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Pull To Refresh Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/pan.imageset/pan@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/pan.imageset/pan@2x.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/pea.imageset/pea@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/pea.imageset/pea@2x.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/cover.imageset/cover@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/cover.imageset/cover@2x.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/water.imageset/water@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/water.imageset/water@2x.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/carrot.imageset/carrot@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/carrot.imageset/carrot@2x.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/circle.imageset/circle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/circle.imageset/circle@2x.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/potato.imageset/potato@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/potato.imageset/potato@2x.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/shadow.imageset/shadow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/shadow.imageset/shadow@2x.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0001.imageset/Flames0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0001.imageset/Flames0001.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0002.imageset/Flames0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0002.imageset/Flames0002.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0003.imageset/Flames0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0003.imageset/Flames0003.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0004.imageset/Flames0004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0004.imageset/Flames0004.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0005.imageset/Flames0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0005.imageset/Flames0005.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0006.imageset/Flames0006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0006.imageset/Flames0006.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0007.imageset/Flames0007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0007.imageset/Flames0007.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0008.imageset/Flames0008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0008.imageset/Flames0008.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0009.imageset/Flames0009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0009.imageset/Flames0009.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0010.imageset/Flames0010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0010.imageset/Flames0010.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0011.imageset/Flames0011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0011.imageset/Flames0011.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0012.imageset/Flames0012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0012.imageset/Flames0012.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0013.imageset/Flames0013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0013.imageset/Flames0013.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0014.imageset/Flames0014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0014.imageset/Flames0014.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0015.imageset/Flames0015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0015.imageset/Flames0015.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0016.imageset/Flames0016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0016.imageset/Flames0016.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0017.imageset/Flames0017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0017.imageset/Flames0017.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0018.imageset/Flames0018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0018.imageset/Flames0018.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0019.imageset/Flames0019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0019.imageset/Flames0019.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0020.imageset/Flames0020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0020.imageset/Flames0020.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0021.imageset/Flames0021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0021.imageset/Flames0021.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0022.imageset/Flames0022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0022.imageset/Flames0022.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0023.imageset/Flames0023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0023.imageset/Flames0023.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0024.imageset/Flames0024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0024.imageset/Flames0024.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0025.imageset/Flames0025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0025.imageset/Flames0025.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0026.imageset/Flames0026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0026.imageset/Flames0026.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0027.imageset/Flames0027.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0027.imageset/Flames0027.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0028.imageset/Flames0028.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0028.imageset/Flames0028.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0029.imageset/Flames0029.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0029.imageset/Flames0029.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0030.imageset/Flames0030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0030.imageset/Flames0030.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0031.imageset/Flames0031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0031.imageset/Flames0031.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0032.imageset/Flames0032.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0032.imageset/Flames0032.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0033.imageset/Flames0033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0033.imageset/Flames0033.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0034.imageset/Flames0034.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0034.imageset/Flames0034.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0035.imageset/Flames0035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0035.imageset/Flames0035.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0036.imageset/Flames0036.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0036.imageset/Flames0036.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0037.imageset/Flames0037.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0037.imageset/Flames0037.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0038.imageset/Flames0038.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0038.imageset/Flames0038.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0039.imageset/Flames0039.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0039.imageset/Flames0039.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0040.imageset/Flames0040.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0040.imageset/Flames0040.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0041.imageset/Flames0041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0041.imageset/Flames0041.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0042.imageset/Flames0042.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0042.imageset/Flames0042.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0043.imageset/Flames0043.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0043.imageset/Flames0043.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0044.imageset/Flames0044.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0044.imageset/Flames0044.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0045.imageset/Flames0045.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0045.imageset/Flames0045.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0046.imageset/Flames0046.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0046.imageset/Flames0046.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0047.imageset/Flames0047.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0047.imageset/Flames0047.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0048.imageset/Flames0048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0048.imageset/Flames0048.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0049.imageset/Flames0049.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0049.imageset/Flames0049.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0050.imageset/Flames0050.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0050.imageset/Flames0050.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0051.imageset/Flames0051.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0051.imageset/Flames0051.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0052.imageset/Flames0052.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0052.imageset/Flames0052.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0053.imageset/Flames0053.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0053.imageset/Flames0053.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0054.imageset/Flames0054.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0054.imageset/Flames0054.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0055.imageset/Flames0055.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0055.imageset/Flames0055.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0056.imageset/Flames0056.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0056.imageset/Flames0056.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0057.imageset/Flames0057.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0057.imageset/Flames0057.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0058.imageset/Flames0058.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0058.imageset/Flames0058.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0059.imageset/Flames0059.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0059.imageset/Flames0059.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0060.imageset/Flames0060.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0060.imageset/Flames0060.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0061.imageset/Flames0061.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0061.imageset/Flames0061.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0062.imageset/Flames0062.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0062.imageset/Flames0062.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0063.imageset/Flames0063.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0063.imageset/Flames0063.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0064.imageset/Flames0064.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0064.imageset/Flames0064.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0065.imageset/Flames0065.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0065.imageset/Flames0065.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0066.imageset/Flames0066.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0066.imageset/Flames0066.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0067.imageset/Flames0067.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0067.imageset/Flames0067.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0068.imageset/Flames0068.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0068.imageset/Flames0068.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0069.imageset/Flames0069.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0069.imageset/Flames0069.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0070.imageset/Flames0070.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0070.imageset/Flames0070.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0071.imageset/Flames0071.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0071.imageset/Flames0071.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0072.imageset/Flames0072.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0072.imageset/Flames0072.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0073.imageset/Flames0073.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0073.imageset/Flames0073.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0074.imageset/Flames0074.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0074.imageset/Flames0074.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0075.imageset/Flames0075.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0075.imageset/Flames0075.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0076.imageset/Flames0076.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0076.imageset/Flames0076.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0077.imageset/Flames0077.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0077.imageset/Flames0077.png -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0078.imageset/Flames0078.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQPullToRefresh/HEAD/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0078.imageset/Flames0078.png -------------------------------------------------------------------------------- /Pull To Refresh Demo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Pull To Refresh Demo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Pull To Refresh Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /IQPullToRefresh/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | NSPrivacyTracking 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/API/IQAPIClient+APIPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQAPIClient+Configuration.swift 3 | // API Client 4 | // 5 | // Created by Iftekhar on 09/09/20. 6 | // Copyright © 2020 Iftekhar. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IQAPIClient 11 | 12 | internal enum APIPath: String { 13 | case users = "/users" 14 | } 15 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/pan.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "pan@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/pea.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "pea@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/carrot.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "carrot@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "circle@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/cover.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "cover@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/potato.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "potato@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/shadow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "shadow@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/water.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "water@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0001.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0001.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0002.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0002.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0003.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0003.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0004.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0004.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0005.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0005.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0006.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0006.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0007.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0007.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0008.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0008.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0009.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0009.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0010.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0010.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0011.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0011.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0012.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0012.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0013.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0013.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0014.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0014.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0015.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0015.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0016.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0016.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0017.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0017.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0018.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0018.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0019.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0019.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0020.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0020.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0021.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0021.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0022.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0022.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0023.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0023.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0024.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0024.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0025.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0025.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0026.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0026.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0027.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0027.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0028.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0028.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0029.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0029.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0030.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0030.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0031.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0031.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0032.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0032.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0033.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0033.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0034.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0034.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0035.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0035.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0036.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0036.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0037.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0037.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0038.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0038.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0039.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0039.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0040.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0040.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0041.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0041.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0042.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0042.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0043.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0043.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0044.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0044.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0045.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0045.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0046.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0046.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0047.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0047.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0048.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0048.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0049.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0049.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0050.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0050.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0051.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0051.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0052.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0052.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0053.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0053.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0054.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0054.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0055.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0055.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0056.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0056.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0057.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0057.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0058.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0058.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0059.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0059.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0060.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0060.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0061.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0061.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0062.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0062.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0063.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0063.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0064.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0064.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0065.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0065.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0066.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0066.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0067.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0067.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0068.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0068.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0069.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0069.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0070.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0070.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0071.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0071.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0072.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0072.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0073.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0073.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0074.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0074.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0075.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0075.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0076.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0076.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0077.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0077.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/CookingRefresher.xcassets/flames/Flames0078.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Flames0078.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Pull To Refresh Demo/Store/UsersStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UsersStore.swift 3 | // Pull To Refresh Demo 4 | // 5 | // Created by Iftekhar on 1/18/23. 6 | // 7 | 8 | import UIKit 9 | import IQPullToRefresh 10 | import IQAPIClient 11 | 12 | class UsersStore: IQRefreshAbstractWrapper { 13 | 14 | override func request(page: Int, size: Int, completion: @MainActor @escaping (Result<[User], Error>) -> Void) { 15 | IQAPIClient.default.users(page: page, perPage: size, completion: completion) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Assets.xcassets/arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "arrow@3x.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/cover-path-only.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: {} 3 | push: 4 | branches: 5 | - main 6 | - master 7 | paths: 8 | - .github/workflows/semgrep.yml 9 | schedule: 10 | # random HH:MM to avoid a load spike on GitHub Actions at 00:00 11 | - cron: 57 1 * * * 12 | name: Semgrep 13 | jobs: 14 | semgrep: 15 | name: Scan 16 | runs-on: ubuntu-20.04 17 | env: 18 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 19 | container: 20 | image: returntocorp/semgrep 21 | steps: 22 | - uses: actions/checkout@v3 23 | - run: semgrep ci 24 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.6 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "IQPullToRefresh", 7 | platforms: [ 8 | .iOS(.v13) 9 | ], 10 | products: [ 11 | .library( 12 | name: "IQPullToRefresh", 13 | targets: ["IQPullToRefresh"] 14 | ) 15 | ], 16 | targets: [ 17 | .target( 18 | name: "IQPullToRefresh", 19 | path: "IQPullToRefresh", 20 | resources: [ 21 | .copy("PrivacyInfo.xcprivacy") 22 | ] 23 | ) 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/pea-from-left-path-only.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/potato-path-only.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/carrot-path-only.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/pea-from-right-path-only.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - language: swift 4 | branches: 5 | only: 6 | - master 7 | os: osx 8 | osx_image: xcode12 9 | xcode_workspace: Pull To Refresh Demo.xcworkspace 10 | xcode_project: Pull To Refresh Demo.xcodeproj 11 | xcode_scheme: Pull To Refresh Demo 12 | before_install: 13 | - sudo gem install activesupport -v 4.2.6 14 | - rvm install 2.3.1 15 | - rvm use 2.3.1 16 | - sudo gem install cocoapods 17 | script: 18 | - xcodebuild -workspace "Pull To Refresh Demo.xcworkspace" -scheme "Pull To Refresh Demo" -sdk iphonesimulator 19 | -------------------------------------------------------------------------------- /IQPullToRefresh.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IQPullToRefresh", 3 | "version": "3.0.3", 4 | "source": { 5 | "git": "https://github.com/hackiftekhar/IQPullToRefresh.git", 6 | "tag": "3.0.3" 7 | }, 8 | "summary": "Easy pull to refresh and Load more handling with custom implementation support", 9 | "homepage": "https://github.com/hackiftekhar/IQPullToRefresh", 10 | "license": "MIT", 11 | "authors": { 12 | "Iftekhar Qurashi": "hack.iftekhar@gmail.com" 13 | }, 14 | "platforms": { 15 | "ios": "13.0" 16 | }, 17 | "swift_versions": [ 18 | "5.7", 19 | "5.8", 20 | "5.9" 21 | ], 22 | "source_files": "IQPullToRefresh/**/*.{swift}", 23 | "resource_bundles": {"IQPullToRefresh": "IQPullToRefresh/PrivacyInfo.xcprivacy"}, 24 | "frameworks": [ 25 | "UIKit", 26 | "Foundation" 27 | ], 28 | "xcconfig": { 29 | "ENABLE_USER_SCRIPT_SANDBOXING": "NO" 30 | }, 31 | "requires_arc": true 32 | } 33 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Cell/UserCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserCell.swift 3 | // API Client 4 | // 5 | // Created by Iftekhar on 05/02/21. 6 | // Copyright © 2021 Iftekhar. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IQListKit 11 | import AlamofireImage 12 | 13 | class UserCell: UITableViewCell, IQModelableCell { 14 | 15 | @IBOutlet var imageViewProfile: UIImageView? 16 | @IBOutlet var labelName: UILabel? 17 | @IBOutlet var labelEmail: UILabel? 18 | 19 | typealias Model = User 20 | 21 | var model: Model? { 22 | didSet { 23 | guard let model = model else { 24 | return 25 | } 26 | 27 | labelName?.text = model.name 28 | labelEmail?.text = model.email 29 | if let avatar = model.avatar { 30 | imageViewProfile?.af.setImage(withURL: avatar) 31 | } else { 32 | imageViewProfile?.image = nil 33 | } 34 | } 35 | } 36 | 37 | nonisolated static func size(for model: AnyHashable?, listView: IQListView) -> CGSize { 38 | return CGSize(width: 0, height: 80) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mohd Iftekhar Qurashi 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 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Cell/CollectionViewCell/UserCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserCollectionViewCell.swift 3 | // Pull To Refresh Demo 4 | // 5 | // Created by IE Mac 07 on 03/01/24. 6 | // 7 | 8 | import UIKit 9 | import IQListKit 10 | import AlamofireImage 11 | 12 | class UserCollectionViewCell: UICollectionViewCell, IQModelableCell { 13 | 14 | @IBOutlet var imageViewProfile: UIImageView? 15 | 16 | typealias Model = User 17 | 18 | var model: Model? { 19 | didSet { 20 | guard let model = model else { 21 | return 22 | } 23 | if let avatar = model.avatar { 24 | imageViewProfile?.af.setImage(withURL: avatar) 25 | } else { 26 | imageViewProfile?.image = nil 27 | } 28 | } 29 | } 30 | 31 | nonisolated static func estimatedSize(for model: AnyHashable?, listView: IQListView) -> CGSize { 32 | return CGSize(width: 80, height: 80) 33 | } 34 | nonisolated static func size(for model: AnyHashable?, listView: IQListView) -> CGSize { 35 | return CGSize(width: 80, height: 80) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | project 'Pull To Refresh Demo.xcodeproj' 2 | 3 | platform :ios, '13.0' 4 | use_frameworks! 5 | 6 | target 'Pull To Refresh Demo' do 7 | pod 'IQPullToRefresh', :path => '.' 8 | pod 'IQAPIClient' 9 | pod 'SwiftLint' 10 | pod 'IQListKit' 11 | pod 'AlamofireImage' 12 | end 13 | 14 | post_install do |installer| 15 | 16 | installer.pods_project.targets.each do |target| 17 | 18 | target.build_configurations.each do |config| 19 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' 20 | if config.name == 'Debug' 21 | config.build_settings["EXCLUDED_ARCHS[sdk=iphoneos*]"] = "x86_64" 22 | config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" # For apple silicon, it should be "x86_64" 23 | config.build_settings["EXCLUDED_ARCHS[sdk=macosx*]"] = "arm64" # For apple silicon, it should be "x86_64" 24 | end 25 | end 26 | if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle" 27 | target.build_configurations.each do |config| 28 | config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/API/IQAPIClient+User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ITAPiClient+User.swift 3 | // Institute 4 | // 5 | // Created by Iftekhar on 31/05/20. 6 | // Copyright © 2020 Iftekhar. All rights reserved. 7 | // 8 | 9 | import IQAPIClient 10 | import Alamofire 11 | 12 | extension IQAPIClient { 13 | 14 | @discardableResult 15 | func users(page: Int, perPage: Int, 16 | completion: @escaping (_ result: Swift.Result<[User], Error>) -> Void) -> DataRequest { 17 | // let delay = page <= 1 ? 5 : 5 18 | let delay = 2 19 | let parameters = ["delay": delay, "page": page, "per_page": perPage] 20 | let path = APIPath.users.rawValue 21 | return sendRequest(path: path, parameters: parameters, completionHandler: completion) 22 | } 23 | 24 | @discardableResult 25 | func user(id: Int, 26 | completion: @escaping (_ result: Swift.Result) -> Void) -> DataRequest { 27 | let parameters = ["delay": 2, "per_page": 10] 28 | let path = APIPath.users.rawValue + "/\(id)" 29 | return sendRequest(path: path, parameters: parameters, completionHandler: completion) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/API/Sample Responses/Colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "per_page": 6, 4 | "total": 12, 5 | "total_pages": 2, 6 | "data": [ 7 | { 8 | "id": 1, 9 | "name": "cerulean", 10 | "year": 2000, 11 | "color": "#98B2D1", 12 | "pantone_value": "15-4020" 13 | }, 14 | { 15 | "id": 2, 16 | "name": "fuchsia rose", 17 | "year": 2001, 18 | "color": "#C74375", 19 | "pantone_value": "17-2031" 20 | }, 21 | { 22 | "id": 3, 23 | "name": "true red", 24 | "year": 2002, 25 | "color": "#BF1932", 26 | "pantone_value": "19-1664" 27 | }, 28 | { 29 | "id": 4, 30 | "name": "aqua sky", 31 | "year": 2003, 32 | "color": "#7BC4C4", 33 | "pantone_value": "14-4811" 34 | }, 35 | { 36 | "id": 5, 37 | "name": "tigerlily", 38 | "year": 2004, 39 | "color": "#E2583E", 40 | "pantone_value": "17-1456" 41 | }, 42 | { 43 | "id": 6, 44 | "name": "blue turquoise", 45 | "year": 2005, 46 | "color": "#53B0AE", 47 | "pantone_value": "15-5217" 48 | } 49 | ], 50 | "support": { 51 | "url": "https://reqres.in/#support-heading", 52 | "text": "To keep ReqRes free, contributions towards server costs are appreciated!" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/PocketSVG/PocketSVG+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anastasiya Gorban on 4/21/15. 3 | // Copyright (c) 2015 Yalantis. All rights reserved. 4 | // 5 | // Licensed under the MIT license: http://opensource.org/licenses/MIT 6 | // Latest version can be found at https://github.com/Yalantis/PullToMakeSoup 7 | // 8 | 9 | public extension PocketSVG { 10 | 11 | class func pathFromSVGFileNamed( 12 | _ named: String, 13 | origin: CGPoint, 14 | mirrorX: Bool, 15 | mirrorY: Bool, 16 | scale: CGFloat) -> CGPath { 17 | 18 | let path = PocketSVG.path(fromSVGFileNamed: named) 19 | 20 | let bezierPath = UIBezierPath(cgPath: (path?.takeUnretainedValue())!) 21 | 22 | bezierPath.apply(CGAffineTransform(scaleX: scale, y: scale)) 23 | 24 | if mirrorX { 25 | let mirrorOverXOrigin = CGAffineTransform(scaleX: -1.0, y: 1.0) 26 | bezierPath.apply(mirrorOverXOrigin) 27 | } 28 | 29 | if mirrorY { 30 | let mirrorOverYOrigin = CGAffineTransform(scaleX: 1.0, y: -1.0) 31 | bezierPath.apply(mirrorOverYOrigin) 32 | } 33 | 34 | let translate = CGAffineTransform(translationX: origin.x - bezierPath.bounds.origin.x, 35 | y: origin.y - bezierPath.bounds.origin.y) 36 | bezierPath.apply(translate) 37 | 38 | return bezierPath.cgPath 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Model/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // 4 | // Created by Iftekhar on 14/04/20. 5 | // Copyright © 2020 Iftekhar. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | { 12 | "id": 2, 13 | "email": "janet.weaver@reqres.in", 14 | "first_name": "Janet", 15 | "last_name": "Weaver", 16 | "avatar": "https://reqres.in/img/faces/2-image.jpg" 17 | } 18 | */ 19 | struct User: Decodable, Hashable, Sendable { 20 | 21 | func hash(into hasher: inout Hasher) { 22 | hasher.combine(id) 23 | } 24 | 25 | let id: Int 26 | var firstName: String 27 | var lastName: String 28 | var email: String 29 | var avatar: URL? 30 | 31 | var name: String { 32 | return [firstName, lastName].joined(separator: " ") 33 | } 34 | 35 | enum CodingKeys: String, CodingKey { 36 | case id = "id" 37 | case firstName = "first_name" 38 | case lastName = "last_name" 39 | case email = "email" 40 | case avatar = "avatar" 41 | } 42 | 43 | init(from decoder: Decoder) throws { 44 | 45 | let container = try decoder.container(keyedBy: CodingKeys.self) 46 | 47 | id = try container.decode(Int.self, forKey: .id) 48 | firstName = try container.decode(String.self, forKey: .firstName) 49 | lastName = try container.decode(String.self, forKey: .lastName) 50 | email = try container.decode(String.self, forKey: .email) 51 | avatar = try container.decodeIfPresent(URL.self, forKey: .avatar) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/API/Sample Responses/Users.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "per_page": 6, 4 | "total": 12, 5 | "total_pages": 2, 6 | "data": [ 7 | { 8 | "id": 1, 9 | "email": "george.bluth@reqres.in", 10 | "first_name": "George", 11 | "last_name": "Bluth", 12 | "avatar": "https://reqres.in/img/faces/1-image.jpg" 13 | }, 14 | { 15 | "id": 2, 16 | "email": "janet.weaver@reqres.in", 17 | "first_name": "Janet", 18 | "last_name": "Weaver", 19 | "avatar": "https://reqres.in/img/faces/2-image.jpg" 20 | }, 21 | { 22 | "id": 3, 23 | "email": "emma.wong@reqres.in", 24 | "first_name": "Emma", 25 | "last_name": "Wong", 26 | "avatar": "https://reqres.in/img/faces/3-image.jpg" 27 | }, 28 | { 29 | "id": 4, 30 | "email": "eve.holt@reqres.in", 31 | "first_name": "Eve", 32 | "last_name": "Holt", 33 | "avatar": "https://reqres.in/img/faces/4-image.jpg" 34 | }, 35 | { 36 | "id": 5, 37 | "email": "charles.morris@reqres.in", 38 | "first_name": "Charles", 39 | "last_name": "Morris", 40 | "avatar": "https://reqres.in/img/faces/5-image.jpg" 41 | }, 42 | { 43 | "id": 6, 44 | "email": "tracey.ramos@reqres.in", 45 | "first_name": "Tracey", 46 | "last_name": "Ramos", 47 | "avatar": "https://reqres.in/img/faces/6-image.jpg" 48 | } 49 | ], 50 | "support": { 51 | "url": "https://reqres.in/#support-heading", 52 | "text": "To keep ReqRes free, contributions towards server costs are appreciated!" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.8.0) 3 | - AlamofireImage (4.3.0): 4 | - Alamofire (~> 5.8) 5 | - DiffableDataSources (0.4.0): 6 | - DifferenceKit/AppKitExtension (~> 1.1) 7 | - DifferenceKit/UIKitExtension (~> 1.1) 8 | - DifferenceKit/Core (1.3.0) 9 | - DifferenceKit/UIKitExtension (1.3.0): 10 | - DifferenceKit/Core 11 | - IQAPIClient (3.0.1): 12 | - Alamofire (~> 5) 13 | - IQListKit (3.0.0): 14 | - DiffableDataSources (~> 0.4.0) 15 | - SwiftTryCatch (~> 0.0.1) 16 | - IQPullToRefresh (2.3.0) 17 | - SwiftLint (0.52.4) 18 | - SwiftTryCatch (0.0.1) 19 | 20 | DEPENDENCIES: 21 | - AlamofireImage 22 | - IQAPIClient 23 | - IQListKit 24 | - IQPullToRefresh (from `.`) 25 | - SwiftLint 26 | 27 | SPEC REPOS: 28 | trunk: 29 | - Alamofire 30 | - AlamofireImage 31 | - DiffableDataSources 32 | - DifferenceKit 33 | - IQAPIClient 34 | - IQListKit 35 | - SwiftLint 36 | - SwiftTryCatch 37 | 38 | EXTERNAL SOURCES: 39 | IQPullToRefresh: 40 | :path: "." 41 | 42 | SPEC CHECKSUMS: 43 | Alamofire: 0e92e751b3e9e66d7982db43919d01f313b8eb91 44 | AlamofireImage: 843953fa97bee5f561cf05d83abd759e590b068d 45 | DiffableDataSources: 80396cec405c9b29e1e07b55c9be7d11b46c8741 46 | DifferenceKit: ab185c4d7f9cef8af3fcf593e5b387fb81e999ca 47 | IQAPIClient: 99870c81101a8776fee0ed49d6bcf1109b5f4d87 48 | IQListKit: 91f14441e5cd7e74fefbedf6c62ff668e65a44da 49 | IQPullToRefresh: 132db8e25611fb60245e800a1c95cd921aa5b948 50 | SwiftLint: 1cc5cd61ba9bacb2194e340aeb47a2a37fda00b3 51 | SwiftTryCatch: fb6d2b34abe48efd69578dac919293a44f95b481 52 | 53 | PODFILE CHECKSUM: e8095c20c534dfa1f6ec9de62b903662175c42a8 54 | 55 | COCOAPODS: 1.13.0 56 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/ProgressPullToRefresh.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomPullToRefresh.swift 3 | // Pull To Refresh Demo 4 | // 5 | // Created by Iftekhar on 08/02/21. 6 | // 7 | 8 | import UIKit 9 | import IQPullToRefresh 10 | 11 | class ProgressPullToRefresh: UILabel, IQAnimatableRefresh { 12 | 13 | var refreshLength: CGFloat { 14 | return 100 15 | } 16 | 17 | var refreshState: IQAnimatableRefreshState = .unknown { 18 | didSet { 19 | guard refreshState != oldValue else { 20 | return 21 | } 22 | 23 | switch refreshState { 24 | case .unknown, .none: 25 | alpha = 0 26 | case .pulling(let progress): 27 | alpha = progress 28 | text = NSString(format: "Pull progress... %.0f%%", progress*100) as String 29 | progressView.progress = Float(progress) 30 | case .eligible: 31 | alpha = 1 32 | text = "Release to refresh..." 33 | progressView.progress = 1 34 | case .refreshing: 35 | alpha = 1 36 | progressView.progress = 1 37 | text = "Refreshing..." 38 | } 39 | } 40 | } 41 | 42 | let progressView = UIProgressView() 43 | 44 | override init(frame: CGRect) { 45 | super.init(frame: frame) 46 | textAlignment = .center 47 | textColor = UIColor.white 48 | backgroundColor = .systemGreen 49 | self.addSubview(progressView) 50 | alpha = 0 51 | } 52 | 53 | required init?(coder: NSCoder) { 54 | fatalError("init(coder:) has not been implemented") 55 | } 56 | 57 | override func layoutSubviews() { 58 | super.layoutSubviews() 59 | progressView.frame = CGRect(origin: .zero, 60 | size: CGSize(width: self.frame.width, height: progressView.frame.height)) 61 | } 62 | 63 | override var intrinsicContentSize: CGSize { 64 | CGSize(width: 200, height: refreshLength) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /IQPullToRefresh/MainActor+AssumeIsolated.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainActor+AssumeIsolated.swift 3 | // https://github.com/hackiftekhar/IQPullToRefresh 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | import Foundation 24 | 25 | extension MainActor { 26 | // https://forums.swift.org/t/replacement-for-mainactor-unsafe/65956/2 27 | @_unavailableFromAsync 28 | static func assumeIsolatedBackDeployed(_ body: @MainActor () throws -> T) rethrows -> T { 29 | #if swift(>=6) // Xcode 16 and above 30 | if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { 31 | return try assumeIsolated(body) 32 | } 33 | #elseif swift(>=5.9) // Xcode 15 34 | if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { 35 | return try assumeIsolated(body) 36 | } 37 | #endif 38 | // All other Xcodes 39 | dispatchPrecondition(condition: .onQueue(.main)) 40 | return try withoutActuallyEscaping(body) { function in 41 | try unsafeBitCast(function, to: (() throws -> T).self)() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Pull To Refresh Demo 4 | // 5 | // Created by Iftekhar on 08/02/21. 6 | // 7 | 8 | import UIKit 9 | 10 | @available(iOS 13.0, *) 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 16 | options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` 18 | // to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new 21 | // (see `application:configurationForConnectingSceneSession` instead). 22 | } 23 | 24 | func sceneDidDisconnect(_ scene: UIScene) { 25 | // Called as the scene is being released by the system. 26 | // This occurs shortly after the scene enters the background, or when its session is discarded. 27 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 28 | // The scene may re-connect later, as its session was not necessarily discarded 29 | // (see `application:didDiscardSceneSessions` instead). 30 | } 31 | 32 | func sceneDidBecomeActive(_ scene: UIScene) { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | func sceneWillResignActive(_ scene: UIScene) { 38 | // Called when the scene will move from an active state to an inactive state. 39 | // This may occur due to temporary interruptions (ex. an incoming phone call). 40 | } 41 | 42 | func sceneWillEnterForeground(_ scene: UIScene) { 43 | // Called as the scene transitions from the background to the foreground. 44 | // Use this method to undo the changes made on entering the background. 45 | } 46 | 47 | func sceneDidEnterBackground(_ scene: UIScene) { 48 | // Called as the scene transitions from the foreground to the background. 49 | // Use this method to save data, release shared resources, and store enough scene-specific state information 50 | // to restore the scene back to its current state. 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/CAKeyframeAnimation+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anastasiya Gorban on 4/20/15. 3 | // Copyright (c) 2015 Yalantis. All rights reserved. 4 | // 5 | // Licensed under the MIT license: http://opensource.org/licenses/MIT 6 | // Latest version can be found at https://github.com/Yalantis/PullToMakeSoup 7 | // 8 | 9 | import CoreGraphics 10 | import QuartzCore 11 | import UIKit 12 | 13 | enum AnimationType: String { 14 | case rotation = "transform.rotation.z" 15 | case opacity = "opacity" 16 | case translationX = "transform.translation.x" 17 | case translationY = "transform.translation.y" 18 | } 19 | 20 | enum TimingFunction { 21 | case linear, easeIn, easeOut, easeInEaseOut 22 | } 23 | 24 | func mediaTimingFunction(_ function: TimingFunction) -> CAMediaTimingFunction { 25 | switch function { 26 | case .linear: return CAMediaTimingFunction(name: .linear) 27 | case .easeIn: return CAMediaTimingFunction(name: .easeIn) 28 | case .easeOut: return CAMediaTimingFunction(name: .easeOut) 29 | case .easeInEaseOut: return CAMediaTimingFunction(name: .easeInEaseOut) 30 | } 31 | } 32 | 33 | extension CAKeyframeAnimation { 34 | class func animationWith( 35 | _ type: AnimationType, 36 | values: [Double], 37 | keyTimes: [Double], 38 | duration: Double, 39 | beginTime: Double) -> CAKeyframeAnimation { 40 | 41 | let animation = CAKeyframeAnimation(keyPath: type.rawValue) 42 | animation.values = values 43 | animation.keyTimes = keyTimes as [NSNumber]? 44 | animation.duration = duration 45 | animation.beginTime = beginTime 46 | animation.timingFunction = CAMediaTimingFunction(name: .linear) 47 | 48 | return animation 49 | } 50 | 51 | class func animationPosition(_ path: CGPath, duration: Double, 52 | timingFunction: TimingFunction, beginTime: Double) -> CAKeyframeAnimation { 53 | let animation = CAKeyframeAnimation(keyPath: "position") 54 | animation.path = path 55 | animation.duration = duration 56 | animation.beginTime = beginTime 57 | animation.timingFunction = mediaTimingFunction(timingFunction) 58 | return animation 59 | } 60 | } 61 | 62 | extension UIView { 63 | func addAnimation(_ animation: CAKeyframeAnimation) { 64 | layer.add(animation, forKey: description + animation.keyPath!) 65 | layer.speed = 0 66 | } 67 | 68 | func removeAllAnimations() { 69 | layer.removeAllAnimations() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/HorizontalCollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalCollectionViewController.swift 3 | // Pull To Refresh Demo 4 | // 5 | // Created by Iftekhar on 03/01/24. 6 | // 7 | 8 | import UIKit 9 | import IQListKit 10 | import IQPullToRefresh 11 | import IQAPIClient 12 | 13 | class HorizontalCollectionViewController: UIViewController { 14 | 15 | @IBOutlet weak var collectionView: UICollectionView! 16 | typealias Cell = UserCollectionViewCell 17 | 18 | let pageSize = 3 19 | 20 | lazy var list = IQList(listView: collectionView, delegateDataSource: self) 21 | private lazy var usersStore: UsersStore = UsersStore(scrollView: collectionView, 22 | pageOffsetStyle: .pageFrom1, 23 | pageSize: pageSize) 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | refreshUI(animated: false) 28 | collectionView.contentInset = .zero 29 | usersStore.pullToRefresh.refreshControl.mode = .userInteraction 30 | usersStore.pullToRefresh.refreshControl.refreshStyle = .touchRelease 31 | usersStore.pullToRefresh.loadMoreControl.mode = .userInteraction 32 | usersStore.pullToRefresh.loadMoreControl.refreshStyle = .touchRelease 33 | usersStore.pullToRefresh.enablePullToRefresh = true 34 | addObservers() 35 | } 36 | 37 | private func addObservers() { 38 | usersStore.addModelsUpdatedObserver(identifier: "\(Self.self)") { result in 39 | switch result { 40 | case .success: 41 | self.refreshUI(animated: true) 42 | case .failure: 43 | break 44 | } 45 | } 46 | 47 | usersStore.addStateObserver(identifier: "\(Self.self)") { result in 48 | switch result { 49 | case .none: 50 | print("None") 51 | case .refreshing: 52 | print("Refreshing") 53 | case .moreLoading: 54 | print("More Loading") 55 | } 56 | } 57 | } 58 | 59 | override func viewWillAppear(_ animated: Bool) { 60 | super.viewWillAppear(animated) 61 | usersStore.pullToRefresh.refresh() 62 | } 63 | } 64 | 65 | extension HorizontalCollectionViewController: IQListViewDelegateDataSource { 66 | 67 | func refreshUI(animated: Bool = true) { 68 | list.performUpdates({ 69 | let section = IQSection(identifier: 0) 70 | list.append(section) 71 | 72 | list.append(Cell.self, models: usersStore.models, section: section) 73 | }, completion: { [weak self] in 74 | self?.collectionView.bounces = true 75 | }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PreloadActivityIndicatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreloadActivityIndicatorView.swift 3 | // Pull To Refresh Demo 4 | // 5 | // Created by Iftekhar on 18/02/21. 6 | // 7 | 8 | import UIKit 9 | import IQPullToRefresh 10 | 11 | class PreloadActivityIndicatorView: UIActivityIndicatorView, IQAnimatableRefresh { 12 | 13 | public var refreshStyle: IQRefreshTriggerStyle { 14 | return .progressCompletion 15 | } 16 | 17 | var preloadOffset: CGFloat { 18 | return 500 19 | } 20 | 21 | var refreshLength: CGFloat { 22 | return 40 23 | } 24 | 25 | public var refreshState: IQAnimatableRefreshState = .unknown { 26 | didSet { 27 | 28 | guard oldValue != refreshState else { 29 | return 30 | } 31 | 32 | switch refreshState { 33 | case .unknown, .none: 34 | 35 | UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState, 36 | animations: { [weak self] in 37 | self?.alpha = 0 38 | self?.transform = .identity 39 | }, completion: nil) 40 | 41 | if isAnimating { 42 | stopAnimating() 43 | } 44 | 45 | case .pulling(let progress): 46 | 47 | UIView.animate(withDuration: 0.1, delay: 0, options: .beginFromCurrentState, 48 | animations: { [weak self] in 49 | self?.transform = .identity 50 | self?.alpha = progress 51 | }, completion: nil) 52 | 53 | case .eligible: 54 | 55 | UIView.animate(withDuration: 0.1, delay: 0, options: .beginFromCurrentState, 56 | animations: { [weak self] in 57 | self?.transform = .init(scaleX: 1.5, y: 1.5) 58 | self?.alpha = 1 59 | }, completion: nil) 60 | 61 | case .refreshing: 62 | 63 | alpha = 1 64 | 65 | if !isAnimating { 66 | startAnimating() 67 | 68 | UIView.animate(withDuration: 2, delay: 0, options: [.beginFromCurrentState, .curveEaseOut], 69 | animations: { [weak self] in 70 | self?.transform = .init(rotationAngle: CGFloat.pi) 71 | }, completion: nil) 72 | } 73 | } 74 | } 75 | } 76 | 77 | public override init(style: UIActivityIndicatorView.Style) { 78 | super.init(style: style) 79 | hidesWhenStopped = false 80 | } 81 | 82 | required init(coder: NSCoder) { 83 | fatalError("init(coder:) has not been implemented") 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/UsersViewModelController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UsersViewModelController.swift 3 | // Pull To Refresh Demo 4 | // 5 | // Created by Iftekhar on 1/18/23. 6 | // 7 | 8 | import UIKit 9 | import IQAPIClient 10 | import IQPullToRefresh 11 | import IQListKit 12 | 13 | class UsersViewModelController: UITableViewController { 14 | 15 | typealias Cell = UserCell 16 | typealias Model = User 17 | 18 | let pageSize = 3 19 | 20 | @IBOutlet var refreshButton: UIBarButtonItem! 21 | @IBOutlet var loadMoreButton: UIBarButtonItem! 22 | @IBOutlet var clearButton: UIBarButtonItem! 23 | 24 | lazy var list = IQList(listView: tableView, delegateDataSource: self) 25 | private lazy var usersStore: UsersStore = UsersStore(scrollView: tableView, 26 | pageOffsetStyle: .pageFrom1, 27 | pageSize: pageSize) 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | (usersStore.pullToRefresh.loadMoreControl as? UIActivityIndicatorView)?.style = .large 33 | addObservers() 34 | } 35 | 36 | private func addObservers() { 37 | usersStore.addModelsUpdatedObserver(identifier: "\(Self.self)") { result in 38 | switch result { 39 | case .success: 40 | self.refreshUI(animated: true) 41 | case .failure: 42 | break 43 | } 44 | } 45 | 46 | usersStore.addStateObserver(identifier: "\(Self.self)") { result in 47 | switch result { 48 | case .none: 49 | print("None") 50 | case .refreshing: 51 | print("Refreshing") 52 | case .moreLoading: 53 | print("More Loading") 54 | } 55 | } 56 | } 57 | 58 | @IBAction func clearAction(_ sender: UIBarButtonItem) { 59 | usersStore.models = [] 60 | refreshUI() 61 | } 62 | 63 | @IBAction func refreshAction(_ sender: UIBarButtonItem) { 64 | usersStore.pullToRefresh.refresh() 65 | } 66 | 67 | @IBAction func loadMoreAction(_ sender: UIBarButtonItem) { 68 | usersStore.pullToRefresh.loadMore() 69 | } 70 | 71 | @IBAction func stopRefreshAction(_ sender: UIBarButtonItem) { 72 | usersStore.pullToRefresh.stopRefresh() 73 | } 74 | 75 | @IBAction func stopLoadMoreAction(_ sender: UIBarButtonItem) { 76 | usersStore.pullToRefresh.stopLoadMore() 77 | } 78 | } 79 | 80 | extension UsersViewModelController: IQListViewDelegateDataSource { 81 | 82 | func refreshUI(animated: Bool = true) { 83 | list.performUpdates({ 84 | let section = IQSection(identifier: 0) 85 | list.append(section) 86 | 87 | list.append(Cell.self, models: usersStore.models, section: section) 88 | }, completion: { [weak self] in 89 | self?.tableView.bounces = true 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Pull To Refresh Demo.xcodeproj/xcshareddata/xcschemes/Pull To Refresh Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Cell/CollectionViewCell/UserCollectionViewCell.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /IQPullToRefresh/RefreshView/IQRefreshIndicatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQRefreshIndicatorView.swift 3 | // https://github.com/hackiftekhar/IQPullToRefresh 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | import UIKit 24 | 25 | @MainActor 26 | public class IQRefreshIndicatorView: UIActivityIndicatorView, IQAnimatableRefresh { 27 | 28 | public var mode: IQRefreshTriggerMode = .scrollLimitReached 29 | public var refreshStyle: IQRefreshTriggerStyle = .progressCompletion 30 | 31 | public var refreshLength: CGFloat { 32 | return 60 33 | } 34 | 35 | public var refreshState: IQAnimatableRefreshState = .unknown { 36 | didSet { 37 | 38 | guard oldValue != refreshState else { 39 | return 40 | } 41 | 42 | switch refreshState { 43 | case .unknown, .none: 44 | 45 | UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState, 46 | animations: { [weak self] in 47 | self?.alpha = 0 48 | self?.transform = .identity 49 | }, completion: nil) 50 | 51 | if isAnimating { 52 | stopAnimating() 53 | } 54 | 55 | case .pulling(let progress): 56 | 57 | UIView.animate(withDuration: 0.1, delay: 0, options: .beginFromCurrentState, 58 | animations: { [weak self] in 59 | self?.transform = .identity 60 | self?.alpha = progress 61 | }, completion: nil) 62 | 63 | case .eligible: 64 | 65 | UIView.animate(withDuration: 0.1, delay: 0, options: .beginFromCurrentState, 66 | animations: { [weak self] in 67 | self?.transform = .init(scaleX: 1.5, y: 1.5) 68 | self?.alpha = 1 69 | }, completion: nil) 70 | 71 | case .refreshing: 72 | 73 | alpha = 1 74 | 75 | if !isAnimating { 76 | startAnimating() 77 | 78 | UIView.animate(withDuration: 2, delay: 0, options: [.beginFromCurrentState, .curveEaseOut], 79 | animations: { [weak self] in 80 | self?.transform = .init(rotationAngle: CGFloat.pi) 81 | }, completion: nil) 82 | } 83 | } 84 | } 85 | } 86 | 87 | public override init(style: UIActivityIndicatorView.Style) { 88 | super.init(style: style) 89 | hidesWhenStopped = false 90 | } 91 | 92 | required init(coder: NSCoder) { 93 | fatalError("init(coder:) has not been implemented") 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /IQPullToRefresh/IQPullToRefresh+LoadMore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQPullToRefresh+LoadMore.swift 3 | // https://github.com/hackiftekhar/IQPullToRefresh 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | import UIKit 24 | 25 | @MainActor 26 | public protocol MoreLoadable: AnyObject { 27 | 28 | func loadMoreTriggered(type: IQPullToRefresh.LoadMoreType, 29 | loadingBegin: @Sendable @escaping @MainActor (_ success: Bool) -> Void, 30 | loadingFinished: @Sendable @escaping @MainActor (_ success: Bool) -> Void) 31 | } 32 | 33 | @MainActor 34 | public extension IQPullToRefresh { 35 | 36 | enum LoadMoreType: Sendable { 37 | case manual 38 | case reachAtEnd 39 | } 40 | 41 | var isMoreLoading: Bool { 42 | loadMoreControl.refreshState == .refreshing 43 | } 44 | 45 | func loadMore() { 46 | triggerSafeLoadMore(type: .manual) 47 | } 48 | 49 | func stopLoadMore() { 50 | endLoadMoreAnimation() 51 | } 52 | 53 | internal func beginLoadMoreAnimation() { 54 | 55 | guard !isMoreLoading else { 56 | return 57 | } 58 | 59 | var contentInset: UIEdgeInsets = scrollView.contentInset 60 | if scrollDirection == .horizontal { 61 | contentInset.right += loadMoreControl.refreshLength 62 | } else { 63 | contentInset.bottom += loadMoreControl.refreshLength 64 | } 65 | 66 | loadMoreControl.refreshState = .refreshing 67 | 68 | UIView.animate(withDuration: 0.1, delay: 0, options: .beginFromCurrentState, animations: { [weak self] in 69 | self?.scrollView.contentInset = contentInset 70 | self?.scrollView.layoutIfNeeded() 71 | }, completion: nil) 72 | } 73 | 74 | internal func endLoadMoreAnimation() { 75 | 76 | guard isMoreLoading else { 77 | return 78 | } 79 | 80 | var contentInset: UIEdgeInsets = scrollView.contentInset 81 | if scrollDirection == .horizontal { 82 | contentInset.right -= loadMoreControl.refreshLength 83 | } else { 84 | contentInset.bottom -= loadMoreControl.refreshLength 85 | } 86 | 87 | UIView.animate(withDuration: 0.1, delay: 0, options: .beginFromCurrentState, animations: { [weak self] in 88 | self?.scrollView.contentInset = contentInset 89 | self?.scrollView.layoutIfNeeded() 90 | }, completion: nil) 91 | 92 | loadMoreControl.refreshState = .none 93 | } 94 | 95 | internal func triggerSafeLoadMore(type: LoadMoreType) { 96 | 97 | if enableLoadMore, !isMoreLoading || type == .reachAtEnd, let moreLoader = moreLoader { 98 | 99 | moreLoader.loadMoreTriggered(type: type, loadingBegin: { [weak self] (success) in 100 | if success { 101 | if self?.isMoreLoading == false { 102 | self?.beginLoadMoreAnimation() 103 | } 104 | } else { 105 | self?.endLoadMoreAnimation() 106 | } 107 | }, loadingFinished: { [weak self] _ in 108 | self?.endLoadMoreAnimation() 109 | }) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/CustomPullToRefresh.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomPullToRefresh.swift 3 | // Pull To Refresh Demo 4 | // 5 | // Created by Iftekhar on 10/02/21. 6 | // 7 | 8 | import UIKit 9 | import IQPullToRefresh 10 | 11 | class CustomPullToRefresh: UIView, IQAnimatableRefresh { 12 | 13 | var refreshLength: CGFloat { 14 | return 80 15 | } 16 | 17 | var refreshState: IQAnimatableRefreshState = .unknown { 18 | didSet { 19 | guard refreshState != oldValue else { 20 | return 21 | } 22 | 23 | switch refreshState { 24 | case .unknown, .none: 25 | let frame = self.frame 26 | UIView.animate(withDuration: 0.2, animations: { [weak self] in 27 | self?.alpha = 0 28 | self?.transform = .init(translationX: 0, y: -frame.height) 29 | }, completion: nil) 30 | activityIndicatorView.stopAnimating() 31 | case .pulling(let progress): 32 | let frame = self.frame 33 | UIView.animate(withDuration: 0.2, animations: { [weak self] in 34 | self?.alpha = 1 35 | self?.transform = .init(translationX: 0, y: (frame.height * progress) - frame.height) 36 | 37 | let color = UIColor.systemRed 38 | 39 | self?.imageView.tintColor = color 40 | self?.imageView.isHidden = false 41 | self?.imageView.transform = .init(rotationAngle: CGFloat.pi * progress) 42 | 43 | self?.label.text = "Pull to refresh" 44 | self?.label.textColor = color 45 | 46 | }, completion: nil) 47 | case .eligible: 48 | UIView.animate(withDuration: 0.2, animations: { [weak self] in 49 | self?.alpha = 1 50 | self?.transform = .identity 51 | 52 | let color = UIColor.systemBlue 53 | 54 | self?.imageView.tintColor = color 55 | self?.imageView.isHidden = false 56 | self?.imageView.transform = .init(rotationAngle: .pi) 57 | 58 | self?.label.text = "Release to refresh" 59 | self?.label.textColor = color 60 | 61 | }, completion: nil) 62 | 63 | case .refreshing: 64 | UIView.animate(withDuration: 0.2, animations: { [weak self] in 65 | self?.alpha = 1 66 | self?.transform = .identity 67 | 68 | let color = UIColor.systemGreen 69 | 70 | self?.activityIndicatorView.color = color 71 | 72 | self?.imageView.isHidden = true 73 | self?.imageView.tintColor = color 74 | 75 | self?.label.text = "Loading" 76 | self?.label.textColor = color 77 | 78 | }, completion: nil) 79 | 80 | activityIndicatorView.startAnimating() 81 | } 82 | } 83 | } 84 | 85 | let imageView = UIImageView(image: UIImage(named: "arrow")) 86 | let label = UILabel() 87 | let activityIndicatorView = UIActivityIndicatorView(style: .medium) 88 | let stackView = UIStackView() 89 | override init(frame: CGRect) { 90 | super.init(frame: frame) 91 | 92 | stackView.axis = .vertical 93 | stackView.spacing = 4 94 | stackView.alignment = .center 95 | stackView.addArrangedSubview(imageView) 96 | stackView.addArrangedSubview(activityIndicatorView) 97 | stackView.addArrangedSubview(label) 98 | stackView.translatesAutoresizingMaskIntoConstraints = false 99 | self.addSubview(stackView) 100 | 101 | stackView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true 102 | stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true 103 | 104 | alpha = 0 105 | } 106 | 107 | required init(coder: NSCoder) { 108 | fatalError("init(coder:) has not been implemented") 109 | } 110 | 111 | override var intrinsicContentSize: CGSize { 112 | var intrinsicContentSize = super.intrinsicContentSize 113 | intrinsicContentSize.height = refreshLength 114 | return intrinsicContentSize 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/PocketSVG/PocketSVG.h: -------------------------------------------------------------------------------- 1 | // 2 | // PocketSVG.h 3 | // 4 | // Copyright (c) 2013 Ponderwell, Ariel Elkin, and Contributors 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #import 26 | 27 | @interface PocketSVG : NSObject { 28 | @private 29 | float pathScale; 30 | #if TARGET_OS_IPHONE 31 | UIBezierPath *bezier; 32 | #else 33 | NSBezierPath *bezier; 34 | #endif 35 | CGPoint lastPoint; 36 | CGPoint lastControlPoint; 37 | BOOL validLastControlPoint; 38 | NSCharacterSet *separatorSet; 39 | NSCharacterSet *commandSet; 40 | 41 | NSMutableArray *tokens; 42 | } 43 | #if TARGET_OS_IPHONE 44 | @property(nonatomic, readonly) UIBezierPath *bezier; 45 | #else 46 | @property(nonatomic, readonly) NSBezierPath *bezier; 47 | #endif 48 | 49 | 50 | /*! 51 | * Returns a CGPathRef corresponding to the path represented by a local SVG file's d attribute. 52 | * 53 | * @param nameOfSVG The name of the SVG file. The methods looks for a SVG with the specified in the application's main bundle. 54 | * 55 | * @return A CGPathRef object for the SVG in the specified file, or nil if the object could not be found or could not be parsed. 56 | */ 57 | + (CGPathRef)pathFromSVGFileNamed:(NSString *)nameOfSVG; 58 | 59 | /*! 60 | * Returns a CGPathRef corresponding to the path represented by a local SVG file's D attribute 61 | * 62 | * @param svgFileURL The URL to the file. 63 | * 64 | * @return A CGPathRef object for the SVG in the specified file, or nil if the object could not be found or could not be parsed. 65 | */ 66 | + (CGPathRef)pathFromSVGFileAtURL:(NSURL *)svgFileURL; 67 | 68 | /*! 69 | * Returns a CGPathRef corresponding to the path represented by a string with SVG formatted contents. 70 | * 71 | * @param svgString The string containing the SVG formatted path. 72 | * 73 | * @return A CGPathRef object for the SVG in the string, or nil if no path is found or the string could not be parsed. 74 | */ 75 | + (CGPathRef)pathFromSVGString:(NSString *)svgString; 76 | 77 | /*! 78 | * Returns a CGPathRef corresponding to the path represented by a string with the contents of the d attribute of a path node in an SVG file. 79 | * 80 | * @param dAttribute The string containing the d attribute with the path. 81 | * 82 | * @return A CGPathRef object for the path in the string, or nil if no path is found or the string could not be parsed. 83 | */ 84 | + (CGPathRef)pathFromDAttribute:(NSString *)dAttribute; 85 | 86 | 87 | /*! 88 | * Returns a PocketSVG object initialized with nameOfSVG 89 | * 90 | * @param nameOfSVG The name of the SVG file. The methods looks for a SVG with the specified in the application's main bundle. 91 | * 92 | * @return The PocketSVG object for the specified file, or nil if the object could not be found or could not be parsed. 93 | */ 94 | - (instancetype)initFromSVGFileNamed:(NSString *)nameOfSVG __attribute__((deprecated)); 95 | 96 | /*! 97 | * Returns a PocketSVG object initialized with svgFileURL 98 | * 99 | * @param svgFileURL The URL to the file. 100 | * 101 | * @return The PocketSVG object for the specified file, or nil if the object could not be found or could not be parsed. 102 | */ 103 | - (instancetype)initWithURL:(NSURL *)svgFileURL __attribute__((deprecated)); 104 | 105 | 106 | 107 | #if !TARGET_OS_IPHONE 108 | + (CGPathRef)getCGPathFromNSBezierPath:(NSBezierPath *)quartzPath; 109 | #endif 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/ProgrammaticUsersViewModelController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgrammaticUsersViewModelController.swift 3 | // Pull To Refresh Demo 4 | // 5 | // Created by Iftekhar on 5/16/23. 6 | // 7 | 8 | import UIKit 9 | 10 | import IQAPIClient 11 | import IQPullToRefresh 12 | import IQListKit 13 | 14 | class ProgrammaticUsersViewModelController: UIViewController { 15 | 16 | typealias Cell = UserCell 17 | typealias Model = User 18 | 19 | let pageSize = 3 20 | 21 | let tableView = UITableView() 22 | @IBOutlet var loadMoreButton: UIBarButtonItem! 23 | @IBOutlet var clearButton: UIBarButtonItem! 24 | 25 | lazy var list = IQList(listView: tableView, delegateDataSource: self) 26 | private lazy var usersStore: UsersStore = UsersStore(scrollView: tableView, 27 | pageOffsetStyle: .pageFrom1, 28 | pageSize: pageSize) 29 | 30 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 31 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 32 | usersStore.pullToRefresh.refresh() 33 | } 34 | 35 | required init?(coder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | 39 | override func viewDidLoad() { 40 | super.viewDidLoad() 41 | 42 | view.addSubview(tableView) 43 | tableView.translatesAutoresizingMaskIntoConstraints = false 44 | NSLayoutConstraint.activate([ 45 | tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), 46 | tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 47 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 48 | tableView.topAnchor.constraint(equalTo: view.topAnchor) 49 | ]) 50 | 51 | let searchBar = UISearchBar() 52 | searchBar.sizeToFit() 53 | let headerView = UIView(frame: searchBar.bounds) 54 | headerView.addSubview(searchBar) 55 | self.tableView.tableHeaderView = headerView 56 | 57 | tableView.separatorStyle = .none 58 | tableView.allowsSelection = true 59 | tableView.rowHeight = UITableView.automaticDimension 60 | tableView.estimatedRowHeight = 116 61 | tableView.estimatedSectionFooterHeight = 1 62 | tableView.estimatedSectionFooterHeight = 1 63 | tableView.bounces = true 64 | tableView.alwaysBounceVertical = true 65 | 66 | if #available(iOS 15.0, *) { 67 | tableView.sectionHeaderTopPadding = 0 68 | } 69 | 70 | (usersStore.pullToRefresh.loadMoreControl as? UIActivityIndicatorView)?.style = .large 71 | addObservers() 72 | } 73 | 74 | private func addObservers() { 75 | usersStore.addModelsUpdatedObserver(identifier: "\(Self.self)") { result in 76 | switch result { 77 | case .success: 78 | self.refreshUI(animated: true) 79 | case .failure: 80 | break 81 | } 82 | } 83 | 84 | usersStore.addStateObserver(identifier: "\(Self.self)") { result in 85 | switch result { 86 | case .none: 87 | print("None") 88 | case .refreshing: 89 | print("Refreshing") 90 | case .moreLoading: 91 | print("More Loading") 92 | } 93 | } 94 | } 95 | 96 | @IBAction func clearAction(_ sender: UIBarButtonItem) { 97 | usersStore.models = [] 98 | refreshUI() 99 | } 100 | 101 | @IBAction func refreshAction(_ sender: UIBarButtonItem) { 102 | usersStore.pullToRefresh.refresh() 103 | } 104 | 105 | @IBAction func loadMoreAction(_ sender: UIBarButtonItem) { 106 | usersStore.pullToRefresh.loadMore() 107 | } 108 | 109 | @IBAction func stopRefreshAction(_ sender: UIBarButtonItem) { 110 | usersStore.pullToRefresh.stopRefresh() 111 | } 112 | 113 | @IBAction func stopLoadMoreAction(_ sender: UIBarButtonItem) { 114 | usersStore.pullToRefresh.stopLoadMore() 115 | } 116 | } 117 | 118 | extension ProgrammaticUsersViewModelController: IQListViewDelegateDataSource { 119 | 120 | func refreshUI(animated: Bool = true) { 121 | list.performUpdates({ 122 | let section = IQSection(identifier: 0) 123 | list.append(section) 124 | 125 | list.append(Cell.self, models: usersStore.models, section: section) 126 | }, completion: { [weak self] in 127 | self?.tableView.bounces = true 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /IQPullToRefresh/RefreshView/IQAnimatableRefresh.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQAnimatableRefresh.swift 3 | // https://github.com/hackiftekhar/IQPullToRefresh 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | import UIKit 24 | 25 | public enum IQAnimatableRefreshState: Equatable, Sendable { 26 | case unknown // Unknown state for initialization 27 | case none // refreshController is not active 28 | case pulling(CGFloat) // Pulling the refreshControl 29 | case eligible // Progress is completed but touch not released 30 | case refreshing // Triggered refreshing 31 | } 32 | 33 | @objc public enum IQRefreshTriggerMode: Int, Sendable { 34 | 35 | case userInteraction // Trigger when user manually pull (load more) 36 | 37 | case scrollLimitReached // Trigger when the scrollView reach at the end (load more) 38 | } 39 | 40 | @objc public enum IQRefreshTriggerStyle: Int, Equatable, Sendable { 41 | 42 | case touchRelease // Trigger when user pull 100% and then release touch 43 | 44 | case progressCompletion // Trigger when user pull 100% 45 | } 46 | 47 | @MainActor 48 | public protocol IQAnimatableRefresh where Self: UIView { 49 | 50 | // Default is userInteraction 51 | @MainActor 52 | var mode: IQRefreshTriggerMode { get set } 53 | 54 | // Default is touchRelease 55 | @MainActor 56 | var refreshStyle: IQRefreshTriggerStyle { get set } 57 | 58 | // Default is 0 59 | @MainActor 60 | var preloadOffset: CGFloat { get set } 61 | 62 | // Height of the refreshControl 63 | @MainActor 64 | @available(*, deprecated, message: "use 'refreshLength' instead") 65 | var refreshHeight: CGFloat { get } 66 | 67 | @MainActor 68 | var refreshLength: CGFloat { get } 69 | 70 | // Changes of the refreshControl state 71 | // You must implement didSet and do your UI updates based on the state 72 | @MainActor 73 | var refreshState: IQAnimatableRefreshState { get set } 74 | } 75 | 76 | public extension IQAnimatableRefresh { 77 | var refreshHeight: CGFloat { refreshLength } 78 | } 79 | 80 | @MainActor 81 | private struct AssociatedKeys { 82 | static var mode: Int = 0 83 | static var refreshStyle: Int = 0 84 | static var preloadOffset: Int = 0 85 | } 86 | 87 | @MainActor 88 | extension IQAnimatableRefresh { 89 | 90 | public var mode: IQRefreshTriggerMode { 91 | get { 92 | return objc_getAssociatedObject(self, &AssociatedKeys.mode) as? IQRefreshTriggerMode ?? .userInteraction 93 | } 94 | set { 95 | objc_setAssociatedObject(self, &AssociatedKeys.mode, newValue, 96 | objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 97 | } 98 | } 99 | 100 | public var refreshStyle: IQRefreshTriggerStyle { 101 | get { 102 | if let value = objc_getAssociatedObject(self, &AssociatedKeys.refreshStyle) as? IQRefreshTriggerStyle { 103 | return value 104 | } else { 105 | return .touchRelease 106 | } 107 | } 108 | set { 109 | objc_setAssociatedObject(self, &AssociatedKeys.refreshStyle, newValue, 110 | objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 111 | } 112 | } 113 | 114 | public var preloadOffset: CGFloat { 115 | get { 116 | return objc_getAssociatedObject(self, &AssociatedKeys.preloadOffset) as? CGFloat ?? 0 117 | } 118 | set { 119 | objc_setAssociatedObject(self, &AssociatedKeys.preloadOffset, newValue, 120 | objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /IQPullToRefresh/IQPullToRefresh+Refresh.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQPullToRefresh+Refresh.swift 3 | // https://github.com/hackiftekhar/IQPullToRefresh 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | import UIKit 24 | 25 | @MainActor 26 | public protocol Refreshable: AnyObject { 27 | 28 | @MainActor 29 | func refreshTriggered(type: IQPullToRefresh.RefreshType, 30 | loadingBegin: @Sendable @escaping @MainActor (_ success: Bool) -> Void, 31 | loadingFinished: @Sendable @escaping @MainActor (_ success: Bool) -> Void) 32 | } 33 | 34 | @MainActor 35 | public extension IQPullToRefresh { 36 | 37 | enum RefreshType: Sendable { 38 | case manual 39 | case refreshControl 40 | } 41 | 42 | var isRefreshing: Bool { 43 | refreshControl.refreshState == .refreshing 44 | } 45 | 46 | func refresh() { 47 | triggerSafeRefresh(type: .manual) 48 | } 49 | 50 | func stopRefresh() { 51 | endPullToRefreshAnimation() 52 | } 53 | 54 | internal func beginPullToRefreshAnimation() { 55 | 56 | guard !isRefreshing else { 57 | return 58 | } 59 | 60 | if let refreshControl: UIRefreshControl = refreshControl as? UIRefreshControl { 61 | refreshControl.refreshState = .refreshing 62 | } else { 63 | var contentInset: UIEdgeInsets = scrollView.contentInset 64 | if scrollDirection == .horizontal { 65 | contentInset.left += refreshControl.refreshLength 66 | } else { 67 | contentInset.top += refreshControl.refreshLength 68 | } 69 | 70 | refreshControl.refreshState = .refreshing 71 | 72 | UIView.animate(withDuration: 0.1, delay: 0, options: .beginFromCurrentState, animations: { [weak self] in 73 | self?.scrollView.contentInset = contentInset 74 | self?.scrollView.layoutIfNeeded() 75 | }, completion: nil) 76 | } 77 | } 78 | 79 | internal func endPullToRefreshAnimation() { 80 | 81 | guard isRefreshing else { 82 | return 83 | } 84 | 85 | if let refreshControl: UIRefreshControl = refreshControl as? UIRefreshControl { 86 | refreshControl.refreshState = .none 87 | } else { 88 | var contentInset: UIEdgeInsets = scrollView.contentInset 89 | if scrollDirection == .horizontal { 90 | contentInset.left -= refreshControl.refreshLength 91 | } else { 92 | contentInset.top -= refreshControl.refreshLength 93 | } 94 | 95 | UIView.animate(withDuration: 0.1, delay: 0, options: .beginFromCurrentState, animations: { [weak self] in 96 | self?.scrollView.contentInset = contentInset 97 | self?.scrollView.layoutIfNeeded() 98 | }, completion: nil) 99 | 100 | refreshControl.refreshState = .none 101 | } 102 | } 103 | 104 | internal func triggerSafeRefresh(type: RefreshType) { 105 | 106 | if enablePullToRefresh, !isRefreshing || type == .refreshControl, let refresher = refresher { 107 | refresher.refreshTriggered(type: type, loadingBegin: { [weak self] (success) in 108 | if success { 109 | if self?.isRefreshing == false { 110 | self?.beginPullToRefreshAnimation() 111 | } 112 | } else { 113 | self?.endPullToRefreshAnimation() 114 | } 115 | }, loadingFinished: { [weak self] _ in 116 | self?.endPullToRefreshAnimation() 117 | }) 118 | } 119 | } 120 | 121 | @objc internal func refreshControlDidRefresh() { 122 | triggerSafeRefresh(type: .refreshControl) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /IQPullToRefresh/RefreshView/UIRefreshControl+IQAnimatableRefresh.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIRefreshControl+IQAnimatableRefresh.swift 3 | // https://github.com/hackiftekhar/IQPullToRefresh 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | import UIKit 24 | 25 | @MainActor 26 | extension UIRefreshControl: IQAnimatableRefresh { 27 | 28 | @MainActor 29 | private struct AssociatedKeys { 30 | static var state: Int = 0 31 | static var mode: Int = 0 32 | static var style: Int = 0 33 | } 34 | 35 | public var mode: IQRefreshTriggerMode { 36 | get { 37 | return objc_getAssociatedObject(self, &AssociatedKeys.mode) as? IQRefreshTriggerMode ?? .userInteraction 38 | } 39 | set { 40 | objc_setAssociatedObject(self, &AssociatedKeys.mode, newValue, 41 | objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 42 | } 43 | } 44 | public var refreshStyle: IQRefreshTriggerStyle { 45 | get { 46 | guard let value = objc_getAssociatedObject(self, &AssociatedKeys.style) as? IQRefreshTriggerStyle else { 47 | return .progressCompletion 48 | } 49 | return value 50 | } 51 | set { 52 | objc_setAssociatedObject(self, &AssociatedKeys.style, newValue, 53 | objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 54 | } 55 | } 56 | 57 | public var refreshLength: CGFloat { 58 | return 170 59 | } 60 | 61 | public var refreshState: IQAnimatableRefreshState { 62 | get { 63 | return objc_getAssociatedObject(self, &AssociatedKeys.state) as? IQAnimatableRefreshState ?? .unknown 64 | } 65 | set { 66 | let oldValue = refreshState 67 | 68 | guard oldValue != newValue else { 69 | return 70 | } 71 | 72 | objc_setAssociatedObject(self, &AssociatedKeys.state, newValue, 73 | objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 74 | guard window != nil else { return } 75 | switch newValue { 76 | case .unknown, .none: 77 | if isRefreshing { 78 | endRefreshing() 79 | self.superview?.layoutIfNeeded() 80 | } 81 | case .pulling, .eligible: 82 | break 83 | case .refreshing: 84 | if !isRefreshing { 85 | beginRefreshing() 86 | self.superview?.layoutIfNeeded() 87 | } 88 | } 89 | } 90 | } 91 | 92 | open override func didMoveToWindow() { 93 | super.didMoveToWindow() 94 | if self.window != nil { 95 | switch refreshState { 96 | case .unknown, .none: 97 | endRefreshing() 98 | self.superview?.layoutIfNeeded() 99 | case .pulling, .eligible: 100 | break 101 | case .refreshing: 102 | // When we attach the UIRefreshControl to a UITableView which is not yet added 103 | // to the window hierarchy. For example when we create UITableView programmatically 104 | // but haven't added to the UI or haven't moved to that particular screen yet but we 105 | // triggered the refresh event programmatically. 106 | // In this case we had a bug when we move to that screen later, the refresh animation 107 | // doesn't show and the refresh indicator shows as stopped. To fix this bug we 108 | // end refreshing and begin refreshing again to restart loading indicator animation. 109 | endRefreshing() 110 | beginRefreshing() 111 | self.superview?.layoutIfNeeded() 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Pull To Refresh Demo 4 | // 5 | // Created by Iftekhar on 08/02/21. 6 | // 7 | 8 | import UIKit 9 | import IQAPIClient 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, 15 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | configureAPIClient() 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | @available(iOS 13.0, *) 24 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, 25 | options: UIScene.ConnectionOptions) -> UISceneConfiguration { 26 | // Called when a new scene session is being created. 27 | // Use this method to select a configuration to create the new scene with. 28 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 29 | } 30 | 31 | @available(iOS 13.0, *) 32 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 33 | // Called when the user discards a scene session. 34 | // If any sessions were discarded while the application was not running, 35 | // this will be called shortly after application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | } 39 | 40 | extension AppDelegate { 41 | 42 | // swiftlint:disable function_body_length 43 | func configureAPIClient() { 44 | 45 | func topViewController() -> UIViewController? { 46 | var parentController = UIApplication.shared.windows.first { $0.isKeyWindow }?.rootViewController 47 | while parentController != nil, let newParent = parentController?.presentedViewController { 48 | parentController = newParent 49 | } 50 | return parentController 51 | } 52 | 53 | IQAPIClient.default.baseURL = URL(string: "https://reqres.in/api") 54 | IQAPIClient.default.httpHeaders["Content-Type"] = "application/json" 55 | IQAPIClient.default.httpHeaders["Accept"] = "application/json" 56 | IQAPIClient.default.debuggingEnabled = false 57 | 58 | // Common error handler block is common for all requests, 59 | // so we could just write UIAlertController presentation logic 60 | // at single place for showing error from any API response. 61 | IQAPIClient.default.commonErrorHandlerBlock = { (_, _, _, error) in 62 | 63 | switch (error as NSError).code { 64 | case NSURLClientError.unauthorized401.rawValue: 65 | 66 | let window: UIWindow? 67 | #if swift(>=5.1) 68 | if #available(iOS 13, *) { 69 | window = UIApplication.shared.connectedScenes.compactMap { 70 | $0 as? UIWindowScene 71 | }.flatMap { 72 | $0.windows 73 | }.first(where: { 74 | $0.isKeyWindow 75 | }) 76 | } else { 77 | window = UIApplication.shared.keyWindow 78 | } 79 | #else 80 | window = UIApplication.shared.keyWindow 81 | #endif 82 | 83 | window?.rootViewController?.dismiss(animated: true, completion: nil) 84 | 85 | default: 86 | let alertController = UIAlertController(title: "Error!", 87 | message: error.localizedDescription, preferredStyle: .alert) 88 | alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 89 | topViewController()?.present(alertController, animated: true, completion: nil) 90 | } 91 | } 92 | 93 | IQAPIClient.default.responseModifierBlock = { (_, response) in 94 | 95 | let unintendedResponseMessage: String = IQAPIClient.default.unintentedResponseErrorMessage 96 | guard let response = response as? [String: Any] else { 97 | let error = NSError(domain: "ServerError", code: NSURLErrorBadServerResponse, 98 | userInfo: [NSLocalizedDescriptionKey: unintendedResponseMessage]) 99 | return .error(error) 100 | } 101 | 102 | if let data = response["data"] as? [String: Any] { 103 | if data.count == 0 { 104 | let error = NSError(domain: "ServerError", code: NSURLClientError.notFound404.rawValue, 105 | userInfo: [NSLocalizedDescriptionKey: "Record does not exist"]) 106 | return .failure(error) 107 | } else { 108 | return .success(data) 109 | } 110 | } else if let data = response["data"] as? [[String: Any]] { 111 | return .success(data) 112 | } else { 113 | let error = NSError(domain: "ServerError", code: NSURLErrorBadServerResponse, 114 | userInfo: [NSLocalizedDescriptionKey: unintendedResponseMessage]) 115 | return .error(error) 116 | } 117 | } 118 | } 119 | // swiftlint:enable function_body_length 120 | } 121 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Cell/UserCell.xib: -------------------------------------------------------------------------------- 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 | 41 | 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 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/UsersViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UsersViewController.swift 3 | // RefreshLoadMore 4 | // 5 | // Created by iftekhar on 07/02/21. 6 | // 7 | 8 | import UIKit 9 | import IQAPIClient 10 | import IQListKit 11 | import IQPullToRefresh 12 | 13 | class UsersViewController: UITableViewController { 14 | 15 | typealias Cell = UserCell 16 | typealias Model = User 17 | 18 | let pageSize = 3 19 | 20 | @IBOutlet var refreshButton: UIBarButtonItem! 21 | @IBOutlet var loadMoreButton: UIBarButtonItem! 22 | @IBOutlet var clearButton: UIBarButtonItem! 23 | var models = [Model]() 24 | 25 | lazy var list = IQList(listView: tableView, delegateDataSource: self) 26 | lazy var refresher = IQPullToRefresh(scrollView: tableView, refresher: self, moreLoader: self) 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | refresher.refresh() 31 | 32 | // if var viewControllers = self.tabBarController?.viewControllers { 33 | // let programmaticallyVC = ProgrammaticUsersViewModelController() 34 | // let navController = UINavigationController(rootViewController: programmaticallyVC) 35 | // navController.tabBarItem.title = "Programmatically" 36 | // viewControllers.insert(navController, at: 0) 37 | // self.tabBarController?.viewControllers = viewControllers 38 | // } 39 | // tableView.contentInset = UIEdgeInsets(top: 100, left: 0, bottom: 100, right: 0) 40 | 41 | refresher.enablePullToRefresh = true 42 | 43 | // let customPullToRefresh = CustomPullToRefresh() 44 | // refresher.refreshControl = customPullToRefresh 45 | 46 | // let soupPullToRefresh = SoupView.soapView() 47 | // refresher.refreshControl = soupPullToRefresh 48 | 49 | // let customPullToRefresh = ProgressPullToRefresh() 50 | // refresher.refreshControl = customPullToRefresh 51 | 52 | // let customLoadMore = ProgressPullToRefresh() 53 | // refresher.loadMoreControl = customLoadMore 54 | 55 | // let customLoadMore = PreloadActivityIndicatorView(style: .gray) 56 | // refresher.loadMoreControl = customLoadMore 57 | 58 | // let newRefreshIndicatorView = UIActivityIndicatorView(style: .whiteLarge) 59 | // newRefreshIndicatorView.hidesWhenStopped = false 60 | // newRefreshIndicatorView.color = UIColor.green 61 | // refresher.refreshControl = newRefreshIndicatorView 62 | 63 | // let newLoadMoreIndicatorView = UIActivityIndicatorView(style: .whiteLarge) 64 | // newLoadMoreIndicatorView.hidesWhenStopped = false 65 | // newLoadMoreIndicatorView.color = UIColor.purple 66 | // refresher.loadMoreControl = newLoadMoreIndicatorView 67 | } 68 | 69 | @IBAction func clearAction(_ sender: UIBarButtonItem) { 70 | models = [] 71 | refresher.enableLoadMore = false 72 | refreshUI() 73 | } 74 | 75 | @IBAction func refreshAction(_ sender: UIBarButtonItem) { 76 | refresher.refresh() 77 | } 78 | 79 | @IBAction func loadMoreAction(_ sender: UIBarButtonItem) { 80 | refresher.loadMore() 81 | } 82 | 83 | @IBAction func stopRefreshAction(_ sender: UIBarButtonItem) { 84 | refresher.stopRefresh() 85 | } 86 | 87 | @IBAction func stopLoadMoreAction(_ sender: UIBarButtonItem) { 88 | refresher.stopLoadMore() 89 | } 90 | } 91 | 92 | // Refresher 93 | extension UsersViewController: Refreshable, MoreLoadable { 94 | 95 | func refreshTriggered(type: IQPullToRefresh.RefreshType, 96 | loadingBegin: @escaping @MainActor (Bool) -> Void, 97 | loadingFinished: @escaping @MainActor (Bool) -> Void) { 98 | 99 | refresher.enableLoadMore = false 100 | loadingBegin(true) 101 | 102 | IQAPIClient.default.users(page: 1, perPage: pageSize, completion: { [weak self] result in 103 | guard let self = self else { 104 | return 105 | } 106 | 107 | let isReallyRefreshing: Bool = self.refresher.enablePullToRefresh && self.refresher.isRefreshing 108 | 109 | loadingFinished(true) 110 | 111 | guard isReallyRefreshing else { 112 | return 113 | } 114 | 115 | switch result { 116 | case .success(let models): 117 | self.models = models 118 | let gotAllRecords = models.count == self.pageSize 119 | self.refresher.enableLoadMore = gotAllRecords 120 | 121 | self.refreshUI() 122 | case .failure: 123 | break 124 | } 125 | }) 126 | } 127 | 128 | func loadMoreTriggered(type: IQPullToRefresh.LoadMoreType, 129 | loadingBegin: @escaping @MainActor (Bool) -> Void, 130 | loadingFinished: @escaping @MainActor (Bool) -> Void) { 131 | 132 | // If it's not multiple of 10 then probably we've loaded all records 133 | guard models.count.isMultiple(of: pageSize) else { 134 | loadingBegin(false) 135 | return 136 | } 137 | 138 | loadingBegin(true) 139 | 140 | let page = (models.count / pageSize) + 1 141 | 142 | IQAPIClient.default.users(page: page, perPage: pageSize, completion: { [weak self] result in 143 | guard let self = self else { 144 | return 145 | } 146 | 147 | let isReallyMoreLoading: Bool = self.refresher.enableLoadMore && self.refresher.isMoreLoading 148 | 149 | loadingFinished(true) 150 | 151 | guard isReallyMoreLoading else { 152 | return 153 | } 154 | 155 | switch result { 156 | case .success(let models): 157 | 158 | var allModels = self.models + models 159 | 160 | // Removing duplicate elements 161 | do { 162 | var seen = Set() 163 | 164 | allModels = allModels.compactMap { element -> Model? in 165 | guard !seen.contains(element) else { 166 | return nil 167 | } 168 | 169 | seen.insert(element) 170 | return element 171 | } 172 | } 173 | 174 | self.models = allModels 175 | 176 | let gotAllRecords = models.count == self.pageSize 177 | self.refresher.enableLoadMore = gotAllRecords 178 | 179 | self.refreshUI() 180 | case .failure: 181 | break 182 | } 183 | }) 184 | } 185 | } 186 | 187 | extension UsersViewController: IQListViewDelegateDataSource { 188 | 189 | func refreshUI(animated: Bool = true) { 190 | list.performUpdates({ 191 | let section = IQSection(identifier: 0) 192 | list.append(section) 193 | 194 | list.append(Cell.self, models: models, section: section) 195 | }, completion: { [weak self] in 196 | self?.tableView.bounces = true 197 | }) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/SoupView.xib: -------------------------------------------------------------------------------- 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 | --------------------------------------------------------------------------------