Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8448a53b79 | |||
| 42f0886c77 | |||
| 4e63a2a1e3 | |||
| 9523446c53 | |||
| dd66df733c | |||
| 8021f1260b | |||
| 3f3b4f498b | |||
| 6a07b423a9 | |||
| 6bd03081d6 | |||
| 8f891f02b0 | |||
| 9ef6d3e48d | |||
| 1862179c44 | |||
| fc66cc5c0b | |||
| ef48129ef0 | |||
| e24969220f | |||
| e123bed684 | |||
| dbb570dab7 | |||
| cdbb4dc6a0 | |||
| b6c1c89645 | |||
| f583fe3316 | |||
| befa9b0442 | |||
| 07075f02d1 | |||
| b8f52e7ce5 | |||
| 9e3396d934 | |||
| 5a68d03af4 | |||
| 91b4ac2bf6 | |||
| f6d28d9492 |
@@ -0,0 +1,23 @@
|
||||
⚠️ Please fill out this template when filing an issue.
|
||||
|
||||
### What did you do?
|
||||
|
||||
*Please replace this with what you did.*
|
||||
|
||||
### What did you expect to happen?
|
||||
|
||||
*Please replace this with what you expected to happen.*
|
||||
|
||||
### What happened instead?
|
||||
|
||||
*Please replace this with of what happened instead.*
|
||||
|
||||
### Steps to reproduce the behavior
|
||||
|
||||
*Please replace this with the steps to reproduce the behavior.*
|
||||
|
||||
### SkeletonView Environment
|
||||
|
||||
**SkeletonView version:**
|
||||
**Xcode version:**
|
||||
**Swift version:**
|
||||
@@ -0,0 +1,6 @@
|
||||
language: objective-c
|
||||
osx_image: xcode9
|
||||
script:
|
||||
- xcodebuild -project SkeletonView.xcodeproj -target SkeletonView-iOS -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 7,OS=11'
|
||||
- xcodebuild -project SkeletonView.xcodeproj -target SkeletonViewExample -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 7,OS=11'
|
||||
after_success:
|
||||
|
Before Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 29 KiB |
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.1</string>
|
||||
<string>1.0.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.1</string>
|
||||
<string>1.0.4</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -96,6 +96,9 @@
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="lastLineFillPercent">
|
||||
<integer key="value" value="40"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="nMj-pU-5wJ">
|
||||
@@ -195,6 +198,7 @@
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="BEI-dU-kr2"/>
|
||||
<connections>
|
||||
<outlet property="avatarImage" destination="nMj-pU-5wJ" id="9fa-Z7-vYi"/>
|
||||
<outlet property="colorSelectedView" destination="iGp-rp-t1d" id="0Zm-9d-jRU"/>
|
||||
<outlet property="skeletonTypeSelector" destination="xOL-Sq-r4i" id="yTr-8L-I4Y"/>
|
||||
<outlet property="switchAnimated" destination="vz0-qg-GcZ" id="d2R-8x-lRb"/>
|
||||
@@ -203,7 +207,7 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-594.39999999999998" y="85.007496251874073"/>
|
||||
<point key="canvasLocation" x="-482" y="-6"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
||||
@@ -14,6 +14,14 @@ let colors = [(UIColor.turquoise,"turquoise"), (UIColor.emerald,"emerald"), (UIC
|
||||
class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var tableview: UITableView!
|
||||
|
||||
@IBOutlet weak var avatarImage: UIImageView! {
|
||||
didSet {
|
||||
avatarImage.layer.cornerRadius = avatarImage.frame.width/2
|
||||
avatarImage.layer.masksToBounds = true
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var colorSelectedView: UIView! {
|
||||
didSet {
|
||||
colorSelectedView.layer.cornerRadius = 5
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/Juanpe/SkeletonView">
|
||||
<img src="https://img.shields.io/travis/Juanpe/SkeletonView.svg">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/Swift-4.0-orange.svg" />
|
||||
<a href="https://cocoapods.org/pods/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/v/SkeletonView.svg" alt="CocoaPods" />
|
||||
@@ -8,14 +11,39 @@
|
||||
<a href="https://github.com/Carthage/Carthage">
|
||||
<img src="https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat" alt="Carthage" />
|
||||
</a>
|
||||
<a href="https://twitter.com/juanpe_catalan">
|
||||
<img src="https://img.shields.io/badge/contact-@juanpe_catalan-blue.svg?style=flat" alt="Twitter: @juanpe_catalan" />
|
||||
<a href="https://twitter.com/JuanpeCatalan">
|
||||
<img src="https://img.shields.io/badge/contact-@JuanpeCatalan-blue.svg?style=flat" alt="Twitter: @JuanpeCatalan" />
|
||||
</a>
|
||||
<a href="https://opensource.org/licenses/MIT">
|
||||
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License" />
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License" />
|
||||
</a>
|
||||
<a href="https://twitter.com/intent/tweet?text=Wow%20This%20library%20is%20awesome:&url=https%3A%2F%2Fgithub.com%2FJuanpe%2FSkeletonView">
|
||||
<img src="https://img.shields.io/twitter/url/https/github.com/Juanpe/SkeletonView.svg?style=social" alt="License" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Today almost all apps have async processes, such as Api requests, long running processes, etc. And while the processes are working, usually developers place a loading view to show users that something is going on.
|
||||
|
||||
```SkeletonView``` has been conceived to address this need, an elegant way to show users that something is happening and also prepare them to which contents he is waiting.
|
||||
|
||||
Enjoy it! 🙂
|
||||
|
||||
* [Features](#-features)
|
||||
* [Requirements](#-supported-os--sdk-versions)
|
||||
* [Example Project](#-example)
|
||||
* [Installation](#-installation)
|
||||
* [Cocoapods](#using-cocoapods)
|
||||
* [Carthage](#using-carthage)
|
||||
* [How to use](#-how-to-use)
|
||||
* [Collections](#-collections)
|
||||
* [Multiline text](#-multiline-text)
|
||||
* [Custom colors](#-custom-colors)
|
||||
* [Custom animations](#-custom-animations)
|
||||
* [Hierarchy](#-hierarchy)
|
||||
* [Documentation](#-documentation)
|
||||
* [Contributed](#-contributed)
|
||||
* [Author](#-author)
|
||||
* [License](#-license)
|
||||
|
||||
|
||||
## 🌟 Features
|
||||
@@ -31,23 +59,13 @@
|
||||
### 📋 Supported OS & SDK Versions
|
||||
|
||||
* iOS 9.0+
|
||||
* Swift 4
|
||||
|
||||
### 🎤 Introduction
|
||||
|
||||
Today almost all apps have async processes, such as Api requests, long running processes, etc. And while the processes are working, usually developers place a loading view to show users that something is going on.
|
||||
|
||||
```SkeletonView``` has been conceived to address this need, an elegant way to show users that something is happening and also prepare them to which contents he is waiting.
|
||||
|
||||
Enjoy it! 🙂
|
||||
|
||||
###### Project generated with [SwiftPlate](https://github.com/JohnSundell/SwiftPlate)
|
||||
* Swift 4 (Swift 3 compatible)
|
||||
|
||||
### 🔮 Example
|
||||
|
||||
To run the example project, clone the repo and run `SkeletonViewExample` target.
|
||||
|
||||

|
||||

|
||||
|
||||
## 📲 Installation
|
||||
|
||||
@@ -144,7 +162,7 @@ public protocol SkeletonTableViewDataSource: UITableViewDataSource {
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdenfierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
}
|
||||
```
|
||||
As you can see, this protocol inherits from ```UITableViewDataSource``, so you can replace this protocol with the skeleton protocol.
|
||||
As you can see, this protocol inherits from ```UITableViewDataSource```, so you can replace this protocol with the skeleton protocol.
|
||||
|
||||
This protocol has a default implementation:
|
||||
|
||||
@@ -174,18 +192,32 @@ There is only one method you need to implement to let Skeleton know the cell ide
|
||||
### 📰 Multiline text
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||
**NEW** Now, you can set the filling percent of the last line. **Default: 80%**
|
||||
|
||||
To modify the percent **using code**, set the property:
|
||||
```swift
|
||||
descriptionTextView.lastLineFillPercent = 50
|
||||
```
|
||||
|
||||
Or, if you prefer use **IB/Storyboard**:
|
||||
|
||||

|
||||
|
||||
|
||||
### 🎨 Custom colors
|
||||
|
||||
You can decide which color the skeleton is tinted with. You only need to pass as a parameter the color or gradient you want.
|
||||
|
||||
**Using solid colors**
|
||||
``` swift
|
||||
view.showSkeleton(usingColor: UIColor.midnightBlue) // Solid
|
||||
view.showSkeleton(usingColor: UIColor.gray) // Solid
|
||||
// or
|
||||
view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0))
|
||||
```
|
||||
**Using gradients**
|
||||
``` swift
|
||||
@@ -193,7 +225,9 @@ let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue)
|
||||
view.showGradientSkeleton(usingGradient: gradient) // Gradient
|
||||
```
|
||||
|
||||
```SkeletonView``` features 20 flat colors 🤙🏼:
|
||||
Besides, ```SkeletonView``` features 20 flat colors 🤙🏼
|
||||
|
||||
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
|
||||
|
||||

|
||||
###### Image captured from website [https://flatuicolors.com](https://flatuicolors.com)
|
||||
@@ -222,6 +256,33 @@ view.showAnimatedSkeleton { (layer) -> CAAnimation in
|
||||
}
|
||||
```
|
||||
|
||||
**NEW** It's available ```SkeletonAnimationBuilder```. It's a builder to make ```SkeletonLayerAnimation```.
|
||||
|
||||
Today, you can create **sliding animations** for gradients, deciding the **direction** and setting the **duration** of the animation (default = 1.5s).
|
||||
|
||||
```swift
|
||||
// func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation
|
||||
|
||||
let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight)
|
||||
view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
|
||||
|
||||
```
|
||||
|
||||
```GradientDirection``` is an enum, with this cases:
|
||||
|
||||
| Direction | Preview
|
||||
|------- | -------
|
||||
| .leftRight | 
|
||||
| .rightLeft | 
|
||||
| .topBottom | 
|
||||
| .bottomTop | 
|
||||
| .topLeftBottomRight | 
|
||||
| .bottomRightTopLeft | 
|
||||
|
||||
> **😉 TRICK!**
|
||||
Exist another way to create sliding animations, just using this shortcut:
|
||||
>>```let animation = GradientDirection.leftToRight.slidingAnimation()```
|
||||
|
||||
### 👨👧👦 Hierarchy
|
||||
|
||||
Since ```SkeletonView``` is recursive, and we want skeleton to be very efficient, we want to stop recursion as soon as possible. For this reason, you must set the container view as `Skeletonable`, because Skeleton will stop looking for `skeletonable` subviews as soon as a view is not Skeletonable, breaking then the recursion.
|
||||
@@ -250,9 +311,11 @@ This is an open source project, so feel free to contribute. How?
|
||||
|
||||
See [all contributors](https://github.com/Juanpe/SkeletonView/graphs/contributors)
|
||||
|
||||
###### Project generated with [SwiftPlate](https://github.com/JohnSundell/SwiftPlate)
|
||||
|
||||
## 👨🏻💻 Author
|
||||
[1.1]: http://i.imgur.com/tXSoThF.png
|
||||
[1]: http://www.twitter.com/juanpe_catalan
|
||||
[1]: http://www.twitter.com/JuanpeCatalan
|
||||
|
||||
* Juanpe Catalán [![alt text][1.1]][1]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.0.1"
|
||||
s.version = "1.0.4"
|
||||
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.
|
||||
@@ -9,7 +9,7 @@ Pod::Spec.new do |s|
|
||||
s.homepage = "https://github.com/Juanpe/SkeletonView"
|
||||
s.license = { :type => "MIT", :file => "LICENSE" }
|
||||
s.author = { "Juanpe Catalán" => "juanpecm@gmail.com" }
|
||||
s.social_media_url = "https://twitter.com/juanpe_catalan"
|
||||
s.social_media_url = "https://twitter.com/JuanpeCatalan"
|
||||
s.ios.deployment_target = "9.0"
|
||||
s.source = { :git => "https://github.com/Juanpe/SkeletonView.git", :tag => s.version.to_s }
|
||||
s.source_files = "Sources/**/*"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
8933C7851EB5B820000D00A4 /* SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* SkeletonView.swift */; };
|
||||
F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */; };
|
||||
F5307E2C1FAF6BC900EE67C5 /* SkeletonGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2B1FAF6BC900EE67C5 /* SkeletonGradient.swift */; };
|
||||
F5307E2E1FB0E5E400EE67C5 /* UIView+Frame.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */; };
|
||||
F5307E301FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2F1FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift */; };
|
||||
@@ -61,6 +62,7 @@
|
||||
52D6D97C1BEFF229002C0205 /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8933C7841EB5B820000D00A4 /* SkeletonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkeletonView.swift; sourceTree = "<group>"; };
|
||||
AD2FAA261CD0B6D800659CF4 /* SkeletonView.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = SkeletonView.plist; sourceTree = "<group>"; };
|
||||
F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonAnimationBuilder.swift; sourceTree = "<group>"; };
|
||||
F5307E2B1FAF6BC900EE67C5 /* SkeletonGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonGradient.swift; sourceTree = "<group>"; };
|
||||
F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Frame.swift"; sourceTree = "<group>"; };
|
||||
F5307E2F1FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonDefaultConfig.swift; sourceTree = "<group>"; };
|
||||
@@ -140,6 +142,7 @@
|
||||
F5307E331FB1068500EE67C5 /* Collections */,
|
||||
F5307E341FB106A500EE67C5 /* Extensions */,
|
||||
F5307E351FB106BF00EE67C5 /* Helpers */,
|
||||
F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */,
|
||||
F5307E2F1FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift */,
|
||||
F5307E2B1FAF6BC900EE67C5 /* SkeletonGradient.swift */,
|
||||
F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */,
|
||||
@@ -349,6 +352,7 @@
|
||||
F5307E3B1FB123C100EE67C5 /* ContainsMultilineText.swift in Sources */,
|
||||
F5F899E91FAB9D2B002E8FDA /* SkeletonLayer.swift in Sources */,
|
||||
F5307E2E1FB0E5E400EE67C5 /* UIView+Frame.swift in Sources */,
|
||||
F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -36,10 +36,16 @@ extension CALayer {
|
||||
return sublayers?.filter { $0.name == CALayer.skeletonSubLayersName } ?? [CALayer]()
|
||||
}
|
||||
|
||||
func addMultilinesLayers(lines: Int, type: SkeletonType) {
|
||||
func addMultilinesLayers(lines: Int, type: SkeletonType, lastLineFillPercent: Int) {
|
||||
let numberOfSublayers = calculateNumLines(maxLines: lines)
|
||||
for index in 0..<numberOfSublayers {
|
||||
let layer = SkeletonLayerFactory().makeMultilineLayer(withType: type, for: index, width: Int(bounds.width))
|
||||
var width = bounds.width
|
||||
|
||||
if index == numberOfSublayers-1 && numberOfSublayers != 1 {
|
||||
width = width * CGFloat(lastLineFillPercent)/100;
|
||||
}
|
||||
|
||||
let layer = SkeletonLayerFactory().makeMultilineLayer(withType: type, for: index, width: width)
|
||||
addSublayer(layer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ extension UIView {
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.status, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
private var skeletonable: Bool! {
|
||||
fileprivate var skeletonable: Bool! {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.skeletonable) as? Bool ?? false }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.skeletonable, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
@@ -8,17 +8,51 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
private enum AssociatedKeys {
|
||||
static var lastLineFillingPercent = "lastLineFillingPercent"
|
||||
}
|
||||
|
||||
protocol ContainsMultilineText {
|
||||
var numLines: Int { get }
|
||||
var lastLineFillingPercent: Int { get }
|
||||
}
|
||||
|
||||
extension ContainsMultilineText {
|
||||
var numLines: Int { return 0 }
|
||||
}
|
||||
|
||||
public extension UILabel {
|
||||
|
||||
@IBInspectable
|
||||
var lastLineFillPercent: Int {
|
||||
get { return lastLineFillingPercent }
|
||||
set { lastLineFillingPercent = min(newValue, 100) }
|
||||
}
|
||||
}
|
||||
|
||||
public extension UITextView {
|
||||
|
||||
@IBInspectable
|
||||
var lastLineFillPercent: Int {
|
||||
get { return lastLineFillingPercent }
|
||||
set { lastLineFillingPercent = min(newValue, 100) }
|
||||
}
|
||||
}
|
||||
|
||||
extension UILabel: ContainsMultilineText {
|
||||
var numLines: Int {
|
||||
return numberOfLines
|
||||
}
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonDefaultConfig.multilineLastLineFillPercent }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
}
|
||||
extension UITextView: ContainsMultilineText {
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonDefaultConfig.multilineLastLineFillPercent }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
}
|
||||
extension UITextView: ContainsMultilineText {}
|
||||
|
||||
@@ -15,12 +15,14 @@ protocol PrepareForSkeleton {
|
||||
extension UILabel: PrepareForSkeleton {
|
||||
func prepareViewForSkeleton() {
|
||||
text = nil
|
||||
resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
extension UITextView: PrepareForSkeleton {
|
||||
func prepareViewForSkeleton() {
|
||||
text = nil
|
||||
resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// SkeletonAnimationBuilder.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 17/11/2017.
|
||||
// Copyright © 2017 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
typealias GradientAnimationPoint = (from: CGPoint, to: CGPoint)
|
||||
|
||||
public enum GradientDirection {
|
||||
case leftRight
|
||||
case rightLeft
|
||||
case topBottom
|
||||
case bottomTop
|
||||
case topLeftBottomRight
|
||||
case bottomRightTopLeft
|
||||
|
||||
public func slidingAnimation(duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation {
|
||||
return SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: self, duration: duration)
|
||||
}
|
||||
|
||||
var startPoint: GradientAnimationPoint {
|
||||
switch self {
|
||||
case .leftRight:
|
||||
return (from: CGPoint(x:-1, y:0.5), to: CGPoint(x:1, y:0.5))
|
||||
case .rightLeft:
|
||||
return (from: CGPoint(x:1, y:0.5), to: CGPoint(x:-1, y:0.5))
|
||||
case .topBottom:
|
||||
return (from: CGPoint(x:0.5, y:-1), to: CGPoint(x:0.5, y:1))
|
||||
case .bottomTop:
|
||||
return (from: CGPoint(x:0.5, y:1), to: CGPoint(x:0.5, y:-1))
|
||||
case .topLeftBottomRight:
|
||||
return (from: CGPoint(x:-1, y:-1), to: CGPoint(x:1, y:1))
|
||||
case .bottomRightTopLeft:
|
||||
return (from: CGPoint(x:1, y:1), to: CGPoint(x:-1, y:-1))
|
||||
}
|
||||
}
|
||||
|
||||
var endPoint: GradientAnimationPoint {
|
||||
switch self {
|
||||
case .leftRight:
|
||||
return (from: CGPoint(x:0, y:0.5), to: CGPoint(x:2, y:0.5))
|
||||
case .rightLeft:
|
||||
return ( from: CGPoint(x:2, y:0.5), to: CGPoint(x:0, y:0.5))
|
||||
case .topBottom:
|
||||
return ( from: CGPoint(x:0.5, y:0), to: CGPoint(x:0.5, y:2))
|
||||
case .bottomTop:
|
||||
return ( from: CGPoint(x:0.5, y:2), to: CGPoint(x:0.5, y:0))
|
||||
case .topLeftBottomRight:
|
||||
return ( from: CGPoint(x:0, y:0), to: CGPoint(x:2, y:2))
|
||||
case .bottomRightTopLeft:
|
||||
return ( from: CGPoint(x:2, y:2), to: CGPoint(x:0, y:0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SkeletonAnimationBuilder {
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
public func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation {
|
||||
return { layer in
|
||||
|
||||
let startPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.startPoint))
|
||||
startPointAnim.fromValue = NSValue(cgPoint: direction.startPoint.from)
|
||||
startPointAnim.toValue = NSValue(cgPoint: direction.startPoint.to)
|
||||
|
||||
let endPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.endPoint))
|
||||
endPointAnim.fromValue = NSValue(cgPoint: direction.endPoint.from)
|
||||
endPointAnim.toValue = NSValue(cgPoint: direction.endPoint.to)
|
||||
|
||||
let animGroup = CAAnimationGroup()
|
||||
animGroup.animations = [startPointAnim, endPointAnim]
|
||||
animGroup.duration = duration
|
||||
animGroup.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
|
||||
animGroup.repeatCount = .infinity
|
||||
|
||||
return animGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,9 @@ public enum SkeletonDefaultConfig {
|
||||
|
||||
public static let gradient = SkeletonGradient(baseColor: tintColor)
|
||||
|
||||
public static let multilineHeight = 15
|
||||
public static let multilineHeight: CGFloat = 15
|
||||
|
||||
public static let multilineSpacing = 10
|
||||
public static let multilineSpacing: CGFloat = 10
|
||||
|
||||
public static let multilineLastLineFillPercent = 70
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ class SkeletonLayerFactory {
|
||||
return SkeletonLayer(withType: type, usingColors: colors, andSkeletonHolder: holder)
|
||||
}
|
||||
|
||||
func makeMultilineLayer(withType type: SkeletonType, for index: Int, width: Int) -> CALayer {
|
||||
let spaceRequitedForEachLine = SkeletonDefaultConfig.multilineHeight + SkeletonDefaultConfig.multilineSpacing
|
||||
func makeMultilineLayer(withType type: SkeletonType, for index: Int, width: CGFloat) -> CALayer {
|
||||
let spaceRequiredForEachLine = SkeletonDefaultConfig.multilineHeight + SkeletonDefaultConfig.multilineSpacing
|
||||
let layer = type.layer
|
||||
layer.anchorPoint = .zero
|
||||
layer.name = CALayer.skeletonSubLayersName
|
||||
layer.frame = CGRect(x: 0, y: (index * spaceRequitedForEachLine), width: width, height: SkeletonDefaultConfig.multilineHeight)
|
||||
layer.frame = CGRect(x: 0.0, y: CGFloat(index) * spaceRequiredForEachLine, width: width, height: SkeletonDefaultConfig.multilineHeight)
|
||||
return layer
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ struct SkeletonLayer {
|
||||
|
||||
func addMultilinesIfNeeded() {
|
||||
guard let multiLineView = holder as? ContainsMultilineText else { return }
|
||||
maskLayer.addMultilinesLayers(lines: multiLineView.numLines, type: type)
|
||||
maskLayer.addMultilinesLayers(lines: multiLineView.numLines, type: type, lastLineFillPercent: multiLineView.lastLineFillingPercent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ public extension UIView {
|
||||
|
||||
func hideSkeleton(reloadDataAfter reload: Bool = true) {
|
||||
removeDummyDataSourceIfNeeded(reloadAfter: reload)
|
||||
isUserInteractionEnabled = true
|
||||
recursiveSearch(inArray: subviewsSkeletonables,
|
||||
leafBlock: { removeSkeletonLayer() },
|
||||
recursiveBlock: {
|
||||
@@ -57,6 +58,7 @@ extension UIView {
|
||||
recursiveSearch(inArray: subviewsSkeletonables,
|
||||
leafBlock: {
|
||||
guard !isSkeletonActive else { return }
|
||||
isUserInteractionEnabled = false
|
||||
(self as? PrepareForSkeleton)?.prepareViewForSkeleton()
|
||||
addSkeletonLayer(withType: type, usingColors: colors, animated: animated, animation: animation)
|
||||
}) {
|
||||
@@ -64,14 +66,14 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
private func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock {
|
||||
fileprivate func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock {
|
||||
return {
|
||||
guard let layer = self.skeletonLayer else { return }
|
||||
layer.start(anim)
|
||||
}
|
||||
}
|
||||
|
||||
private var stopSkeletonLayerAnimationBlock: VoidBlock {
|
||||
fileprivate var stopSkeletonLayerAnimationBlock: VoidBlock {
|
||||
return {
|
||||
guard let layer = self.skeletonLayer else { return }
|
||||
layer.stopAnimation()
|
||||
@@ -105,7 +107,7 @@ extension UIStackView {
|
||||
|
||||
extension UIView {
|
||||
|
||||
func addSkeletonLayer(withType type: SkeletonType, usingColors colors: [UIColor], animated: Bool, animation: SkeletonLayerAnimation? = nil) {
|
||||
func addSkeletonLayer(withType type: SkeletonType, usingColors colors: [UIColor], gradientDirection direction: GradientDirection? = nil, animated: Bool, animation: SkeletonLayerAnimation? = nil) {
|
||||
self.skeletonLayer = SkeletonLayerFactory().makeLayer(withType: type, usingColors: colors, andHolder: self)
|
||||
layer.insertSublayer(skeletonLayer!.contentLayer, at: UInt32.max)
|
||||
if animated { skeletonLayer!.start(animation) }
|
||||
|
||||