Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 793c79f3e2 | |||
| 20c2fe7e98 | |||
| b3a6fca8fa | |||
| 5bcf7f114e | |||
| 145982894a | |||
| e89de65a2b | |||
| 34b9122e0a | |||
| 9613f36f19 | |||
| 38b42f8713 | |||
| 4c3c996816 | |||
| 3284db700c | |||
| 7958826036 | |||
| fb1a5e3b8e | |||
| fcae5ac799 | |||
| 9063a5bc9f | |||
| 605b1e368f | |||
| 6e769ed0d4 | |||
| e880c00815 | |||
| 75ec8bc1f1 | |||
| 988284f78c | |||
| c41c2f0d41 | |||
| 0fcbdfa575 | |||
| db1e789aa5 | |||
| e3e8d5b51a | |||
| 53adf155b9 | |||
| ad511ee64c | |||
| 86bb15bc25 | |||
| c49e4505c8 | |||
| cfd8d15240 | |||
| 8448a53b79 | |||
| 42f0886c77 | |||
| 4e63a2a1e3 | |||
| 9523446c53 | |||
| dd66df733c | |||
| 8021f1260b | |||
| 3f3b4f498b | |||
| 6a07b423a9 | |||
| 6bd03081d6 | |||
| 13b041b5f1 | |||
| 8f891f02b0 | |||
| 9ef6d3e48d | |||
| 1862179c44 | |||
| fc66cc5c0b | |||
| ef48129ef0 | |||
| e24969220f | |||
| e123bed684 | |||
| dbb570dab7 | |||
| cdbb4dc6a0 | |||
| b6c1c89645 | |||
| f583fe3316 | |||
| befa9b0442 | |||
| 07075f02d1 | |||
| b8f52e7ce5 | |||
| 9e3396d934 | |||
| 5a68d03af4 | |||
| 91b4ac2bf6 | |||
| f6d28d9492 | |||
| 57129f50aa | |||
| fdaa1f52a6 | |||
| 772e1da6c9 | |||
| 86d64764a7 | |||
| d1094c391c | |||
| d1d88747b6 | |||
| 6af1221f4c | |||
| ef180e076c | |||
| 75e46e040d | |||
| cca7126226 |
@@ -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: 19 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 34 KiB |
|
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 |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 34 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 |
@@ -0,0 +1,55 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file
|
||||
|
||||
## [Hotfix (1.1.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.1.1)
|
||||
|
||||
### Bug fixes
|
||||
- Now yes, solved issue [#39](https://github.com/Juanpe/SkeletonView/issues/39)
|
||||
|
||||
## [Needed (1.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.1)
|
||||
|
||||
### New
|
||||
- Now ```SkeletonView```supports **UICollectionViews**! 🎉. (thanks @Renatdz)
|
||||
|
||||
### Bug fixes
|
||||
- Solved issue [#39](https://github.com/Juanpe/SkeletonView/issues/39). Gradient animation did not work when app becomes active.
|
||||
|
||||
|
||||
## [Resizable (1.0.5)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.5)
|
||||
|
||||
### New
|
||||
- Now you can use table views with resizable cells.
|
||||
|
||||
### Bug fixes
|
||||
- Solved issues.
|
||||
[#17](https://github.com/Juanpe/SkeletonView/issues/17),
|
||||
[#30](https://github.com/Juanpe/SkeletonView/issues/30),
|
||||
[#34](https://github.com/Juanpe/SkeletonView/issues/34).
|
||||
|
||||
## [Filled or not (1.0.4)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.4)
|
||||
|
||||
### New
|
||||
- You can set the filling percent of the last line in multiline elements (thanks @jontelang!)
|
||||
|
||||
### Bug fixes
|
||||
- Solved issue [#14](https://github.com/Juanpe/SkeletonView/issues/14). You could edit text views with skeleton active.
|
||||
|
||||
## [In all directions (1.0.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.3)
|
||||
|
||||
### New
|
||||
- Create ```SkeletonAnimationBuilder```, to facilitate the creation of layer animations.
|
||||
```GradientDirection``` enum.
|
||||
|
||||
## [Retro (1.0.2)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.2)
|
||||
|
||||
### New
|
||||
- Change some private keywords, to be Swift 3 compatible
|
||||
|
||||
## [Early bird bug (1.0.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.2)
|
||||
|
||||
### Bug fixes
|
||||
- It was not removing the skeleton layer
|
||||
|
||||
## [Starter (1.0)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0)
|
||||
|
||||
- First release
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>1.1.1</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</string>
|
||||
<string>1.1.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@@ -19,7 +19,7 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="120" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="UCB-SP-lQk">
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="UCB-SP-lQk">
|
||||
<rect key="frame" x="0.0" y="263" width="375" height="264"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="separatorColor" red="0.1061807256" green="0.84678786989999999" blue="0.031482450150000001" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
@@ -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="-897" y="-376"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Constants.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Renato Mendes on 28/11/2017.
|
||||
// Copyright © 2017 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
let colors = [(UIColor.turquoise,"turquoise"), (UIColor.emerald,"emerald"), (UIColor.peterRiver,"peterRiver"), (UIColor.amethyst,"amethyst"),(UIColor.wetAsphalt,"wetAsphalt"), (UIColor.nephritis,"nephritis"), (UIColor.belizeHole,"belizeHole"), (UIColor.wisteria,"wisteria"), (UIColor.midnightBlue,"midnightBlue"), (UIColor.sunFlower,"sunFlower"), (UIColor.carrot,"carrot"), (UIColor.alizarin,"alizarin"),(UIColor.clouds,"clouds"), (UIColor.concrete,"concrete"), (UIColor.flatOrange,"flatOrange"), (UIColor.pumpkin,"pumpkin"), (UIColor.pomegranate,"pomegranate"), (UIColor.silver,"silver"), (UIColor.asbestos,"asbestos")]
|
||||
@@ -9,11 +9,22 @@
|
||||
import UIKit
|
||||
import SkeletonView
|
||||
|
||||
let colors = [(UIColor.turquoise,"turquoise"), (UIColor.emerald,"emerald"), (UIColor.peterRiver,"peterRiver"), (UIColor.amethyst,"amethyst"),(UIColor.wetAsphalt,"wetAsphalt"), (UIColor.nephritis,"nephritis"), (UIColor.belizeHole,"belizeHole"), (UIColor.wisteria,"wisteria"), (UIColor.midnightBlue,"midnightBlue"), (UIColor.sunFlower,"sunFlower"), (UIColor.carrot,"carrot"), (UIColor.alizarin,"alizarin"),(UIColor.clouds,"clouds"), (UIColor.concrete,"concrete"), (UIColor.flatOrange,"flatOrange"), (UIColor.pumpkin,"pumpkin"), (UIColor.pomegranate,"pomegranate"), (UIColor.silver,"silver"), (UIColor.asbestos,"asbestos")]
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var tableview: UITableView!
|
||||
@IBOutlet weak var tableview: UITableView! {
|
||||
didSet {
|
||||
tableview.rowHeight = UITableViewAutomaticDimension
|
||||
tableview.estimatedRowHeight = 120.0
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -29,6 +40,11 @@ class ViewController: UIViewController {
|
||||
return skeletonTypeSelector.selectedSegmentIndex == 0 ? .solid : .gradient
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableview.isSkeletonable = true
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
view.showAnimatedSkeleton()
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// swift-tools-version:4.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "SkeletonView",
|
||||
products: [
|
||||
.library(
|
||||
name: "SkeletonView",
|
||||
targets: ["SkeletonView"]),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "SkeletonView",
|
||||
dependencies: []),
|
||||
.testTarget(
|
||||
name: "SkeletonViewTests",
|
||||
dependencies: ["SkeletonView"]),
|
||||
]
|
||||
)
|
||||
@@ -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,40 @@
|
||||
<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)
|
||||
* [Next steps](#-next-steps)
|
||||
* [Contributed](#-contributed)
|
||||
* [Author](#-author)
|
||||
* [License](#-license)
|
||||
|
||||
|
||||
## 🌟 Features
|
||||
@@ -33,21 +62,11 @@
|
||||
* iOS 9.0+
|
||||
* Swift 4
|
||||
|
||||
### 🎤 Introduction
|
||||
|
||||
Today almost all apps have async process, as API requests, long process, etc. And while the process is working, normally developers put a loading to show users that something is happening.
|
||||
|
||||
For this reason born ```SkeletonView```, an elegant way to show users that something is happening and also prepare users to which contents he is waiting.
|
||||
|
||||
This library try to be a very easy library, and isolated from your code.
|
||||
|
||||
*Project generated with [SwiftPlate](https://github.com/JohnSundell/SwiftPlate)*
|
||||
|
||||
### 🔮 Example
|
||||
|
||||
To run the example project, clone the repo and run `SkeletonViewExample` target.
|
||||
|
||||

|
||||

|
||||
|
||||
## 📲 Installation
|
||||
|
||||
@@ -133,7 +152,9 @@ avatarImageView.isSkeletonable = true
|
||||
|
||||
### 🌿 Collections
|
||||
|
||||
Currently, ```SkeletonView``` only is compatible with ```UITableView```. We are working hard to support ```UICollectionView``` too 💪🏼
|
||||
Now, ```SkeletonView``` is compatible with ```UITableView``` and ```UICollectionView```.
|
||||
|
||||
###### UITableView
|
||||
|
||||
If you want to show the skeleton in a ```UITableView```, you need to conform to ```SkeletonTableViewDataSource``` protocol.
|
||||
|
||||
@@ -144,7 +165,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:
|
||||
|
||||
@@ -171,21 +192,52 @@ There is only one method you need to implement to let Skeleton know the cell ide
|
||||
}
|
||||
```
|
||||
|
||||
> **IMPORTANT!**
|
||||
> If you are using resizable cells (`tableView.rowHeight = UITableViewAutomaticDimension` ), it's mandatory define the `estimatedRowHeight`.
|
||||
|
||||
###### UICollectionView
|
||||
|
||||
For ```UICollectionView```, you need to conform to ```SkeletonCollectionViewDataSource``` protocol.
|
||||
|
||||
``` swift
|
||||
public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
}
|
||||
```
|
||||
|
||||
The rest of the process is the same as ```UITableView```
|
||||
|
||||
### 📰 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: 70%**
|
||||
|
||||
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 +245,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)
|
||||
@@ -217,11 +271,38 @@ You can call the function like this:
|
||||
view.showAnimatedSkeleton { (layer) -> CAAnimation in
|
||||
let animation = CAAnimation()
|
||||
// Customize here your animation
|
||||
|
||||
|
||||
return animation
|
||||
}
|
||||
```
|
||||
|
||||
**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.
|
||||
@@ -242,6 +323,17 @@ Because an image is worth a thousand words:
|
||||
### 📚 Documentation
|
||||
Coming soon...😅
|
||||
|
||||
## 📬 Next steps
|
||||
|
||||
* [x] Set the filling percent of the last line in multiline elements
|
||||
* [x] Add more gradient animations
|
||||
* [x] Supported resizable cells
|
||||
* [x] CollectionView compatible
|
||||
* [ ] Add recovery state
|
||||
* [ ] Custom collections compatible
|
||||
* [ ] Add animations when it shows/hides the skeletons
|
||||
* [ ] MacOS and WatchOS compatible
|
||||
|
||||
## ❤️ Contributed
|
||||
This is an open source project, so feel free to contribute. How?
|
||||
- Open an [issue](https://github.com/Juanpe/SkeletonView/issues/new).
|
||||
@@ -250,9 +342,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]
|
||||
|
||||
@@ -261,7 +355,7 @@ See [all contributors](https://github.com/Juanpe/SkeletonView/graphs/contributor
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 swift-code
|
||||
Copyright (c) 2017 Juanpe Catalán
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.0"
|
||||
s.summary = ""
|
||||
s.version = "1.1.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
|
||||
Your description here.
|
||||
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.
|
||||
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.
|
||||
DESC
|
||||
s.homepage = ""
|
||||
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 = ""
|
||||
s.social_media_url = "https://twitter.com/JuanpeCatalan"
|
||||
s.ios.deployment_target = "9.0"
|
||||
s.source = { :git => ".git", :tag => s.version.to_s }
|
||||
s.source = { :git => "https://github.com/Juanpe/SkeletonView.git", :tag => s.version.to_s }
|
||||
s.source_files = "Sources/**/*"
|
||||
end
|
||||
|
||||
@@ -7,15 +7,24 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
88DEA97F1FCDBD78006C80EF /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DEA97D1FCDBD1F006C80EF /* Constants.swift */; };
|
||||
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 */; };
|
||||
F5307E321FB0F42F00EE67C5 /* RecursiveProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */; };
|
||||
F5307E371FB1076E00EE67C5 /* SkeletonUITableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E361FB1076E00EE67C5 /* SkeletonUITableViewDataSource.swift */; };
|
||||
F5307E391FB1078E00EE67C5 /* SkeletonUICollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E381FB1078E00EE67C5 /* SkeletonUICollectionViewDataSource.swift */; };
|
||||
F5307E371FB1076E00EE67C5 /* SkeletonTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E361FB1076E00EE67C5 /* SkeletonTableViewDataSource.swift */; };
|
||||
F5307E391FB1078E00EE67C5 /* SkeletonCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E381FB1078E00EE67C5 /* SkeletonCollectionViewDataSource.swift */; };
|
||||
F5307E3B1FB123C100EE67C5 /* ContainsMultilineText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E3A1FB123C100EE67C5 /* ContainsMultilineText.swift */; };
|
||||
F5307E411FB3B84500EE67C5 /* SkeletonView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* SkeletonView.framework */; };
|
||||
F5307E421FB3B84500EE67C5 /* SkeletonView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* SkeletonView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
F54CF5E42024CEB000330B0D /* UIView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F54CF5E12024CEAF00330B0D /* UIView+CollectionSkeleton.swift */; };
|
||||
F54CF5E52024CEB000330B0D /* UITableView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F54CF5E22024CEAF00330B0D /* UITableView+CollectionSkeleton.swift */; };
|
||||
F54CF5E62024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F54CF5E32024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift */; };
|
||||
F56B94461FAE20AF0095662F /* PrepareForSkeletonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */; };
|
||||
F587FB84202CBFC8002DB5FE /* SkeletonFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F587FB83202CBFC8002DB5FE /* SkeletonFlow.swift */; };
|
||||
F587FB86202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */; };
|
||||
F5F622411FAC6E31007C062A /* UIColor+Skeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */; };
|
||||
F5F622431FAC81FD007C062A /* CALayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622421FAC81FD007C062A /* CALayer+Extensions.swift */; };
|
||||
F5F622451FACA338007C062A /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622441FACA338007C062A /* Cell.swift */; };
|
||||
@@ -29,21 +38,51 @@
|
||||
F5F899FA1FABA607002E8FDA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F5F899F81FABA607002E8FDA /* Main.storyboard */; };
|
||||
F5F899FC1FABA607002E8FDA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F5F899FB1FABA607002E8FDA /* Assets.xcassets */; };
|
||||
F5F899FF1FABA607002E8FDA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F5F899FD1FABA607002E8FDA /* LaunchScreen.storyboard */; };
|
||||
F5F89A051FABA614002E8FDA /* SkeletonView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* SkeletonView.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
F5307E431FB3B84500EE67C5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 52D6D9731BEFF229002C0205 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 52D6D97B1BEFF229002C0205;
|
||||
remoteInfo = "SkeletonView-iOS";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
F5307E451FB3B84600EE67C5 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
F5307E421FB3B84500EE67C5 /* SkeletonView.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
52D6D97C1BEFF229002C0205 /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
88DEA97D1FCDBD1F006C80EF /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecursiveProtocol.swift; sourceTree = "<group>"; };
|
||||
F5307E361FB1076E00EE67C5 /* SkeletonUITableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonUITableViewDataSource.swift; sourceTree = "<group>"; };
|
||||
F5307E381FB1078E00EE67C5 /* SkeletonUICollectionViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonUICollectionViewDataSource.swift; sourceTree = "<group>"; };
|
||||
F5307E361FB1076E00EE67C5 /* SkeletonTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTableViewDataSource.swift; sourceTree = "<group>"; };
|
||||
F5307E381FB1078E00EE67C5 /* SkeletonCollectionViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCollectionViewDataSource.swift; sourceTree = "<group>"; };
|
||||
F5307E3A1FB123C100EE67C5 /* ContainsMultilineText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainsMultilineText.swift; sourceTree = "<group>"; };
|
||||
F54CF5E12024CEAF00330B0D /* UIView+CollectionSkeleton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+CollectionSkeleton.swift"; sourceTree = "<group>"; };
|
||||
F54CF5E22024CEAF00330B0D /* UITableView+CollectionSkeleton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+CollectionSkeleton.swift"; sourceTree = "<group>"; };
|
||||
F54CF5E32024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+CollectionSkeleton.swift"; sourceTree = "<group>"; };
|
||||
F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrepareForSkeletonProtocol.swift; sourceTree = "<group>"; };
|
||||
F587FB83202CBFC8002DB5FE /* SkeletonFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonFlow.swift; sourceTree = "<group>"; };
|
||||
F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+UIApplicationDelegate.swift"; sourceTree = "<group>"; };
|
||||
F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Skeleton.swift"; sourceTree = "<group>"; };
|
||||
F5F622421FAC81FD007C062A /* CALayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+Extensions.swift"; sourceTree = "<group>"; };
|
||||
F5F622441FACA338007C062A /* Cell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cell.swift; sourceTree = "<group>"; };
|
||||
@@ -73,7 +112,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F5F89A051FABA614002E8FDA /* SkeletonView.framework in Frameworks */,
|
||||
F5307E411FB3B84500EE67C5 /* SkeletonView.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -115,10 +154,12 @@
|
||||
F5307E331FB1068500EE67C5 /* Collections */,
|
||||
F5307E341FB106A500EE67C5 /* Extensions */,
|
||||
F5307E351FB106BF00EE67C5 /* Helpers */,
|
||||
F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */,
|
||||
F5307E2F1FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift */,
|
||||
F5307E2B1FAF6BC900EE67C5 /* SkeletonGradient.swift */,
|
||||
F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */,
|
||||
8933C7841EB5B820000D00A4 /* SkeletonView.swift */,
|
||||
F587FB83202CBFC8002DB5FE /* SkeletonFlow.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -136,8 +177,11 @@
|
||||
children = (
|
||||
F5F899EC1FAB9F04002E8FDA /* CollectionSkeletonProtocol.swift */,
|
||||
F5F899EA1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift */,
|
||||
F5307E381FB1078E00EE67C5 /* SkeletonUICollectionViewDataSource.swift */,
|
||||
F5307E361FB1076E00EE67C5 /* SkeletonUITableViewDataSource.swift */,
|
||||
F5307E381FB1078E00EE67C5 /* SkeletonCollectionViewDataSource.swift */,
|
||||
F5307E361FB1076E00EE67C5 /* SkeletonTableViewDataSource.swift */,
|
||||
F54CF5E32024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift */,
|
||||
F54CF5E22024CEAF00330B0D /* UITableView+CollectionSkeleton.swift */,
|
||||
F54CF5E12024CEAF00330B0D /* UIView+CollectionSkeleton.swift */,
|
||||
);
|
||||
path = Collections;
|
||||
sourceTree = "<group>";
|
||||
@@ -149,6 +193,7 @@
|
||||
F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */,
|
||||
F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */,
|
||||
F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */,
|
||||
F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -168,6 +213,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F5F899F41FABA607002E8FDA /* AppDelegate.swift */,
|
||||
88DEA97D1FCDBD1F006C80EF /* Constants.swift */,
|
||||
F5F899F61FABA607002E8FDA /* ViewController.swift */,
|
||||
F5F622441FACA338007C062A /* Cell.swift */,
|
||||
F5F899F81FABA607002E8FDA /* Main.storyboard */,
|
||||
@@ -230,10 +276,12 @@
|
||||
F5F899EE1FABA607002E8FDA /* Sources */,
|
||||
F5F899EF1FABA607002E8FDA /* Frameworks */,
|
||||
F5F899F01FABA607002E8FDA /* Resources */,
|
||||
F5307E451FB3B84600EE67C5 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
F5307E441FB3B84500EE67C5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SkeletonViewExample;
|
||||
productName = SkeletonViewExample;
|
||||
@@ -247,15 +295,17 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0910;
|
||||
LastUpgradeCheck = 0900;
|
||||
LastUpgradeCheck = 0920;
|
||||
ORGANIZATIONNAME = SkeletonView;
|
||||
TargetAttributes = {
|
||||
52D6D97B1BEFF229002C0205 = {
|
||||
CreatedOnToolsVersion = 7.1;
|
||||
DevelopmentTeam = KWEMDK92F4;
|
||||
LastSwiftMigration = 0800;
|
||||
};
|
||||
F5F899F11FABA607002E8FDA = {
|
||||
CreatedOnToolsVersion = 9.1;
|
||||
DevelopmentTeam = KWEMDK92F4;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
@@ -305,21 +355,27 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F5F899D01FAA6A4D002E8FDA /* UIView+IBInspectable.swift in Sources */,
|
||||
F5307E371FB1076E00EE67C5 /* SkeletonUITableViewDataSource.swift in Sources */,
|
||||
F54CF5E42024CEB000330B0D /* UIView+CollectionSkeleton.swift in Sources */,
|
||||
F5307E371FB1076E00EE67C5 /* SkeletonTableViewDataSource.swift in Sources */,
|
||||
8933C7851EB5B820000D00A4 /* SkeletonView.swift in Sources */,
|
||||
F5307E301FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift in Sources */,
|
||||
F54CF5E52024CEB000330B0D /* UITableView+CollectionSkeleton.swift in Sources */,
|
||||
F5307E321FB0F42F00EE67C5 /* RecursiveProtocol.swift in Sources */,
|
||||
F5F622431FAC81FD007C062A /* CALayer+Extensions.swift in Sources */,
|
||||
F5F899ED1FAB9F04002E8FDA /* CollectionSkeletonProtocol.swift in Sources */,
|
||||
F5307E2C1FAF6BC900EE67C5 /* SkeletonGradient.swift in Sources */,
|
||||
F587FB86202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift in Sources */,
|
||||
F5F899EB1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift in Sources */,
|
||||
F5F622411FAC6E31007C062A /* UIColor+Skeleton.swift in Sources */,
|
||||
F587FB84202CBFC8002DB5FE /* SkeletonFlow.swift in Sources */,
|
||||
F5F899D21FAB9630002E8FDA /* AssociationPolicy.swift in Sources */,
|
||||
F54CF5E62024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift in Sources */,
|
||||
F56B94461FAE20AF0095662F /* PrepareForSkeletonProtocol.swift in Sources */,
|
||||
F5307E391FB1078E00EE67C5 /* SkeletonUICollectionViewDataSource.swift in Sources */,
|
||||
F5307E391FB1078E00EE67C5 /* SkeletonCollectionViewDataSource.swift in Sources */,
|
||||
F5307E3B1FB123C100EE67C5 /* ContainsMultilineText.swift in Sources */,
|
||||
F5F899E91FAB9D2B002E8FDA /* SkeletonLayer.swift in Sources */,
|
||||
F5307E2E1FB0E5E400EE67C5 /* UIView+Frame.swift in Sources */,
|
||||
F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -330,11 +386,20 @@
|
||||
F5F899F71FABA607002E8FDA /* ViewController.swift in Sources */,
|
||||
F5F899F51FABA607002E8FDA /* AppDelegate.swift in Sources */,
|
||||
F5F622451FACA338007C062A /* Cell.swift in Sources */,
|
||||
88DEA97F1FCDBD78006C80EF /* Constants.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
F5307E441FB3B84500EE67C5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 52D6D97B1BEFF229002C0205 /* SkeletonView-iOS */;
|
||||
targetProxy = F5307E431FB3B84500EE67C5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
F5F899F81FABA607002E8FDA /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@@ -467,8 +532,10 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = KWEMDK92F4;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
@@ -490,8 +557,10 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = KWEMDK92F4;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
@@ -510,6 +579,7 @@
|
||||
F5F89A021FABA607002E8FDA /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@@ -518,6 +588,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = KWEMDK92F4;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Configs/SkeletonViewExampleInfo.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
@@ -533,6 +604,7 @@
|
||||
F5F89A031FABA607002E8FDA /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@@ -541,6 +613,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = KWEMDK92F4;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Configs/SkeletonViewExampleInfo.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0910"
|
||||
LastUpgradeVersion = "0920"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -8,72 +8,25 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
|
||||
func addDummyDataSourceIfNeeded() {
|
||||
guard let collection = self as? CollectionSkeleton else { return }
|
||||
collection.addDummyDataSource()
|
||||
collection.disableScrolling()
|
||||
}
|
||||
|
||||
func removeDummyDataSourceIfNeeded(reloadAfter reload: Bool = true) {
|
||||
guard let collection = self as? CollectionSkeleton else { return }
|
||||
collection.removeDummyDataSource(reloadAfter: reload)
|
||||
collection.enableScrolling()
|
||||
}
|
||||
enum DataSourceAssociatedKeys {
|
||||
static var dummyDataSource = "dummyDataSource"
|
||||
}
|
||||
|
||||
protocol CollectionSkeleton {
|
||||
var skeletonDataSource: SkeletonCollectionDataSource? { get set }
|
||||
var estimatedNumberOfRows: Int { get }
|
||||
|
||||
func addDummyDataSource()
|
||||
func removeDummyDataSource(reloadAfter: Bool)
|
||||
func disableScrolling()
|
||||
func enableScrolling()
|
||||
}
|
||||
|
||||
private enum AssociatedKeys {
|
||||
static var dummyDataSource = "dummyDataSource"
|
||||
}
|
||||
|
||||
extension CollectionSkeleton where Self: UIScrollView {
|
||||
|
||||
var estimatedNumberOfRows: Int { return 0 }
|
||||
func addDummyDataSource() {}
|
||||
func removeDummyDataSource(reloadAfter: Bool) {}
|
||||
func disableScrolling() { isScrollEnabled = false }
|
||||
func enableScrolling() { isScrollEnabled = true }
|
||||
}
|
||||
|
||||
extension UITableView: CollectionSkeleton {
|
||||
|
||||
var skeletonDataSource: SkeletonCollectionDataSource? {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
|
||||
set {
|
||||
objc_setAssociatedObject(self, &AssociatedKeys.dummyDataSource, newValue, AssociationPolicy.retain.objc)
|
||||
self.dataSource = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func addDummyDataSource() {
|
||||
guard let originalDataSource = self.dataSource as? SkeletonTableViewDataSource,
|
||||
!(originalDataSource is SkeletonCollectionDataSource)
|
||||
else { return }
|
||||
let dataSource = SkeletonCollectionDataSource(tableViewDataSource: originalDataSource)
|
||||
self.skeletonDataSource = dataSource
|
||||
reloadData()
|
||||
}
|
||||
|
||||
func removeDummyDataSource(reloadAfter: Bool) {
|
||||
guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
|
||||
self.skeletonDataSource = nil
|
||||
self.dataSource = dataSource.originalTableViewDataSource
|
||||
if reloadAfter { self.reloadData() }
|
||||
}
|
||||
}
|
||||
|
||||
extension UICollectionView: CollectionSkeleton {
|
||||
|
||||
var skeletonDataSource: SkeletonCollectionDataSource? {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.dummyDataSource, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@ public typealias ReusableCellIdentifier = String
|
||||
class SkeletonCollectionDataSource: NSObject {
|
||||
|
||||
weak var originalTableViewDataSource: SkeletonTableViewDataSource?
|
||||
weak var originalCollectionViewDataSource: UICollectionViewDataSource?
|
||||
weak var originalCollectionViewDataSource: SkeletonCollectionViewDataSource?
|
||||
var rowHeight: CGFloat = 0.0
|
||||
|
||||
convenience init(tableViewDataSource: SkeletonTableViewDataSource? = nil, collectionViewDataSource: UICollectionViewDataSource? = nil) {
|
||||
convenience init(tableViewDataSource: SkeletonTableViewDataSource? = nil, collectionViewDataSource: SkeletonCollectionViewDataSource? = nil, rowHeight: CGFloat = 0.0) {
|
||||
self.init()
|
||||
self.originalTableViewDataSource = tableViewDataSource
|
||||
self.originalCollectionViewDataSource = collectionViewDataSource
|
||||
self.rowHeight = rowHeight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,20 +40,24 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
|
||||
return cell
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDataSource
|
||||
extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
|
||||
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
return 1
|
||||
return originalCollectionViewDataSource?.numSections(in: collectionView) ?? 0
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return 4
|
||||
return originalCollectionViewDataSource?.collectionSkeletonView(collectionView, numberOfItemsInSection: section) ?? 0
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
return UICollectionViewCell()
|
||||
let cellIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, cellIdentifierForItemAt: indexPath) ?? ""
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
|
||||
return cell
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// SkeletonUICollectionViewDataSource.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 06/11/2017.
|
||||
// Copyright © 2017 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
}
|
||||
|
||||
public extension SkeletonCollectionViewDataSource {
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return skeletonView.estimatedNumberOfRows
|
||||
}
|
||||
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int { return 1 }
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public protocol SkeletonTableViewDataSource: UITableViewDataSource {
|
||||
public extension SkeletonTableViewDataSource {
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return Int(ceil(skeletonView.frame.height/skeletonView.rowHeight))
|
||||
return skeletonView.estimatedNumberOfRows
|
||||
}
|
||||
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int { return 1 }
|
||||
@@ -1,11 +0,0 @@
|
||||
//
|
||||
// SkeletonUICollectionViewDataSource.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 06/11/2017.
|
||||
// Copyright © 2017 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol SkeletonUICollectionViewDataSource: UICollectionViewDataSource {}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// UICollectionView+CollectionSkeleton.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 02/02/2018.
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UICollectionView: CollectionSkeleton {
|
||||
|
||||
var estimatedNumberOfRows: Int {
|
||||
guard let flowlayout = collectionViewLayout as? UICollectionViewFlowLayout else { return 0 }
|
||||
return Int(ceil(frame.height/flowlayout.itemSize.height))
|
||||
}
|
||||
|
||||
var skeletonDataSource: SkeletonCollectionDataSource? {
|
||||
get { return objc_getAssociatedObject(self, &DataSourceAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
|
||||
set {
|
||||
objc_setAssociatedObject(self, &DataSourceAssociatedKeys.dummyDataSource, newValue, AssociationPolicy.retain.objc)
|
||||
self.dataSource = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func addDummyDataSource() {
|
||||
guard let originalDataSource = self.dataSource as? SkeletonCollectionViewDataSource,
|
||||
!(originalDataSource is SkeletonCollectionDataSource)
|
||||
else { return }
|
||||
|
||||
let dataSource = SkeletonCollectionDataSource(collectionViewDataSource: originalDataSource, rowHeight: 0.0)
|
||||
self.skeletonDataSource = dataSource
|
||||
reloadData()
|
||||
}
|
||||
|
||||
func removeDummyDataSource(reloadAfter: Bool) {
|
||||
guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
|
||||
self.skeletonDataSource = nil
|
||||
self.dataSource = dataSource.originalCollectionViewDataSource
|
||||
if reloadAfter { self.reloadData() }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// UITableView+CollectionSkeleton.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 02/02/2018.
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UITableView: CollectionSkeleton {
|
||||
|
||||
var estimatedNumberOfRows: Int {
|
||||
return Int(ceil(frame.height/rowHeight))
|
||||
}
|
||||
|
||||
var skeletonDataSource: SkeletonCollectionDataSource? {
|
||||
get { return objc_getAssociatedObject(self, &DataSourceAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
|
||||
set {
|
||||
objc_setAssociatedObject(self, &DataSourceAssociatedKeys.dummyDataSource, newValue, AssociationPolicy.retain.objc)
|
||||
self.dataSource = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func addDummyDataSource() {
|
||||
guard let originalDataSource = self.dataSource as? SkeletonTableViewDataSource,
|
||||
!(originalDataSource is SkeletonCollectionDataSource)
|
||||
else { return }
|
||||
let dataSource = SkeletonCollectionDataSource(tableViewDataSource: originalDataSource, rowHeight: calculateRowHeight())
|
||||
self.skeletonDataSource = dataSource
|
||||
reloadData()
|
||||
}
|
||||
|
||||
func removeDummyDataSource(reloadAfter: Bool) {
|
||||
guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
|
||||
restoreRowHeight()
|
||||
self.skeletonDataSource = nil
|
||||
self.dataSource = dataSource.originalTableViewDataSource
|
||||
if reloadAfter { self.reloadData() }
|
||||
}
|
||||
|
||||
private func restoreRowHeight() {
|
||||
guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
|
||||
rowHeight = dataSource.rowHeight
|
||||
}
|
||||
|
||||
private func calculateRowHeight() -> CGFloat {
|
||||
guard rowHeight == UITableViewAutomaticDimension else { return rowHeight }
|
||||
rowHeight = estimatedRowHeight
|
||||
return estimatedRowHeight
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// UIView+CollectionSkeleton.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 02/02/2018.
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
func addDummyDataSourceIfNeeded() {
|
||||
guard let collection = self as? CollectionSkeleton else { return }
|
||||
collection.addDummyDataSource()
|
||||
collection.disableScrolling()
|
||||
}
|
||||
|
||||
func removeDummyDataSourceIfNeeded(reloadAfter reload: Bool = true) {
|
||||
guard let collection = self as? CollectionSkeleton else { return }
|
||||
collection.removeDummyDataSource(reloadAfter: reload)
|
||||
collection.enableScrolling()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -68,12 +74,12 @@ public extension CALayer {
|
||||
|
||||
var sliding: CAAnimation {
|
||||
let startPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.startPoint))
|
||||
startPointAnim.fromValue = NSValue(cgPoint:CGPoint(x: -1, y: 0.5))
|
||||
startPointAnim.toValue = NSValue(cgPoint:CGPoint(x:1, y: 0.5))
|
||||
startPointAnim.fromValue = CGPoint(x: -1, y: 0.5)
|
||||
startPointAnim.toValue = CGPoint(x:1, y: 0.5)
|
||||
|
||||
let endPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.endPoint))
|
||||
endPointAnim.fromValue = NSValue(cgPoint:CGPoint(x: 0, y: 0.5))
|
||||
endPointAnim.toValue = NSValue(cgPoint:CGPoint(x:2, y: 0.5))
|
||||
endPointAnim.fromValue = CGPoint(x: 0, y: 0.5)
|
||||
endPointAnim.toValue = CGPoint(x:2, y: 0.5)
|
||||
|
||||
let animGroup = CAAnimationGroup()
|
||||
animGroup.animations = [startPointAnim, endPointAnim]
|
||||
|
||||
@@ -12,6 +12,8 @@ private enum AssociatedKeys {
|
||||
static var skeletonable = "skeletonable"
|
||||
static var status = "status"
|
||||
static var skeletonLayer = "layer"
|
||||
static var flowDelegate = "flowDelegate"
|
||||
static var isSkeletonAnimated = "isSkeletonAnimated"
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
@@ -34,6 +36,11 @@ extension UIView {
|
||||
case off
|
||||
}
|
||||
|
||||
var flowDelegate: SkeletonFlowDelegate? {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.flowDelegate) as? SkeletonFlowDelegate }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.flowDelegate, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
var skeletonLayer: SkeletonLayer? {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.skeletonLayer) as? SkeletonLayer }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.skeletonLayer, newValue, AssociationPolicy.retain.objc) }
|
||||
@@ -43,8 +50,13 @@ extension UIView {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.status) as? Status ?? .off }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.status, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
var skeletonIsAnimated: Bool! {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.isSkeletonAnimated) as? Bool ?? false }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.isSkeletonAnimated, 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) }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// UIView+UIApplicationDelegate.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 08/02/2018.
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
|
||||
enum Constants {
|
||||
static let becomeActiveNotification = NSNotification.Name.UIApplicationDidBecomeActive
|
||||
static let enterForegroundNotification = NSNotification.Name.UIApplicationDidEnterBackground
|
||||
static let needAnimatedSkeletonKey = "needAnimateSkeleton"
|
||||
}
|
||||
|
||||
func addAppNotificationsObservers() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: Constants.becomeActiveNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: Constants.enterForegroundNotification, object: nil)
|
||||
}
|
||||
|
||||
func removeAppNoticationsObserver() {
|
||||
NotificationCenter.default.removeObserver(self, name: Constants.becomeActiveNotification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: Constants.enterForegroundNotification, object: nil)
|
||||
}
|
||||
|
||||
@objc func appDidBecomeActive() {
|
||||
if UserDefaults.standard.bool(forKey: Constants.needAnimatedSkeletonKey) {
|
||||
startSkeletonAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func appDidEnterBackground() {
|
||||
UserDefaults.standard.set((isSkeletonActive && skeletonIsAnimated), forKey: Constants.needAnimatedSkeletonKey)
|
||||
}
|
||||
}
|
||||
@@ -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 = direction.startPoint.from
|
||||
startPointAnim.toValue = direction.startPoint.to
|
||||
|
||||
let endPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.endPoint))
|
||||
endPointAnim.fromValue = direction.endPoint.from
|
||||
endPointAnim.toValue = 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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// SkeletonFlow.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 08/02/2018.
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol SkeletonFlowDelegate {
|
||||
func willBeginShowingSkeletons(withRootView rootView: UIView)
|
||||
func willBeginHidingSkeletons(withRootView rootView: UIView)
|
||||
}
|
||||
|
||||
class SkeletonFlowHandler: SkeletonFlowDelegate {
|
||||
|
||||
func willBeginShowingSkeletons(withRootView rootView: UIView) {
|
||||
rootView.addAppNotificationsObservers()
|
||||
}
|
||||
|
||||
func willBeginHidingSkeletons(withRootView rootView: UIView) {
|
||||
rootView.removeAppNoticationsObserver()
|
||||
rootView.flowDelegate = nil
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,16 @@ import UIKit
|
||||
|
||||
class SkeletonLayerFactory {
|
||||
|
||||
func makeLayer(withType type: SkeletonType, usingColors colors: [UIColor], andHolder holder: UIView) -> SkeletonLayer {
|
||||
func makeSkeletonLayer(withType type: SkeletonType, usingColors colors: [UIColor], andHolder holder: UIView) -> SkeletonLayer {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,15 +27,12 @@ public extension UIView {
|
||||
}
|
||||
|
||||
func hideSkeleton(reloadDataAfter reload: Bool = true) {
|
||||
removeDummyDataSourceIfNeeded(reloadAfter: reload)
|
||||
recursiveSearch(inArray: subviewsSkeletonables,
|
||||
leafBlock: { removeSkeletonLayer() },
|
||||
recursiveBlock: {
|
||||
$0.hideSkeleton(reloadDataAfter: reload)
|
||||
})
|
||||
flowDelegate?.willBeginHidingSkeletons(withRootView: self)
|
||||
recursiveHideSkeleton(reloadDataAfter: reload)
|
||||
}
|
||||
|
||||
func startSkeletonAnimation(_ anim: SkeletonLayerAnimation? = nil) {
|
||||
skeletonIsAnimated = true
|
||||
recursiveSearch(inArray: subviewsSkeletonables,
|
||||
leafBlock: startSkeletonLayerAnimationBlock(anim)) {
|
||||
$0.startSkeletonAnimation(anim)
|
||||
@@ -43,6 +40,7 @@ public extension UIView {
|
||||
}
|
||||
|
||||
func stopSkeletonAnimation() {
|
||||
skeletonIsAnimated = false
|
||||
recursiveSearch(inArray: subviewsSkeletonables,
|
||||
leafBlock: stopSkeletonLayerAnimationBlock) {
|
||||
$0.stopSkeletonAnimation()
|
||||
@@ -53,25 +51,44 @@ public extension UIView {
|
||||
extension UIView {
|
||||
|
||||
func showSkeleton(withType type: SkeletonType = .solid, usingColors colors: [UIColor], animated: Bool = false, animation: SkeletonLayerAnimation? = nil) {
|
||||
skeletonIsAnimated = animated
|
||||
flowDelegate = SkeletonFlowHandler()
|
||||
flowDelegate?.willBeginShowingSkeletons(withRootView: self)
|
||||
recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation)
|
||||
}
|
||||
|
||||
fileprivate func recursiveShowSkeleton(withType type: SkeletonType = .solid, usingColors colors: [UIColor], animated: Bool = false, animation: SkeletonLayerAnimation? = nil) {
|
||||
addDummyDataSourceIfNeeded()
|
||||
recursiveSearch(inArray: subviewsSkeletonables,
|
||||
leafBlock: {
|
||||
guard !isSkeletonActive else { return }
|
||||
isUserInteractionEnabled = false
|
||||
(self as? PrepareForSkeleton)?.prepareViewForSkeleton()
|
||||
addSkeletonLayer(withType: type, usingColors: colors, animated: animated, animation: animation)
|
||||
}) {
|
||||
$0.showSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation)
|
||||
}
|
||||
}) {
|
||||
$0.recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation)
|
||||
}
|
||||
}
|
||||
|
||||
private func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock {
|
||||
fileprivate func recursiveHideSkeleton(reloadDataAfter reload: Bool = true) {
|
||||
removeDummyDataSourceIfNeeded()
|
||||
isUserInteractionEnabled = true
|
||||
recursiveSearch(inArray: subviewsSkeletonables,
|
||||
leafBlock: {
|
||||
removeSkeletonLayer()
|
||||
}, recursiveBlock: {
|
||||
$0.recursiveHideSkeleton(reloadDataAfter: reload)
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -97,6 +114,18 @@ extension UITableViewCell {
|
||||
}
|
||||
}
|
||||
|
||||
extension UICollectionView {
|
||||
override var subviewsSkeletonables: [UIView] {
|
||||
return subviews.filter { $0.isSkeletonable }
|
||||
}
|
||||
}
|
||||
|
||||
extension UICollectionViewCell {
|
||||
override var subviewsSkeletonables: [UIView] {
|
||||
return contentView.subviews.filter { $0.isSkeletonable }
|
||||
}
|
||||
}
|
||||
|
||||
extension UIStackView {
|
||||
override var subviewsSkeletonables: [UIView] {
|
||||
return arrangedSubviews.filter { $0.isSkeletonable }
|
||||
@@ -105,8 +134,8 @@ extension UIStackView {
|
||||
|
||||
extension UIView {
|
||||
|
||||
func addSkeletonLayer(withType type: SkeletonType, usingColors colors: [UIColor], animated: Bool, animation: SkeletonLayerAnimation? = nil) {
|
||||
self.skeletonLayer = SkeletonLayerFactory().makeLayer(withType: type, usingColors: colors, andHolder: self)
|
||||
func addSkeletonLayer(withType type: SkeletonType, usingColors colors: [UIColor], gradientDirection direction: GradientDirection? = nil, animated: Bool, animation: SkeletonLayerAnimation? = nil) {
|
||||
self.skeletonLayer = SkeletonLayerFactory().makeSkeletonLayer(withType: type, usingColors: colors, andHolder: self)
|
||||
layer.insertSublayer(skeletonLayer!.contentLayer, at: UInt32.max)
|
||||
if animated { skeletonLayer!.start(animation) }
|
||||
status = .on
|
||||
@@ -116,6 +145,7 @@ extension UIView {
|
||||
guard isSkeletonActive,
|
||||
let layer = skeletonLayer else { return }
|
||||
layer.stopAnimation()
|
||||
layer.removeLayer()
|
||||
skeletonLayer = nil
|
||||
status = .off
|
||||
}
|
||||
|
||||