Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 795fd7d0f1 | |||
| 6cc6f5aa80 | |||
| 3ecc3c3b39 | |||
| 511535921f | |||
| a6d1ae0b95 | |||
| 220fc4016d | |||
| ee94dd8aec | |||
| be2aa4f4ab | |||
| 55f16d9d51 | |||
| 9fccaf4fbd | |||
| 58959a5f9b | |||
| 5838f7881b | |||
| 41173471f6 | |||
| 12e5688b31 | |||
| 816b2965ff | |||
| e12e4a0fd1 | |||
| 6f78f5c378 | |||
| c8fdd6998d | |||
| 134463e529 | |||
| ee59239c59 | |||
| f1e61aa9c0 | |||
| 135778aa1a |
@@ -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 {
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/CI/badge.svg">
|
||||
</a>
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<img src="http://img.shields.io/badge/dependency%20manager-swiftpm%2Bcocoapods%2Bcarthage-green" />
|
||||
<img src="https://img.shields.io/badge/platforms-ios%2Btvos-green" />
|
||||
<a href="https://badge.bow-swift.io/recipe?name=SkeletonView&description=An%20elegant%20way%20to%20show%20users%20that%20something%20is%20happening%20and%20also%20prepare%20them%20to%20which%20contents%20he%20is%20waiting&url=https://github.com/juanpe/skeletonview&owner=Juanpe&avatar=https://avatars0.githubusercontent.com/u/1409041?v=4&tag=1.8.7"><img src="https://raw.githubusercontent.com/bow-swift/bow-art/master/badges/nef-playgrounds-badge.svg" alt="SkeletonView Playground" style="height:20px"></a>
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-main"><img alt="codebeat badge" src="https://codebeat.co/badges/1f37bbab-a1c8-4a4a-94d7-f21740d461e9" /></a>
|
||||
<a href="https://cocoapods.org/pods/SkeletonView"><img src="https://img.shields.io/cocoapods/v/SkeletonView.svg?style=flat"></a>
|
||||
<a href="https://github.com/Carthage/Carthage/"><img src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat"></a>
|
||||
<a href="https://swift.org/package-manager/"><img src="https://img.shields.io/badge/SPM-supported-Green.svg?style=flat"></a>
|
||||
<img src="https://img.shields.io/badge/platforms-iOS_tvOS-green" />
|
||||
<a href="https://badge.bow-swift.io/recipe?name=SkeletonView&description=An%20elegant%20way%20to%20show%20users%20that%20something%20is%20happening%20and%20also%20prepare%20them%20to%20which%20contents%20he%20is%20waiting&url=https://github.com/juanpe/skeletonview&owner=Juanpe&avatar=https://avatars0.githubusercontent.com/u/1409041?v=4&tag=1.20.0"><img src="https://raw.githubusercontent.com/bow-swift/bow-art/master/badges/nef-playgrounds-badge.svg" alt="SkeletonView Playground" style="height:20px"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -43,6 +45,7 @@ Enjoy it! 🙂
|
||||
- [✨ Miscellaneous](#-miscellaneous)
|
||||
- [❤️ Contributing](#️-contributing)
|
||||
- [📢 Mentions](#-mentions)
|
||||
- [🏆 Sponsors](#-sponsors)
|
||||
- [👨🏻💻 Author](#-author)
|
||||
- [👮🏻 License](#-license)
|
||||
|
||||
@@ -169,19 +172,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
|
||||
@@ -191,20 +191,33 @@ func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection s
|
||||
|
||||
> 📣 **IMPORTANT!**
|
||||
>
|
||||
> If you return `SkeletonCollectionDataSource.automaticNumberOfRows` in the above method, it acts like the default behavior (i.e. it calculates how many cells needed to populate the whole tableview).
|
||||
> 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 +245,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)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -498,10 +513,28 @@ view.isHiddenWhenSkeletonIsActive = true // This works only when isSkeletonable
|
||||
**Don't modify user interaction when the skeleton is active**
|
||||
|
||||
|
||||
By default, user interaction is disabled for skeletonized items, but if you don't want to modify the user interaction indicator when skeleton is active, you can use the `isDisableWhenSkeletonIsActive` property:
|
||||
By default, the user interaction is disabled for skeletonized items, but if you don't want to modify the user interaction indicator when skeleton is active, you can use the `isUserInteractionDisabledWhenSkeletonIsActive` property:
|
||||
|
||||
```swift
|
||||
view.isDisableWhenSkeletonIsActive = false // The view will be active when the skeleton will be active.
|
||||
view.isUserInteractionDisabledWhenSkeletonIsActive = false // The view will be active when the skeleton will be active.
|
||||
```
|
||||
|
||||
**Delayed show skeleton**
|
||||
|
||||
You can delay the presentation of the skeleton if the views update quickly.
|
||||
|
||||
```swift
|
||||
func showSkeleton(usingColor: UIColor,
|
||||
animated: Bool,
|
||||
delay: TimeInterval,
|
||||
transition: SkeletonTransitionStyle)
|
||||
```
|
||||
|
||||
```swift
|
||||
func showGradientSkeleton(usingGradient: SkeletonGradient,
|
||||
animated: Bool,
|
||||
delay: TimeInterval,
|
||||
transition: SkeletonTransitionStyle)
|
||||
```
|
||||
|
||||
**Debug**
|
||||
@@ -533,11 +566,11 @@ Then, when the skeleton appears, you can see the view hierarchy in the Xcode con
|
||||
|
||||
* iOS 9.0+
|
||||
* tvOS 9.0+
|
||||
* Swift 5
|
||||
|
||||
* Swift 5.3
|
||||
|
||||
## ❤️ 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.
|
||||
@@ -563,7 +596,12 @@ For more information, please read the [contributing guidelines](https://github.c
|
||||
- [Swift News #36](https://www.youtube.com/watch?v=mAGpsQiy6so)
|
||||
- [Best iOS articles, new tools & more](https://medium.com/flawless-app-stories/best-ios-articles-new-tools-more-fcbe673e10d)
|
||||
|
||||
## 🏆 Sponsors
|
||||
|
||||
Open-source projects cannot live long without your help. If you find **SkeletonView** is useful, please consider supporting this
|
||||
project by becoming a sponsor.
|
||||
|
||||
Become a sponsor through [GitHub Sponsors](https://github.com/sponsors/Juanpe) :heart:
|
||||
|
||||
## 👨🏻💻 Author
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.16.0"
|
||||
s.version = "1.20.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.
|
||||
|
||||
@@ -31,14 +31,14 @@ extension CollectionSkeleton where Self: UIScrollView {
|
||||
func removeDummyDataSource(reloadAfter: Bool) {}
|
||||
|
||||
func disableUserInteraction() {
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
isScrollEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
func enableUserInteraction() {
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = true
|
||||
isScrollEnabled = true
|
||||
}
|
||||
|
||||
@@ -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 SkeletonCollectionDataSource.automaticNumberOfRows
|
||||
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 { }
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import UIKit
|
||||
|
||||
extension UICollectionView: CollectionSkeleton {
|
||||
public static let automaticNumberOfSkeletonItems = -1
|
||||
|
||||
var estimatedNumberOfRows: Int {
|
||||
guard let flowlayout = collectionViewLayout as? UICollectionViewFlowLayout else { return 0 }
|
||||
switch flowlayout.scrollDirection {
|
||||
|
||||
@@ -11,8 +11,6 @@ import UIKit
|
||||
public typealias ReusableCellIdentifier = String
|
||||
|
||||
class SkeletonCollectionDataSource: NSObject {
|
||||
static let automaticNumberOfRows = -1
|
||||
|
||||
weak var originalTableViewDataSource: SkeletonTableViewDataSource?
|
||||
weak var originalCollectionViewDataSource: SkeletonCollectionViewDataSource?
|
||||
var rowHeight: CGFloat = 0.0
|
||||
@@ -40,7 +38,7 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
|
||||
|
||||
let numberOfRows = originalTableViewDataSource.collectionSkeletonView(tableView, numberOfRowsInSection: section)
|
||||
|
||||
if numberOfRows == Self.automaticNumberOfRows {
|
||||
if numberOfRows == UITableView.automaticNumberOfSkeletonRows {
|
||||
return tableView.estimatedNumberOfRows
|
||||
} else {
|
||||
return numberOfRows
|
||||
@@ -48,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
|
||||
}
|
||||
@@ -68,7 +75,7 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
|
||||
let numberOfItems = originalCollectionViewDataSource.collectionSkeletonView(collectionView, numberOfItemsInSection: section)
|
||||
|
||||
if numberOfItems == Self.automaticNumberOfRows {
|
||||
if numberOfItems == UICollectionView.automaticNumberOfSkeletonItems {
|
||||
return collectionView.estimatedNumberOfRows
|
||||
} else {
|
||||
return numberOfItems
|
||||
@@ -76,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
|
||||
}
|
||||
@@ -91,7 +107,7 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
return view
|
||||
}
|
||||
|
||||
return UICollectionReusableView()
|
||||
return originalCollectionViewDataSource?.collectionView?(collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath) ?? UICollectionReusableView()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ 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 {
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return SkeletonCollectionDataSource.automaticNumberOfRows
|
||||
return UITableView.automaticNumberOfSkeletonRows
|
||||
}
|
||||
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int { return 1 }
|
||||
@@ -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 {
|
||||
|
||||
@@ -11,6 +11,8 @@ import UIKit
|
||||
public typealias ReusableHeaderFooterIdentifier = String
|
||||
|
||||
extension UITableView: CollectionSkeleton {
|
||||
public static let automaticNumberOfSkeletonRows = -1
|
||||
|
||||
var estimatedNumberOfRows: Int {
|
||||
return Int(ceil(frame.height / rowHeight))
|
||||
}
|
||||
|
||||
@@ -12,9 +12,17 @@ extension UIView {
|
||||
nonContentSizeLayoutConstraints.filter { $0.firstAttribute == NSLayoutConstraint.Attribute.height }
|
||||
}
|
||||
|
||||
var skeletonHeightConstraints: [NSLayoutConstraint] {
|
||||
nonContentSizeLayoutConstraints.filter {
|
||||
$0.firstAttribute == NSLayoutConstraint.Attribute.height
|
||||
&& $0.identifier?.contains("SkeletonView.Constraint.Height") ?? false
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setHeight(equalToConstant constant: CGFloat) -> NSLayoutConstraint {
|
||||
let heightConstraint = heightAnchor.constraint(equalToConstant: constant)
|
||||
heightConstraint.identifier = "SkeletonView.Constraint.Height.\(constant)"
|
||||
NSLayoutConstraint.activate([heightConstraint])
|
||||
return heightConstraint
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ 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 disableWhenSkeletonIsActive = "disableWhenSkeletonIsActive"
|
||||
static var disabledWhenSkeletonIsActive = "disabledWhenSkeletonIsActive"
|
||||
static var delayedShowSkeletonWorkItem = "delayedShowSkeletonWorkItem"
|
||||
}
|
||||
// codebeat:enable[TOO_MANY_IVARS]
|
||||
|
||||
@@ -54,4 +56,9 @@ extension UIView {
|
||||
var isSuperviewAStackView: Bool {
|
||||
superview is UIStackView
|
||||
}
|
||||
|
||||
var delayedShowSkeletonWorkItem: DispatchWorkItem? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.delayedShowSkeletonWorkItem) as? DispatchWorkItem }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.delayedShowSkeletonWorkItem) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ public extension UIView {
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var isDisableWhenSkeletonIsActive: Bool {
|
||||
get { return disableWhenSkeletonIsActive }
|
||||
set { disableWhenSkeletonIsActive = newValue }
|
||||
var isUserInteractionDisabledWhenSkeletonIsActive: Bool {
|
||||
get { return disabledWhenSkeletonIsActive }
|
||||
set { disabledWhenSkeletonIsActive = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
@@ -41,9 +41,9 @@ public extension UIView {
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.hiddenWhenSkeletonIsActive) }
|
||||
}
|
||||
|
||||
private var disableWhenSkeletonIsActive: Bool {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.disableWhenSkeletonIsActive) as? Bool ?? true }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.disableWhenSkeletonIsActive) }
|
||||
private var disabledWhenSkeletonIsActive: Bool {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.disabledWhenSkeletonIsActive) as? Bool ?? true }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.disabledWhenSkeletonIsActive) }
|
||||
}
|
||||
|
||||
private var skeletonableCornerRadius: Float {
|
||||
|
||||
@@ -10,7 +10,7 @@ import UIKit
|
||||
|
||||
extension UIView {
|
||||
@objc func prepareViewForSkeleton() {
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ extension UIView {
|
||||
|
||||
extension UILabel {
|
||||
var desiredHeightBasedOnNumberOfLines: CGFloat {
|
||||
let lineHeight = constraintHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
let spaceNeededForEachLine = lineHeight * CGFloat(numberOfLines)
|
||||
let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(numberOfLines - 1)
|
||||
let padding = paddingInsets.top + paddingInsets.bottom
|
||||
@@ -47,10 +46,7 @@ extension UILabel {
|
||||
}
|
||||
}
|
||||
|
||||
func restoreBackupHeightConstraints() {
|
||||
heightConstraints.forEach {
|
||||
removeConstraint($0)
|
||||
}
|
||||
func restoreBackupHeightConstraintsIfNeeded() {
|
||||
guard !backupHeightConstraints.isEmpty else { return }
|
||||
NSLayoutConstraint.activate(backupHeightConstraints)
|
||||
backupHeightConstraints.removeAll()
|
||||
@@ -59,7 +55,7 @@ extension UILabel {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
@@ -75,7 +71,7 @@ extension UITextView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
@@ -102,7 +98,7 @@ extension UIImageView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
@@ -116,7 +112,7 @@ extension UIButton {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
@@ -125,3 +121,13 @@ extension UIButton {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UITableViewHeaderFooterView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundView?.backgroundColor = .clear
|
||||
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ enum MultilineAssociatedKeys {
|
||||
}
|
||||
|
||||
protocol ContainsMultilineText {
|
||||
var constraintHeight: CGFloat? { get }
|
||||
var numLines: Int { get }
|
||||
var lineHeight: CGFloat { get }
|
||||
var numberOfLines: Int { get }
|
||||
var lastLineFillingPercent: Int { get }
|
||||
var multilineCornerRadius: Int { get }
|
||||
var multilineSpacing: CGFloat { get }
|
||||
|
||||
@@ -27,15 +27,11 @@ public extension UILabel {
|
||||
}
|
||||
}
|
||||
|
||||
extension UILabel: ContainsMultilineText {
|
||||
var constraintHeight: CGFloat? {
|
||||
backupHeightConstraints.first?.constant
|
||||
extension UILabel: ContainsMultilineText {
|
||||
var lineHeight: CGFloat {
|
||||
backupHeightConstraints.first?.constant ?? SkeletonAppearance.default.multilineHeight
|
||||
}
|
||||
|
||||
var numLines: Int {
|
||||
return numberOfLines
|
||||
}
|
||||
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
get { return ao_get(pkey: &MultilineAssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonAppearance.default.multilineLastLineFillPercent }
|
||||
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.lastLineFillingPercent) }
|
||||
|
||||
@@ -28,11 +28,19 @@ public extension UITextView {
|
||||
}
|
||||
|
||||
extension UITextView: ContainsMultilineText {
|
||||
var constraintHeight: CGFloat? {
|
||||
heightConstraints.first?.constant
|
||||
var lineHeight: CGFloat {
|
||||
if let fontLineHeight = font?.lineHeight {
|
||||
if let heightConstraints = heightConstraints.first?.constant {
|
||||
return (fontLineHeight > heightConstraints) ? heightConstraints : fontLineHeight
|
||||
}
|
||||
|
||||
return fontLineHeight
|
||||
}
|
||||
|
||||
return SkeletonAppearance.default.multilineHeight
|
||||
}
|
||||
|
||||
var numLines: Int {
|
||||
var numberOfLines: Int {
|
||||
-1
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ extension UIView: Recoverable {
|
||||
self.layer.cornerRadius = storedViewState.cornerRadius
|
||||
self.layer.masksToBounds = storedViewState.clipToBounds
|
||||
|
||||
if self.isDisableWhenSkeletonIsActive {
|
||||
if self.isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
self.isUserInteractionEnabled = storedViewState.isUserInteractionsEnabled
|
||||
}
|
||||
|
||||
@@ -57,12 +57,16 @@ extension UILabel {
|
||||
override func recoverViewState(forced: Bool) {
|
||||
super.recoverViewState(forced: forced)
|
||||
startTransition { [weak self] in
|
||||
guard let storedLabelState = self?.labelState else { return }
|
||||
guard let self = self,
|
||||
let storedLabelState = self.labelState else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.restoreBackupHeightConstraints()
|
||||
NSLayoutConstraint.deactivate(self.skeletonHeightConstraints)
|
||||
self.restoreBackupHeightConstraintsIfNeeded()
|
||||
|
||||
if self?.textColor == .clear || forced {
|
||||
self?.textColor = storedLabelState.textColor
|
||||
if self.textColor == .clear || forced {
|
||||
self.textColor = storedLabelState.textColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +83,8 @@ struct SkeletonLayer {
|
||||
/// If there is more than one line, or custom preferences have been set for a single line, draw custom layers
|
||||
func addTextLinesIfNeeded() {
|
||||
guard let textView = holderAsTextView else { return }
|
||||
let lineHeight = textView.constraintHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numLines,
|
||||
lineHeight: lineHeight,
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numberOfLines,
|
||||
lineHeight: textView.lineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
multilineCornerRadius: textView.multilineCornerRadius,
|
||||
@@ -98,9 +97,8 @@ struct SkeletonLayer {
|
||||
|
||||
func updateLinesIfNeeded() {
|
||||
guard let textView = holderAsTextView else { return }
|
||||
let lineHeight = textView.constraintHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numLines,
|
||||
lineHeight: lineHeight,
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numberOfLines,
|
||||
lineHeight: textView.lineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
multilineCornerRadius: textView.multilineCornerRadius,
|
||||
@@ -113,7 +111,7 @@ struct SkeletonLayer {
|
||||
|
||||
var holderAsTextView: ContainsMultilineText? {
|
||||
guard let textView = holder as? ContainsMultilineText,
|
||||
(textView.numLines == -1 || textView.numLines == 0 || textView.numLines > 1 || textView.numLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
|
||||
(textView.numberOfLines == -1 || textView.numberOfLines == 0 || textView.numberOfLines > 1 || textView.numberOfLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
|
||||
return nil
|
||||
}
|
||||
return textView
|
||||
|
||||
@@ -9,20 +9,63 @@ public extension UIView {
|
||||
/// - color: The color of the skeleton. Defaults to `SkeletonAppearance.default.tintColor`.
|
||||
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
|
||||
func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
|
||||
delayedShowSkeletonWorkItem?.cancel()
|
||||
let config = SkeletonConfig(type: .solid, colors: [color], transition: transition)
|
||||
showSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
/// Shows the skeleton using the view that calls this method as root view.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - color: The color of the skeleton. Defaults to `SkeletonAppearance.default.tintColor`.
|
||||
/// - animated: If the skeleton is animated or not. Defaults to `true`.
|
||||
/// - delay: The amount of time (measured in seconds) to wait before show the skeleton.
|
||||
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
|
||||
func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animated: Bool = true, delay: TimeInterval, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
|
||||
delayedShowSkeletonWorkItem?.cancel()
|
||||
|
||||
delayedShowSkeletonWorkItem = DispatchWorkItem { [weak self] in
|
||||
let config = SkeletonConfig(type: .solid, colors: [color], animated: animated, transition: transition)
|
||||
self?.showSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: delayedShowSkeletonWorkItem!)
|
||||
}
|
||||
|
||||
/// Shows the gradient skeleton without animation using the view that calls this method as root view.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - gradient: The gradient of the skeleton. Defaults to `SkeletonAppearance.default.gradient`.
|
||||
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
|
||||
func showGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
|
||||
delayedShowSkeletonWorkItem?.cancel()
|
||||
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, transition: transition)
|
||||
showSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
/// Shows the gradient skeleton using the view that calls this method as root view.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - gradient: The gradient of the skeleton. Defaults to `SkeletonAppearance.default.gradient`.
|
||||
/// - animated: If the skeleton is animated or not. Defaults to `true`.
|
||||
/// - delay: The amount of time (measured in seconds) to wait before show the skeleton.
|
||||
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
|
||||
func showGradientSkeleton(
|
||||
usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient,
|
||||
animated: Bool = true,
|
||||
delay: TimeInterval,
|
||||
transition: SkeletonTransitionStyle = .crossDissolve(0.25)
|
||||
) {
|
||||
delayedShowSkeletonWorkItem?.cancel()
|
||||
|
||||
delayedShowSkeletonWorkItem = DispatchWorkItem { [weak self] in
|
||||
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: animated, transition: transition)
|
||||
self?.showSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: delayedShowSkeletonWorkItem!)
|
||||
}
|
||||
|
||||
/// Shows the animated skeleton using the view that calls this method as root view.
|
||||
///
|
||||
/// If animation is nil, sliding animation will be used, with direction left to right.
|
||||
@@ -32,6 +75,7 @@ public extension UIView {
|
||||
/// - animation: The animation of the skeleton. Defaults to `nil`.
|
||||
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
|
||||
func showAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
|
||||
delayedShowSkeletonWorkItem?.cancel()
|
||||
let config = SkeletonConfig(type: .solid, colors: [color], animated: true, animation: animation, transition: transition)
|
||||
showSkeleton(skeletonConfig: config)
|
||||
}
|
||||
@@ -45,6 +89,7 @@ public extension UIView {
|
||||
/// - animation: The animation of the skeleton. Defaults to `nil`.
|
||||
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
|
||||
func showAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
|
||||
delayedShowSkeletonWorkItem?.cancel()
|
||||
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: true, animation: animation, transition: transition)
|
||||
showSkeleton(skeletonConfig: config)
|
||||
}
|
||||
@@ -75,6 +120,7 @@ public extension UIView {
|
||||
}
|
||||
|
||||
func hideSkeleton(reloadDataAfter reload: Bool = true, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
|
||||
delayedShowSkeletonWorkItem?.cancel()
|
||||
flowDelegate?.willBeginHidingSkeletons(rootView: self)
|
||||
recursiveHideSkeleton(reloadDataAfter: reload, transition: transition, root: self)
|
||||
}
|
||||
@@ -277,14 +323,22 @@ extension UIView {
|
||||
.setHolder(self)
|
||||
.build()
|
||||
else { return }
|
||||
|
||||
|
||||
self.skeletonLayer = skeletonLayer
|
||||
layer.insertSublayer(skeletonLayer,
|
||||
at: UInt32.max,
|
||||
transition: config.transition) { [weak self] in
|
||||
if config.animated {
|
||||
self?.startSkeletonAnimation(config.animation)
|
||||
}
|
||||
layer.insertSkeletonLayer(
|
||||
skeletonLayer,
|
||||
atIndex: UInt32.max,
|
||||
transition: config.transition
|
||||
) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
/// Workaround to fix the problem when inserting a sublayer and
|
||||
/// the content offset is modified by the system.
|
||||
(self as? UITextView)?.setContentOffset(.zero, animated: false)
|
||||
|
||||
if config.animated {
|
||||
self.startSkeletonAnimation(config.animation)
|
||||
}
|
||||
}
|
||||
status = .on
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import UIKit
|
||||
|
||||
extension CALayer {
|
||||
func insertSublayer(_ layer: SkeletonLayer, at idx: UInt32, transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
|
||||
insertSublayer(layer.contentLayer, at: idx)
|
||||
func insertSkeletonLayer(_ sublayer: SkeletonLayer, atIndex index: UInt32, transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
|
||||
insertSublayer(sublayer.contentLayer, at: index)
|
||||
switch transition {
|
||||
case .none:
|
||||
completion?()
|
||||
case .crossDissolve(let duration):
|
||||
layer.contentLayer.setOpacity(from: 0, to: 1, duration: duration, completion: completion)
|
||||
sublayer.contentLayer.setOpacity(from: 0, to: 1, duration: duration, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user