Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca7d2edcee | |||
| 3688549eb8 | |||
| f27a4f69ff | |||
| 2ac515b97e | |||
| 969f068480 | |||
| fbc2e6f8d2 | |||
| 8be6c81729 | |||
| 87c4706be8 | |||
| b4d6dc2602 | |||
| f859d197f3 | |||
| 3a11e233c7 | |||
| ddd4fdbb1f | |||
| 00394e0b9f | |||
| daea88df8f | |||
| b640c1d8ee | |||
| 2eaf72ed0e | |||
| 9e61ff3a85 | |||
| 204b871f17 | |||
| ecb41f1885 | |||
| dffbd8cbfd | |||
| 458e5fc29b | |||
| a6f335df01 | |||
| 106692fbfb | |||
| cb4dcfd07a | |||
| 419172104d | |||
| b40a771510 | |||
| 73b137cda6 | |||
| add7401058 |
@@ -1,10 +0,0 @@
|
||||
daysUntilStale: 7
|
||||
daysUntilClose: 7
|
||||
onlyLabels:
|
||||
- awaiting user input
|
||||
staleLabel: given up
|
||||
markComment: >
|
||||
🤖 This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions 🙂
|
||||
closeComment: false
|
||||
@@ -12,8 +12,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup fastlane
|
||||
run: brew install fastlane
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
brew install fastlane
|
||||
brew install cocoapods
|
||||
|
||||
- name: Publish release
|
||||
id: publish_release
|
||||
@@ -33,11 +35,9 @@ jobs:
|
||||
commit_message: 'Bump version ${{ steps.publish_release.outputs.tag_name }}'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Cocoapods
|
||||
run: gem install cocoapods
|
||||
|
||||
- name: Deploy to Cocoapods
|
||||
continue-on-error: true
|
||||
env:
|
||||
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
|
||||
run: |
|
||||
@@ -45,14 +45,6 @@ jobs:
|
||||
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:
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Issue Needs Attention
|
||||
# This workflow is triggered on issue comments.
|
||||
on:
|
||||
issue_comment:
|
||||
types: created
|
||||
|
||||
jobs:
|
||||
applyNeedsAttentionLabel:
|
||||
name: Apply Needs Attention Label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Apply Needs Attention Label
|
||||
uses: hramos/needs-attention@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
response-required-label: 'awaiting user info'
|
||||
needs-attention-label: 'needs triage'
|
||||
@@ -0,0 +1,18 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 5 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v4
|
||||
with:
|
||||
close-issue-message: 'Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.'
|
||||
stale-issue-message: '🤖 This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions 🙂'
|
||||
days-before-stale: 5
|
||||
days-before-close: 3
|
||||
enable-statistics: true
|
||||
operations-per-run: 60
|
||||
only-labels: 'awaiting user input'
|
||||
@@ -2,13 +2,8 @@
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
"platform" : "universal",
|
||||
"reference" : "systemBlueColor"
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ GEM
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.4)
|
||||
cocoapods-downloader (1.2.2)
|
||||
cocoapods-downloader (1.6.3)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.0)
|
||||
@@ -182,7 +182,7 @@ GEM
|
||||
tty-screen (0.7.0)
|
||||
tty-spinner (0.9.1)
|
||||
tty-cursor (~> 0.7)
|
||||
tzinfo (1.2.5)
|
||||
tzinfo (1.2.10)
|
||||
thread_safe (~> 0.1)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// swift-tools-version:5.0
|
||||
// swift-tools-version:5.3
|
||||
|
||||
import PackageDescription
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<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" />
|
||||
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FJuanpe%2FSkeletonView%2Fbadge%3Ftype%3Dplatforms"/>
|
||||
<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>
|
||||
|
||||
@@ -90,6 +90,10 @@ dependencies: [
|
||||
]
|
||||
```
|
||||
|
||||
> 📣 **IMPORTANT!**
|
||||
>
|
||||
> Since version 1.30.0, `SkeletonView` supports **XCFrameworks**, so if you want to install it as a **XCFramework**, please use [this repo](https://github.com/Juanpe/SkeletonView-XCFramework.git) instead.
|
||||
|
||||
|
||||
## 🐒 Usage
|
||||
|
||||
@@ -250,6 +254,7 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
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)
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareViewForSkeleton view: UICollectionReusableView, at indexPath: IndexPath)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -261,7 +266,6 @@ The rest of the process is the same as ```UITableView```
|
||||

|
||||
|
||||
When using elements with text, ```SkeletonView``` draws lines to simulate text.
|
||||
Besides, you can decide how many lines you want. If ```numberOfLines``` is set to zero, it will calculate how many lines needed to populate the whole skeleton and it will be drawn. Instead, if you set it to one, two or any number greater than zero, it will only draw this number of lines.
|
||||
|
||||
You can set some properties for multilines elements.
|
||||
|
||||
@@ -272,6 +276,34 @@ You can set some properties for multilines elements.
|
||||
| **skeletonLineSpacing** | `CGFloat` | `10` | 
|
||||
| **skeletonPaddingInsets** | `UIEdgeInsets` | `.zero` | 
|
||||
| **skeletonTextLineHeight** | `SkeletonTextLineHeight` | `.fixed(15)` | 
|
||||
| **skeletonTextNumberOfLines** | `SkeletonTextNumberOfLines` | `.inherited` | 
|
||||
|
||||
<br />
|
||||
|
||||
To modify the percent or radius **using code**, set the properties:
|
||||
```swift
|
||||
descriptionTextView.lastLineFillPercent = 50
|
||||
descriptionTextView.linesCornerRadius = 5
|
||||
```
|
||||
|
||||
Or, if you prefer use **IB/Storyboard**:
|
||||
|
||||

|
||||
|
||||
<br />
|
||||
|
||||
**How to define the number of lines?**
|
||||
|
||||
|
||||
By default, the number of lines is the same as the value of the `numberOfLines` property. And, if it's set to **zero**, it'll calculate how many lines are needed to populate the whole skeleton and draw it.
|
||||
|
||||
However, if you want to set a specific number of skeleton lines you can do it by setting the `skeletonTextNumberOfLines` property. This property has two possible values, `inherited` which returns `numberOfLines` value and `custom(Int)` which returns the specific number of lines specified as the associated value.
|
||||
|
||||
For example:
|
||||
|
||||
```swift
|
||||
label.skeletonTextNumberOfLines = 3 // .custom(3)
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
@@ -288,18 +320,6 @@ You can set some properties for multilines elements.
|
||||
> as the last line.
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
To modify the percent or radius **using code**, set the properties:
|
||||
```swift
|
||||
descriptionTextView.lastLineFillPercent = 50
|
||||
descriptionTextView.linesCornerRadius = 5
|
||||
```
|
||||
|
||||
Or, if you prefer use **IB/Storyboard**:
|
||||
|
||||

