Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 220fc4016d | |||
| ee94dd8aec | |||
| be2aa4f4ab | |||
| 55f16d9d51 | |||
| 9fccaf4fbd | |||
| 58959a5f9b | |||
| 5838f7881b | |||
| 41173471f6 |
@@ -0,0 +1,64 @@
|
||||
name: CD
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
release_version:
|
||||
if: github.event.pull_request.milestone == null && github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup fastlane
|
||||
run: brew install fastlane
|
||||
|
||||
- name: Publish release
|
||||
id: publish_release
|
||||
uses: release-drafter/release-drafter@v5
|
||||
with:
|
||||
publish: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update podspec
|
||||
run: fastlane bump_version next_version:${{ steps.publish_release.outputs.tag_name }}
|
||||
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
branch: 'main'
|
||||
commit_message: 'Bump version ${{ steps.publish_release.outputs.tag_name }}'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Cocoapods
|
||||
env:
|
||||
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
|
||||
run: |
|
||||
set -eo pipefail
|
||||
pod lib lint --allow-warnings
|
||||
pod trunk push --allow-warnings
|
||||
|
||||
- name: Communicate on PR released
|
||||
uses: unsplash/comment-on-pr@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
msg: |
|
||||
Congratulations! 🎉 This was released as part of [SkeletonView ${{ steps.publish_release.outputs.tag_name }}](${{ steps.publish_release.outputs.html_url }}) 🚀
|
||||
|
||||
- name: Tweet the release
|
||||
uses: ethomson/send-tweet-action@v1
|
||||
with:
|
||||
consumer-key: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
|
||||
consumer-secret: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
|
||||
access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
|
||||
access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
|
||||
status: |
|
||||
🎉 New release ${{ steps.publish_release.outputs.tag_name }} is out 🚀
|
||||
|
||||
Check out all the changes here:
|
||||
${{ steps.publish_release.outputs.html_url }}
|
||||
@@ -174,6 +174,12 @@ extension ViewController: SkeletonCollectionViewDataSource {
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return 10
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as? CollectionViewCell
|
||||
cell?.isSkeletonable = indexPath.row != 0
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDataSource
|
||||
|
||||
@@ -184,6 +190,10 @@ extension ViewController: SkeletonCollectionViewDataSource {
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
|
||||
return cell
|
||||
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) {
|
||||
let cell = cell as? CollectionViewCell
|
||||
cell?.isSkeletonable = indexPath.row != 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +183,17 @@ extension ViewController: SkeletonTableViewDataSource {
|
||||
cell.label1.text = "cell -> \(indexPath.row)"
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? {
|
||||
let cell = skeletonView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as? Cell
|
||||
cell?.textField.isHidden = indexPath.row == 0
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) {
|
||||
let cell = cell as? Cell
|
||||
cell?.textField.isHidden = indexPath.row == 0
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: SkeletonTableViewDelegate {
|
||||
|
||||
@@ -169,19 +169,16 @@ If you want to show the skeleton in a ```UITableView```, you need to conform to
|
||||
|
||||
``` swift
|
||||
public protocol SkeletonTableViewDataSource: UITableViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int // Default: 1
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? // Default: nil
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath)
|
||||
}
|
||||
```
|
||||
As you can see, this protocol inherits from ```UITableViewDataSource```, so you can replace this protocol with the skeleton protocol.
|
||||
|
||||
This protocol has a default implementation:
|
||||
|
||||
``` swift
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int
|
||||
// Default: 1
|
||||
```
|
||||
This protocol has a default implementation for some methods. For example, the number of rows for each section is calculated in runtime:
|
||||
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
@@ -194,17 +191,30 @@ func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection s
|
||||
> If you return `UITableView.automaticNumberOfSkeletonRows` in the above method, it acts like the default behavior (i.e. it calculates how many cells needed to populate the whole tableview).
|
||||
|
||||
There is only one method you need to implement to let Skeleton know the cell identifier. This method doesn't have default implementation:
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
```
|
||||
|
||||
**Example**
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
|
||||
return "CellIdentifier"
|
||||
}
|
||||
```
|
||||
|
||||
By default, the library dequeues the cells from each indexPath, but you can also do this if you want to make some changes before the skeleton appears:
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? {
|
||||
let cell = skeletonView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as? Cell
|
||||
cell?.textField.isHidden = indexPath.row == 0
|
||||
return cell
|
||||
}
|
||||
```
|
||||
|
||||
If you prefer to leave the deque part to the library you can configure the cell using this method:
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) {
|
||||
let cell = cell as? Cell
|
||||
cell?.textField.isHidden = indexPath.row == 0
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Besides, you can skeletonize both the headers and footers. You need to conform to `SkeletonTableViewDelegate` protocol.
|
||||
|
||||
```swift
|
||||
@@ -232,10 +242,12 @@ For `UICollectionView`, you need to conform to `SkeletonCollectionViewDataSource
|
||||
|
||||
``` swift
|
||||
public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int // default: 1
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int // default: 1
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? // default: nil
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? // default: nil
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -506,7 +518,7 @@ view.isUserInteractionDisabledWhenSkeletonIsActive = false // The view will be
|
||||
|
||||
**Delayed show skeleton**
|
||||
|
||||
You can delay the showIf the views are updated quickly you canAn optional delay applied to the transition, like the transition duration.
|
||||
You can delay the presentation of the skeleton if the views update quickly.
|
||||
|
||||
```swift
|
||||
func showSkeleton(usingColor: UIColor,
|
||||
@@ -553,9 +565,12 @@ Then, when the skeleton appears, you can see the view hierarchy in the Xcode con
|
||||
* tvOS 9.0+
|
||||
* Swift 5
|
||||
|
||||
## :sweat_smile: Need help
|
||||
If you need help about how to use **SkeletonView**, use [Stack Overflow](https://stackoverflow.com/questions/tagged/skeletonview) and tag `skeletonview`.
|
||||
|
||||
## ❤️ Contributing
|
||||
This is an open source project, so feel free to contribute. How?
|
||||
|
||||
- Open an [issue](https://github.com/Juanpe/SkeletonView/issues/new).
|
||||
- Send feedback via [email](mailto://juanpecatalan.com).
|
||||
- Propose your own fixes, suggestions and open a pull request with the changes.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.17.2"
|
||||
s.version = "1.19.0"
|
||||
s.summary = "An elegant way to show users that something is happening and also prepare them to which contents he is waiting"
|
||||
s.description = <<-DESC
|
||||
Today almost all apps have async processes, as API requests, long runing processes, etc. And while the processes are working, usually developers place a loading view to show users that something is going on.
|
||||
|
||||
@@ -13,20 +13,28 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier?
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell?
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath)
|
||||
}
|
||||
|
||||
public extension SkeletonCollectionViewDataSource {
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return UICollectionView.automaticNumberOfSkeletonItems
|
||||
UICollectionView.automaticNumberOfSkeletonItems
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView,
|
||||
supplementaryViewIdentifierOfKind: String,
|
||||
at indexPath: IndexPath) -> ReusableCellIdentifier? {
|
||||
return nil
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? {
|
||||
nil
|
||||
}
|
||||
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int { return 1 }
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int {
|
||||
1
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? {
|
||||
nil
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) { }
|
||||
}
|
||||
|
||||
public protocol SkeletonCollectionViewDelegate: UICollectionViewDelegate { }
|
||||
|
||||
@@ -46,8 +46,17 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cellIdentifier = originalTableViewDataSource?.collectionSkeletonView(tableView, cellIdentifierForRowAt: indexPath) ?? ""
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
|
||||
guard let cell = originalTableViewDataSource?.collectionSkeletonView(tableView, skeletonCellForRowAt: indexPath) else {
|
||||
let cellIdentifier = originalTableViewDataSource?.collectionSkeletonView(tableView, cellIdentifierForRowAt: indexPath) ?? ""
|
||||
let fakeCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
|
||||
|
||||
originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: fakeCell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: fakeCell)
|
||||
|
||||
return fakeCell
|
||||
}
|
||||
|
||||
originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: cell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: cell)
|
||||
return cell
|
||||
}
|
||||
@@ -74,8 +83,17 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cellIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, cellIdentifierForItemAt: indexPath) ?? ""
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
|
||||
guard let cell = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, skeletonCellForItemAt: indexPath) else {
|
||||
let cellIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, cellIdentifierForItemAt: indexPath) ?? ""
|
||||
let fakeCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
|
||||
|
||||
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: fakeCell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: fakeCell)
|
||||
|
||||
return fakeCell
|
||||
}
|
||||
|
||||
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: cell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: cell)
|
||||
return cell
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ public protocol SkeletonTableViewDataSource: UITableViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell?
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath)
|
||||
}
|
||||
|
||||
public extension SkeletonTableViewDataSource {
|
||||
@@ -27,6 +29,12 @@ public extension SkeletonTableViewDataSource {
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdenfierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
|
||||
return collectionSkeletonView(skeletonView, cellIdentifierForRowAt: indexPath)
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? {
|
||||
nil
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) { }
|
||||
}
|
||||
|
||||
public protocol SkeletonTableViewDelegate: UITableViewDelegate {
|
||||
|
||||
@@ -14,6 +14,7 @@ enum ViewAssociatedKeys {
|
||||
static var labelViewState = "labelViewState"
|
||||
static var imageViewState = "imageViewState"
|
||||
static var buttonViewState = "buttonViewState"
|
||||
static var headerFooterViewState = "headerFooterViewState"
|
||||
static var currentSkeletonConfig = "currentSkeletonConfig"
|
||||
static var skeletonCornerRadius = "skeletonCornerRadius"
|
||||
static var disabledWhenSkeletonIsActive = "disabledWhenSkeletonIsActive"
|
||||
|
||||
@@ -121,3 +121,13 @@ extension UIButton {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UITableViewHeaderFooterView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundView?.backgroundColor = .clear
|
||||
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,3 +161,22 @@ extension UIButton {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UITableViewHeaderFooterView {
|
||||
var headerFooterState: RecoverableTableViewHeaderFooterViewState? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.headerFooterViewState) as? RecoverableTableViewHeaderFooterViewState }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.headerFooterViewState) }
|
||||
}
|
||||
|
||||
override func saveViewState() {
|
||||
super.saveViewState()
|
||||
headerFooterState = RecoverableTableViewHeaderFooterViewState(view: self)
|
||||
}
|
||||
|
||||
override func recoverViewState(forced: Bool) {
|
||||
super.recoverViewState(forced: forced)
|
||||
startTransition { [weak self] in
|
||||
self?.backgroundView?.backgroundColor = self?.headerFooterState?.backgroundViewColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,3 +59,11 @@ struct RecoverableButtonViewState {
|
||||
self.title = view.titleLabel?.text
|
||||
}
|
||||
}
|
||||
|
||||
struct RecoverableTableViewHeaderFooterViewState {
|
||||
var backgroundViewColor: UIColor?
|
||||
|
||||
init(view: UITableViewHeaderFooterView) {
|
||||
self.backgroundViewColor = view.backgroundView?.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user