Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b40a771510 | |||
| 73b137cda6 | |||
| add7401058 | |||
| 947e2a63c2 | |||
| c5462de1db | |||
| 00b5d614b6 | |||
| 8ba48071de | |||
| a0980380f2 | |||
| dd80fabeea | |||
| d89a70052c | |||
| cd12c64e18 | |||
| 2edd715a87 | |||
| f3cc8f0aba | |||
| a9c22f502a | |||
| ee17db61c6 |
@@ -32,12 +32,14 @@ jobs:
|
||||
branch: 'main'
|
||||
commit_message: 'Bump version ${{ steps.publish_release.outputs.tag_name }}'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Cocoapods
|
||||
continue-on-error: true
|
||||
env:
|
||||
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
|
||||
run: |
|
||||
gem install cocoapods
|
||||
set -eo pipefail
|
||||
pod lib lint --allow-warnings
|
||||
pod trunk push --allow-warnings
|
||||
|
||||
@@ -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
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -261,17 +261,19 @@ The rest of the process is the same as ```UITableView```
|
||||

|
||||
|
||||
When using elements with text, ```SkeletonView``` draws lines to simulate text.
|
||||
Besides, you can decide how many lines you want. If ```numberOfLines``` is set to zero, it will calculate how many lines needed to populate the whole skeleton and it will be drawn. Instead, if you set it to one, two or any number greater than zero, it will only draw this number of lines.
|
||||
|
||||
You can set some properties for multilines elements.
|
||||
|
||||
|
||||
| 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)` | 
|
||||
| **skeletonTextNumberOfLines** | `SkeletonTextNumberOfLines` | `.inherited` | 
|
||||
|
||||
<br />
|
||||
|
||||
To modify the percent or radius **using code**, set the properties:
|
||||
```swift
|
||||
@@ -283,27 +285,55 @@ Or, if you prefer use **IB/Storyboard**:
|
||||
|
||||

