Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ba48071de | |||
| a0980380f2 | |||
| dd80fabeea | |||
| d89a70052c | |||
| cd12c64e18 | |||
| 2edd715a87 | |||
| f3cc8f0aba | |||
| a9c22f502a | |||
| ee17db61c6 | |||
| ff1a7e299b | |||
| 4994907234 | |||
| 17fb1b9950 | |||
| 138dc8bf82 | |||
| 0b308f5ef5 | |||
| af94b7e30b | |||
| 14c138ec3e | |||
| 2691572392 | |||
| 1b9586e2d7 | |||
| a819e69cee | |||
| 88cd082d8e | |||
| 1ab26cd869 | |||
| 8e369c9df2 | |||
| 5fc720d3ab | |||
| 34ce9365f6 | |||
| 3708c0da7f | |||
| 9103c14cc5 |
@@ -1,7 +1,7 @@
|
||||
name: CD
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
branches: [main]
|
||||
types: [closed]
|
||||
|
||||
@@ -61,4 +61,4 @@ jobs:
|
||||
🎉 New release ${{ steps.publish_release.outputs.tag_name }} is out 🚀
|
||||
|
||||
Check out all the changes here:
|
||||
${{ steps.publish_release.outputs.html_url }}
|
||||
${{ steps.publish_release.outputs.html_url }}
|
||||
|
||||
@@ -3,6 +3,7 @@ name: CI
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Pod trunk
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
release_version:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- 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
|
||||
@@ -35,4 +35,16 @@ jobs:
|
||||
run: |
|
||||
set -eo pipefail
|
||||
pod lib lint --allow-warnings
|
||||
pod trunk push --allow-warnings
|
||||
pod trunk push --allow-warnings
|
||||
|
||||
- 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 }}
|
||||
|
||||
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 |
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" 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="18093"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
|
||||
<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"/>
|
||||
@@ -12,7 +12,7 @@
|
||||
<!--Item-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="SkeletonViewExample" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="iOS_Example" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -88,8 +88,8 @@
|
||||
<rect key="frame" x="0.0" y="287" width="375" height="282"/>
|
||||
<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="24.333333969116211" width="375" height="120"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="CellIdentifier" rowHeight="120" id="2dN-Bd-tdy" customClass="Cell" customModule="iOS_Example" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="44.666666030883789" 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"/>
|
||||
@@ -299,7 +299,7 @@
|
||||
<!--Item-->
|
||||
<scene sceneID="Cfc-AT-AS1">
|
||||
<objects>
|
||||
<viewController id="dv8-ph-Ehg" sceneMemberID="viewController">
|
||||
<viewController id="dv8-ph-Ehg" customClass="UITextViewByCodeViewController" customModule="iOS_Example" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Jwx-gI-Qod">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
|
||||
@@ -5,12 +5,12 @@ import UIKit
|
||||
class HeaderFooterSection: UITableViewHeaderFooterView {
|
||||
|
||||
lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
|
||||
let label = UILabel()
|
||||
|
||||
label.text = " "
|
||||
label.isSkeletonable = true
|
||||
label.linesCornerRadius = 5
|
||||
|
||||
label.linesCornerRadius = 10
|
||||
|
||||
return label
|
||||
}()
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright © 2022 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import SkeletonView
|
||||
|
||||
class UITextViewByCodeViewController: UIViewController {
|
||||
lazy var textView: UITextView = {
|
||||
let tv = UITextView()
|
||||
|
||||
tv.text = " "
|
||||
tv.linesCornerRadius = 10
|
||||
tv.isSkeletonable = true
|
||||
tv.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
return tv
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupUI()
|
||||
setupElementsConstraints()
|
||||
showSkeletonForElements()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
func setupUI() {
|
||||
view.addSubview(textView)
|
||||
}
|
||||
|
||||
func setupElementsConstraints() {
|
||||
textView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
|
||||
textView.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor, constant: 10).isActive = true
|
||||
textView.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
|
||||
textView.heightAnchor.constraint(equalToConstant: 100).isActive = true
|
||||
}
|
||||
|
||||
func showSkeletonForElements() {
|
||||
textView.showSkeleton()
|
||||
}
|
||||
}
|
||||
@@ -76,8 +76,8 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
@IBAction func showOrHideSkeleton(_ sender: Any) {
|
||||
showOrHideSkeletonButton.setTitle((view.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal)
|
||||
view.isSkeletonActive ? hideSkeleton() : showSkeleton()
|
||||
showOrHideSkeletonButton.setTitle((view.sk.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal)
|
||||
view.sk.isSkeletonActive ? hideSkeleton() : showSkeleton()
|
||||
}
|
||||
|
||||
@IBAction func transitionDurationStepperAction(_ sender: Any) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
E21D8BB727888D050041DBCE /* UITextViewByCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21D8BB627888D050041DBCE /* UITextViewByCodeViewController.swift */; };
|
||||
F556F5C026CD20A300A80B83 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F5B726CD20A300A80B83 /* ViewController.swift */; };
|
||||
F556F5C126CD20A300A80B83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F556F5B826CD20A300A80B83 /* Assets.xcassets */; };
|
||||
F556F5C226CD20A300A80B83 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F556F5B926CD20A300A80B83 /* LaunchScreen.storyboard */; };
|
||||
@@ -57,6 +58,7 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
E21D8BB627888D050041DBCE /* UITextViewByCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextViewByCodeViewController.swift; sourceTree = "<group>"; };
|
||||
F556F59F26CD201B00A80B83 /* iOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F556F5B726CD20A300A80B83 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
F556F5B826CD20A300A80B83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
@@ -106,6 +108,7 @@
|
||||
F556F6EC26CE813F00A80B83 /* Constants.swift */,
|
||||
F556F6ED26CE813F00A80B83 /* HeaderFooterSection.swift */,
|
||||
F556F5B726CD20A300A80B83 /* ViewController.swift */,
|
||||
E21D8BB627888D050041DBCE /* UITextViewByCodeViewController.swift */,
|
||||
F556F5B826CD20A300A80B83 /* Assets.xcassets */,
|
||||
F556F5B926CD20A300A80B83 /* LaunchScreen.storyboard */,
|
||||
F556F5BB26CD20A300A80B83 /* Main.storyboard */,
|
||||
@@ -154,7 +157,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1250;
|
||||
LastUpgradeCheck = 1250;
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
F556F59E26CD201B00A80B83 = {
|
||||
CreatedOnToolsVersion = 12.5.1;
|
||||
@@ -239,6 +242,7 @@
|
||||
F556F6EE26CE813F00A80B83 /* Cell.swift in Sources */,
|
||||
F556F5C026CD20A300A80B83 /* ViewController.swift in Sources */,
|
||||
F556F6F026CE813F00A80B83 /* HeaderFooterSection.swift in Sources */,
|
||||
E21D8BB727888D050041DBCE /* UITextViewByCodeViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -395,7 +399,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 +418,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;
|
||||
};
|
||||
|
||||
@@ -63,9 +63,9 @@ Enjoy it! 🙂
|
||||
|
||||
## 🎬 Guides
|
||||
|
||||
| [](https://youtu.be/75kgOhWsPNA)|[](https://youtu.be/MVCiM_VdxVA)|[](https://youtu.be/Qq3Evspeea8)|[](https://youtu.be/ZOoPtBwDRT0)|[](https://www.youtube.com/watch?v=Zx1Pg1gPfxA)
|
||||
|:---: | :---: |:---: | :---: | :---:
|
||||
|[**SkeletonView Guides - Getting started**](https://youtu.be/75kgOhWsPNA)|[**How to Create Loading View with Skeleton View in Swift 5.2**](https://youtu.be/MVCiM_VdxVA) by iKh4ever Studio|[**Create Skeleton Loading View in App (Swift 5) - Xcode 11, 2020**](https://youtu.be/Qq3Evspeea8) by iOS Academy| [**Add An Elegant Loading Animation in Swift***](https://youtu.be/ZOoPtBwDRT0) by Gary Tokman| [**Cómo crear una ANIMACIÓN de CARGA de DATOS en iOS**](https://www.youtube.com/watch?v=Zx1Pg1gPfxA) by MoureDev
|
||||
| [](https://youtu.be/75kgOhWsPNA)|[](https://youtu.be/MVCiM_VdxVA)|[](https://youtu.be/Qq3Evspeea8)|[](https://www.youtube.com/watch?v=Zx1Pg1gPfxA)
|
||||
|:---: | :---: | :---: | :---:
|
||||
|[**SkeletonView Guides - Getting started**](https://youtu.be/75kgOhWsPNA)|[**How to Create Loading View with Skeleton View in Swift 5.2**](https://youtu.be/MVCiM_VdxVA) by iKh4ever Studio|[**Create Skeleton Loading View in App (Swift 5) - Xcode 11, 2020**](https://youtu.be/Qq3Evspeea8) by iOS Academy| [**Cómo crear una ANIMACIÓN de CARGA de DATOS en iOS**](https://www.youtube.com/watch?v=Zx1Pg1gPfxA) by MoureDev
|
||||
|
||||
|
||||
## 📲 Installation
|
||||
@@ -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. | `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,19 +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*
|
||||
- **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:
|
||||
@@ -310,11 +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
|
||||
> **⚠️ DEPRECATED!**
|
||||
>
|
||||
> **useFontLineHeight** has been deprecated. You can use **textLineHeight** instead:
|
||||
> ```swift
|
||||
> SkeletonAppearance.default.textLineHeight = .relativeToFont
|
||||
> ```
|
||||
|
||||
|
||||
### 🎨 Custom colors
|
||||
@@ -461,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.~
|
||||
@@ -548,14 +556,11 @@ func showGradientSkeleton(usingGradient: SkeletonGradient,
|
||||
|
||||
To facilitate the debug tasks when something is not working fine. **`SkeletonView`** has some new tools.
|
||||
|
||||
First, `UIView` has available a new property with his skeleton info:
|
||||
First, `UIView` has available a property with his skeleton info:
|
||||
```swift
|
||||
var skeletonDescription: String
|
||||
var sk.skeletonTreeDescription: String
|
||||
|
||||
```
|
||||
The skeleton representation looks like this:
|
||||
|
||||

|
||||
|
||||
Besides, you can activate the new **debug mode**. You just add the environment variable `SKELETON_DEBUG` and activate it.
|
||||
|
||||
@@ -563,11 +568,21 @@ Besides, you can activate the new **debug mode**. You just add the environment v
|
||||
|
||||
Then, when the skeleton appears, you can see the view hierarchy in the Xcode console.
|
||||
|
||||
<details>
|
||||
<summary>Open to see an output example </summary>
|
||||
<img src="Assets/hierarchy_output.png" />
|
||||
</details>
|
||||
|
||||
```
|
||||
{
|
||||
"type" : "UIView", // UITableView, UILabel...
|
||||
"isSkeletonable" : true,
|
||||
"reference" : "0x000000014751ce30",
|
||||
"children" : [
|
||||
{
|
||||
"type" : "UIView",
|
||||
"isSkeletonable" : true,
|
||||
"children" : [ ... ],
|
||||
"reference" : "0x000000014751cfa0"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Supported OS & SDK Versions**
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.23.1"
|
||||
s.version = "1.26.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,13 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
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 */; };
|
||||
F53D731C26D3A35100249D46 /* SkeletonExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731A26D3A35100249D46 /* SkeletonExtended.swift */; };
|
||||
F53D731F26D3AC4000249D46 /* SkeletonTreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */; };
|
||||
F53D732326D3C3A800249D46 /* UILabel+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D732226D3C3A800249D46 /* UILabel+SKExtensions.swift */; };
|
||||
F53D732426D3C3A800249D46 /* UILabel+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D732226D3C3A800249D46 /* UILabel+SKExtensions.swift */; };
|
||||
F556F56626CD1F3900A80B83 /* SkeletonAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* SkeletonAppearance.swift */; };
|
||||
F556F56726CD1F3900A80B83 /* SkeletonLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* SkeletonLayerBuilder.swift */; };
|
||||
F556F56826CD1F3900A80B83 /* SkeletonMultilineLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* SkeletonMultilineLayerBuilder.swift */; };
|
||||
@@ -57,8 +64,8 @@
|
||||
F556F69626CD509E00A80B83 /* Notification+SkeletonFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F69426CD509E00A80B83 /* Notification+SkeletonFlow.swift */; };
|
||||
F556F69E26CD553B00A80B83 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F69D26CD553B00A80B83 /* UIView+Extensions.swift */; };
|
||||
F556F69F26CD553B00A80B83 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F69D26CD553B00A80B83 /* UIView+Extensions.swift */; };
|
||||
F556F6A126CD566C00A80B83 /* UIView+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A026CD566C00A80B83 /* UIView+Debug.swift */; };
|
||||
F556F6A226CD566C00A80B83 /* UIView+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A026CD566C00A80B83 /* UIView+Debug.swift */; };
|
||||
F556F6A126CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A026CD566C00A80B83 /* UIView+SKExtensions.swift */; };
|
||||
F556F6A226CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A026CD566C00A80B83 /* UIView+SKExtensions.swift */; };
|
||||
F556F6A426CD5A9000A80B83 /* CALayer+Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A326CD5A9000A80B83 /* CALayer+Animations.swift */; };
|
||||
F556F6A526CD5A9000A80B83 /* CALayer+Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A326CD5A9000A80B83 /* CALayer+Animations.swift */; };
|
||||
F556F6A726CD5B0400A80B83 /* CALayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A626CD5B0400A80B83 /* CALayer+Extensions.swift */; };
|
||||
@@ -81,8 +88,8 @@
|
||||
F556F6C726CE2A2100A80B83 /* UILabel+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6C526CE2A2100A80B83 /* UILabel+IBInspectable.swift */; };
|
||||
F556F6C926CE2A4A00A80B83 /* UITextView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6C826CE2A4A00A80B83 /* UITextView+IBInspectable.swift */; };
|
||||
F556F6CA26CE2A4A00A80B83 /* UITextView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6C826CE2A4A00A80B83 /* UITextView+IBInspectable.swift */; };
|
||||
F556F6CC26CE2A7400A80B83 /* UITextView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6CB26CE2A7400A80B83 /* UITextView+Extensions.swift */; };
|
||||
F556F6CD26CE2A7400A80B83 /* UITextView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6CB26CE2A7400A80B83 /* UITextView+Extensions.swift */; };
|
||||
F556F6CC26CE2A7400A80B83 /* UITextView+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6CB26CE2A7400A80B83 /* UITextView+SKExtensions.swift */; };
|
||||
F556F6CD26CE2A7400A80B83 /* UITextView+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6CB26CE2A7400A80B83 /* UITextView+SKExtensions.swift */; };
|
||||
F556F6CF26CE2AB800A80B83 /* SkeletonTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6CE26CE2AB800A80B83 /* SkeletonTextNode.swift */; };
|
||||
F556F6D026CE2AB800A80B83 /* SkeletonTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6CE26CE2AB800A80B83 /* SkeletonTextNode.swift */; };
|
||||
F556F6D926CE315A00A80B83 /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6D826CE315A00A80B83 /* UICollectionView+Extensions.swift */; };
|
||||
@@ -90,9 +97,10 @@
|
||||
F556F6DD26CE33CE00A80B83 /* UIView+Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */; };
|
||||
F556F6E026CE367600A80B83 /* UIView+SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */; };
|
||||
F556F6E126CE367600A80B83 /* UIView+SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */; };
|
||||
F556F6F226CE818B00A80B83 /* UIView+Flags.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6F126CE818B00A80B83 /* UIView+Flags.swift */; };
|
||||
F556F6F326CE818B00A80B83 /* UIView+Flags.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6F126CE818B00A80B83 /* UIView+Flags.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 */; };
|
||||
@@ -143,6 +151,9 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
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>"; };
|
||||
F556F51026CD1B7900A80B83 /* SkeletonView.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = SkeletonView.podspec; sourceTree = "<group>"; };
|
||||
F556F51126CD1B8000A80B83 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
F556F51426CD1BFF00A80B83 /* README_zh.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README_zh.md; path = Translations/README_zh.md; sourceTree = "<group>"; };
|
||||
@@ -181,7 +192,7 @@
|
||||
F556F69126CD506C00A80B83 /* Deprecated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deprecated.swift; sourceTree = "<group>"; };
|
||||
F556F69426CD509E00A80B83 /* Notification+SkeletonFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+SkeletonFlow.swift"; sourceTree = "<group>"; };
|
||||
F556F69D26CD553B00A80B83 /* UIView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = "<group>"; };
|
||||
F556F6A026CD566C00A80B83 /* UIView+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Debug.swift"; sourceTree = "<group>"; };
|
||||
F556F6A026CD566C00A80B83 /* UIView+SKExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+SKExtensions.swift"; sourceTree = "<group>"; };
|
||||
F556F6A326CD5A9000A80B83 /* CALayer+Animations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+Animations.swift"; sourceTree = "<group>"; };
|
||||
F556F6A626CD5B0400A80B83 /* CALayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+Extensions.swift"; sourceTree = "<group>"; };
|
||||
F556F6AA26CD5C4900A80B83 /* SkeletonMultilinesLayerConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonMultilinesLayerConfig.swift; sourceTree = "<group>"; };
|
||||
@@ -193,12 +204,13 @@
|
||||
F556F6C126CE27FD00A80B83 /* SkeletonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonType.swift; sourceTree = "<group>"; };
|
||||
F556F6C526CE2A2100A80B83 /* UILabel+IBInspectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+IBInspectable.swift"; sourceTree = "<group>"; };
|
||||
F556F6C826CE2A4A00A80B83 /* UITextView+IBInspectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+IBInspectable.swift"; sourceTree = "<group>"; };
|
||||
F556F6CB26CE2A7400A80B83 /* UITextView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+Extensions.swift"; sourceTree = "<group>"; };
|
||||
F556F6CB26CE2A7400A80B83 /* UITextView+SKExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+SKExtensions.swift"; sourceTree = "<group>"; };
|
||||
F556F6CE26CE2AB800A80B83 /* SkeletonTextNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTextNode.swift; sourceTree = "<group>"; };
|
||||
F556F6D826CE315A00A80B83 /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
F556F6F126CE818B00A80B83 /* UIView+Flags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Flags.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>"; };
|
||||
@@ -356,6 +368,7 @@
|
||||
F556F68C26CD4EB400A80B83 /* FoundationExtensions */,
|
||||
F556F6B726CE25B100A80B83 /* Models */,
|
||||
F556F64F26CD2DFD00A80B83 /* SkeletonView.swift */,
|
||||
F53D731A26D3A35100249D46 /* SkeletonExtended.swift */,
|
||||
F556F68526CD49E900A80B83 /* UIKitExtensions */,
|
||||
);
|
||||
path = API;
|
||||
@@ -364,6 +377,7 @@
|
||||
F556F64B26CD2CD600A80B83 /* Internal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F556F70626D38E8300A80B83 /* SkeletonTree */,
|
||||
F556F6D326CE2F3700A80B83 /* Collections */,
|
||||
F556F65226CD2E0A00A80B83 /* Debug */,
|
||||
F556F67E26CD476300A80B83 /* FoundationExtensions */,
|
||||
@@ -403,7 +417,6 @@
|
||||
F556F6BB26CE272600A80B83 /* UILabel+Extensions.swift */,
|
||||
OBJ_25 /* UITableView+CollectionSkeleton.swift */,
|
||||
OBJ_34 /* UITableView+Extensions.swift */,
|
||||
F556F6CB26CE2A7400A80B83 /* UITextView+Extensions.swift */,
|
||||
OBJ_39 /* UIView+AppLifecycleNotifications.swift */,
|
||||
F556F68226CD48F700A80B83 /* UIView+AssociatedObjects.swift */,
|
||||
OBJ_26 /* UIView+CollectionSkeleton.swift */,
|
||||
@@ -411,6 +424,7 @@
|
||||
F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */,
|
||||
F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */,
|
||||
OBJ_61 /* UIView+Transitions.swift */,
|
||||
F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */,
|
||||
);
|
||||
path = UIKitExtensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -431,11 +445,12 @@
|
||||
children = (
|
||||
F556F6A326CD5A9000A80B83 /* CALayer+Animations.swift */,
|
||||
F556F68626CD49F900A80B83 /* UIView+IBInspectable.swift */,
|
||||
F556F6A026CD566C00A80B83 /* UIView+Debug.swift */,
|
||||
F556F6A026CD566C00A80B83 /* UIView+SKExtensions.swift */,
|
||||
F556F6C526CE2A2100A80B83 /* UILabel+IBInspectable.swift */,
|
||||
F53D732226D3C3A800249D46 /* UILabel+SKExtensions.swift */,
|
||||
F556F6C826CE2A4A00A80B83 /* UITextView+IBInspectable.swift */,
|
||||
F556F6D826CE315A00A80B83 /* UICollectionView+Extensions.swift */,
|
||||
F556F6F126CE818B00A80B83 /* UIView+Flags.swift */,
|
||||
F556F6CB26CE2A7400A80B83 /* UITextView+SKExtensions.swift */,
|
||||
);
|
||||
path = UIKitExtensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -503,6 +518,7 @@
|
||||
OBJ_55 /* SkeletonGradient.swift */,
|
||||
F556F6B826CE262700A80B83 /* GradientDirection.swift */,
|
||||
F556F6C126CE27FD00A80B83 /* SkeletonType.swift */,
|
||||
F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -544,6 +560,14 @@
|
||||
path = Appearance;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F556F70626D38E8300A80B83 /* SkeletonTree */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */,
|
||||
);
|
||||
path = SkeletonTree;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
OBJ_15 /* CollectionViews */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -771,6 +795,7 @@
|
||||
F556F6D026CE2AB800A80B83 /* SkeletonTextNode.swift in Sources */,
|
||||
F556F65126CD2DFD00A80B83 /* SkeletonView.swift in Sources */,
|
||||
F556F56826CD1F3900A80B83 /* SkeletonMultilineLayerBuilder.swift in Sources */,
|
||||
F53D731926D399E100249D46 /* SkeletonTreeNode+Extensions.swift in Sources */,
|
||||
F556F56926CD1F3900A80B83 /* CollectionSkeleton.swift in Sources */,
|
||||
F556F56A26CD1F3900A80B83 /* SkeletonCollectionViewProtocols.swift in Sources */,
|
||||
F556F6C026CE277F00A80B83 /* PrepareViewForSkeleton.swift in Sources */,
|
||||
@@ -780,7 +805,7 @@
|
||||
F556F56E26CD1F3900A80B83 /* SkeletonCollectionDataSource.swift in Sources */,
|
||||
F556F56F26CD1F3900A80B83 /* SkeletonCollectionDelegate.swift in Sources */,
|
||||
F556F68426CD48F700A80B83 /* UIView+AssociatedObjects.swift in Sources */,
|
||||
F556F6CD26CE2A7400A80B83 /* UITextView+Extensions.swift in Sources */,
|
||||
F556F6CD26CE2A7400A80B83 /* UITextView+SKExtensions.swift in Sources */,
|
||||
F556F57026CD1F3900A80B83 /* SkeletonTableViewProtocols.swift in Sources */,
|
||||
F556F57126CD1F3900A80B83 /* UITableView+CollectionSkeleton.swift in Sources */,
|
||||
F556F57226CD1F3900A80B83 /* UIView+CollectionSkeleton.swift in Sources */,
|
||||
@@ -794,25 +819,28 @@
|
||||
F556F57E26CD1F3900A80B83 /* AssociationPolicy.swift in Sources */,
|
||||
F556F6B026CE244100A80B83 /* DispatchQueue+Extensions.swift in Sources */,
|
||||
F556F58026CD1F3900A80B83 /* Recursive.swift in Sources */,
|
||||
F53D731C26D3A35100249D46 /* SkeletonExtended.swift in Sources */,
|
||||
F556F6F626CE876300A80B83 /* UIView+Swizzling.swift in Sources */,
|
||||
F556F58126CD1F3900A80B83 /* Swizzling.swift in Sources */,
|
||||
F53D732426D3C3A800249D46 /* UILabel+SKExtensions.swift in Sources */,
|
||||
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 */,
|
||||
F556F58926CD1F3900A80B83 /* SkeletonFlowHandler.swift in Sources */,
|
||||
F556F58A26CD1F3900A80B83 /* SkeletonGradient.swift in Sources */,
|
||||
F556F6C326CE27FD00A80B83 /* SkeletonType.swift in Sources */,
|
||||
F556F6F326CE818B00A80B83 /* UIView+Flags.swift in Sources */,
|
||||
F556F58B26CD1F3900A80B83 /* SkeletonLayer.swift in Sources */,
|
||||
F556F6A226CD566C00A80B83 /* UIView+Debug.swift in Sources */,
|
||||
F556F6A226CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */,
|
||||
F556F69326CD506C00A80B83 /* Deprecated.swift in Sources */,
|
||||
F556F6BA26CE262700A80B83 /* GradientDirection.swift in Sources */,
|
||||
F556F58D26CD1F3900A80B83 /* SubviewsSkeletonables.swift in Sources */,
|
||||
F556F58E26CD1F3900A80B83 /* SkeletonTransitionStyle.swift in Sources */,
|
||||
F556F6AC26CD5C4900A80B83 /* SkeletonMultilinesLayerConfig.swift in Sources */,
|
||||
F53D731F26D3AC4000249D46 /* SkeletonTreeNode.swift in Sources */,
|
||||
F556F68126CD47CF00A80B83 /* ProcessInfo+Extensions.swift in Sources */,
|
||||
F556F68826CD49F900A80B83 /* UIView+IBInspectable.swift in Sources */,
|
||||
F556F6C726CE2A2100A80B83 /* UILabel+IBInspectable.swift in Sources */,
|
||||
@@ -849,7 +877,7 @@
|
||||
F556F6CF26CE2AB800A80B83 /* SkeletonTextNode.swift in Sources */,
|
||||
F556F65026CD2DFD00A80B83 /* SkeletonView.swift in Sources */,
|
||||
OBJ_88 /* SkeletonMultilineLayerBuilder.swift in Sources */,
|
||||
F556F6F226CE818B00A80B83 /* UIView+Flags.swift in Sources */,
|
||||
F53D731B26D3A35100249D46 /* SkeletonExtended.swift in Sources */,
|
||||
OBJ_89 /* CollectionSkeleton.swift in Sources */,
|
||||
OBJ_90 /* SkeletonCollectionViewProtocols.swift in Sources */,
|
||||
F556F6BF26CE277F00A80B83 /* PrepareViewForSkeleton.swift in Sources */,
|
||||
@@ -859,7 +887,7 @@
|
||||
OBJ_94 /* SkeletonCollectionDataSource.swift in Sources */,
|
||||
OBJ_95 /* SkeletonCollectionDelegate.swift in Sources */,
|
||||
F556F68326CD48F700A80B83 /* UIView+AssociatedObjects.swift in Sources */,
|
||||
F556F6CC26CE2A7400A80B83 /* UITextView+Extensions.swift in Sources */,
|
||||
F556F6CC26CE2A7400A80B83 /* UITextView+SKExtensions.swift in Sources */,
|
||||
OBJ_96 /* SkeletonTableViewProtocols.swift in Sources */,
|
||||
OBJ_97 /* UITableView+CollectionSkeleton.swift in Sources */,
|
||||
OBJ_98 /* UIView+CollectionSkeleton.swift in Sources */,
|
||||
@@ -872,12 +900,16 @@
|
||||
OBJ_109 /* UIView+AppLifecycleNotifications.swift in Sources */,
|
||||
OBJ_110 /* AssociationPolicy.swift in Sources */,
|
||||
F556F6AF26CE244100A80B83 /* DispatchQueue+Extensions.swift in Sources */,
|
||||
F53D731826D399E100249D46 /* SkeletonTreeNode+Extensions.swift in Sources */,
|
||||
OBJ_112 /* Recursive.swift in Sources */,
|
||||
F556F70826D38F3100A80B83 /* SkeletonTreeNode.swift in Sources */,
|
||||
OBJ_113 /* Swizzling.swift in Sources */,
|
||||
F556F6B526CE258300A80B83 /* GradientDirection+Animations.swift in Sources */,
|
||||
F53D732326D3C3A800249D46 /* UILabel+SKExtensions.swift in Sources */,
|
||||
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 */,
|
||||
@@ -885,7 +917,7 @@
|
||||
F556F6C226CE27FD00A80B83 /* SkeletonType.swift in Sources */,
|
||||
OBJ_123 /* SkeletonLayer.swift in Sources */,
|
||||
F556F6E026CE367600A80B83 /* UIView+SkeletonView.swift in Sources */,
|
||||
F556F6A126CD566C00A80B83 /* UIView+Debug.swift in Sources */,
|
||||
F556F6A126CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */,
|
||||
F556F69226CD506C00A80B83 /* Deprecated.swift in Sources */,
|
||||
F556F6B926CE262700A80B83 /* GradientDirection.swift in Sources */,
|
||||
OBJ_125 /* SubviewsSkeletonables.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,7 +26,9 @@ public class SkeletonViewAppearance {
|
||||
public var gradient = SkeletonGradient(baseColor: .skeletonDefault)
|
||||
|
||||
public var multilineHeight: CGFloat = 15
|
||||
|
||||
|
||||
public lazy var textLineHeight: SkeletonTextLineHeight = .fixed(SkeletonAppearance.default.multilineHeight)
|
||||
|
||||
public var multilineSpacing: CGFloat = 10
|
||||
|
||||
public var multilineLastLineFillPercent: Int = 70
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//
|
||||
// Created by Juanpe Catalán on 18/8/21.
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public extension Notification.Name {
|
||||
|
||||
@@ -34,3 +34,61 @@ public extension Notification.Name {
|
||||
static let didHideSkeletons = Notification.Name.skeletonDidDisappearNotification
|
||||
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
|
||||
@available(*, deprecated, renamed: "sk.treeNodesDescription")
|
||||
var skeletonDescription: String {
|
||||
sk.skeletonTreeDescription
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "sk.isSkeletonActive")
|
||||
var isSkeletonActive: Bool {
|
||||
sk.isSkeletonActive
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// SkeletonExtended.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 23/8/21.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Type that acts as a generic extension point for all `SkeletonViewExtended` types.
|
||||
public struct SkeletonViewExtension<ExtendedType> {
|
||||
/// Stores the type or meta-type of any extended type.
|
||||
public private(set) var type: ExtendedType
|
||||
|
||||
/// Create an instance from the provided value.
|
||||
///
|
||||
/// - Parameter type: Instance being extended.
|
||||
public init(_ type: ExtendedType) {
|
||||
self.type = type
|
||||
}
|
||||
}
|
||||
|
||||
/// Protocol describing the `sk` extension points for SkeletonView extended types.
|
||||
public protocol SkeletonViewExtended {
|
||||
/// Type being extended.
|
||||
associatedtype ExtendedType
|
||||
|
||||
/// Instance SkeletonView extension point.
|
||||
var sk: SkeletonViewExtension<ExtendedType> { get set }
|
||||
}
|
||||
|
||||
extension SkeletonViewExtended {
|
||||
/// Instance SkeletonView extension point.
|
||||
public var sk: SkeletonViewExtension<Self> {
|
||||
get { SkeletonViewExtension(self) }
|
||||
// swiftlint:disable:next unused_setter_value
|
||||
set {}
|
||||
}
|
||||
}
|
||||
@@ -33,10 +33,4 @@ public extension UILabel {
|
||||
set { multilineSpacing = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var useFontLineHeight: Bool {
|
||||
get { usesTextHeightForLines }
|
||||
set { usesTextHeightForLines = newValue }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// UILabel+SKExtensions.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 23/8/21.
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UILabel {
|
||||
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
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 }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
-4
@@ -7,17 +7,30 @@
|
||||
//
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// UITextView+Extensions.swift
|
||||
// UITextView+SKExtensions.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 19/8/21.
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UITextView {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// UIView+Debug.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 18/8/21.
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIView {
|
||||
|
||||
/// Returns a string that describes the hierarchy of the skeleton, indicating
|
||||
/// whether the receiver is skeletonable and all skeletonable children.
|
||||
var skeletonDescription: String {
|
||||
var description = "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())"
|
||||
let subSkeletons = subviewsSkeletonables
|
||||
|
||||
if !subSkeletons.isEmpty {
|
||||
description += " | (\(subSkeletons.count)) subSkeletons"
|
||||
}
|
||||
|
||||
if isSkeletonable {
|
||||
description += " | ☠️ "
|
||||
}
|
||||
|
||||
return description + ">"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// UIView+Flags.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 19/8/21.
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UIView {
|
||||
|
||||
var isSkeletonActive: Bool {
|
||||
return _status == .on || subviewsSkeletonables.contains(where: { $0.isSkeletonActive })
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// UIView+SKExtensions.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 18/8/21.
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension SkeletonViewExtension where ExtendedType: UIView {
|
||||
|
||||
/// Returns a string that describes the hierarchy of the skeleton, indicating
|
||||
/// whether the receiver is skeletonable and all skeletonable children.
|
||||
var skeletonTreeDescription: String {
|
||||
guard let theJSONData = try? JSONSerialization.data(withJSONObject: treeNode.dictionaryRepresentation, options: [.prettyPrinted]) else {
|
||||
skeletonLog("Skeleton tree generation has failed!")
|
||||
return ""
|
||||
}
|
||||
|
||||
return String(data: theJSONData, encoding: .utf8)!
|
||||
}
|
||||
|
||||
var isSkeletonActive: Bool {
|
||||
type._status == .on || type.subviewsSkeletonables.contains(where: { $0.sk.isSkeletonActive })
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
guard container.isSkeletonActive,
|
||||
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) {
|
||||
guard container.isSkeletonActive,
|
||||
let skeletonConfig = container._currentSkeletonConfig else {
|
||||
private extension SkeletonCollectionDelegate {
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,10 +25,6 @@ extension Dictionary {
|
||||
}
|
||||
}
|
||||
|
||||
func printSkeletonHierarchy(in view: UIView) {
|
||||
skeletonLog(view.skeletonHierarchy())
|
||||
}
|
||||
|
||||
func skeletonLog(_ message: String) {
|
||||
#if DEBUG
|
||||
if ProcessInfo.processInfo.environment[.debugMode] != nil {
|
||||
@@ -36,17 +32,3 @@ func skeletonLog(_ message: String) {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
func skeletonHierarchy(indentationLevel level: Int = 0) -> String {
|
||||
var description = level == 0 ? "\n ⬇⬇ ☠️ Root view hierarchy with Skeletons ⬇⬇ \n" : ""
|
||||
description += "\(level == 0 ? "\n" : 3.whitespaces) \(skeletonDescription) \n"
|
||||
subviewsToSkeleton.forEach {
|
||||
description += (level + 2).whitespaces
|
||||
description += $0.skeletonHierarchy(indentationLevel: level + 1)
|
||||
}
|
||||
return description
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,14 +60,15 @@ 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,
|
||||
multilineSpacing: textView.multilineSpacing,
|
||||
paddingInsets: textView.paddingInsets,
|
||||
alignment: textView.textAlignment,
|
||||
isRTL: holder?.isRTL ?? false)
|
||||
isRTL: holder?.isRTL ?? false,
|
||||
shouldCenterVertically: textView.shouldCenterTextVertically)
|
||||
|
||||
maskLayer.addMultilinesLayers(for: config)
|
||||
}
|
||||
@@ -75,14 +76,15 @@ 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,
|
||||
multilineSpacing: textView.multilineSpacing,
|
||||
paddingInsets: textView.paddingInsets,
|
||||
alignment: textView.textAlignment,
|
||||
isRTL: holder?.isRTL ?? false)
|
||||
isRTL: holder?.isRTL ?? false,
|
||||
shouldCenterVertically: textView.shouldCenterTextVertically)
|
||||
|
||||
maskLayer.updateMultilinesLayers(for: config)
|
||||
}
|
||||
|
||||
+2
-1
@@ -24,7 +24,8 @@ struct SkeletonMultilinesLayerConfig {
|
||||
var paddingInsets: UIEdgeInsets
|
||||
var alignment: NSTextAlignment
|
||||
var isRTL: Bool
|
||||
|
||||
var shouldCenterVertically: Bool
|
||||
|
||||
/// Returns padding insets taking into account if the RTL is activated
|
||||
var calculatedPaddingInsets: UIEdgeInsets {
|
||||
UIEdgeInsets(top: paddingInsets.top,
|
||||
|
||||
@@ -15,14 +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 {
|
||||
@@ -32,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 ?? true }
|
||||
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 {
|
||||
@@ -79,24 +86,37 @@ extension UILabel: SkeletonTextNode {
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.backupHeightConstraints) }
|
||||
}
|
||||
|
||||
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 ?? true }
|
||||
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 {
|
||||
@@ -129,4 +149,17 @@ extension UITextView: SkeletonTextNode {
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.paddingInsets) }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,16 +20,16 @@ class SkeletonFlowHandler: SkeletonFlowDelegate {
|
||||
}
|
||||
|
||||
func didShowSkeletons(rootView: UIView) {
|
||||
printSkeletonHierarchy(in: rootView)
|
||||
NotificationCenter.default.post(name: .skeletonWillAppearNotification, object: rootView, userInfo: nil)
|
||||
skeletonLog(rootView.sk.skeletonTreeDescription)
|
||||
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) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// SkeletonTreeNode.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 23/8/21.
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SkeletonTreeNode<Base> {
|
||||
/// Base object to extend.
|
||||
let base: Base
|
||||
|
||||
/// Creates extensions with base object.
|
||||
///
|
||||
/// - parameter base: Base object.
|
||||
init(_ base: Base) {
|
||||
self.base = base
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,15 +88,16 @@ extension CALayer {
|
||||
|
||||
private extension CALayer {
|
||||
|
||||
func alignLayerFrame(_ rect: CGRect, alignment: NSTextAlignment, isRTL: Bool) -> CGRect {
|
||||
func alignLayerFrame(_ rect: CGRect, paddingInsets: UIEdgeInsets, alignment: NSTextAlignment, isRTL: Bool) -> CGRect {
|
||||
var newRect = rect
|
||||
let superlayerWidth = (superlayer?.bounds.width ?? 0)
|
||||
|
||||
switch alignment {
|
||||
case .natural where isRTL,
|
||||
.right:
|
||||
newRect.origin.x = (superlayer?.bounds.width ?? 0) - rect.origin.x - rect.width
|
||||
newRect.origin.x = superlayerWidth - rect.width - paddingInsets.right
|
||||
case .center:
|
||||
newRect.origin.x = rect.origin.x + ((superlayer?.bounds.width ?? 0) - rect.width) / 2
|
||||
newRect.origin.x = (superlayerWidth + paddingInsets.left - paddingInsets.right - rect.width) / 2
|
||||
case .natural, .left, .justified:
|
||||
break
|
||||
@unknown default:
|
||||
@@ -108,7 +109,7 @@ private extension CALayer {
|
||||
|
||||
func calculatedWidthForLine(at index: Int, totalLines: Int, lastLineFillPercent: Int, paddingInsets: UIEdgeInsets) -> CGFloat {
|
||||
var width = bounds.width - paddingInsets.left - paddingInsets.right
|
||||
if index == totalLines - 1 && totalLines != 1 {
|
||||
if index == totalLines - 1 {
|
||||
width = width * CGFloat(lastLineFillPercent) / 100
|
||||
}
|
||||
return width
|
||||
@@ -188,6 +189,19 @@ extension CALayer {
|
||||
isRTL: config.isRTL
|
||||
)
|
||||
}
|
||||
|
||||
guard config.shouldCenterVertically,
|
||||
let maxY = currentSkeletonSublayers.last?.frame.maxY else {
|
||||
return
|
||||
}
|
||||
let verticallyCenterAlignedFrames = currentSkeletonSublayers.map { layer -> CGRect in
|
||||
let moveDownBy = (bounds.height - (maxY + paddingInsets.top + paddingInsets.bottom)) / 2
|
||||
return layer.frame.offsetBy(dx: 0, dy: moveDownBy)
|
||||
}
|
||||
|
||||
for (index, layer) in currentSkeletonSublayers.enumerated() {
|
||||
layer.frame = verticallyCenterAlignedFrames[index]
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayerFrame(for index: Int, totalLines: Int, size: CGSize, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets, alignment: NSTextAlignment, isRTL: Bool) {
|
||||
@@ -197,8 +211,8 @@ extension CALayer {
|
||||
width: size.width,
|
||||
height: size.height)
|
||||
|
||||
if index == totalLines - 1 && totalLines != 1 {
|
||||
frame = alignLayerFrame(newFrame, alignment: alignment, isRTL: isRTL)
|
||||
if index == totalLines - 1 {
|
||||
frame = alignLayerFrame(newFrame, paddingInsets: paddingInsets, alignment: alignment, isRTL: isRTL)
|
||||
} else {
|
||||
frame = newFrame
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// SkeletonTreeNode+Extensions.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 23/8/21.
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView: SkeletonViewExtended { }
|
||||
|
||||
extension SkeletonTreeNode where Base: UIView {
|
||||
|
||||
var children: [SkeletonTreeNode<UIView>] {
|
||||
base.subviewsSkeletonables.map { $0.sk.treeNode }
|
||||
}
|
||||
|
||||
var parent: SkeletonTreeNode<UIView>? {
|
||||
base.superview?.sk.treeNode
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Debug
|
||||
|
||||
extension SkeletonTreeNode where Base: UIView {
|
||||
|
||||
var dictionaryRepresentation: [String: Any] {
|
||||
let skeletonableChildren = children
|
||||
|
||||
var nodeInfo: [String: Any] = [
|
||||
"type": "\(type(of: base))",
|
||||
"reference": "\(Unmanaged.passUnretained(base).toOpaque())",
|
||||
"isSkeletonable": base.isSkeletonable
|
||||
]
|
||||
|
||||
if !skeletonableChildren.isEmpty {
|
||||
nodeInfo["children"] = skeletonableChildren.map { $0.dictionaryRepresentation }
|
||||
}
|
||||
|
||||
return nodeInfo
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,13 +15,8 @@ import UIKit
|
||||
|
||||
extension UILabel {
|
||||
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ extension UIView {
|
||||
}
|
||||
|
||||
@objc func appDidEnterBackground() {
|
||||
UserDefaults.standard.set((isSkeletonActive && _isSkeletonAnimated), forKey: Constants.needAnimatedSkeletonKey)
|
||||
UserDefaults.standard.set((sk.isSkeletonActive && _isSkeletonAnimated), forKey: Constants.needAnimatedSkeletonKey)
|
||||
}
|
||||
|
||||
@objc func willTerminateNotification() {
|
||||
|
||||
@@ -13,6 +13,14 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension SkeletonViewExtension where ExtendedType: UIView {
|
||||
|
||||
var treeNode: SkeletonTreeNode<ExtendedType> {
|
||||
SkeletonTreeNode<ExtendedType>(self.type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
/// Flags
|
||||
@@ -159,7 +167,7 @@ extension UIView {
|
||||
}
|
||||
|
||||
func removeSkeletonLayer() {
|
||||
guard isSkeletonActive,
|
||||
guard sk.isSkeletonActive,
|
||||
let skeletonLayer = _skeletonLayer,
|
||||
let transitionStyle = _currentSkeletonConfig?.transition else { return }
|
||||
skeletonLayer.stopAnimation()
|
||||
|
||||
@@ -15,22 +15,36 @@ 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)
|
||||
}
|
||||
|
||||
func recursiveLayoutSkeletonIfNeeded(root: UIView? = nil) {
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
guard isSkeletonable, isSkeletonActive else { return }
|
||||
guard isSkeletonable, sk.isSkeletonActive else { return }
|
||||
layoutSkeletonLayerIfNeeded()
|
||||
if let config = _currentSkeletonConfig, config.animated, !_isSkeletonAnimated {
|
||||
startSkeletonAnimation(config.animation)
|
||||
@@ -45,7 +59,7 @@ extension UIView {
|
||||
}
|
||||
|
||||
func recursiveHideSkeleton(reloadDataAfter reload: Bool, transition: SkeletonTransitionStyle, root: UIView? = nil) {
|
||||
guard isSkeletonActive else { return }
|
||||
guard sk.isSkeletonActive else { return }
|
||||
if isHiddenWhenSkeletonIsActive {
|
||||
isHidden = false
|
||||
}
|
||||
@@ -70,7 +84,7 @@ extension UIView {
|
||||
private extension UIView {
|
||||
|
||||
func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
|
||||
guard !isSkeletonActive else { return }
|
||||
guard !sk.isSkeletonActive else { return }
|
||||
saveViewState()
|
||||
|
||||
prepareViewForSkeleton()
|
||||
@@ -81,7 +95,7 @@ private extension UIView {
|
||||
if isHiddenWhenSkeletonIsActive {
|
||||
isHidden = true
|
||||
}
|
||||
guard isSkeletonable && !isSkeletonActive else { return }
|
||||
guard isSkeletonable && !sk.isSkeletonActive else { return }
|
||||
_currentSkeletonConfig = config
|
||||
swizzleLayoutSubviews()
|
||||
swizzleTraitCollectionDidChange()
|
||||
@@ -98,7 +112,7 @@ private extension UIView {
|
||||
}
|
||||
|
||||
func recursiveUpdateSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
|
||||
guard isSkeletonActive else { return }
|
||||
guard sk.isSkeletonActive else { return }
|
||||
_currentSkeletonConfig = config
|
||||
updateDummyDataSourceIfNeeded()
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
|
||||
@@ -18,13 +18,13 @@ extension UIView {
|
||||
@objc func skeletonLayoutSubviews() {
|
||||
guard Thread.isMainThread else { return }
|
||||
skeletonLayoutSubviews()
|
||||
guard isSkeletonActive else { return }
|
||||
guard sk.isSkeletonActive else { return }
|
||||
layoutSkeletonIfNeeded()
|
||||
}
|
||||
|
||||
@objc func skeletonTraitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
skeletonTraitCollectionDidChange(previousTraitCollection)
|
||||
guard isSkeletonable, isSkeletonActive, let config = _currentSkeletonConfig else { return }
|
||||
guard isSkeletonable, sk.isSkeletonActive, let config = _currentSkeletonConfig else { return }
|
||||
updateSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-13
@@ -1,33 +1,38 @@
|
||||
fastlane documentation
|
||||
================
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
Install _fastlane_ using
|
||||
```
|
||||
[sudo] gem install fastlane -NV
|
||||
```
|
||||
or alternatively using `brew install fastlane`
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
### bump_version
|
||||
```
|
||||
fastlane bump_version
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane bump_version
|
||||
```
|
||||
|
||||
|
||||
|
||||
### release_current
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane release_current
|
||||
```
|
||||
fastlane release_current
|
||||
```
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
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).
|
||||
|
||||
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