|
||||
|
||||
|
||||
### 🦋 Appearance
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.26.2"
|
||||
s.version = "1.30.1"
|
||||
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.
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
F5225F2A278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */; };
|
||||
F5225F2B278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */; };
|
||||
F53D731826D399E100249D46 /* SkeletonTreeNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */; };
|
||||
F53D731926D399E100249D46 /* SkeletonTreeNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */; };
|
||||
F53D731B26D3A35100249D46 /* SkeletonExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731A26D3A35100249D46 /* SkeletonExtended.swift */; };
|
||||
@@ -151,6 +153,7 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTextNumberOfLines.swift; sourceTree = "<group>"; };
|
||||
F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SkeletonTreeNode+Extensions.swift"; sourceTree = "<group>"; };
|
||||
F53D731A26D3A35100249D46 /* SkeletonExtended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonExtended.swift; sourceTree = "<group>"; };
|
||||
F53D732226D3C3A800249D46 /* UILabel+SKExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+SKExtensions.swift"; sourceTree = "<group>"; };
|
||||
@@ -519,6 +522,7 @@
|
||||
F556F6B826CE262700A80B83 /* GradientDirection.swift */,
|
||||
F556F6C126CE27FD00A80B83 /* SkeletonType.swift */,
|
||||
F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */,
|
||||
F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -834,6 +838,7 @@
|
||||
F556F58A26CD1F3900A80B83 /* SkeletonGradient.swift in Sources */,
|
||||
F556F6C326CE27FD00A80B83 /* SkeletonType.swift in Sources */,
|
||||
F556F58B26CD1F3900A80B83 /* SkeletonLayer.swift in Sources */,
|
||||
F5225F2B278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift in Sources */,
|
||||
F556F6A226CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */,
|
||||
F556F69326CD506C00A80B83 /* Deprecated.swift in Sources */,
|
||||
F556F6BA26CE262700A80B83 /* GradientDirection.swift in Sources */,
|
||||
@@ -917,6 +922,7 @@
|
||||
F556F6C226CE27FD00A80B83 /* SkeletonType.swift in Sources */,
|
||||
OBJ_123 /* SkeletonLayer.swift in Sources */,
|
||||
F556F6E026CE367600A80B83 /* UIView+SkeletonView.swift in Sources */,
|
||||
F5225F2A278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift in Sources */,
|
||||
F556F6A126CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */,
|
||||
F556F69226CD506C00A80B83 /* Deprecated.swift in Sources */,
|
||||
F556F6B926CE262700A80B83 /* GradientDirection.swift in Sources */,
|
||||
|
||||
+3
@@ -15,6 +15,7 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
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)
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareViewForSkeleton view: UICollectionReusableView, at indexPath: IndexPath)
|
||||
}
|
||||
|
||||
public extension SkeletonCollectionViewDataSource {
|
||||
@@ -35,6 +36,8 @@ public extension SkeletonCollectionViewDataSource {
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) { }
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareViewForSkeleton view: UICollectionReusableView, at indexPath: IndexPath) { }
|
||||
}
|
||||
|
||||
public protocol SkeletonCollectionViewDelegate: UICollectionViewDelegate { }
|
||||
|
||||
@@ -29,4 +29,8 @@ public struct SkeletonGradient {
|
||||
}
|
||||
}
|
||||
|
||||
public init(colors: [UIColor]) {
|
||||
self.gradientColors = colors
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// SkeletonTextNumberOfLines.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 10/1/22.
|
||||
|
||||
import UIKit
|
||||
|
||||
public enum SkeletonTextNumberOfLines: Equatable, ExpressibleByIntegerLiteral {
|
||||
|
||||
/// Returns `numberOfLines` value.
|
||||
case inherited
|
||||
|
||||
/// Returns the specific number of lines specified as the associated value.
|
||||
case custom(Int)
|
||||
|
||||
}
|
||||
|
||||
public extension SkeletonTextNumberOfLines {
|
||||
|
||||
init(integerLiteral value: Int) {
|
||||
self = .custom(value)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import UIKit
|
||||
|
||||
public extension UILabel {
|
||||
|
||||
/// Defines the skeleton paddings.
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get {
|
||||
paddingInsets
|
||||
@@ -24,6 +25,8 @@ public extension UILabel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the logic for calculating the height of the skeleton lines.
|
||||
/// Default: `SkeletonAppearance.default.textLineHeight`
|
||||
var skeletonTextLineHeight: SkeletonTextLineHeight {
|
||||
get {
|
||||
textLineHeight
|
||||
@@ -33,4 +36,15 @@ public extension UILabel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the logic for calculating the number of lines of the skeleton.
|
||||
/// Default: `inherited`
|
||||
var skeletonTextNumberOfLines: SkeletonTextNumberOfLines {
|
||||
get {
|
||||
skeletonNumberOfLines
|
||||
}
|
||||
set {
|
||||
skeletonNumberOfLines = newValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import UIKit
|
||||
|
||||
public extension UITextView {
|
||||
|
||||
/// Defines the skeleton paddings.
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get {
|
||||
paddingInsets
|
||||
@@ -24,6 +25,8 @@ public extension UITextView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the logic for calculating the height of the skeleton lines.
|
||||
/// Default: `SkeletonAppearance.default.textLineHeight`
|
||||
var skeletonTextLineHeight: SkeletonTextLineHeight {
|
||||
get {
|
||||
textLineHeight
|
||||
@@ -33,4 +36,15 @@ public extension UITextView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the logic for calculating the number of lines of the skeleton.
|
||||
/// Default: `inherited`
|
||||
var skeletonTextNumberOfLines: SkeletonTextNumberOfLines {
|
||||
get {
|
||||
skeletonNumberOfLines
|
||||
}
|
||||
set {
|
||||
skeletonNumberOfLines = newValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
at indexPath: IndexPath) -> UICollectionReusableView {
|
||||
if let viewIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, supplementaryViewIdentifierOfKind: kind, at: indexPath) {
|
||||
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: viewIdentifier, for: indexPath)
|
||||
|
||||
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareViewForSkeleton: view, at: indexPath)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: view)
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ struct SkeletonLayer {
|
||||
self.maskLayer.bounds = holder.definedMaxBounds
|
||||
self.maskLayer.cornerRadius = CGFloat(holder.skeletonCornerRadius)
|
||||
addTextLinesIfNeeded()
|
||||
self.maskLayer.tint(withColors: colors)
|
||||
self.maskLayer.tint(withColors: colors, traitCollection: holder.traitCollection)
|
||||
}
|
||||
|
||||
func update(usingColors colors: [UIColor]) {
|
||||
layoutIfNeeded()
|
||||
maskLayer.tint(withColors: colors)
|
||||
maskLayer.tint(withColors: colors, traitCollection: holder?.traitCollection)
|
||||
}
|
||||
|
||||
func layoutIfNeeded() {
|
||||
@@ -59,7 +59,7 @@ 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 config = SkeletonMultilinesLayerConfig(lines: textView.numberOfLines,
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.estimatedNumberOfLines,
|
||||
lineHeight: textView.estimatedLineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
@@ -75,7 +75,7 @@ struct SkeletonLayer {
|
||||
|
||||
func updateLinesIfNeeded() {
|
||||
guard let textView = holderAsTextView else { return }
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numberOfLines,
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.estimatedNumberOfLines,
|
||||
lineHeight: textView.estimatedLineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
@@ -91,7 +91,7 @@ struct SkeletonLayer {
|
||||
|
||||
var holderAsTextView: SkeletonTextNode? {
|
||||
guard let textView = holder as? SkeletonTextNode,
|
||||
(textView.numberOfLines == -1 || textView.numberOfLines == 0 || textView.numberOfLines > 1 || textView.numberOfLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
|
||||
(textView.estimatedNumberOfLines == -1 || textView.estimatedNumberOfLines == 0 || textView.estimatedNumberOfLines > 1 || textView.estimatedNumberOfLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
|
||||
return nil
|
||||
}
|
||||
return textView
|
||||
|
||||
@@ -17,7 +17,7 @@ protocol SkeletonTextNode {
|
||||
|
||||
var textLineHeight: SkeletonTextLineHeight { get }
|
||||
var estimatedLineHeight: CGFloat { get }
|
||||
var numberOfLines: Int { get }
|
||||
var estimatedNumberOfLines: Int { get }
|
||||
var textAlignment: NSTextAlignment { get }
|
||||
var lastLineFillingPercent: Int { get }
|
||||
var multilineCornerRadius: Int { get }
|
||||
@@ -35,6 +35,7 @@ enum SkeletonTextNodeAssociatedKeys {
|
||||
static var paddingInsets = "paddingInsets"
|
||||
static var backupHeightConstraints = "backupHeightConstraints"
|
||||
static var textLineHeight = "textLineHeight"
|
||||
static var skeletonNumberOfLines = "skeletonNumberOfLines"
|
||||
|
||||
}
|
||||
|
||||
@@ -48,11 +49,11 @@ extension UILabel: SkeletonTextNode {
|
||||
return fontLineHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
case .relativeToConstraints:
|
||||
guard let constraintsLineHeight = heightConstraints.first?.constant,
|
||||
numberOfLines != 0 else {
|
||||
estimatedNumberOfLines != 0 else {
|
||||
return SkeletonAppearance.default.multilineHeight
|
||||
}
|
||||
|
||||
return constraintsLineHeight / CGFloat(numberOfLines)
|
||||
return constraintsLineHeight / CGFloat(estimatedNumberOfLines)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +62,20 @@ extension UILabel: SkeletonTextNode {
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) }
|
||||
}
|
||||
|
||||
var skeletonNumberOfLines: SkeletonTextNumberOfLines {
|
||||
get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) as? SkeletonTextNumberOfLines ?? SkeletonTextNumberOfLines.inherited }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) }
|
||||
}
|
||||
|
||||
var estimatedNumberOfLines: Int {
|
||||
switch skeletonNumberOfLines {
|
||||
case .inherited:
|
||||
return numberOfLines
|
||||
case .custom(let lines):
|
||||
return lines >= 0 ? lines : 1
|
||||
}
|
||||
}
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonAppearance.default.multilineLastLineFillPercent }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.lastLineFillingPercent) }
|
||||
@@ -91,9 +106,11 @@ extension UILabel: SkeletonTextNode {
|
||||
}
|
||||
|
||||
var fontLineHeight: CGFloat? {
|
||||
if let attributes = attributedText?.attributes(at: 0, effectiveRange: nil),
|
||||
let fontAttribute = attributes.first(where: { $0.key == .font }) {
|
||||
return fontAttribute.value as? CGFloat ?? font.lineHeight
|
||||
if let attributedText = attributedText,
|
||||
attributedText.length > 0 {
|
||||
let attributes = attributedText.attributes(at: 0, effectiveRange: nil)
|
||||
let fontAttribute = attributes.first(where: { $0.key == .font })
|
||||
return fontAttribute?.value as? CGFloat ?? font.lineHeight
|
||||
} else {
|
||||
return font.lineHeight
|
||||
}
|
||||
@@ -119,8 +136,18 @@ extension UITextView: SkeletonTextNode {
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) }
|
||||
}
|
||||
|
||||
var numberOfLines: Int {
|
||||
-1
|
||||
var skeletonNumberOfLines: SkeletonTextNumberOfLines {
|
||||
get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) as? SkeletonTextNumberOfLines ?? SkeletonTextNumberOfLines.inherited }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) }
|
||||
}
|
||||
|
||||
var estimatedNumberOfLines: Int {
|
||||
switch skeletonNumberOfLines {
|
||||
case .inherited:
|
||||
return -1
|
||||
case .custom(let lines):
|
||||
return lines >= -1 ? lines : 1
|
||||
}
|
||||
}
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
@@ -154,9 +181,11 @@ extension UITextView: SkeletonTextNode {
|
||||
}
|
||||
|
||||
var fontLineHeight: CGFloat? {
|
||||
if let attributes = attributedText?.attributes(at: 0, effectiveRange: nil),
|
||||
let fontAttribute = attributes.first(where: { $0.key == .font }) {
|
||||
return fontAttribute.value as? CGFloat ?? font?.lineHeight
|
||||
if let attributedText = attributedText,
|
||||
attributedText.length > 0 {
|
||||
let attributes = attributedText.attributes(at: 0, effectiveRange: nil)
|
||||
let fontAttribute = attributes.first(where: { $0.key == .font })
|
||||
return fontAttribute?.value as? CGFloat ?? font?.lineHeight
|
||||
} else {
|
||||
return font?.lineHeight
|
||||
}
|
||||
|
||||
@@ -21,7 +21,18 @@ extension UITableView {
|
||||
// Some developer trying to call `view.showAnimatedSkeleton()`
|
||||
// when the request or data is loading which sometimes happens before the ViewDidAppear
|
||||
guard window != nil else { return [] }
|
||||
return subviews
|
||||
|
||||
var result = [UIView]()
|
||||
|
||||
for subview in subviews {
|
||||
if String(describing: type(of: subview)) == "UITableViewWrapperView" {
|
||||
result.append(contentsOf: subview.subviews)
|
||||
} else {
|
||||
result.append(subview)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,11 +15,15 @@ import UIKit
|
||||
|
||||
extension CAGradientLayer {
|
||||
|
||||
override func tint(withColors colors: [UIColor]) {
|
||||
override func tint(withColors colors: [UIColor], traitCollection: UITraitCollection?) {
|
||||
skeletonSublayers.recursiveSearch(leafBlock: {
|
||||
self.colors = colors.map { $0.cgColor }
|
||||
if #available(iOS 13.0, tvOS 13, *), let traitCollection = traitCollection {
|
||||
self.colors = colors.map { $0.resolvedColor(with: traitCollection).cgColor }
|
||||
} else {
|
||||
self.colors = colors.map { $0.cgColor }
|
||||
}
|
||||
}) {
|
||||
$0.tint(withColors: colors)
|
||||
$0.tint(withColors: colors, traitCollection: traitCollection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +39,15 @@ extension CALayer {
|
||||
return sublayers?.filter { $0.name == Constants.skeletonSubLayersName } ?? [CALayer]()
|
||||
}
|
||||
|
||||
@objc func tint(withColors colors: [UIColor]) {
|
||||
@objc func tint(withColors colors: [UIColor], traitCollection: UITraitCollection?) {
|
||||
skeletonSublayers.recursiveSearch(leafBlock: {
|
||||
backgroundColor = colors.first?.cgColor
|
||||
if #available(iOS 13.0, tvOS 13, *), let traitCollection = traitCollection {
|
||||
backgroundColor = colors.first?.resolvedColor(with: traitCollection).cgColor
|
||||
} else {
|
||||
backgroundColor = colors.first?.cgColor
|
||||
}
|
||||
}) {
|
||||
$0.tint(withColors: colors)
|
||||
$0.tint(withColors: colors, traitCollection: traitCollection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +86,7 @@ extension CALayer {
|
||||
insertSublayer(sublayer.contentLayer, at: index)
|
||||
switch transition {
|
||||
case .none:
|
||||
completion?()
|
||||
DispatchQueue.main.async { completion?() }
|
||||
case .crossDissolve(let duration):
|
||||
sublayer.contentLayer.setOpacity(from: 0, to: 1, duration: duration, completion: completion)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,13 @@ public extension UIColor {
|
||||
}
|
||||
|
||||
var complementaryColor: UIColor {
|
||||
isLight ? darker : lighter
|
||||
if #available(iOS 13, tvOS 13, *) {
|
||||
return UIColor { _ in
|
||||
self.isLight ? self.darker : self.lighter
|
||||
}
|
||||
} else {
|
||||
return isLight ? darker : lighter
|
||||
}
|
||||
}
|
||||
|
||||
var lighter: UIColor {
|
||||
|
||||
@@ -16,15 +16,15 @@ import UIKit
|
||||
extension UILabel {
|
||||
|
||||
var desiredHeightBasedOnNumberOfLines: CGFloat {
|
||||
let spaceNeededForEachLine = estimatedLineHeight * CGFloat(numberOfLines)
|
||||
let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(numberOfLines - 1)
|
||||
let spaceNeededForEachLine = estimatedLineHeight * CGFloat(estimatedNumberOfLines)
|
||||
let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(estimatedNumberOfLines - 1)
|
||||
let padding = paddingInsets.top + paddingInsets.bottom
|
||||
|
||||
return spaceNeededForEachLine + spaceNeededForSpaces + padding
|
||||
}
|
||||
|
||||
func updateHeightConstraintsIfNeeded() {
|
||||
guard numberOfLines > 1 || numberOfLines == 0 else { return }
|
||||
guard estimatedNumberOfLines > 1 || estimatedNumberOfLines == 0 else { return }
|
||||
|
||||
// Workaround to simulate content when the label is contained in a `UIStackView`.
|
||||
if isSuperviewAStackView, bounds.height == 0 {
|
||||
|
||||
Reference in New Issue
Block a user