Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ba48071de | |||
| a0980380f2 | |||
| dd80fabeea | |||
| d89a70052c | |||
| cd12c64e18 | |||
| 2edd715a87 | |||
| f3cc8f0aba | |||
| a9c22f502a | |||
| ee17db61c6 | |||
| ff1a7e299b | |||
| 4994907234 | |||
| 17fb1b9950 |
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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.<br/>Please note that for views without multiple lines, the single line will be considered as the last line and **lastLineFillPercent** will be applied to that single line. | `0...100` | `70%`| 
|
||||
| **Corner radius** of lines. (**NEW**) | `0...10` | `0` | 
|
||||
| **lastLineFillPercent** | `CGFloat` | `70`| 
|
||||
| **linesCornerRadius** | `Int` | `0` | 
|
||||
| **skeletonLineSpacing** | `CGFloat` | `10` | 
|
||||
| **skeletonPaddingInsets** | `UIEdgeInsets` | `.zero` | 
|
||||
| **skeletonTextLineHeight** | `SkeletonTextLineHeight` | `.fixed(15)` | 
|
||||
|
||||
<br />
|
||||
|
||||
> **⚠️ DEPRECATED!**
|
||||
>
|
||||
> **useFontLineHeight** has been deprecated. You can use **skeletonTextLineHeight** instead:
|
||||
> ```swift
|
||||
> descriptionTextView.skeletonTextLineHeight = .relativeToFont
|
||||
> ```
|
||||
|
||||
> **📣 IMPORTANT!**
|
||||
>
|
||||
> Please note that for views without multiple lines, the single line will be considered
|
||||
> as the last line.
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
To modify the percent or radius **using code**, set the properties:
|
||||
```swift
|
||||
@@ -289,21 +306,19 @@ Or, if you prefer use **IB/Storyboard**:
|
||||
The skeletons have a default appearance. So, when you don't specify the color, gradient or multilines properties, `SkeletonView` uses the default values.
|
||||
|
||||
Default values:
|
||||
- **tintColor**: UIColor
|
||||
- **tintColor**: `UIColor`
|
||||
- *default: `.skeletonDefault` (same as `.clouds` but adaptive to dark mode)*
|
||||
- **gradient**: SkeletonGradient
|
||||
- *default: `SkeletonGradient(baseColor: .skeletonDefault)`*
|
||||
- **multilineHeight**: CGFloat
|
||||
- **multilineHeight**: `CGFloat`
|
||||
- *default: 15*
|
||||
- **useFontLineHeight**: Bool
|
||||
- *default: true*
|
||||
- **multilineSpacing**: CGFloat
|
||||
- **multilineSpacing**: `CGFloat`
|
||||
- *default: 10*
|
||||
- **multilineLastLineFillPercent**: Int
|
||||
- **multilineLastLineFillPercent**: `Int`
|
||||
- *default: 70*
|
||||
- **multilineCornerRadius**: Int
|
||||
- **multilineCornerRadius**: `Int`
|
||||
- *default: 0*
|
||||
- **skeletonCornerRadius**: CGFloat (IBInspectable) (Make your skeleton view with corner)
|
||||
- **skeletonCornerRadius**: `CGFloat` (IBInspectable) (Make your skeleton view with corner)
|
||||
- *default: 0*
|
||||
|
||||
To get these default values you can use `SkeletonAppearance.default`. Using this property you can set the values as well:
|
||||
@@ -312,12 +327,12 @@ SkeletonAppearance.default.multilineHeight = 20
|
||||
SkeletonAppearance.default.tintColor = .green
|
||||
```
|
||||
|
||||
You can also specifiy these line appearance properties on a per-label basis:
|
||||
- **lastLineFillPercent**: Int
|
||||
- **linesCornerRadius**: Int
|
||||
- **skeletonLineSpacing**: CGFloat
|
||||
- **skeletonPaddingInsets**: UIEdgeInsets
|
||||
- **useFontLineHeight**: Bool
|
||||
> **⚠️ DEPRECATED!**
|
||||
>
|
||||
> **useFontLineHeight** has been deprecated. You can use **textLineHeight** instead:
|
||||
> ```swift
|
||||
> SkeletonAppearance.default.textLineHeight = .relativeToFont
|
||||
> ```
|
||||
|
||||
|
||||
### 🎨 Custom colors
|
||||
@@ -464,16 +479,6 @@ view.showSkeleton()
|
||||
|
||||
|
||||
|
||||
**Hierarchy in collections**
|
||||
|
||||
Here is an illustration that shows how you should specify which elements are skeletonables when you are using an `UITableView`:
|
||||
|
||||
<img src="Assets/tableview_scheme.png" width="700px">
|
||||
|
||||
As you can see, we have to make skeletonable the tableview, the cell and the UI elements, but we don't need to set as skeletonable the `contentView`
|
||||
|
||||
|
||||
|
||||
**Skeleton views layout**
|
||||
|
||||
Sometimes skeleton layout may not fit your layout because the parent view bounds have changed. ~For example, rotating the device.~
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.25.0"
|
||||
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.
|
||||
|
||||
@@ -99,6 +99,8 @@
|
||||
F556F6E126CE367600A80B83 /* UIView+SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */; };
|
||||
F556F6F626CE876300A80B83 /* UIView+Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */; };
|
||||
F556F70826D38F3100A80B83 /* SkeletonTreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */; };
|
||||
F5C84884274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */; };
|
||||
F5C84885274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */; };
|
||||
OBJ_101 /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* Int+Extensions.swift */; };
|
||||
OBJ_103 /* UIColor+Skeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* UIColor+Skeleton.swift */; };
|
||||
OBJ_104 /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* UITableView+Extensions.swift */; };
|
||||
@@ -208,6 +210,7 @@
|
||||
F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Swizzling.swift"; sourceTree = "<group>"; };
|
||||
F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+SkeletonView.swift"; sourceTree = "<group>"; };
|
||||
F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTreeNode.swift; sourceTree = "<group>"; };
|
||||
F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTextLineHeight.swift; sourceTree = "<group>"; };
|
||||
OBJ_11 /* SkeletonLayerBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonLayerBuilder.swift; sourceTree = "<group>"; };
|
||||
OBJ_12 /* SkeletonMultilineLayerBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonMultilineLayerBuilder.swift; sourceTree = "<group>"; };
|
||||
OBJ_14 /* CollectionSkeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionSkeleton.swift; sourceTree = "<group>"; };
|
||||
@@ -515,6 +518,7 @@
|
||||
OBJ_55 /* SkeletonGradient.swift */,
|
||||
F556F6B826CE262700A80B83 /* GradientDirection.swift */,
|
||||
F556F6C126CE27FD00A80B83 /* SkeletonType.swift */,
|
||||
F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -822,6 +826,7 @@
|
||||
F556F6B626CE258300A80B83 /* GradientDirection+Animations.swift in Sources */,
|
||||
F556F69F26CD553B00A80B83 /* UIView+Extensions.swift in Sources */,
|
||||
F556F58526CD1F3900A80B83 /* Recoverable.swift in Sources */,
|
||||
F5C84885274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */,
|
||||
F556F58626CD1F3900A80B83 /* RecoverableViewState.swift in Sources */,
|
||||
F556F58726CD1F3900A80B83 /* SkeletonAnimationBuilder.swift in Sources */,
|
||||
F556F58826CD1F3900A80B83 /* SkeletonConfig.swift in Sources */,
|
||||
@@ -904,6 +909,7 @@
|
||||
F556F69E26CD553B00A80B83 /* UIView+Extensions.swift in Sources */,
|
||||
OBJ_117 /* Recoverable.swift in Sources */,
|
||||
OBJ_118 /* RecoverableViewState.swift in Sources */,
|
||||
F5C84884274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */,
|
||||
OBJ_119 /* SkeletonAnimationBuilder.swift in Sources */,
|
||||
OBJ_120 /* SkeletonConfig.swift in Sources */,
|
||||
OBJ_121 /* SkeletonFlowHandler.swift in Sources */,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "9999"
|
||||
LastUpgradeVersion = "1300"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "9999"
|
||||
LastUpgradeVersion = "1300"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -26,8 +26,8 @@ public class SkeletonViewAppearance {
|
||||
public var gradient = SkeletonGradient(baseColor: .skeletonDefault)
|
||||
|
||||
public var multilineHeight: CGFloat = 15
|
||||
|
||||
public var useFontLineHeight: Bool = true
|
||||
|
||||
public lazy var textLineHeight: SkeletonTextLineHeight = .fixed(SkeletonAppearance.default.multilineHeight)
|
||||
|
||||
public var multilineSpacing: CGFloat = 10
|
||||
|
||||
|
||||
@@ -48,3 +48,47 @@ public extension UIView {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension UILabel {
|
||||
|
||||
@IBInspectable
|
||||
@available(*, deprecated, renamed: "skeletonTextLineHeight")
|
||||
var useFontLineHeight: Bool {
|
||||
get {
|
||||
textLineHeight == .relativeToFont
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension UITextView {
|
||||
|
||||
@IBInspectable
|
||||
@available(*, deprecated, renamed: "skeletonTextLineHeight")
|
||||
var useFontLineHeight: Bool {
|
||||
get {
|
||||
textLineHeight == .relativeToFont
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension SkeletonViewAppearance {
|
||||
|
||||
@available(*, deprecated, renamed: "textLineHeight")
|
||||
var useFontLineHeight: Bool {
|
||||
get {
|
||||
textLineHeight == .relativeToFont
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright SkeletonView. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://opensource.org/licenses/MIT
|
||||
//
|
||||
// SkeletonTextLineHeight.swift
|
||||
//
|
||||
// Created by Juanpe Catalán on 22/11/21.
|
||||
|
||||
import UIKit
|
||||
|
||||
public enum SkeletonTextLineHeight: Equatable {
|
||||
|
||||
/// Calculates the line height based on the font line height.
|
||||
case relativeToFont
|
||||
|
||||
/// Calculates the line height based on the height constraints.
|
||||
///
|
||||
/// If no constraints exist, the height will be set to the `multilineHeight`
|
||||
/// value defined in the `SkeletonAppearance`.
|
||||
case relativeToConstraints
|
||||
|
||||
/// Returns the specific height specified as the associated value.
|
||||
case fixed(CGFloat)
|
||||
|
||||
}
|
||||
@@ -33,10 +33,4 @@ public extension UILabel {
|
||||
set { multilineSpacing = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var useFontLineHeight: Bool {
|
||||
get { usesTextHeightForLines }
|
||||
set { usesTextHeightForLines = newValue }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,21 @@ import UIKit
|
||||
public extension UILabel {
|
||||
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
get {
|
||||
paddingInsets
|
||||
}
|
||||
set {
|
||||
paddingInsets = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var skeletonTextLineHeight: SkeletonTextLineHeight {
|
||||
get {
|
||||
textLineHeight
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,11 +32,5 @@ public extension UITextView {
|
||||
get { return multilineSpacing }
|
||||
set { multilineSpacing = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var useFontLineHeight: Bool {
|
||||
get { usesTextHeightForLines }
|
||||
set { usesTextHeightForLines = newValue }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,21 @@ import UIKit
|
||||
public extension UITextView {
|
||||
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
get {
|
||||
paddingInsets
|
||||
}
|
||||
set {
|
||||
paddingInsets = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var skeletonTextLineHeight: SkeletonTextLineHeight {
|
||||
get {
|
||||
textLineHeight
|
||||
}
|
||||
set {
|
||||
textLineHeight = newValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -51,13 +51,13 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
|
||||
let fakeCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
|
||||
|
||||
originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: fakeCell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: fakeCell)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: tableView, view: fakeCell)
|
||||
|
||||
return fakeCell
|
||||
}
|
||||
|
||||
originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: cell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: cell)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: tableView, view: cell)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
@@ -88,13 +88,13 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
let fakeCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
|
||||
|
||||
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: fakeCell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: fakeCell)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: fakeCell)
|
||||
|
||||
return fakeCell
|
||||
}
|
||||
|
||||
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: cell, at: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: cell)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: cell)
|
||||
return cell
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
at indexPath: IndexPath) -> UICollectionReusableView {
|
||||
if let viewIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, supplementaryViewIdentifierOfKind: kind, at: indexPath) {
|
||||
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: viewIdentifier, for: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: view)
|
||||
skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: view)
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -113,12 +113,15 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
}
|
||||
|
||||
extension SkeletonCollectionDataSource {
|
||||
private func skeletonViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
|
||||
private func skeletonizeViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
|
||||
guard container.sk.isSkeletonActive,
|
||||
let skeletonConfig = container._currentSkeletonConfig else {
|
||||
return
|
||||
}
|
||||
|
||||
view.showSkeleton(skeletonConfig: skeletonConfig)
|
||||
view.showSkeleton(
|
||||
skeletonConfig: skeletonConfig,
|
||||
notifyDelegate: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,23 @@
|
||||
import UIKit
|
||||
|
||||
class SkeletonCollectionDelegate: NSObject {
|
||||
|
||||
weak var originalTableViewDelegate: SkeletonTableViewDelegate?
|
||||
weak var originalCollectionViewDelegate: SkeletonCollectionViewDelegate?
|
||||
|
||||
init(tableViewDelegate: SkeletonTableViewDelegate? = nil, collectionViewDelegate: SkeletonCollectionViewDelegate? = nil) {
|
||||
init(
|
||||
tableViewDelegate: SkeletonTableViewDelegate? = nil,
|
||||
collectionViewDelegate: SkeletonCollectionViewDelegate? = nil
|
||||
) {
|
||||
self.originalTableViewDelegate = tableViewDelegate
|
||||
self.originalCollectionViewDelegate = collectionViewDelegate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension SkeletonCollectionDelegate: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForHeaderInSection: section))
|
||||
}
|
||||
@@ -42,24 +48,40 @@ extension SkeletonCollectionDelegate: UITableViewDelegate {
|
||||
cell.hideSkeleton()
|
||||
originalTableViewDelegate?.tableView?(tableView, didEndDisplaying: cell, forRowAt: indexPath)
|
||||
}
|
||||
|
||||
private func headerOrFooterView(_ tableView: UITableView, for viewIdentifier: String? ) -> UIView? {
|
||||
guard let viewIdentifier = viewIdentifier, let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier) else { return nil }
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: header)
|
||||
return header
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDelegate
|
||||
extension SkeletonCollectionDelegate: UICollectionViewDelegate { }
|
||||
|
||||
extension SkeletonCollectionDelegate {
|
||||
private func skeletonViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
|
||||
private extension SkeletonCollectionDelegate {
|
||||
|
||||
func skeletonizeViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
|
||||
guard container.sk.isSkeletonActive,
|
||||
let skeletonConfig = container._currentSkeletonConfig else {
|
||||
let skeletonConfig = container._currentSkeletonConfig
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
view.showSkeleton(skeletonConfig: skeletonConfig)
|
||||
view.showSkeleton(
|
||||
skeletonConfig: skeletonConfig,
|
||||
notifyDelegate: false
|
||||
)
|
||||
}
|
||||
|
||||
func headerOrFooterView(_ tableView: UITableView, for viewIdentifier: String? ) -> UIView? {
|
||||
guard let viewIdentifier = viewIdentifier,
|
||||
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
skeletonizeViewIfContainerSkeletonIsActive(
|
||||
container: tableView,
|
||||
view: header
|
||||
)
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,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 ?? SkeletonAppearance.default.useFontLineHeight }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.usesTextHeightForLines) }
|
||||
var textLineHeight: SkeletonTextLineHeight {
|
||||
get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) as? SkeletonTextLineHeight ?? SkeletonAppearance.default.textLineHeight }
|
||||
set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) }
|
||||
}
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
@@ -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 ?? 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 {
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
@@ -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) {
|
||||
@@ -198,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,7 +16,7 @@ import UIKit
|
||||
extension UILabel {
|
||||
|
||||
var desiredHeightBasedOnNumberOfLines: CGFloat {
|
||||
let spaceNeededForEachLine = lineHeight * CGFloat(numberOfLines)
|
||||
let spaceNeededForEachLine = estimatedLineHeight * CGFloat(numberOfLines)
|
||||
let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(numberOfLines - 1)
|
||||
let padding = paddingInsets.top + paddingInsets.bottom
|
||||
|
||||
|
||||
@@ -15,16 +15,30 @@ import UIKit
|
||||
|
||||
extension UIView {
|
||||
|
||||
func showSkeleton(skeletonConfig config: SkeletonConfig) {
|
||||
func showSkeleton(
|
||||
skeletonConfig config: SkeletonConfig,
|
||||
notifyDelegate: Bool = true
|
||||
) {
|
||||
_isSkeletonAnimated = config.animated
|
||||
_flowDelegate = SkeletonFlowHandler()
|
||||
_flowDelegate?.willBeginShowingSkeletons(rootView: self)
|
||||
|
||||
if notifyDelegate {
|
||||
_flowDelegate = SkeletonFlowHandler()
|
||||
_flowDelegate?.willBeginShowingSkeletons(rootView: self)
|
||||
}
|
||||
|
||||
recursiveShowSkeleton(skeletonConfig: config, root: self)
|
||||
}
|
||||
|
||||
func updateSkeleton(skeletonConfig config: SkeletonConfig) {
|
||||
func updateSkeleton(
|
||||
skeletonConfig config: SkeletonConfig,
|
||||
notifyDelegate: Bool = true
|
||||
) {
|
||||
_isSkeletonAnimated = config.animated
|
||||
_flowDelegate?.willBeginUpdatingSkeletons(rootView: self)
|
||||
|
||||
if notifyDelegate {
|
||||
_flowDelegate?.willBeginUpdatingSkeletons(rootView: self)
|
||||
}
|
||||
|
||||
recursiveUpdateSkeleton(skeletonConfig: config, root: self)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,67 +19,17 @@ class SkeletonDebugTests: XCTestCase {
|
||||
func testSkeletonDescriptionWithViewNotSkeletonableNotReturnsSkullEmojiAndChildren() {
|
||||
/// given
|
||||
let view = UIView()
|
||||
let expectedDescription = "<UIView: \(Unmanaged.passUnretained(view).toOpaque())>"
|
||||
let expectedDictionary: [String : Any] = [
|
||||
"isSkeletonable" : false,
|
||||
"type" : "UIView",
|
||||
"reference" : "\(Unmanaged.passUnretained(view).toOpaque())"
|
||||
]
|
||||
|
||||
/// when
|
||||
let obtainedDescription = view.skeletonDescription
|
||||
let obtainedDictionary = view.sk.treeNode.dictionaryRepresentation
|
||||
|
||||
/// then
|
||||
XCTAssertEqual(expectedDescription, obtainedDescription)
|
||||
}
|
||||
|
||||
func testSkeletonDescriptionWithViewSkeletonableReturnsSkullEmojiButNotChildren() {
|
||||
/// given
|
||||
let view = UIView()
|
||||
view.isSkeletonable = true
|
||||
let expectedDescription = "<UIView: \(Unmanaged.passUnretained(view).toOpaque()) | ☠️ >"
|
||||
|
||||
/// when
|
||||
let obtainedDescription = view.skeletonDescription
|
||||
|
||||
/// then
|
||||
XCTAssertEqual(expectedDescription, obtainedDescription)
|
||||
}
|
||||
|
||||
func testSkeletonDescriptionWithViewSkeletonableAndChildrenReturnsSkullEmojiAndChildren() {
|
||||
/// given
|
||||
let view = UIView()
|
||||
let childView = UIView()
|
||||
view.addSubview(childView)
|
||||
view.isSkeletonable = true
|
||||
childView.isSkeletonable = true
|
||||
let expectedDescription = """
|
||||
<UIView: \(Unmanaged.passUnretained(view).toOpaque()) | (1) subSkeletons | ☠️ >
|
||||
"""
|
||||
|
||||
/// when
|
||||
let obtainedDescription = view.skeletonDescription
|
||||
|
||||
/// then
|
||||
XCTAssertEqual(expectedDescription, obtainedDescription)
|
||||
}
|
||||
|
||||
func testSkeletonHierarchyWithSkeletonableViewsReturnsFullHierarchy() {
|
||||
/// given
|
||||
let view = UIView()
|
||||
let childView = UIView()
|
||||
let subchildView = UIView()
|
||||
view.addSubview(childView)
|
||||
childView.addSubview(subchildView)
|
||||
view.isSkeletonable = true
|
||||
childView.isSkeletonable = true
|
||||
subchildView.isSkeletonable = true
|
||||
|
||||
let expectedDescription = "⬇⬇ ☠️ Root view hierarchy with Skeletons ⬇⬇<UIView:\(Unmanaged.passUnretained(view).toOpaque()) | (1) subSkeletons | ☠️ ><UIView: \(Unmanaged.passUnretained(childView).toOpaque()) | (1) subSkeletons | ☠️ ><UIView: \(Unmanaged.passUnretained(subchildView).toOpaque()) | ☠️ >"
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
|
||||
/// when
|
||||
let obtainedDescription = view.skeletonHierarchy()
|
||||
.replacingOccurrences(of: "\n", with: "")
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
|
||||
/// then
|
||||
XCTAssertEqual(expectedDescription, obtainedDescription)
|
||||
XCTAssertEqual(expectedDictionary.keys, obtainedDictionary.keys)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+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