Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae44d9f07a | |||
| 25c47d3029 | |||
| 11c90283ff | |||
| 49fafccd3f | |||
| 1f70ee4573 | |||
| d13369a0dd | |||
| 35d63041d2 | |||
| 4514b509cc | |||
| 795fd7d0f1 | |||
| 6cc6f5aa80 | |||
| 3ecc3c3b39 | |||
| 511535921f | |||
| a6d1ae0b95 | |||
| 220fc4016d | |||
| ee94dd8aec | |||
| be2aa4f4ab | |||
| 55f16d9d51 | |||
| 9fccaf4fbd | |||
| 58959a5f9b | |||
| 5838f7881b | |||
| 41173471f6 | |||
| 12e5688b31 | |||
| 816b2965ff | |||
| e12e4a0fd1 |
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
|
||||
<device id="retina5_9" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@@ -50,7 +50,7 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CJW-A4-Fb8">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CJW-A4-Fb8">
|
||||
<rect key="frame" x="166" y="27" width="166" height="12"/>
|
||||
<color key="backgroundColor" red="0.92156862750000001" green="0.16862745100000001" blue="0.54901960780000003" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="10"/>
|
||||
@@ -89,7 +89,7 @@
|
||||
<color key="separatorColor" red="0.1061807256" green="0.84678786989999999" blue="0.031482450150000001" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="CellIdentifier" rowHeight="120" id="2dN-Bd-tdy" customClass="Cell" customModule="SkeletonViewExample" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="120"/>
|
||||
<rect key="frame" x="0.0" y="24.333333969116211" width="375" height="120"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="2dN-Bd-tdy" id="7IN-F3-Mr6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="120"/>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ GEM
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.6.0)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
atomos (0.1.3)
|
||||
babosa (1.0.2)
|
||||
claide (1.0.2)
|
||||
|
||||
@@ -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">
|
||||
@@ -37,12 +39,12 @@ Enjoy it! 🙂
|
||||
- [🔠 Texts](#-texts)
|
||||
- [🦋 Appearance](#-appearance)
|
||||
- [🎨 Custom colors](#-custom-colors)
|
||||
- [Image captured from website https://flatuicolors.com](#image-captured-from-website-httpsflatuicolorscom)
|
||||
- [🏃♀️ Animations](#️-animations)
|
||||
- [🏄 Transitions](#-transitions)
|
||||
- [✨ Miscellaneous](#-miscellaneous)
|
||||
- [❤️ Contributing](#️-contributing)
|
||||
- [📢 Mentions](#-mentions)
|
||||
- [🏆 Sponsors](#-sponsors)
|
||||
- [👨🏻💻 Author](#-author)
|
||||
- [👮🏻 License](#-license)
|
||||
|
||||
@@ -169,19 +171,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 +193,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 +244,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)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -504,6 +518,24 @@ By default, the user interaction is disabled for skeletonized items, but if you
|
||||
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**
|
||||
|
||||
To facilitate the debug tasks when something is not working fine. **`SkeletonView`** has some new tools.
|
||||
@@ -533,11 +565,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 +595,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.17.1"
|
||||
s.version = "1.21.2"
|
||||
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.
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>FILEHEADER</key>
|
||||
<string> ___COPYRIGHT___</string>
|
||||
<string>
|
||||
// Copyright SkeletonView. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// ___FILENAME___
|
||||
//
|
||||
// Created by ___FULLUSERNAME___ on ___DATE___.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
@@ -38,8 +36,8 @@
|
||||
ReferencedContainer = "container:SkeletonView.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -61,8 +59,6 @@
|
||||
ReferencedContainer = "container:SkeletonView.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -12,6 +12,7 @@ class SkeletonMultilineLayerBuilder {
|
||||
var cornerRadius: Int?
|
||||
var multilineSpacing: CGFloat = SkeletonAppearance.default.multilineSpacing
|
||||
var paddingInsets: UIEdgeInsets = .zero
|
||||
var alignment: NSTextAlignment = .natural
|
||||
var isRTL: Bool = false
|
||||
|
||||
@discardableResult
|
||||
@@ -55,6 +56,12 @@ class SkeletonMultilineLayerBuilder {
|
||||
self.paddingInsets = insets
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setAlignment(_ alignment: NSTextAlignment) -> SkeletonMultilineLayerBuilder {
|
||||
self.alignment = alignment
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setIsRTL(_ isRTL: Bool) -> SkeletonMultilineLayerBuilder {
|
||||
@@ -74,9 +81,11 @@ class SkeletonMultilineLayerBuilder {
|
||||
layer.anchorPoint = .zero
|
||||
layer.name = CALayer.skeletonSubLayersName
|
||||
layer.updateLayerFrame(for: index,
|
||||
totalLines: layer.skeletonSublayers.count,
|
||||
size: CGSize(width: width, height: height),
|
||||
multilineSpacing: multilineSpacing,
|
||||
paddingInsets: paddingInsets,
|
||||
alignment: alignment,
|
||||
isRTL: isRTL)
|
||||
|
||||
layer.cornerRadius = CGFloat(radius)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -89,7 +107,7 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
return view
|
||||
}
|
||||
|
||||
return UICollectionReusableView()
|
||||
return originalCollectionViewDataSource?.collectionView?(collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath) ?? UICollectionReusableView()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -36,6 +36,7 @@ struct SkeletonMultilinesLayerConfig {
|
||||
var multilineCornerRadius: Int
|
||||
var multilineSpacing: CGFloat
|
||||
var paddingInsets: UIEdgeInsets
|
||||
var alignment: NSTextAlignment
|
||||
var isRTL: Bool
|
||||
|
||||
/// Returns padding insets taking into account if the RTL is activated
|
||||
@@ -69,6 +70,7 @@ extension CALayer {
|
||||
.setMultilineSpacing(config.multilineSpacing)
|
||||
.setPadding(config.paddingInsets)
|
||||
.setHeight(height)
|
||||
.setAlignment(config.alignment)
|
||||
.setIsRTL(config.isRTL)
|
||||
|
||||
(0..<numberOfSublayers).forEach { index in
|
||||
@@ -97,10 +99,13 @@ extension CALayer {
|
||||
for (index, layer) in currentSkeletonSublayers.enumerated() {
|
||||
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: lastLineFillPercent, paddingInsets: paddingInsets)
|
||||
layer.updateLayerFrame(for: index,
|
||||
totalLines: numberOfSublayers,
|
||||
size: CGSize(width: width, height: height),
|
||||
multilineSpacing: multilineSpacing,
|
||||
paddingInsets: paddingInsets,
|
||||
isRTL: config.isRTL)
|
||||
alignment: config.alignment,
|
||||
isRTL: config.isRTL
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,14 +117,18 @@ extension CALayer {
|
||||
return width
|
||||
}
|
||||
|
||||
func updateLayerFrame(for index: Int, size: CGSize, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets, isRTL: Bool) {
|
||||
func updateLayerFrame(for index: Int, totalLines: Int, size: CGSize, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets, alignment: NSTextAlignment, isRTL: Bool) {
|
||||
let spaceRequiredForEachLine = size.height + multilineSpacing
|
||||
let newFrame = CGRect(x: paddingInsets.left,
|
||||
y: CGFloat(index) * spaceRequiredForEachLine + paddingInsets.top,
|
||||
width: size.width,
|
||||
height: size.height)
|
||||
|
||||
frame = flipRectForRTLIfNeeded(newFrame, isRTL: isRTL)
|
||||
|
||||
if index == totalLines - 1 && totalLines != 1 {
|
||||
frame = alignLayerFrame(newFrame, alignment: alignment, isRTL: isRTL)
|
||||
} else {
|
||||
frame = newFrame
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateNumLines(for config: SkeletonMultilinesLayerConfig) -> Int {
|
||||
@@ -141,12 +150,22 @@ extension CALayer {
|
||||
|
||||
return calculatedNumberOfLines
|
||||
}
|
||||
|
||||
private func flipRectForRTLIfNeeded(_ rect: CGRect, isRTL: Bool) -> CGRect {
|
||||
|
||||
private func alignLayerFrame(_ rect: CGRect, alignment: NSTextAlignment, isRTL: Bool) -> CGRect {
|
||||
var newRect = rect
|
||||
if isRTL {
|
||||
|
||||
switch alignment {
|
||||
case .natural where isRTL,
|
||||
.right:
|
||||
newRect.origin.x = (superlayer?.bounds.width ?? 0) - rect.origin.x - rect.width
|
||||
case .center:
|
||||
newRect.origin.x = rect.origin.x + ((superlayer?.bounds.width ?? 0) - rect.width) / 2
|
||||
case .natural, .left, .justified:
|
||||
break
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
|
||||
return newRect
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -122,3 +121,13 @@ extension UIButton {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UITableViewHeaderFooterView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundView?.backgroundColor = .clear
|
||||
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ enum MultilineAssociatedKeys {
|
||||
}
|
||||
|
||||
protocol ContainsMultilineText {
|
||||
var constraintHeight: CGFloat? { get }
|
||||
var numLines: Int { get }
|
||||
var lineHeight: CGFloat { get }
|
||||
var numberOfLines: Int { get }
|
||||
var textAlignment: NSTextAlignment { 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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,14 +83,14 @@ 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,
|
||||
multilineSpacing: textView.multilineSpacing,
|
||||
paddingInsets: textView.paddingInsets,
|
||||
alignment: textView.textAlignment,
|
||||
isRTL: holder?.isRTL ?? false)
|
||||
|
||||
maskLayer.addMultilinesLayers(for: config)
|
||||
@@ -98,14 +98,14 @@ 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,
|
||||
multilineSpacing: textView.multilineSpacing,
|
||||
paddingInsets: textView.paddingInsets,
|
||||
alignment: textView.textAlignment,
|
||||
isRTL: holder?.isRTL ?? false)
|
||||
|
||||
maskLayer.updateMultilinesLayers(for: config)
|
||||
@@ -113,7 +113,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
|
||||
}
|
||||
@@ -309,9 +363,9 @@ extension UIView {
|
||||
let skeletonLayer = skeletonLayer,
|
||||
let transitionStyle = currentSkeletonConfig?.transition else { return }
|
||||
skeletonLayer.stopAnimation()
|
||||
status = .off
|
||||
skeletonLayer.removeLayer(transition: transitionStyle) {
|
||||
self.skeletonLayer = nil
|
||||
self.status = .off
|
||||
self.currentSkeletonConfig = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,6 +28,6 @@ fastlane release_current
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
|
||||
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
|
||||
Reference in New Issue
Block a user