Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 988284f78c | |||
| c41c2f0d41 | |||
| 0fcbdfa575 | |||
| db1e789aa5 | |||
| 53adf155b9 | |||
| cfd8d15240 | |||
| 8448a53b79 | |||
| 42f0886c77 | |||
| 4e63a2a1e3 | |||
| 9523446c53 | |||
| dd66df733c | |||
| 8021f1260b | |||
| 3f3b4f498b | |||
| 6a07b423a9 | |||
| 6bd03081d6 |
Binary file not shown.
|
Before Width: | Height: | Size: 200 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 164 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
Executable
+30
@@ -0,0 +1,30 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file
|
||||
|
||||
## [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.3</string>
|
||||
<string>1.0.5</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.3</string>
|
||||
<string>1.0.5</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>
|
||||
|
||||
@@ -13,7 +13,20 @@ let colors = [(UIColor.turquoise,"turquoise"), (UIColor.emerald,"emerald"), (UIC
|
||||
|
||||
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
|
||||
|
||||
@@ -41,6 +41,7 @@ Enjoy it! 🙂
|
||||
* [Custom animations](#-custom-animations)
|
||||
* [Hierarchy](#-hierarchy)
|
||||
* [Documentation](#-documentation)
|
||||
* [Next steps](#-next-steps)
|
||||
* [Contributed](#-contributed)
|
||||
* [Author](#-author)
|
||||
* [License](#-license)
|
||||
@@ -65,7 +66,7 @@ Enjoy it! 🙂
|
||||
|
||||
To run the example project, clone the repo and run `SkeletonViewExample` target.
|
||||
|
||||

|
||||

|
||||
|
||||
## 📲 Installation
|
||||
|
||||
@@ -189,14 +190,29 @@ 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`.
|
||||
|
||||
### 📰 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.
|
||||
@@ -291,6 +307,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
|
||||
* [ ] 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).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.0.3"
|
||||
s.version = "1.0.5"
|
||||
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.
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
@@ -70,6 +73,9 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -166,6 +172,9 @@
|
||||
F5F899EA1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift */,
|
||||
F5307E381FB1078E00EE67C5 /* SkeletonUICollectionViewDataSource.swift */,
|
||||
F5307E361FB1076E00EE67C5 /* SkeletonUITableViewDataSource.swift */,
|
||||
F54CF5E32024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift */,
|
||||
F54CF5E22024CEAF00330B0D /* UITableView+CollectionSkeleton.swift */,
|
||||
F54CF5E12024CEAF00330B0D /* UIView+CollectionSkeleton.swift */,
|
||||
);
|
||||
path = Collections;
|
||||
sourceTree = "<group>";
|
||||
@@ -337,9 +346,11 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F5F899D01FAA6A4D002E8FDA /* UIView+IBInspectable.swift in Sources */,
|
||||
F54CF5E42024CEB000330B0D /* UIView+CollectionSkeleton.swift in Sources */,
|
||||
F5307E371FB1076E00EE67C5 /* SkeletonUITableViewDataSource.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 */,
|
||||
@@ -347,6 +358,7 @@
|
||||
F5F899EB1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift in Sources */,
|
||||
F5F622411FAC6E31007C062A /* UIColor+Skeleton.swift in Sources */,
|
||||
F5F899D21FAB9630002E8FDA /* AssociationPolicy.swift in Sources */,
|
||||
F54CF5E62024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift in Sources */,
|
||||
F56B94461FAE20AF0095662F /* PrepareForSkeletonProtocol.swift in Sources */,
|
||||
F5307E391FB1078E00EE67C5 /* SkeletonUICollectionViewDataSource.swift in Sources */,
|
||||
F5307E3B1FB123C100EE67C5 /* ContainsMultilineText.swift in Sources */,
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@ class SkeletonCollectionDataSource: NSObject {
|
||||
|
||||
weak var originalTableViewDataSource: SkeletonTableViewDataSource?
|
||||
weak var originalCollectionViewDataSource: UICollectionViewDataSource?
|
||||
var rowHeight: CGFloat = 0.0
|
||||
|
||||
convenience init(tableViewDataSource: SkeletonTableViewDataSource? = nil, collectionViewDataSource: UICollectionViewDataSource? = nil) {
|
||||
convenience init(tableViewDataSource: SkeletonTableViewDataSource? = nil, collectionViewDataSource: UICollectionViewDataSource? = nil, rowHeight: CGFloat = 0.0) {
|
||||
self.init()
|
||||
self.originalTableViewDataSource = tableViewDataSource
|
||||
self.originalCollectionViewDataSource = collectionViewDataSource
|
||||
self.rowHeight = rowHeight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +42,7 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDataSource
|
||||
// MARK: - [WIP] UICollectionViewDataSource
|
||||
extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
|
||||
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
|
||||
@@ -8,4 +8,5 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
// Work in progress
|
||||
protocol SkeletonUICollectionViewDataSource: UICollectionViewDataSource {}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// 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 skeletonDataSource: SkeletonCollectionDataSource? {
|
||||
get { return objc_getAssociatedObject(self, &DataSourceAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
|
||||
set { objc_setAssociatedObject(self, &DataSourceAssociatedKeys.dummyDataSource, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}) {
|
||||
|
||||
Reference in New Issue
Block a user