├── .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 |
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 |
8 |
--------------------------------------------------------------------------------
/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/potato-path-only.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/carrot-path-only.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/Pull To Refresh Demo/Customization/PullToMakeSoup/Resources/pea-from-right-path-only.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
--------------------------------------------------------------------------------