Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2edd715a87 | |||
| f3cc8f0aba | |||
| a9c22f502a | |||
| ee17db61c6 |
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -154,7 +154,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1250;
|
||||
LastUpgradeCheck = 1250;
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
F556F59E26CD201B00A80B83 = {
|
||||
CreatedOnToolsVersion = 12.5.1;
|
||||
@@ -395,7 +395,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -414,7 +414,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1250;
|
||||
LastUpgradeCheck = 1250;
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
F556F5F326CD221300A80B83 = {
|
||||
CreatedOnToolsVersion = 12.5.1;
|
||||
@@ -317,7 +317,7 @@
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 12.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -371,7 +371,7 @@
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 12.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
@@ -391,7 +391,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 12.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -410,7 +410,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 12.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -265,13 +265,30 @@ Besides, you can decide how many lines you want. If ```numberOfLines``` is set
|
||||
|
||||
You can set some properties for multilines elements.
|
||||
|
||||
|
||||
| Property | Values | Default | Preview
|
||||
| Property | Type | Default | Preview
|
||||
| ------- | ------- |------- | -------
|
||||
| **Filling percent** of the last line.<br/>Please note that for views without multiple lines, the single line will be considered as the last line and **lastLineFillPercent** will be applied to that single line. | `0...100` | `70%`| 
|
||||
| **Corner radius** of lines. (**NEW**) | `0...10` | `0` | 
|
||||
| **lastLineFillPercent** | `CGFloat` | `70`| 
|
||||
| **linesCornerRadius** | `Int` | `0` | 
|
||||
| **skeletonLineSpacing** | `CGFloat` | `10` | 
|
||||
| **skeletonPaddingInsets** | `UIEdgeInsets` | `.zero` | 
|
||||
| **skeletonTextLineHeight** | `SkeletonTextLineHeight` | `.fixed(15)` | 
|
||||
|
||||
<br />
|
||||
|
||||
> **⚠️ DEPRECATED!**
|
||||
>
|
||||
> **useFontLineHeight** has been deprecated. You can use **skeletonTextLineHeight** instead:
|
||||
> ```swift
|
||||
> descriptionTextView.skeletonTextLineHeight = .relativeToFont
|
||||
> ```
|
||||
|
||||
> **📣 IMPORTANT!**
|
||||
>
|
||||
> Please note that for views without multiple lines, the single line will be considered
|
||||
> as the last line.
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
To modify the percent or radius **using code**, set the properties:
|
||||
```swift
|
||||
@@ -289,21 +306,19 @@ Or, if you prefer use **IB/Storyboard**:
|
||||
The skeletons have a default appearance. So, when you don't specify the color, gradient or multilines properties, `SkeletonView` uses the default values.
|
||||
|
||||
Default values:
|
||||
- **tintColor**: UIColor
|
||||
- **tintColor**: `UIColor`
|
||||
- *default: `.skeletonDefault` (same as `.clouds` but adaptive to dark mode)*
|
||||
- **gradient**: SkeletonGradient
|
||||
- *default: `SkeletonGradient(baseColor: .skeletonDefault)`*
|
||||
- **multilineHeight**: CGFloat
|
||||
- **multilineHeight**: `CGFloat`
|
||||
- *default: 15*
|
||||
- **useFontLineHeight**: Bool
|
||||
- *default: true*
|
||||
- **multilineSpacing**: CGFloat
|
||||
- **multilineSpacing**: `CGFloat`
|
||||
- *default: 10*
|
||||
- **multilineLastLineFillPercent**: Int
|
||||
- **multilineLastLineFillPercent**: `Int`
|
||||
- *default: 70*
|
||||
- **multilineCornerRadius**: Int
|
||||
- **multilineCornerRadius**: `Int`
|
||||
- *default: 0*
|
||||
- **skeletonCornerRadius**: CGFloat (IBInspectable) (Make your skeleton view with corner)
|
||||
- **skeletonCornerRadius**: `CGFloat` (IBInspectable) (Make your skeleton view with corner)
|
||||
- *default: 0*
|
||||
|
||||
To get these default values you can use `SkeletonAppearance.default`. Using this property you can set the values as well:
|
||||
@@ -312,12 +327,12 @@ SkeletonAppearance.default.multilineHeight = 20
|
||||
SkeletonAppearance.default.tintColor = .green
|
||||
```
|
||||
|
||||
You can also specifiy these line appearance properties on a per-label basis:
|
||||
- **lastLineFillPercent**: Int
|
||||
- **linesCornerRadius**: Int
|
||||
- **skeletonLineSpacing**: CGFloat
|
||||
- **skeletonPaddingInsets**: UIEdgeInsets
|
||||
- **useFontLineHeight**: Bool
|
||||
> **⚠️ DEPRECATED!**
|
||||
>
|
||||
> **useFontLineHeight** has been deprecated. You can use **textLineHeight** instead:
|
||||
> ```swift
|
||||
> SkeletonAppearance.default.textLineHeight = .relativeToFont
|
||||
> ```
|
||||
|
||||
|
||||
### 🎨 Custom colors
|
||||
@@ -464,16 +479,6 @@ view.showSkeleton()
|
||||
|
||||
|
||||
|
||||
**Hierarchy in collections**
|
||||
|
||||
Here is an illustration that shows how you should specify which elements are skeletonables when you are using an `UITableView`:
|
||||
|
||||
<img src="Assets/tableview_scheme.png" width="700px">
|
||||
|
||||
As you can see, we have to make skeletonable the tableview, the cell and the UI elements, but we don't need to set as skeletonable the `contentView`
|
||||
|
||||
|
||||
|
||||
**Skeleton views layout**
|
||||
|
||||
Sometimes skeleton layout may not fit your layout because the parent view bounds have changed. ~For example, rotating the device.~
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.25.1"
|
||||
s.version = "1.26.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.
|
||||
|
||||
@@ -99,6 +99,8 @@
|
||||
F556F6E126CE367600A80B83 /* UIView+SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */; };
|
||||
F556F6F626CE876300A80B83 /* UIView+Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */; };
|
||||
F556F70826D38F3100A80B83 /* SkeletonTreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */; };
|
||||
F5C84884274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */; };
|
||||
F5C84885274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */; };
|
||||
OBJ_101 /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* Int+Extensions.swift */; };
|
||||
OBJ_103 /* UIColor+Skeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* UIColor+Skeleton.swift */; };
|
||||
OBJ_104 /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* UITableView+Extensions.swift */; };
|
||||
@@ -208,6 +210,7 @@
|
||||
F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Swizzling.swift"; sourceTree = "<group>"; };
|
||||
F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+SkeletonView.swift"; sourceTree = "<group>"; };
|
||||
F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTreeNode.swift; sourceTree = "<group>"; };
|
||||
F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTextLineHeight.swift; sourceTree = "<group>"; };
|
||||
OBJ_11 /* SkeletonLayerBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonLayerBuilder.swift; sourceTree = "<group>"; };
|
||||
OBJ_12 /* SkeletonMultilineLayerBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonMultilineLayerBuilder.swift; sourceTree = "<group>"; };
|
||||
OBJ_14 /* CollectionSkeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionSkeleton.swift; sourceTree = "<group>"; };
|
||||
@@ -515,6 +518,7 @@
|
||||
OBJ_55 /* SkeletonGradient.swift */,
|
||||
F556F6B826CE262700A80B83 /* GradientDirection.swift */,
|
||||
F556F6C126CE27FD00A80B83 /* SkeletonType.swift */,
|
||||
F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -822,6 +826,7 @@
|
||||
F556F6B626CE258300A80B83 /* GradientDirection+Animations.swift in Sources */,
|
||||
F556F69F26CD553B00A80B83 /* UIView+Extensions.swift in Sources */,
|
||||
F556F58526CD1F3900A80B83 /* Recoverable.swift in Sources */,
|
||||
F5C84885274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */,
|
||||
F556F58626CD1F3900A80B83 /* RecoverableViewState.swift in Sources */,
|
||||
F556F58726CD1F3900A80B83 /* SkeletonAnimationBuilder.swift in Sources */,
|
||||
F556F58826CD1F3900A80B83 /* SkeletonConfig.swift in Sources */,
|
||||
@@ -904,6 +909,7 @@
|
||||
F556F69E26CD553B00A80B83 /* UIView+Extensions.swift in Sources */,
|
||||
OBJ_117 /* Recoverable.swift in Sources */,
|
||||
OBJ_118 /* RecoverableViewState.swift in Sources */,
|
||||
F5C84884274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */,
|
||||
OBJ_119 /* SkeletonAnimationBuilder.swift in Sources */,
|
||||
OBJ_120 /* SkeletonConfig.swift in Sources */,
|
||||
OBJ_121 /* SkeletonFlowHandler.swift in Sources */,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "9999"
|
||||
LastUpgradeVersion = "1300"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "9999"
|
||||
LastUpgradeVersion = "1300"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -26,8 +26,8 @@ public class SkeletonViewAppearance {
|
||||
public var gradient = SkeletonGradient(baseColor: .skeletonDefault)
|
||||
|
||||
public var multilineHeight: CGFloat = 15
|
||||
|
||||
public var useFontLineHeight: Bool = true
|
||||
|
||||
public lazy var textLineHeight: SkeletonTextLineHeight = .fixed(SkeletonAppearance.default.multilineHeight)
|
||||
|
||||
public var multilineSpacing: CGFloat = 10
|
||||
|
||||
|
||||
@@ -48,3 +48,47 @@ public extension UIView {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension UILabel {
|
||||
|
||||
@IBInspectable
|
||||
@available(*, deprecated, renamed: "skeletonTextLineHeight")
|
||||
var useFontLineHeight: Bool {
|
||||
get {
|
||||
textLineHeight == .relativeToFont
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension UITextView {
|
||||
|
||||
@IBInspectable
|
||||
@available(*, deprecated, renamed: "skeletonTextLineHeight")
|
||||
var useFontLineHeight: Bool {
|
||||
get {
|
||||
textLineHeight == .relativeToFont
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension SkeletonViewAppearance {
|
||||
|
||||
@available(*, deprecated, renamed: "textLineHeight")
|
||||
var useFontLineHeight: Bool {
|
||||
get {
|
||||
textLineHeight == .relativeToFont
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// SkeletonTextLineHeight.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 22/11/21.
|
||||
|
||||
import UIKit
|
||||
|
||||
public enum SkeletonTextLineHeight: Equatable {
|
||||
|
||||
/// Calculates the line height based on the font line height.
|
||||
case relativeToFont
|
||||
|
||||
/// Calculates the line height based on the height constraints.
|
||||
///
|
||||
/// If no constraints exist, the height will be set to the `multilineHeight`
|
||||
/// value defined in the `SkeletonAppearance`.
|
||||
case relativeToConstraints
|
||||
|
||||
/// Returns the specific height specified as the associated value.
|
||||
case fixed(CGFloat)
|
||||
|
||||
}
|
||||
@@ -33,10 +33,4 @@ public extension UILabel {
|
||||
set { multilineSpacing = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var useFontLineHeight: Bool {
|
||||
get { usesTextHeightForLines }
|
||||
set { usesTextHeightForLines = newValue }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,21 @@ import UIKit
|
||||
public extension UILabel {
|
||||
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
get {
|
||||
paddingInsets
|
||||
}
|
||||
set {
|
||||
paddingInsets = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var skeletonTextLineHeight: SkeletonTextLineHeight {
|
||||
get {
|
||||
textLineHeight
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,11 +32,5 @@ public extension UITextView {
|
||||
get { return multilineSpacing }
|
||||
set { multilineSpacing = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var useFontLineHeight: Bool {
|
||||
get { usesTextHeightForLines }
|
||||
set { usesTextHeightForLines = newValue }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,21 @@ import UIKit
|
||||
public extension UITextView {
|
||||
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
get {
|
||||
paddingInsets
|
||||
}
|
||||
set {
|
||||
paddingInsets = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var skeletonTextLineHeight: SkeletonTextLineHeight {
|
||||
get {
|
||||
textLineHeight
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -51,13 +51,13 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
|
||||
let fakeCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
|
||||
|
||||
originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: fakeCell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: fakeCell)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: tableView, view: fakeCell)
|
||||
|
||||
return fakeCell
|
||||
}
|
||||
|
||||
originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: cell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: cell)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: tableView, view: cell)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
@@ -88,13 +88,13 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
let fakeCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
|
||||
|
||||
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: fakeCell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: fakeCell)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: fakeCell)
|
||||
|
||||
return fakeCell
|
||||
}
|
||||
|
||||
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: cell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: cell)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: cell)
|
||||
return cell
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ 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)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: view)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: view)
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -113,12 +113,15 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
}
|
||||
|
||||
extension SkeletonCollectionDataSource {
|
||||
private func skeletonViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
|
||||
private func skeletonizeViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
|
||||
guard container.sk.isSkeletonActive,
|
||||
let skeletonConfig = container._currentSkeletonConfig else {
|
||||
return
|
||||
}
|
||||
|
||||
view.showSkeleton(skeletonConfig: skeletonConfig)
|
||||
view.showSkeleton(
|
||||
skeletonConfig: skeletonConfig,
|
||||
notifyDelegate: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,23 @@
|
||||
import UIKit
|
||||
|
||||
class SkeletonCollectionDelegate: NSObject {
|
||||
|
||||
weak var originalTableViewDelegate: SkeletonTableViewDelegate?
|
||||
weak var originalCollectionViewDelegate: SkeletonCollectionViewDelegate?
|
||||
|
||||
init(tableViewDelegate: SkeletonTableViewDelegate? = nil, collectionViewDelegate: SkeletonCollectionViewDelegate? = nil) {
|
||||
init(
|
||||
tableViewDelegate: SkeletonTableViewDelegate? = nil,
|
||||
collectionViewDelegate: SkeletonCollectionViewDelegate? = nil
|
||||
) {
|
||||
self.originalTableViewDelegate = tableViewDelegate
|
||||
self.originalCollectionViewDelegate = collectionViewDelegate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension SkeletonCollectionDelegate: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForHeaderInSection: section))
|
||||
}
|
||||
@@ -42,24 +48,40 @@ extension SkeletonCollectionDelegate: UITableViewDelegate {
|
||||
cell.hideSkeleton()
|
||||
originalTableViewDelegate?.tableView?(tableView, didEndDisplaying: cell, forRowAt: indexPath)
|
||||
}
|
||||
|
||||
private func headerOrFooterView(_ tableView: UITableView, for viewIdentifier: String? ) -> UIView? {
|
||||
guard let viewIdentifier = viewIdentifier, let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier) else { return nil }
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: header)
|
||||
return header
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDelegate
|
||||
extension SkeletonCollectionDelegate: UICollectionViewDelegate { }
|
||||
|
||||
extension SkeletonCollectionDelegate {
|
||||
private func skeletonViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
|
||||
private extension SkeletonCollectionDelegate {
|
||||
|
||||
func skeletonizeViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
|
||||
guard container.sk.isSkeletonActive,
|
||||
let skeletonConfig = container._currentSkeletonConfig else {
|
||||
let skeletonConfig = container._currentSkeletonConfig
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
view.showSkeleton(skeletonConfig: skeletonConfig)
|
||||
view.showSkeleton(
|
||||
skeletonConfig: skeletonConfig,
|
||||
notifyDelegate: false
|
||||
)
|
||||
}
|
||||
|
||||
func headerOrFooterView(_ tableView: UITableView, for viewIdentifier: String? ) -> UIView? {
|
||||
guard let viewIdentifier = viewIdentifier,
|
||||
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
skeletonizeViewIfContainerSkeletonIsActive(
|
||||
container: tableView,
|
||||
view: header
|
||||
)
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ struct SkeletonLayer {
|
||||
func addTextLinesIfNeeded() {
|
||||
guard let textView = holderAsTextView else { return }
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numberOfLines,
|
||||
lineHeight: textView.lineHeight,
|
||||
lineHeight: textView.estimatedLineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
multilineCornerRadius: textView.multilineCornerRadius,
|
||||
@@ -76,7 +76,7 @@ struct SkeletonLayer {
|
||||
func updateLinesIfNeeded() {
|
||||
guard let textView = holderAsTextView else { return }
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numberOfLines,
|
||||
lineHeight: textView.lineHeight,
|
||||
lineHeight: textView.estimatedLineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
multilineCornerRadius: textView.multilineCornerRadius,
|
||||
|
||||
@@ -15,15 +15,16 @@ import UIKit
|
||||
|
||||
protocol SkeletonTextNode {
|
||||
|
||||
var lineHeight: CGFloat { get }
|
||||
var textLineHeight: SkeletonTextLineHeight { get }
|
||||
var estimatedLineHeight: CGFloat { get }
|
||||
var numberOfLines: Int { get }
|
||||
var textAlignment: NSTextAlignment { get }
|
||||
var lastLineFillingPercent: Int { get }
|
||||
var multilineCornerRadius: Int { get }
|
||||
var multilineSpacing: CGFloat { get }
|
||||
var paddingInsets: UIEdgeInsets { get }
|
||||
var usesTextHeightForLines: Bool { get }
|
||||
var shouldCenterTextVertically: Bool { get }
|
||||
|
||||
}
|
||||
|
||||
enum SkeletonTextNodeAssociatedKeys {
|
||||
@@ -33,26 +34,31 @@ enum SkeletonTextNodeAssociatedKeys {
|
||||
static var multilineSpacing = "multilineSpacing"
|
||||
static var paddingInsets = "paddingInsets"
|
||||
static var backupHeightConstraints = "backupHeightConstraints"
|
||||
static var usesTextHeightForLines = "usesTextHeightForLines"
|
||||
static var textLineHeight = "textLineHeight"
|
||||
|
||||
}
|
||||
|
||||
extension UILabel: SkeletonTextNode {
|
||||
|
||||
var lineHeight: CGFloat {
|
||||
let constraintsLineHeight = backupHeightConstraints.first?.constant ?? SkeletonAppearance.default.multilineHeight
|
||||
|
||||
if useFontLineHeight,
|
||||
let fontLineHeight = font?.lineHeight {
|
||||
return fontLineHeight > constraintsLineHeight ? constraintsLineHeight : fontLineHeight
|
||||
} else {
|
||||
return constraintsLineHeight
|
||||
var estimatedLineHeight: CGFloat {
|
||||
switch textLineHeight {
|
||||
case .fixed(let height):
|
||||
return height
|
||||
case .relativeToFont:
|
||||
return fontLineHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
case .relativeToConstraints:
|
||||
guard let constraintsLineHeight = heightConstraints.first?.constant,
|
||||
numberOfLines != 0 else {
|
||||
return SkeletonAppearance.default.multilineHeight
|
||||
}
|
||||
|
||||
return constraintsLineHeight / CGFloat(numberOfLines)
|
||||
}
|
||||
}
|
||||
|
||||
var usesTextHeightForLines: Bool {
|
||||
get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.usesTextHeightForLines) as? Bool ?? SkeletonAppearance.default.useFontLineHeight }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.usesTextHeightForLines) }
|
||||
var textLineHeight: SkeletonTextLineHeight {
|
||||
get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) as? SkeletonTextLineHeight ?? SkeletonAppearance.default.textLineHeight }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) }
|
||||
}
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
@@ -83,25 +89,34 @@ extension UILabel: SkeletonTextNode {
|
||||
var shouldCenterTextVertically: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
return font.lineHeight
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension UITextView: SkeletonTextNode {
|
||||
|
||||
var lineHeight: CGFloat {
|
||||
let constraintsLineHeight = heightConstraints.first?.constant ?? SkeletonAppearance.default.multilineHeight
|
||||
|
||||
if useFontLineHeight,
|
||||
let fontLineHeight = font?.lineHeight {
|
||||
return fontLineHeight > constraintsLineHeight ? constraintsLineHeight : fontLineHeight
|
||||
} else {
|
||||
return constraintsLineHeight
|
||||
var estimatedLineHeight: CGFloat {
|
||||
switch textLineHeight {
|
||||
case .fixed(let height):
|
||||
return height
|
||||
case .relativeToFont:
|
||||
return fontLineHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
case .relativeToConstraints:
|
||||
return SkeletonAppearance.default.multilineHeight
|
||||
}
|
||||
}
|
||||
|
||||
var usesTextHeightForLines: Bool {
|
||||
get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.usesTextHeightForLines) as? Bool ?? SkeletonAppearance.default.useFontLineHeight }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.usesTextHeightForLines) }
|
||||
var textLineHeight: SkeletonTextLineHeight {
|
||||
get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) as? SkeletonTextLineHeight ?? SkeletonAppearance.default.textLineHeight }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) }
|
||||
}
|
||||
|
||||
var numberOfLines: Int {
|
||||
@@ -137,4 +152,14 @@ extension UITextView: SkeletonTextNode {
|
||||
var shouldCenterTextVertically: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
return font?.lineHeight
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,15 +21,15 @@ class SkeletonFlowHandler: SkeletonFlowDelegate {
|
||||
|
||||
func didShowSkeletons(rootView: UIView) {
|
||||
skeletonLog(rootView.sk.skeletonTreeDescription)
|
||||
NotificationCenter.default.post(name: .skeletonWillAppearNotification, object: rootView, userInfo: nil)
|
||||
NotificationCenter.default.post(name: .skeletonDidAppearNotification, object: rootView, userInfo: nil)
|
||||
}
|
||||
|
||||
func willBeginUpdatingSkeletons(rootView: UIView) {
|
||||
NotificationCenter.default.post(name: .skeletonWillAppearNotification, object: rootView, userInfo: nil)
|
||||
NotificationCenter.default.post(name: .skeletonWillUpdateNotification, object: rootView, userInfo: nil)
|
||||
}
|
||||
|
||||
func didUpdateSkeletons(rootView: UIView) {
|
||||
NotificationCenter.default.post(name: .skeletonWillAppearNotification, object: rootView, userInfo: nil)
|
||||
NotificationCenter.default.post(name: .skeletonDidUpdateNotification, object: rootView, userInfo: nil)
|
||||
}
|
||||
|
||||
func willBeginLayingSkeletonsIfNeeded(rootView: UIView) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import UIKit
|
||||
extension UILabel {
|
||||
|
||||
var desiredHeightBasedOnNumberOfLines: CGFloat {
|
||||
let spaceNeededForEachLine = lineHeight * CGFloat(numberOfLines)
|
||||
let spaceNeededForEachLine = estimatedLineHeight * CGFloat(numberOfLines)
|
||||
let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(numberOfLines - 1)
|
||||
let padding = paddingInsets.top + paddingInsets.bottom
|
||||
|
||||
|
||||
@@ -15,16 +15,30 @@ import UIKit
|
||||
|
||||
extension UIView {
|
||||
|
||||
func showSkeleton(skeletonConfig config: SkeletonConfig) {
|
||||
func showSkeleton(
|
||||
skeletonConfig config: SkeletonConfig,
|
||||
notifyDelegate: Bool = true
|
||||
) {
|
||||
_isSkeletonAnimated = config.animated
|
||||
_flowDelegate = SkeletonFlowHandler()
|
||||
_flowDelegate?.willBeginShowingSkeletons(rootView: self)
|
||||
|
||||
if notifyDelegate {
|
||||
_flowDelegate = SkeletonFlowHandler()
|
||||
_flowDelegate?.willBeginShowingSkeletons(rootView: self)
|
||||
}
|
||||
|
||||
recursiveShowSkeleton(skeletonConfig: config, root: self)
|
||||
}
|
||||
|
||||
func updateSkeleton(skeletonConfig config: SkeletonConfig) {
|
||||
func updateSkeleton(
|
||||
skeletonConfig config: SkeletonConfig,
|
||||
notifyDelegate: Bool = true
|
||||
) {
|
||||
_isSkeletonAnimated = config.animated
|
||||
_flowDelegate?.willBeginUpdatingSkeletons(rootView: self)
|
||||
|
||||
if notifyDelegate {
|
||||
_flowDelegate?.willBeginUpdatingSkeletons(rootView: self)
|
||||
}
|
||||
|
||||
recursiveUpdateSkeleton(skeletonConfig: config, root: self)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,67 +19,17 @@ class SkeletonDebugTests: XCTestCase {
|
||||
func testSkeletonDescriptionWithViewNotSkeletonableNotReturnsSkullEmojiAndChildren() {
|
||||
/// given
|
||||
let view = UIView()
|
||||
let expectedDescription = "<UIView: \(Unmanaged.passUnretained(view).toOpaque())>"
|
||||
let expectedDictionary: [String : Any] = [
|
||||
"isSkeletonable" : false,
|
||||
"type" : "UIView",
|
||||
"reference" : "\(Unmanaged.passUnretained(view).toOpaque())"
|
||||
]
|
||||
|
||||
/// when
|
||||
let obtainedDescription = view.skeletonDescription
|
||||
let obtainedDictionary = view.sk.treeNode.dictionaryRepresentation
|
||||
|
||||
/// then
|
||||
XCTAssertEqual(expectedDescription, obtainedDescription)
|
||||
}
|
||||
|
||||
func testSkeletonDescriptionWithViewSkeletonableReturnsSkullEmojiButNotChildren() {
|
||||
/// given
|
||||
let view = UIView()
|
||||
view.isSkeletonable = true
|
||||
let expectedDescription = "<UIView: \(Unmanaged.passUnretained(view).toOpaque()) | ☠️ >"
|
||||
|
||||
/// when
|
||||
let obtainedDescription = view.skeletonDescription
|
||||
|
||||
/// then
|
||||
XCTAssertEqual(expectedDescription, obtainedDescription)
|
||||
}
|
||||
|
||||
func testSkeletonDescriptionWithViewSkeletonableAndChildrenReturnsSkullEmojiAndChildren() {
|
||||
/// given
|
||||
let view = UIView()
|
||||
let childView = UIView()
|
||||
view.addSubview(childView)
|
||||
view.isSkeletonable = true
|
||||
childView.isSkeletonable = true
|
||||
let expectedDescription = """
|
||||
<UIView: \(Unmanaged.passUnretained(view).toOpaque()) | (1) subSkeletons | ☠️ >
|
||||
"""
|
||||
|
||||
/// when
|
||||
let obtainedDescription = view.skeletonDescription
|
||||
|
||||
/// then
|
||||
XCTAssertEqual(expectedDescription, obtainedDescription)
|
||||
}
|
||||
|
||||
func testSkeletonHierarchyWithSkeletonableViewsReturnsFullHierarchy() {
|
||||
/// given
|
||||
let view = UIView()
|
||||
let childView = UIView()
|
||||
let subchildView = UIView()
|
||||
view.addSubview(childView)
|
||||
childView.addSubview(subchildView)
|
||||
view.isSkeletonable = true
|
||||
childView.isSkeletonable = true
|
||||
subchildView.isSkeletonable = true
|
||||
|
||||
let expectedDescription = "⬇⬇ ☠️ Root view hierarchy with Skeletons ⬇⬇<UIView:\(Unmanaged.passUnretained(view).toOpaque()) | (1) subSkeletons | ☠️ ><UIView: \(Unmanaged.passUnretained(childView).toOpaque()) | (1) subSkeletons | ☠️ ><UIView: \(Unmanaged.passUnretained(subchildView).toOpaque()) | ☠️ >"
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
|
||||
/// when
|
||||
let obtainedDescription = view.skeletonHierarchy()
|
||||
.replacingOccurrences(of: "\n", with: "")
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
|
||||
/// then
|
||||
XCTAssertEqual(expectedDescription, obtainedDescription)
|
||||
XCTAssertEqual(expectedDictionary.keys, obtainedDictionary.keys)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user