|
||||
|
||||
<br />
|
||||
|
||||
**How to define the number of lines?**
|
||||
|
||||
|
||||
By default, the number of lines is the same as the value of the `numberOfLines` property. And, if it's set to **zero**, it'll calculate how many lines are needed to populate the whole skeleton and draw it.
|
||||
|
||||
However, if you want to set a specific number of skeleton lines you can do it by setting the `skeletonTextNumberOfLines` property. This property has two possible values, `inherited` which returns `numberOfLines` value and `custom(Int)` which returns the specific number of lines specified as the associated value.
|
||||
|
||||
For example:
|
||||
|
||||
```swift
|
||||
label.skeletonTextNumberOfLines = 3 // .custom(3)
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
> **⚠️ 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.
|
||||
|
||||
|
||||
|
||||
### 🦋 Appearance
|
||||
|
||||
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 +342,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 +494,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.27.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.
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
F5225F2A278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */; };
|
||||
F5225F2B278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */; };
|
||||
F53D731826D399E100249D46 /* SkeletonTreeNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */; };
|
||||
F53D731926D399E100249D46 /* SkeletonTreeNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */; };
|
||||
F53D731B26D3A35100249D46 /* SkeletonExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731A26D3A35100249D46 /* SkeletonExtended.swift */; };
|
||||
@@ -99,6 +101,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 */; };
|
||||
@@ -149,6 +153,7 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTextNumberOfLines.swift; sourceTree = "<group>"; };
|
||||
F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SkeletonTreeNode+Extensions.swift"; sourceTree = "<group>"; };
|
||||
F53D731A26D3A35100249D46 /* SkeletonExtended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonExtended.swift; sourceTree = "<group>"; };
|
||||
F53D732226D3C3A800249D46 /* UILabel+SKExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+SKExtensions.swift"; sourceTree = "<group>"; };
|
||||
@@ -208,6 +213,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 +521,8 @@
|
||||
OBJ_55 /* SkeletonGradient.swift */,
|
||||
F556F6B826CE262700A80B83 /* GradientDirection.swift */,
|
||||
F556F6C126CE27FD00A80B83 /* SkeletonType.swift */,
|
||||
F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */,
|
||||
F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -822,6 +830,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 */,
|
||||
@@ -829,6 +838,7 @@
|
||||
F556F58A26CD1F3900A80B83 /* SkeletonGradient.swift in Sources */,
|
||||
F556F6C326CE27FD00A80B83 /* SkeletonType.swift in Sources */,
|
||||
F556F58B26CD1F3900A80B83 /* SkeletonLayer.swift in Sources */,
|
||||
F5225F2B278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift in Sources */,
|
||||
F556F6A226CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */,
|
||||
F556F69326CD506C00A80B83 /* Deprecated.swift in Sources */,
|
||||
F556F6BA26CE262700A80B83 /* GradientDirection.swift in Sources */,
|
||||
@@ -904,6 +914,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 */,
|
||||
@@ -911,6 +922,7 @@
|
||||
F556F6C226CE27FD00A80B83 /* SkeletonType.swift in Sources */,
|
||||
OBJ_123 /* SkeletonLayer.swift in Sources */,
|
||||
F556F6E026CE367600A80B83 /* UIView+SkeletonView.swift in Sources */,
|
||||
F5225F2A278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift in Sources */,
|
||||
F556F6A126CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */,
|
||||
F556F69226CD506C00A80B83 /* Deprecated.swift in Sources */,
|
||||
F556F6B926CE262700A80B83 /* GradientDirection.swift in Sources */,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,5 +37,7 @@ public class SkeletonViewAppearance {
|
||||
|
||||
public var renderSingleLineAsView: Bool = false
|
||||
|
||||
public var skeletonCornerRadius: Float = 0
|
||||
|
||||
}
|
||||
// codebeat:enable[TOO_MANY_IVARS]
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright SkeletonView. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// SkeletonTextNumberOfLines.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 10/1/22.
|
||||
|
||||
import UIKit
|
||||
|
||||
public enum SkeletonTextNumberOfLines: Equatable, ExpressibleByIntegerLiteral {
|
||||
|
||||
/// Returns `numberOfLines` value.
|
||||
case inherited
|
||||
|
||||
/// Returns the specific number of lines specified as the associated value.
|
||||
case custom(Int)
|
||||
|
||||
}
|
||||
|
||||
public extension SkeletonTextNumberOfLines {
|
||||
|
||||
init(integerLiteral value: Int) {
|
||||
self = .custom(value)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,10 +33,4 @@ public extension UILabel {
|
||||
set { multilineSpacing = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var useFontLineHeight: Bool {
|
||||
get { usesTextHeightForLines }
|
||||
set { usesTextHeightForLines = newValue }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,36 @@ import UIKit
|
||||
|
||||
public extension UILabel {
|
||||
|
||||
/// Defines the skeleton paddings.
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
get {
|
||||
paddingInsets
|
||||
}
|
||||
set {
|
||||
paddingInsets = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the logic for calculating the height of the skeleton lines.
|
||||
/// Default: `SkeletonAppearance.default.textLineHeight`
|
||||
var skeletonTextLineHeight: SkeletonTextLineHeight {
|
||||
get {
|
||||
textLineHeight
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the logic for calculating the number of lines of the skeleton.
|
||||
/// Default: `inherited`
|
||||
var skeletonTextNumberOfLines: SkeletonTextNumberOfLines {
|
||||
get {
|
||||
skeletonNumberOfLines
|
||||
}
|
||||
set {
|
||||
skeletonNumberOfLines = newValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,11 +32,5 @@ public extension UITextView {
|
||||
get { return multilineSpacing }
|
||||
set { multilineSpacing = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var useFontLineHeight: Bool {
|
||||
get { usesTextHeightForLines }
|
||||
set { usesTextHeightForLines = newValue }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,36 @@ import UIKit
|
||||
|
||||
public extension UITextView {
|
||||
|
||||
/// Defines the skeleton paddings.
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
get {
|
||||
paddingInsets
|
||||
}
|
||||
set {
|
||||
paddingInsets = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the logic for calculating the height of the skeleton lines.
|
||||
/// Default: `SkeletonAppearance.default.textLineHeight`
|
||||
var skeletonTextLineHeight: SkeletonTextLineHeight {
|
||||
get {
|
||||
textLineHeight
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the logic for calculating the number of lines of the skeleton.
|
||||
/// Default: `inherited`
|
||||
var skeletonTextNumberOfLines: SkeletonTextNumberOfLines {
|
||||
get {
|
||||
skeletonNumberOfLines
|
||||
}
|
||||
set {
|
||||
skeletonNumberOfLines = 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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ struct SkeletonLayer {
|
||||
/// If there is more than one line, or custom preferences have been set for a single line, draw custom layers
|
||||
func addTextLinesIfNeeded() {
|
||||
guard let textView = holderAsTextView else { return }
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numberOfLines,
|
||||
lineHeight: textView.lineHeight,
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.estimatedNumberOfLines,
|
||||
lineHeight: textView.estimatedLineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
multilineCornerRadius: textView.multilineCornerRadius,
|
||||
@@ -75,8 +75,8 @@ struct SkeletonLayer {
|
||||
|
||||
func updateLinesIfNeeded() {
|
||||
guard let textView = holderAsTextView else { return }
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numberOfLines,
|
||||
lineHeight: textView.lineHeight,
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.estimatedNumberOfLines,
|
||||
lineHeight: textView.estimatedLineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
multilineCornerRadius: textView.multilineCornerRadius,
|
||||
@@ -91,7 +91,7 @@ struct SkeletonLayer {
|
||||
|
||||
var holderAsTextView: SkeletonTextNode? {
|
||||
guard let textView = holder as? SkeletonTextNode,
|
||||
(textView.numberOfLines == -1 || textView.numberOfLines == 0 || textView.numberOfLines > 1 || textView.numberOfLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
|
||||
(textView.estimatedNumberOfLines == -1 || textView.estimatedNumberOfLines == 0 || textView.estimatedNumberOfLines > 1 || textView.estimatedNumberOfLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
|
||||
return nil
|
||||
}
|
||||
return textView
|
||||
|
||||
@@ -15,15 +15,16 @@ import UIKit
|
||||
|
||||
protocol SkeletonTextNode {
|
||||
|
||||
var lineHeight: CGFloat { get }
|
||||
var numberOfLines: Int { get }
|
||||
var textLineHeight: SkeletonTextLineHeight { get }
|
||||
var estimatedLineHeight: CGFloat { get }
|
||||
var estimatedNumberOfLines: 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,46 @@ enum SkeletonTextNodeAssociatedKeys {
|
||||
static var multilineSpacing = "multilineSpacing"
|
||||
static var paddingInsets = "paddingInsets"
|
||||
static var backupHeightConstraints = "backupHeightConstraints"
|
||||
static var usesTextHeightForLines = "usesTextHeightForLines"
|
||||
static var textLineHeight = "textLineHeight"
|
||||
static var skeletonNumberOfLines = "skeletonNumberOfLines"
|
||||
|
||||
}
|
||||
|
||||
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,
|
||||
estimatedNumberOfLines != 0 else {
|
||||
return SkeletonAppearance.default.multilineHeight
|
||||
}
|
||||
|
||||
return constraintsLineHeight / CGFloat(estimatedNumberOfLines)
|
||||
}
|
||||
}
|
||||
|
||||
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 skeletonNumberOfLines: SkeletonTextNumberOfLines {
|
||||
get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) as? SkeletonTextNumberOfLines ?? SkeletonTextNumberOfLines.inherited }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) }
|
||||
}
|
||||
|
||||
var estimatedNumberOfLines: Int {
|
||||
switch skeletonNumberOfLines {
|
||||
case .inherited:
|
||||
return numberOfLines
|
||||
case .custom(let lines):
|
||||
return lines >= 0 ? lines : 1
|
||||
}
|
||||
}
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
@@ -83,29 +104,48 @@ 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 {
|
||||
-1
|
||||
var skeletonNumberOfLines: SkeletonTextNumberOfLines {
|
||||
get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) as? SkeletonTextNumberOfLines ?? SkeletonTextNumberOfLines.inherited }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) }
|
||||
}
|
||||
|
||||
var estimatedNumberOfLines: Int {
|
||||
switch skeletonNumberOfLines {
|
||||
case .inherited:
|
||||
return -1
|
||||
case .custom(let lines):
|
||||
return lines >= -1 ? lines : 1
|
||||
}
|
||||
}
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
@@ -137,4 +177,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) {
|
||||
|
||||
@@ -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:
|
||||
@@ -211,7 +212,7 @@ extension CALayer {
|
||||
height: size.height)
|
||||
|
||||
if index == totalLines - 1 {
|
||||
frame = alignLayerFrame(newFrame, alignment: alignment, isRTL: isRTL)
|
||||
frame = alignLayerFrame(newFrame, paddingInsets: paddingInsets, alignment: alignment, isRTL: isRTL)
|
||||
} else {
|
||||
frame = newFrame
|
||||
}
|
||||
|
||||
@@ -16,15 +16,15 @@ import UIKit
|
||||
extension UILabel {
|
||||
|
||||
var desiredHeightBasedOnNumberOfLines: CGFloat {
|
||||
let spaceNeededForEachLine = lineHeight * CGFloat(numberOfLines)
|
||||
let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(numberOfLines - 1)
|
||||
let spaceNeededForEachLine = estimatedLineHeight * CGFloat(estimatedNumberOfLines)
|
||||
let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(estimatedNumberOfLines - 1)
|
||||
let padding = paddingInsets.top + paddingInsets.bottom
|
||||
|
||||
return spaceNeededForEachLine + spaceNeededForSpaces + padding
|
||||
}
|
||||
|
||||
func updateHeightConstraintsIfNeeded() {
|
||||
guard numberOfLines > 1 || numberOfLines == 0 else { return }
|
||||
guard estimatedNumberOfLines > 1 || estimatedNumberOfLines == 0 else { return }
|
||||
|
||||
// Workaround to simulate content when the label is contained in a `UIStackView`.
|
||||
if isSuperviewAStackView, bounds.height == 0 {
|
||||
|
||||
@@ -88,7 +88,7 @@ extension UIView {
|
||||
}
|
||||
|
||||
var _skeletonableCornerRadius: Float {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.skeletonCornerRadius) as? Float ?? 0.0 }
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.skeletonCornerRadius) as? Float ?? SkeletonViewAppearance.shared.skeletonCornerRadius }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.skeletonCornerRadius) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+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