Compare commits

..

125 Commits

Author SHA1 Message Date
Juanpe Catalán 5384fd34dd Merge branch 'develop' 2019-09-13 10:51:56 +02:00
Juanpe Catalán 53d965e151 feat: update pod version 2019-09-13 10:51:39 +02:00
Juanpe Catalán 04678fc772 feat: change some signature of methods 2019-09-13 10:50:06 +02:00
Juanpe Catalán 46da5ab6fa Merge branch 'master' into develop 2019-09-13 10:42:13 +02:00
Juanpe Catalán 45871be409 Merge pull request #186 from AhmedOS/master
Fixed animation stopping issue after view controller disappears
2019-09-13 10:41:02 +02:00
Juanpe Catalán 93f54dcc4d Merge pull request #182 from yangzhiquan/fix-spelling-error
fix spelling error
2019-09-10 08:31:25 +02:00
yvan 3bf3038941 fix spelling error 2019-09-10 11:27:29 +08:00
Ahmed Osama df1454c749 Improved example project 2019-09-08 05:48:30 +02:00
Ahmed Osama 8212fc1a0b Fixed animation stopping issue after switching tabs 2019-09-08 05:48:30 +02:00
Juanpe Catalán e4b9416667 Merge pull request #167 from Bilue/fix-single-line-labels
Adjust multiline behaviour to properly handle single lines
2019-09-02 17:52:19 +02:00
Juanpe Catalán f6f001068d Update README.md 2019-08-31 14:00:02 +02:00
Juanpe Catalán cb4ddd487a Update README.md 2019-08-31 13:59:37 +02:00
Juanpe Catalán 18cd0f9aba feat: update stale.yml setting stale label 2019-08-27 23:29:03 +02:00
Juanpe Catalán 71d3e72eec feat: create stale file 2019-08-27 20:21:43 +02:00
Juanpe Catalán a0a2ae760b fix: problem calling layout subviews 2019-08-27 20:04:42 +02:00
Juanpe Catalán 71d40d24b2 feat: update assets 2019-08-27 02:31:59 +02:00
Juanpe Catalán 4236e9d424 feat: update README, include more examples in Hierarchy section 2019-08-27 02:28:19 +02:00
Juanpe Catalán 19b88fce3e feat: update example 2019-08-26 23:27:41 +02:00
Juanpe Catalán b200a1ff3a Merge branch 'master' of https://github.com/Juanpe/SkeletonView 2019-08-26 23:01:10 +02:00
Juanpe Catalán d9408d59b4 feat: update cocoaspec 2019-08-26 23:00:54 +02:00
Juanpe Catalán 5a0f6e2314 Update README.md 2019-08-26 23:00:07 +02:00
Juanpe Catalán 3f7505bed9 Merge branch 'master' of https://github.com/Juanpe/SkeletonView 2019-08-26 22:39:28 +02:00
Juanpe Catalán 5436e44f15 fix: property name 2019-08-26 22:37:11 +02:00
Juanpe Catalán 2f2e542d51 fix: bug #149 animation stopped when modal is presented 2019-08-26 22:34:56 +02:00
Juanpe Catalán 15764debf0 feat: rename alias of delegate 2019-08-26 19:11:28 +02:00
Juanpe Catalán bcd0fa7983 feat: user interaction enabled false 2019-08-26 19:10:44 +02:00
Juanpe Catalán 4cdc5935fc Update CHANGELOG.md 2019-08-25 11:08:42 +02:00
Juanpe Catalán 93769902a3 Merge branch 'master' into develop 2019-08-24 11:46:50 +02:00
Juanpe Catalán a1d54a448d Merge pull request #178 from aadudyrev/completionOnNoneTransition
Fix completion call in .none transition style.
2019-08-23 15:57:20 +02:00
dudyrev cc8d21e7af Fix completion call in .none transition style. 2019-08-23 16:24:47 +03:00
Juanpe Catalán 69a0c8319d Update README.md 2019-08-23 12:58:21 +02:00
Juanpe Catalán 3c173c0a23 Merge branch 'master' into develop 2019-08-22 16:51:16 +02:00
Juanpe Catalán dce910d6d0 feat: update transition gifs 2019-08-22 16:51:04 +02:00
Juanpe Catalán 2f751b9036 Merge branch 'master' into develop 2019-08-22 15:17:28 +02:00
Juanpe Catalán fdd17dd4e9 fix: missing UIView+IBInspectable 2019-08-22 15:16:37 +02:00
Juanpe Catalán 978fd553e1 Merge branch 'master' into develop 2019-08-22 15:11:15 +02:00
Juanpe Catalán 14ef5012f3 feat: refactor Swift format 2019-08-22 15:11:01 +02:00
Juanpe Catalán e62c7d8c0e Merge branch 'develop'
# Conflicts:
#	.codebeatsettings
2019-08-22 12:07:41 +02:00
Juanpe Catalán e7aa9da5ae feat: BUMP cocoapods version 2019-08-22 12:04:29 +02:00
Juanpe Catalán 09a30d36b8 feat: remove whitespaces 2019-08-22 09:34:56 +02:00
Juanpe Catalán d5656b9715 feat: change some methods about animate transition 2019-08-22 03:00:09 +02:00
Juanpe Catalán 66dc7e89c5 fix: SPM install instructions 2019-08-21 13:03:56 +02:00
Juanpe Catalán 5c815d7647 feat: add new rule for codebeat 2019-08-21 13:01:47 +02:00
Juanpe Catalán aa60f82c81 feat: create codebeat settings 2019-08-21 12:12:03 +02:00
Juanpe Catalán 6676760860 feat: config number of functions allowed. Codebeat file 2019-08-21 12:08:52 +02:00
Juanpe Catalán b571988fad update README 2019-08-21 10:04:14 +02:00
Juanpe Catalán abd9130ff6 Merge branch 'master' into develop 2019-08-21 10:02:41 +02:00
Juanpe Catalán fc9dca1730 revert README.md 2019-08-21 10:01:56 +02:00
Juanpe Catalán 46a95eecb4 Merge pull request #170 from pontusjacobsson/master
Fade in and fade out animations of the skeletonViews
2019-08-21 09:57:47 +02:00
Pontus Jacobsson e08e563ec0 Fixed typo 2019-08-21 09:23:54 +02:00
Pontus Jacobsson 1c24785ed6 Merge branch 'master' of https://github.com/pontusjacobsson/SkeletonView
# Conflicts:
#	README.md
2019-08-20 17:17:29 +02:00
Pontus Jacobsson bbcaeedd19 Added hide transition to readme 2019-08-20 17:16:13 +02:00
Pontus Jacobsson 6fc8f5caea Added hide transition to readme 2019-08-20 17:15:06 +02:00
Pontus Jacobsson ab6efd1504 Updated readme 2019-08-20 17:08:18 +02:00
Pontus Jacobsson 6632f5d91e Cleaned up code 2019-08-05 15:52:03 +02:00
Pontus Jacobsson c1a47f098b Cleaned up code 2019-08-02 13:35:41 +02:00
Pontus Jacobsson 02be363c95 Updated tableView tutorial and also added custom transitions on "hideSkeleton()" 2019-08-02 13:11:11 +02:00
Pontus Jacobsson cb0e4c63cf Added fading testing for the collection view example 2019-08-01 22:42:27 +02:00
Pontus Jacobsson 26d839387d Fixed background color bug when showing skeleton without transition 2019-07-31 10:41:20 +02:00
Pontus Jacobsson e05d013af8 Cleaned up some code and aded extra viewState keys 2019-07-31 09:50:13 +02:00
Pontus Jacobsson d8a5df75ee Cleaned up code and changed recoverableStates to structs 2019-07-30 21:51:40 +02:00
Pontus Jacobsson f7eb048317 Changed code to Eduard Bosch's tips with minor adjustments 2019-07-30 19:55:27 +02:00
Pontus Jacobsson 4497e5cdb9 Cleaned up some code 2019-07-30 10:35:54 +02:00
Pontus Jacobsson e83ab2e033 Cleaned up code 2019-07-29 13:44:50 +02:00
Pontus Jacobsson d40a877c42 Handled access level on transitions 2019-07-29 12:56:43 +02:00
Pontus Jacobsson 3b6c61cfcb Cleaned up some code 2019-07-29 11:53:09 +02:00
Pontus Jacobsson f42b77fd4d Removed lost file 2019-07-29 11:48:39 +02:00
Pontus Jacobsson c75df015d8 Added missing compilation file 2019-07-29 11:43:01 +02:00
Pontus Jacobsson 0393c04aa8 Moved some functions 2019-07-29 11:41:02 +02:00
Pontus Jacobsson c3df0bc5df Fixed a file bug in xcode prov 2019-07-29 11:35:57 +02:00
Pontus Jacobsson 0f0bb220f2 Cleaned up some code 2019-07-29 11:33:35 +02:00
Pontus Jacobsson a299c8c0c8 Added support for multiple transitions instead of just one 2019-07-29 11:32:31 +02:00
Pontus Jacobsson aea2ae6793 Added missing fade in call 2019-07-29 09:36:17 +02:00
Pontus Jacobsson a3ed33f53b Added fade in/out duration to config and text color to recoveryState 2019-07-26 17:04:52 +02:00
Pontus Jacobsson 6cf19ca50d Added support to start fade from all "showSkeleton" functions 2019-07-26 16:36:13 +02:00
Pontus Jacobsson ca0b295a80 Added support for fade in and fade out animations of the skeleton load 2019-07-26 16:28:30 +02:00
Juanpe Catalán 60c387b250 feat: update gemfile 2019-07-23 10:54:52 +02:00
Tom Izaks 5b3bc204bf Adjust multiline behaviour to properly handle single lines 2019-07-09 14:00:48 +10:00
Juanpe Catalán 1eece8e013 feat: update gemfile 2019-07-06 11:42:03 +02:00
Juanpe Catalán d665d01469 Merge pull request #160 from techinpark/master
[Feature] Add korean README.md
2019-06-30 12:59:57 +02:00
Eduard Bosch Bertran 623b326e7b Merge pull request #158 from eduardbosch/feature/allow_updating_skeleton_layers
Update skeleton layout and configuration
2019-06-25 23:32:16 +02:00
Fernando 3a4bbadcbe feat: add translator of korean documents 2019-06-21 21:19:51 +09:00
Fernando dc92e652cb feat: update document korean 2019-06-21 21:17:56 +09:00
Fernando e3f38fdd25 feat: update korean docs for custom animations 2019-06-21 18:47:01 +09:00
Fernando 6c1c4dda42 feat: update korean document. 2019-06-21 18:36:15 +09:00
Fernando 39c60b6da2 feat: update korean documents 2019-06-21 13:05:39 +09:00
Fernando c75d34e7de Merge pull request #1 from techinpark/feature/korean-docs
[Feature] Docs: add Korean document
2019-06-21 11:30:34 +09:00
Fernando c5a0bc0050 feat: update korean document 2019-06-20 20:20:09 +09:00
Fernando be74f093f3 feat: add korean document 2019-06-20 20:18:07 +09:00
Fernando 0f90edfc8e feat: update korean documents 2019-06-20 20:07:43 +09:00
Fernando 67daffd7c9 feat: translate for korean document 2019-06-20 18:41:52 +09:00
Fernando 961c731888 feat: add korean documentation 2019-06-20 10:54:47 +09:00
Eduard Bosch Bertran 39fa679ddc doc: Add new functionality to documentation 2019-06-19 23:59:29 +02:00
Eduard Bosch Bertran 40c107af23 doc: Update version 2019-06-19 23:49:54 +02:00
Eduard Bosch Bertran 0781fceab7 doc: Update changelog 2019-06-19 23:49:35 +02:00
Juanpe Catalán eef7a87eb6 Merge pull request #156 from gorillatech/master
fix - support UICollectionView's headers and footers (UICollectionReusableView)
2019-06-19 09:51:04 +02:00
Eduard Bosch Bertran be25db821b config: Add colletion view example scheme 2019-06-18 23:21:54 +02:00
Eduard Bosch Bertran 9e014dab0e refactor: Reorder methods 2019-06-18 23:21:54 +02:00
Eduard Bosch Bertran f53b722b52 refactor: Use SkeletonConfig to show and update skeleton views 2019-06-18 23:21:54 +02:00
Eduard Bosch Bertran bd02497124 fix: Skeleton new cells if container is skeletoned 2019-06-18 23:21:54 +02:00
Eduard Bosch Bertran 1802e76da9 fix: Avoid showing non skeletoned views on sample app 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 318b2546c6 feat: Add method to layout skeleton view if needed 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 37df94b09f style: Add space 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 81599f2980 refactor: Rename showSkeletonLeafBlock and do not return a block 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran b28358663b refactor: showOrUpdate is now update and only updates skeleton properties 2019-06-18 23:21:53 +02:00
Juanpe Catalán b364218213 feat: Add SkeletonConfig documentation 2019-06-18 23:21:53 +02:00
Juanpe Catalán 18048c9bcd feat: Create SkeletonConfig to save the config of skeleton active 2019-06-18 23:21:53 +02:00
Juanpe Catalán 510fe34476 feat: REAMD zh 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 932579edc0 doc: Add missing dot 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 96673df005 doc: Add feature to changelog file 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 43c1f2e5ac refactor: Rename update methods to showOrUpdate 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 37f6205421 refactor: Rename layer to skeletonLayer to match the other methods 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 3471288307 refactor: Wrap layer with guard 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran b77edf84b2 config: Ignore .idea folder 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 3e87293a84 fix: Request layout if needed befor updating skeleton views 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran f637c20ea9 fix: Update multiline skeleton layers on skeleton layer update 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 1737536057 refactor: Use update instead of show when updating skeleton views 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 31fe04b92e refactor: Reuse show skeleton 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran f7d577c269 feat: Add begin updating notification 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran 3a7be98057 fix: Update table / collection skeleton views 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran dab24e40a7 fix: Only create flowDelegate if update is called before show 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran c729dec667 test: Update skeleton views in controller bounds update 2019-06-18 23:21:53 +02:00
Eduard Bosch Bertran f6f0a11968 feat: Update skeleton layer bounds and color 2019-06-18 23:21:53 +02:00
Guglielmo Faglioni 6784de9597 fix - support UICollectionView's headers and footers (UICollectionReusableView) 2019-06-18 17:44:50 +02:00
Juanpe Catalán 6e91b3fa71 Merge branch 'master' into develop 2019-06-04 19:01:14 +02:00
73 changed files with 1763 additions and 354 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"SWIFT": {
"TOO_MANY_FUNCTIONS": [50, 100, 150, 200],
"TOTAL_LOC": [200, 400, 500, 600]
}
}
+15
View File
@@ -0,0 +1,15 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- work in progress
staleLabel: given up
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
+4
View File
@@ -66,3 +66,7 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
# JetBrains
.idea
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+25 -1
View File
@@ -3,8 +3,32 @@ All notable changes to this project will be documented in this file
## Next version
### Improvements
- Fix completion call in .none transition style while hide skeletons. (thanks @aadudyrev)
## [Transitions (1.8)](https://github.com/Juanpe/SkeletonView/releases/tag/1.8)
### New
- Adding swift news to mentioned section (thanks @osterbergmarcus)
- Adding swift news to mentioned section (thanks @osterbergmarcus).
- Create `SkeletonTransitionStyle`. Now, you can animate transition when you show or hide skeletons. (thanks @pontusjacobsson)
### Improvements
- Refactor some methods.
### Bug fixes
- Solved issues.
[#175](https://github.com/Juanpe/SkeletonView/issues/175) Swift Package Manager version format
## [Layout update (1.7)](https://github.com/Juanpe/SkeletonView/releases/tag/1.7)
### New
- Allow updating skeleton layout to recalculate skeleton bounds: `layoutSkeletonIfNeeded`. See the examples to know how to use it. (thanks @eduardbosch)
### Improvements
- Allow updating skeleton layers without recreating them: `updateSkeleton`, `updateGradientSkeleton`, `updateAnimatedSkeleton`, `updateAnimatedGradientSkeleton`. (thanks @eduardbosch)
## [Debug (1.4)](https://github.com/Juanpe/SkeletonView/releases/tag/1.4)
+1 -1
View File
@@ -8,7 +8,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "avatar.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "picture.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

@@ -6,12 +6,14 @@ import SkeletonView
class CollectionViewCell: UICollectionViewCell {
var label: UILabel!
var imageView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
isSkeletonable = true
createLabel()
createImageView()
}
@@ -19,16 +21,34 @@ class CollectionViewCell: UICollectionViewCell {
fatalError("init(coder:) has not been implemented")
}
private func createImageView() {
imageView = UIImageView(image: UIImage(named: "picture"))
imageView.isSkeletonable = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
addSubview(imageView)
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
imageView.topAnchor.constraint(equalTo: topAnchor),
imageView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75),
imageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.75)
])
}
private func createLabel() {
label = UILabel()
label.isSkeletonable = true
label.text = "Lorem ipsum"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: centerYAnchor),
label.heightAnchor.constraint(equalToConstant: frame.height / 2),
label.widthAnchor.constraint(equalToConstant: frame.width / 2)
label.bottomAnchor.constraint(equalTo: bottomAnchor),
label.heightAnchor.constraint(equalToConstant: 40),
label.widthAnchor.constraint(equalToConstant: frame.width)
])
}
+69 -26
View File
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="irH-dz-xqL">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="irH-dz-xqL">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -13,7 +13,7 @@
<!--View Controller-->
<scene sceneID="qda-qV-vJk">
<objects>
<viewController id="irH-dz-xqL" customClass="ViewController" customModule="SkeletonViewExampleUICollectionView" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="irH-dz-xqL" customClass="ViewController" customModule="SkeletonViewExampleUICollectionView" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Fso-nq-n6t">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -37,7 +37,7 @@
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</textView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="Ql9-Jy-aWM">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="Ql9-Jy-aWM">
<rect key="frame" x="141" y="20" width="93" height="93"/>
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
<constraints>
@@ -66,7 +66,7 @@
</userDefinedRuntimeAttributes>
</view>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="HKL-L0-T2w">
<rect key="frame" x="0.0" y="263" width="375" height="264"/>
<rect key="frame" x="0.0" y="263" width="375" height="244"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="mGU-kn-rfE">
<size key="itemSize" width="50" height="50"/>
@@ -80,11 +80,10 @@
</connections>
</collectionView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JjA-MK-YzZ">
<rect key="frame" x="0.0" y="527" width="375" height="140"/>
<rect key="frame" x="0.0" y="507" width="375" height="160"/>
<subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="fMR-vj-7de">
<rect key="frame" x="20" y="23" width="140" height="29"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="fMR-vj-7de">
<rect key="frame" x="20" y="22.5" width="135" height="29"/>
<segments>
<segment title="Solid"/>
<segment title="Gradient"/>
@@ -93,44 +92,85 @@
<action selector="changeSkeletonType:" destination="irH-dz-xqL" eventType="valueChanged" id="lfR-JV-DU4"/>
</connections>
</segmentedControl>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KBe-RM-BG8">
<rect key="frame" x="310" y="21" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KBe-RM-BG8">
<rect key="frame" x="308" y="21" width="51" height="31"/>
<connections>
<action selector="changeAnimated:" destination="irH-dz-xqL" eventType="valueChanged" id="dlH-KK-iee"/>
</connections>
</switch>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Animated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GSj-Ze-UIK">
<rect key="frame" x="211" y="28" width="91" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Animated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GSj-Ze-UIK">
<rect key="frame" x="229" y="26" width="73" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Color" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4Hm-fj-45V">
<rect key="frame" x="32" y="89" width="52" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Color" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4Hm-fj-45V">
<rect key="frame" x="20" y="61" width="41.5" height="52"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="HBJ-nh-56V">
<rect key="frame" x="92" y="84" width="30" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HBJ-nh-56V">
<rect key="frame" x="69.5" y="72" width="30" height="30"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="DVW-Tc-XEQ"/>
<constraint firstAttribute="height" constant="30" id="JfP-3b-yqX"/>
</constraints>
</view>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aUR-Qo-gHK">
<rect key="frame" x="20" y="74" width="140" height="52"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aUR-Qo-gHK">
<rect key="frame" x="20" y="61" width="100" height="52"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="GvX-hq-2Qn"/>
<constraint firstAttribute="height" constant="52" id="UQe-Cf-riE"/>
</constraints>
<connections>
<action selector="btnChangeColorTouchUpInside:" destination="irH-dz-xqL" eventType="touchUpInside" id="Xca-QC-htl"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nPs-17-vfs">
<rect key="frame" x="263" y="72" width="94" height="30"/>
<state key="normal" title="Hide skeleton"/>
<connections>
<action selector="showOrHideSkeleton:" destination="irH-dz-xqL" eventType="touchUpInside" id="lHc-k2-OgV"/>
</connections>
</button>
<stepper opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" maximumValue="5" stepValue="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="3mz-9M-e7Q">
<rect key="frame" x="263" y="126" width="94" height="29"/>
<connections>
<action selector="transitionDurationStepperAction:" destination="irH-dz-xqL" eventType="valueChanged" id="Ll0-Pr-d0V"/>
</connections>
</stepper>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fade Duration: 0 sec" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5gN-Jz-44y">
<rect key="frame" x="113.5" y="131.5" width="141.5" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="140" id="QDV-wu-e3I"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="140" id="qR5-cz-YAm"/>
<constraint firstItem="3mz-9M-e7Q" firstAttribute="leading" secondItem="5gN-Jz-44y" secondAttribute="trailing" constant="8" id="65e-Nj-bKG"/>
<constraint firstItem="HBJ-nh-56V" firstAttribute="centerY" secondItem="aUR-Qo-gHK" secondAttribute="centerY" id="81M-Wq-Avl"/>
<constraint firstItem="fMR-vj-7de" firstAttribute="leading" secondItem="JjA-MK-YzZ" secondAttribute="leading" constant="20" id="AyG-hI-tte"/>
<constraint firstItem="aUR-Qo-gHK" firstAttribute="leading" secondItem="fMR-vj-7de" secondAttribute="leading" id="C1b-Hl-jEg"/>
<constraint firstItem="3mz-9M-e7Q" firstAttribute="trailing" secondItem="nPs-17-vfs" secondAttribute="trailing" id="KuK-fc-jOQ"/>
<constraint firstItem="nPs-17-vfs" firstAttribute="centerY" secondItem="aUR-Qo-gHK" secondAttribute="centerY" id="MQX-E5-IDE"/>
<constraint firstItem="HBJ-nh-56V" firstAttribute="leading" secondItem="4Hm-fj-45V" secondAttribute="trailing" constant="8" id="MhM-jY-LIA"/>
<constraint firstItem="4Hm-fj-45V" firstAttribute="height" secondItem="aUR-Qo-gHK" secondAttribute="height" id="OSn-RA-wQL"/>
<constraint firstItem="4Hm-fj-45V" firstAttribute="leading" secondItem="aUR-Qo-gHK" secondAttribute="leading" id="PwQ-UR-iMq"/>
<constraint firstAttribute="height" constant="160" id="QDV-wu-e3I"/>
<constraint firstItem="5gN-Jz-44y" firstAttribute="centerY" secondItem="3mz-9M-e7Q" secondAttribute="centerY" id="TGP-Ep-0ob"/>
<constraint firstItem="nPs-17-vfs" firstAttribute="top" secondItem="KBe-RM-BG8" secondAttribute="bottom" constant="20" id="TPg-wY-9bc"/>
<constraint firstItem="4Hm-fj-45V" firstAttribute="centerY" secondItem="aUR-Qo-gHK" secondAttribute="centerY" id="V4i-bF-Jed"/>
<constraint firstItem="KBe-RM-BG8" firstAttribute="leading" secondItem="GSj-Ze-UIK" secondAttribute="trailing" constant="6" id="ehg-tW-7kq"/>
<constraint firstItem="GSj-Ze-UIK" firstAttribute="centerY" secondItem="KBe-RM-BG8" secondAttribute="centerY" id="esk-GV-DBS"/>
<constraint firstAttribute="trailing" secondItem="KBe-RM-BG8" secondAttribute="trailing" constant="18" id="hhE-rV-dV7"/>
<constraint firstItem="KBe-RM-BG8" firstAttribute="top" secondItem="JjA-MK-YzZ" secondAttribute="top" constant="21" id="pBQ-H8-xTK"/>
<constraint firstAttribute="bottom" secondItem="3mz-9M-e7Q" secondAttribute="bottom" constant="5" id="pQ9-a6-hM4"/>
<constraint firstItem="fMR-vj-7de" firstAttribute="centerY" secondItem="GSj-Ze-UIK" secondAttribute="centerY" id="q2v-t1-Zu0"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="160" id="qR5-cz-YAm"/>
<constraint firstItem="nPs-17-vfs" firstAttribute="trailing" secondItem="KBe-RM-BG8" secondAttribute="trailing" id="yls-k6-ZfC"/>
</constraints>
</view>
</subviews>
@@ -153,8 +193,11 @@
<outlet property="avatarImage" destination="Ql9-Jy-aWM" id="VoL-by-ygR"/>
<outlet property="collectionView" destination="HKL-L0-T2w" id="HSe-j0-S5d"/>
<outlet property="colorSelectedView" destination="HBJ-nh-56V" id="Iiq-iY-Glj"/>
<outlet property="showOrHideSkeletonButton" destination="nPs-17-vfs" id="vw4-fW-QoD"/>
<outlet property="skeletonTypeSelector" destination="fMR-vj-7de" id="CgX-3A-weo"/>
<outlet property="switchAnimated" destination="KBe-RM-BG8" id="emU-g9-NHT"/>
<outlet property="transitionDurationLabel" destination="5gN-Jz-44y" id="69y-iR-mbi"/>
<outlet property="transitionDurationStepper" destination="3mz-9M-e7Q" id="tzK-W7-A4D"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="PkM-Y0-M5i" userLabel="First Responder" sceneMemberID="firstResponder"/>
+25 -9
View File
@@ -4,7 +4,6 @@ import UIKit
import SkeletonView
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView! {
didSet {
collectionView.isSkeletonable = true
@@ -36,6 +35,9 @@ class ViewController: UIViewController {
@IBOutlet weak var switchAnimated: UISwitch!
@IBOutlet weak var skeletonTypeSelector: UISegmentedControl!
@IBOutlet weak var showOrHideSkeletonButton: UIButton!
@IBOutlet weak var transitionDurationLabel: UILabel!
@IBOutlet weak var transitionDurationStepper: UIStepper!
var type: SkeletonType {
return skeletonTypeSelector.selectedSegmentIndex == 0 ? .solid : .gradient
@@ -65,6 +67,23 @@ class ViewController: UIViewController {
showAlertPicker()
}
@IBAction func showOrHideSkeleton(_ sender: Any) {
showOrHideSkeletonButton.setTitle((view.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal)
view.isSkeletonActive ? hideSkeleton() : showSkeleton()
}
@IBAction func transitionDurationStepperAction(_ sender: Any) {
transitionDurationLabel.text = "transition duration: \(transitionDurationStepper.value) sec"
}
func showSkeleton() {
refreshSkeleton()
}
func hideSkeleton() {
view.hideSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
}
func refreshSkeleton() {
self.view.hideSkeleton()
if type == .gradient { showGradientSkeleton() }
@@ -73,23 +92,22 @@ class ViewController: UIViewController {
func showSolidSkeleton() {
if switchAnimated.isOn {
view.showAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!)
view.showAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!, transition: .crossDissolve(transitionDurationStepper.value))
} else {
view.showSkeleton(usingColor: colorSelectedView.backgroundColor!)
view.showSkeleton(usingColor: colorSelectedView.backgroundColor!, transition: .crossDissolve(transitionDurationStepper.value))
}
}
func showGradientSkeleton() {
let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!)
if switchAnimated.isOn {
view.showAnimatedGradientSkeleton(usingGradient: gradient)
view.showAnimatedGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
} else {
view.showGradientSkeleton(usingGradient: gradient)
view.showGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
}
}
func showAlertPicker() {
let alertView = UIAlertController(title: "Select color", message: "\n\n\n\n\n\n", preferredStyle: .alert)
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 50, width: 260, height: 115))
@@ -117,7 +135,6 @@ class ViewController: UIViewController {
// MARK: - UIPickerViewDelegate, UIPickerViewDataSource
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
@@ -134,7 +151,6 @@ class ViewController: UIViewController {
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width/3 - 10, height: view.frame.width/3 - 10)
}
@@ -162,7 +178,7 @@ extension ViewController: SkeletonCollectionViewDataSource {
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 0
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+158 -79
View File
@@ -1,17 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" 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>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<!--Item-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="SkeletonViewExample" customModuleProvider="target" sceneMemberID="viewController">
@@ -19,8 +16,56 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="F9K-jU-100" userLabel="ContainerView">
<rect key="frame" x="0.0" y="0.0" width="375" height="243"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="e9V-mk-xH0">
<rect key="frame" x="45" y="142" width="287" height="78"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="78" id="gF5-G1-lKI"/>
</constraints>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </string>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="lastLineFillPercent">
<integer key="value" value="40"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="linesCornerRadius">
<integer key="value" value="6"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</textView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="nMj-pU-5wJ">
<rect key="frame" x="141" y="20" width="93" height="93"/>
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="93" id="gw9-nu-cKo"/>
<constraint firstAttribute="width" constant="93" id="zB6-Lp-IUt"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="nMj-pU-5wJ" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" id="9X4-2r-AKx"/>
<constraint firstItem="e9V-mk-xH0" firstAttribute="leading" secondItem="F9K-jU-100" secondAttribute="leading" constant="45" id="HvQ-HY-zYU"/>
<constraint firstItem="e9V-mk-xH0" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" constant="1" id="KcB-tG-NXa"/>
<constraint firstAttribute="height" constant="243" id="MIj-xq-gr1"/>
<constraint firstItem="e9V-mk-xH0" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="142" id="Wcx-nZ-1lR"/>
<constraint firstAttribute="trailing" secondItem="e9V-mk-xH0" secondAttribute="trailing" constant="43" id="XbU-Og-rht"/>
<constraint firstItem="nMj-pU-5wJ" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="20" id="hQL-cr-MaN"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
<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"/>
<rect key="frame" x="0.0" y="243" width="375" height="215"/>
<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"/>
<prototypes>
@@ -42,7 +87,7 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="15" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhU-1t-AaI">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhU-1t-AaI" userLabel="Label">
<rect key="frame" x="118" y="29" width="237" height="20.5"/>
<constraints>
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="71" id="HRL-cI-ieC"/>
@@ -53,7 +98,10 @@
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="linesCornerRadius">
<integer key="value" value="0"/>
<integer key="value" value="5"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="lastLineFillPercent">
<integer key="value" value="20"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</label>
@@ -85,60 +133,11 @@
<outlet property="dataSource" destination="BYZ-38-t0r" id="Hxi-nC-gbY"/>
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="F9K-jU-100">
<rect key="frame" x="0.0" y="20" width="375" height="243"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="e9V-mk-xH0">
<rect key="frame" x="45" y="142" width="287" height="78"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="78" id="gF5-G1-lKI"/>
</constraints>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </string>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="lastLineFillPercent">
<integer key="value" value="40"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="linesCornerRadius">
<integer key="value" value="6"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</textView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="nMj-pU-5wJ">
<rect key="frame" x="141" y="20" width="93" height="93"/>
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="93" id="gw9-nu-cKo"/>
<constraint firstAttribute="width" constant="93" id="zB6-Lp-IUt"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="nMj-pU-5wJ" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" id="9X4-2r-AKx"/>
<constraint firstItem="e9V-mk-xH0" firstAttribute="leading" secondItem="F9K-jU-100" secondAttribute="leading" constant="45" id="HvQ-HY-zYU"/>
<constraint firstItem="e9V-mk-xH0" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" constant="1" id="KcB-tG-NXa"/>
<constraint firstAttribute="height" constant="243" id="MIj-xq-gr1"/>
<constraint firstItem="e9V-mk-xH0" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="142" id="Wcx-nZ-1lR"/>
<constraint firstAttribute="trailing" secondItem="e9V-mk-xH0" secondAttribute="trailing" constant="43" id="XbU-Og-rht"/>
<constraint firstItem="nMj-pU-5wJ" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="20" id="hQL-cr-MaN"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XgY-1a-UGc">
<rect key="frame" x="0.0" y="527" width="375" height="140"/>
<rect key="frame" x="0.0" y="458" width="375" height="160"/>
<subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="xOL-Sq-r4i">
<rect key="frame" x="20" y="23" width="140" height="29"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="xOL-Sq-r4i">
<rect key="frame" x="20" y="23" width="145" height="32"/>
<segments>
<segment title="Solid"/>
<segment title="Gradient"/>
@@ -147,43 +146,84 @@
<action selector="changeSkeletonType:" destination="BYZ-38-t0r" eventType="valueChanged" id="iAS-ab-0jP"/>
</connections>
</segmentedControl>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vz0-qg-GcZ">
<rect key="frame" x="310" y="21" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vz0-qg-GcZ">
<rect key="frame" x="308" y="21" width="51" height="31"/>
<connections>
<action selector="changeAnimated:" destination="BYZ-38-t0r" eventType="valueChanged" id="w1G-gZ-RE0"/>
</connections>
</switch>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Animated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WHN-wR-TKt">
<rect key="frame" x="211" y="28" width="91" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Animated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WHN-wR-TKt">
<rect key="frame" x="229" y="26" width="73" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Color" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7CF-rV-pK2">
<rect key="frame" x="32" y="89" width="52" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Color" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7CF-rV-pK2">
<rect key="frame" x="20" y="73.5" width="90" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iGp-rp-t1d">
<rect key="frame" x="92" y="84" width="30" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="iGp-rp-t1d">
<rect key="frame" x="130" y="69" width="30" height="30"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="Q3k-B1-E88"/>
<constraint firstAttribute="height" constant="30" id="xOD-RY-U4u"/>
</constraints>
</view>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Mde-Cm-CoS">
<rect key="frame" x="20" y="74" width="140" height="52"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Mde-Cm-CoS">
<rect key="frame" x="20" y="58" width="140" height="52"/>
<constraints>
<constraint firstAttribute="height" constant="52" id="3GX-2y-eQj"/>
<constraint firstAttribute="width" constant="140" id="6cC-Y1-RKs"/>
</constraints>
<connections>
<action selector="btnChangeColorTouchUpInside:" destination="BYZ-38-t0r" eventType="touchUpInside" id="cB8-Ik-LIJ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Tdu-YQ-saq">
<rect key="frame" x="263" y="69" width="94" height="30"/>
<state key="normal" title="Hide skeleton"/>
<connections>
<action selector="showOrHideSkeleton:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Ma1-WX-Dzy"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fade Duration: 0 sec" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mrw-PM-jJJ">
<rect key="frame" x="113.5" y="130" width="141.5" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<stepper opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" maximumValue="5" stepValue="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="l4N-LL-ZrJ">
<rect key="frame" x="263" y="123" width="94" height="32"/>
<connections>
<action selector="transitionDurationStepperAction:" destination="BYZ-38-t0r" eventType="valueChanged" id="jPN-df-fNs"/>
</connections>
</stepper>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="140" id="OH5-ja-ZlB"/>
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="leading" secondItem="mrw-PM-jJJ" secondAttribute="trailing" constant="8" id="5iU-dO-qVc"/>
<constraint firstItem="mrw-PM-jJJ" firstAttribute="centerY" secondItem="l4N-LL-ZrJ" secondAttribute="centerY" id="9OM-mx-4Jo"/>
<constraint firstAttribute="trailing" secondItem="Tdu-YQ-saq" secondAttribute="trailing" constant="18" id="BQ0-S0-YMp"/>
<constraint firstItem="iGp-rp-t1d" firstAttribute="trailing" secondItem="Mde-Cm-CoS" secondAttribute="trailing" id="IJ3-CC-5M7"/>
<constraint firstAttribute="height" constant="160" id="OH5-ja-ZlB"/>
<constraint firstItem="Mde-Cm-CoS" firstAttribute="leading" secondItem="XgY-1a-UGc" secondAttribute="leading" constant="20" id="Rek-hz-pDw"/>
<constraint firstItem="xOL-Sq-r4i" firstAttribute="top" secondItem="XgY-1a-UGc" secondAttribute="top" constant="23" id="Udf-0g-bZm"/>
<constraint firstItem="Tdu-YQ-saq" firstAttribute="top" secondItem="vz0-qg-GcZ" secondAttribute="bottom" constant="17" id="WiR-yP-dyv"/>
<constraint firstItem="Mde-Cm-CoS" firstAttribute="centerY" secondItem="7CF-rV-pK2" secondAttribute="centerY" id="eaN-FA-4mF"/>
<constraint firstItem="iGp-rp-t1d" firstAttribute="centerY" secondItem="7CF-rV-pK2" secondAttribute="centerY" id="hBb-mp-AjF"/>
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="top" secondItem="Tdu-YQ-saq" secondAttribute="bottom" constant="24" id="iA5-RB-pW2"/>
<constraint firstItem="vz0-qg-GcZ" firstAttribute="top" secondItem="XgY-1a-UGc" secondAttribute="top" constant="21" id="iad-6N-wNf"/>
<constraint firstItem="vz0-qg-GcZ" firstAttribute="leading" secondItem="WHN-wR-TKt" secondAttribute="trailing" constant="6" id="jgu-tV-gqO"/>
<constraint firstItem="WHN-wR-TKt" firstAttribute="centerY" secondItem="vz0-qg-GcZ" secondAttribute="centerY" id="kTu-fb-Bc8"/>
<constraint firstAttribute="trailing" secondItem="vz0-qg-GcZ" secondAttribute="trailing" constant="18" id="ktq-JT-uoR"/>
<constraint firstItem="xOL-Sq-r4i" firstAttribute="leading" secondItem="XgY-1a-UGc" secondAttribute="leading" constant="20" id="pY0-qd-xmK"/>
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="trailing" secondItem="Tdu-YQ-saq" secondAttribute="trailing" id="ql2-Z9-dnv"/>
<constraint firstItem="iGp-rp-t1d" firstAttribute="leading" secondItem="7CF-rV-pK2" secondAttribute="trailing" constant="20" id="vWD-0m-dMp"/>
<constraint firstItem="7CF-rV-pK2" firstAttribute="centerY" secondItem="Tdu-YQ-saq" secondAttribute="centerY" id="x0d-LB-A4q"/>
<constraint firstItem="7CF-rV-pK2" firstAttribute="leading" secondItem="xOL-Sq-r4i" secondAttribute="leading" id="yEL-Nv-z76"/>
</constraints>
</view>
</subviews>
@@ -205,18 +245,57 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="NO"/>
</userDefinedRuntimeAttributes>
</view>
<tabBarItem key="tabBarItem" title="Item" id="wQY-ap-3n3"/>
<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="showOrHideSkeletonButton" destination="Tdu-YQ-saq" id="gkm-mB-zYD"/>
<outlet property="skeletonTypeSelector" destination="xOL-Sq-r4i" id="yTr-8L-I4Y"/>
<outlet property="switchAnimated" destination="vz0-qg-GcZ" id="d2R-8x-lRb"/>
<outlet property="tableview" destination="UCB-SP-lQk" id="XV0-KX-lAN"/>
<outlet property="transitionDurationLabel" destination="mrw-PM-jJJ" id="BVK-Kl-5Q3"/>
<outlet property="transitionDurationStepper" destination="l4N-LL-ZrJ" id="OJr-Ul-7XR"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-2582" y="-400"/>
<point key="canvasLocation" x="-2682" y="340"/>
</scene>
<!--Item-->
<scene sceneID="Cfc-AT-AS1">
<objects>
<viewController id="dv8-ph-Ehg" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Jwx-gI-Qod">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<viewLayoutGuide key="safeArea" id="Ao1-hk-zrH"/>
</view>
<tabBarItem key="tabBarItem" title="Item" id="iKp-9S-aib"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="M03-a6-GOC" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1644" y="340"/>
</scene>
<!--Tab Bar Controller-->
<scene sceneID="U6k-MC-AHH">
<objects>
<tabBarController automaticallyAdjustsScrollViewInsets="NO" id="Va7-1y-Tel" sceneMemberID="viewController">
<toolbarItems/>
<tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="HSI-2O-RyO">
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tabBar>
<connections>
<segue destination="BYZ-38-t0r" kind="relationship" relationship="viewControllers" id="dL3-9L-KNU"/>
<segue destination="dv8-ph-Ehg" kind="relationship" relationship="viewControllers" id="8QB-uV-gaF"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="huq-Fh-0sW" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-2172" y="-555"/>
</scene>
</scenes>
<resources>
+49 -18
View File
@@ -10,7 +10,6 @@ import UIKit
import SkeletonView
class ViewController: UIViewController {
@IBOutlet weak var tableview: UITableView! {
didSet {
tableview.rowHeight = UITableView.automaticDimension
@@ -35,6 +34,9 @@ class ViewController: UIViewController {
@IBOutlet weak var switchAnimated: UISwitch!
@IBOutlet weak var skeletonTypeSelector: UISegmentedControl!
@IBOutlet weak var showOrHideSkeletonButton: UIButton!
@IBOutlet weak var transitionDurationLabel: UILabel!
@IBOutlet weak var transitionDurationStepper: UIStepper!
var type: SkeletonType {
return skeletonTypeSelector.selectedSegmentIndex == 0 ? .solid : .gradient
@@ -43,11 +45,11 @@ class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableview.isSkeletonable = true
view.showAnimatedSkeleton()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.showAnimatedSkeleton()
}
@IBAction func changeAnimated(_ sender: Any) {
@@ -66,31 +68,62 @@ class ViewController: UIViewController {
showAlertPicker()
}
func refreshSkeleton() {
self.view.hideSkeleton()
if type == .gradient { showGradientSkeleton() }
else { showSolidSkeleton() }
@IBAction func showOrHideSkeleton(_ sender: Any) {
showOrHideSkeletonButton.setTitle((view.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal)
view.isSkeletonActive ? hideSkeleton() : showSkeleton()
}
func showSolidSkeleton() {
if switchAnimated.isOn {
view.showAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!)
} else {
view.showSkeleton(usingColor: colorSelectedView.backgroundColor!)
@IBAction func transitionDurationStepperAction(_ sender: Any) {
transitionDurationLabel.text = "Transition duration: \(transitionDurationStepper.value) sec"
}
func showSkeleton() {
if type == .gradient {
let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!)
if switchAnimated.isOn {
view.showAnimatedGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
}
else {
view.showGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
}
}
else {
if switchAnimated.isOn {
view.showAnimatedSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
}
else {
view.showSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
}
}
}
func showGradientSkeleton() {
func hideSkeleton() {
view.hideSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
}
func refreshSkeleton() {
if type == .gradient { showOrUpdateGradientSkeleton() }
else { showOrUpdatepdateSolidSkeleton() }
}
func showOrUpdatepdateSolidSkeleton() {
if switchAnimated.isOn {
view.updateAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!)
} else {
view.updateSkeleton(usingColor: colorSelectedView.backgroundColor!)
}
}
func showOrUpdateGradientSkeleton() {
let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!)
if switchAnimated.isOn {
view.showAnimatedGradientSkeleton(usingGradient: gradient)
view.updateAnimatedGradientSkeleton(usingGradient: gradient)
} else {
view.showGradientSkeleton(usingGradient: gradient)
view.updateGradientSkeleton(usingGradient: gradient)
}
}
func showAlertPicker() {
let alertView = UIAlertController(title: "Select color", message: "\n\n\n\n\n\n", preferredStyle: .alert)
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 50, width: 260, height: 115))
@@ -116,7 +149,6 @@ class ViewController: UIViewController {
}
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
@@ -131,9 +163,8 @@ extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
}
extension ViewController: SkeletonTableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 9
return 10
}
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
+23 -23
View File
@@ -12,10 +12,10 @@ GEM
atomos (0.1.3)
babosa (1.0.2)
claide (1.0.2)
cocoapods (1.7.0.beta.3)
cocoapods (1.7.5)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.7.0.beta.3)
cocoapods-core (= 1.7.5)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.2.2, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
@@ -25,13 +25,13 @@ GEM
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (>= 2.2.0, < 3.0)
fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0)
molinillo (~> 0.6.6)
nap (~> 1.0)
ruby-macho (~> 1.4)
xcodeproj (>= 1.8.2, < 2.0)
cocoapods-core (1.7.0.beta.3)
xcodeproj (>= 1.10.0, < 2.0)
cocoapods-core (1.7.5)
activesupport (>= 4.0.2, < 6)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
@@ -53,12 +53,12 @@ GEM
declarative (0.0.10)
declarative-option (0.1.0)
digest-crc (0.4.1)
domain_name (0.5.20180417)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.2)
dotenv (2.7.4)
emoji_regex (1.0.1)
escape (0.0.4)
excon (0.64.0)
excon (0.65.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
@@ -67,7 +67,7 @@ GEM
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.5)
fastlane (2.121.0)
fastlane (2.128.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
@@ -86,8 +86,8 @@ GEM
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
mini_magick (~> 4.5.1)
multi_json
jwt (~> 2.1.0)
mini_magick (>= 4.9.4, < 5.0.0)
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
@@ -104,7 +104,7 @@ GEM
xcodeproj (>= 1.8.1, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fourflusher (2.2.0)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
google-api-client (0.23.9)
@@ -117,7 +117,7 @@ GEM
signet (~> 0.9)
google-cloud-core (1.3.0)
google-cloud-env (~> 1.0)
google-cloud-env (1.0.5)
google-cloud-env (1.2.0)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
@@ -143,7 +143,7 @@ GEM
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.0331)
mini_magick (4.5.1)
mini_magick (4.9.5)
minitest (5.11.3)
molinillo (0.6.6)
multi_json (1.13.1)
@@ -153,7 +153,7 @@ GEM
nap (1.1.0)
naturally (2.2.0)
netrc (0.11.0)
os (1.0.0)
os (1.0.1)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
@@ -163,7 +163,7 @@ GEM
retriable (3.1.2)
rouge (2.0.7)
ruby-macho (1.4.0)
rubyzip (1.2.2)
rubyzip (1.2.3)
security (0.1.3)
signet (0.11.0)
addressable (~> 2.3)
@@ -178,19 +178,19 @@ GEM
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
tty-cursor (0.6.1)
tty-screen (0.6.5)
tty-spinner (0.9.0)
tty-cursor (~> 0.6.0)
tty-cursor (0.7.0)
tty-screen (0.7.0)
tty-spinner (0.9.1)
tty-cursor (~> 0.7)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
unicode-display_width (1.5.0)
unicode-display_width (1.6.0)
word_wrap (1.0.0)
xcodeproj (1.8.2)
xcodeproj (1.11.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
@@ -209,4 +209,4 @@ DEPENDENCIES
fastlane
BUNDLED WITH
1.16.6
2.0.1
+86 -10
View File
@@ -22,6 +22,9 @@
<img src="https://img.shields.io/badge/contact-@JuanpeCatalan-blue.svg?style=flat" alt="Twitter: @JuanpeCatalan" />
</a>
<br/>
<a href="https://gitter.im/SkeletonView/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge">
<img src="https://badges.gitter.im/SkeletonView/community.svg?style=flat" />
</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>
@@ -29,7 +32,8 @@
🌎 Translations: </br>
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) by [@WhatsXie](https://twitter.com/WhatsXie) </br>
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) by [@brunomunizaf](https://twitter.com/brunomuniz_af)
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) by [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) by [@techinpark](https://twitter.com/techinpark)
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.
@@ -49,6 +53,7 @@ Enjoy it! 🙂
* [Custom colors](#-custom-colors)
* [Appearance](#-appearance)
* [Custom animations](#-custom-animations)
* [Transitions](#-transitions)
* [Hierarchy](#-hierarchy)
* [Debug](#-debug)
* [Documentation](#-documentation)
@@ -98,7 +103,7 @@ Once you have your Swift package set up, adding `SkeletonView` as a dependency i
```swift
dependencies: [
.package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.6")
.package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0")
]
```
@@ -168,6 +173,33 @@ avatarImageView.isSkeletonable = true
> **IMPORTANT!**
>>```SkeletonView``` is recursive, so if you want show the skeleton in all skeletonable views, you only need to call the show method in the main container view. For example, with UIViewControllers
### Extra
#### Skeleton views layout
Sometimes skeleton layout may not fit your layout because the parent view bounds have changed. ~For example, rotating the device.~
You can relayout the skeleton views like so:
```swift
override func viewDidLayoutSubviews() {
view.layoutSkeletonIfNeeded()
}
```
⚠️⚠️ You shouldn't call this method. From *version 1.8.1* you don't need to call this method, the library does automatically. So, you can use this method *ONLY* in the cases when you need to update the layout of the skeleton manually.
#### Update skeleton configuration
You can change the skeleton configuration at any time like its colour, animation, etc. with the following methods:
```swift
(1) view.updateSkeleton() // Solid
(2) view.updateGradientSkeleton() // Gradient
(3) view.updateAnimatedSkeleton() // Solid animated
(4) view.updateAnimatedGradientSkeleton() // Gradient animated
```
### 🌿 Collections
Now, ```SkeletonView``` is compatible with ```UITableView``` and ```UICollectionView```.
@@ -364,20 +396,63 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
Exist another way to create sliding animations, just using this shortcut:
>>```let animation = GradientDirection.leftToRight.slidingAnimation()```
### 🏄 Transitions
```SkeletonView``` has build-in transitions to **show** or **hide** the skeletons in a *smoother* way 🤙
To use the transition, simply add the ```transition``` parameter to your ```showSkeleton()``` or ```hideSkeleton()``` function with the transition time, like this:
```swift
view.showSkeleton(transition: .crossDissolve(0.25)) //Show skeleton cross dissolve transition with 0.25 seconds fade time
view.hideSkeleton(transition: .crossDissolve(0.25)) //Hide skeleton cross dissolve transition with 0.25 seconds fade time
```
**Preview**
<table>
<tr>
<td width="50%">
<center>None</center>
</td>
<td width="50%">
<center>Cross dissolve</center>
</td>
</tr>
<tr>
<td width="50%">
<img src="Assets/skeleton_transition_nofade.gif"></img>
</td>
<td width="50%">
<img src="Assets/skeleton_transition_fade.gif"></img>
</td>
</tr>
</table>
### 👨‍👧‍👦 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.
Because an image is worth a thousand words:
In this example we have a `UIViewController` with a `ContainerView` and a `UITableView`. When the view is ready, we show the skeleton using this method:
```
view.showSkeleton()
```
> ```ìsSkeletonable```= ☠️
| Configuration | Result
|------- | -------
|![](Assets/no_skeletonable.png) | ![](Assets/no_skeletonables_result.png)
|![](Assets/container_no_skeletonable.png) | ![](Assets/no_skeletonables_result.png)
|![](Assets/container_skeletonable.png) | ![](Assets/container_skeletonable_result.png)
|![](Assets/all_skeletonables.png) | ![](Assets/all_skeletonables_result.png)
| Configuration | Result|
|:-------:|:-------:|
|<img src="Assets/no_skeletonable.jpg" width="350"/> | <img src="Assets/no_skeletonables_result.png" width="350"/>|
|<img src="Assets/container_no_skeletonable.jpg" width="350"/> | <img src="Assets/no_skeletonables_result.png" width="350"/>|
|<img src="Assets/container_skeletonable.jpg" width="350"/> | <img src="Assets/container_skeletonable_result.png" width="350"/>|
|<img src="Assets/all_skeletonables.jpg" width="350"/>| <img src="Assets/all_skeletonables_result.png" width="350"/>|
|<img src="Assets/tableview_no_skeletonable.jpg" width="350"/> | <img src="Assets/tableview_no_skeletonable_result.png" height="350"/>|
|<img src="Assets/tableview_skeletonable.jpg" width="350"/> | <img src="Assets/tableview_skeletonable_result.png" height="350"/>|
### 🔬 Debug
@@ -413,7 +488,7 @@ Coming soon...😅
* iOS 9.0+
* tvOS 9.0+
* Swift 4.2
* Swift 5
## 📬 Next steps
@@ -425,8 +500,8 @@ Coming soon...😅
* [x] Add recovery state
* [x] Custom default appearance
* [x] Debug mode
* [x] Add animations when it shows/hides the skeletons
* [ ] Custom collections compatible
* [ ] Add animations when it shows/hides the skeletons
* [ ] MacOS and WatchOS compatible
## ❤️ Contributing
@@ -453,6 +528,7 @@ See [all contributors](https://github.com/Juanpe/SkeletonView/graphs/contributor
- [CocoaControls](https://www.cocoacontrols.com/controls/skeletonview)
- [Awesome iOS Newsletter #74](https://ios.libhunt.com/newsletter/74)
- [Swift News #36](https://www.youtube.com/watch?v=mAGpsQiy6so)
- [Best iOS articles, new tools & more](https://medium.com/flawless-app-stories/best-ios-articles-new-tools-more-fcbe673e10d)
+501
View File
@@ -0,0 +1,501 @@
![](Assets/header2.jpg)
<p align="center">
<a href="https://app.bitrise.io/app/6d289a17e22c8323">
<img src="https://app.bitrise.io/app/6d289a17e22c8323/status.svg?token=fI7gKC41XD9-aRXDScCKBw&branch=master">
</a>
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
<a href="https://github.com/Juanpe/SkeletonView">
<img src="https://img.shields.io/cocoapods/p/SkeletonView.svg" alt="Platforms">
</a>
<img src="https://img.shields.io/badge/Swift-5-orange.svg" />
<a href="https://cocoapods.org/pods/SkeletonView">
<img src="https://img.shields.io/cocoapods/v/SkeletonView.svg" alt="CocoaPods" />
</a>
<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://github.com/apple/swift-package-manager">
<img src="https://img.shields.io/badge/SPM-compatible-brightgreen.svg" alt="SPM" />
</a>
<a href="https://twitter.com/JuanpeCatalan">
<img src="https://img.shields.io/badge/contact-@JuanpeCatalan-blue.svg?style=flat" alt="Twitter: @JuanpeCatalan" />
</a>
<br/>
<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>
🌎 번역에 도움을 주신분들: </br>
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) by [@WhatsXie](https://twitter.com/WhatsXie) </br>
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) by [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) by [@techinpark](https://twitter.com/techinpark)
오늘날 거의 대부분의 앱들은 비동기 방식의 API 호출을 사용하는 프로세스를 가지고 있습니다.
프로세스가 작동하는동안 개발자들은 작업이 실행되고 있다는것을 사용자들에게 보여주기 위해서 로딩 뷰를 배치합니다.
```SkeletonView```는 이러한 필요에 의해 고안되었고, 사용자들에게 무엇인가 로딩이 되고 있다는것을 보여주면서 기다리는 콘텐츠에 대해서도 미리 준비할 수 있게 해주는 우아하게 표현할수 있는 방법입니다
맘껏 누리세요 🙂
* [기능](#-features)
* [가이드](#-guides)
* [설치방법](#-installation)
* [Cocoapods](#using-cocoapods)
* [Carthage](#using-carthage)
* [SPM](#using-swift-package-manager)
* [어떻게 사용하나요?](#-how-to-use)
* [Collections](#-collections)
* [Multiline text](#-multiline-text)
* [Custom colors](#-custom-colors)
* [Appearance](#-appearance)
* [Custom animations](#-custom-animations)
* [Hierarchy](#-hierarchy)
* [Debug](#-debug)
* [문서화](#-documentation)
* [지원되는 OS와 SDK 버전](#-supported-os--sdk-versions)
* [Next steps](#-next-steps)
* [Contributing](#-contributing)
* [Mentions](#-mentions)
* [개발자](#-author)
* [라이센스](#-license)
## 🌟 기능
- [x] 사용이 쉽습니다
- [x] 모든 `UIView`에서 사용가능합니다
- [x] 전체 커스터마이징이 가능합니다
- [x] 공통으로 이용가능합니다 (iPhone & iPad)
- [x] `Interface Builder` 에서 사용 가능합니다.
- [x] 간단한 스위프트 문법
- [x] 가볍고 가독성 좋은 코드
## 🎬 사용가이드
[<img src="Assets/thumb_getting_started.png">](https://youtu.be/75kgOhWsPNA)
## 📲 설치 방법
#### [CocoaPods](https://cocoapods.org) 로 사용하기
당신의 프로젝트 `Podfile` 파일에 아래와 같이 입력합니다:
```ruby
pod "SkeletonView"
```
#### [Carthage](https://github.com/carthage)로 사용하기
당신의 프로젝트 `Cartfile` 파일에 아래와 같이 입력합니다:
```bash
github "Juanpe/SkeletonView"
```
#### [Swift Package Manager](https://github.com/apple/swift-package-manager)로 사용하기
당신의 프로젝트에 Swift package를 설정한다면, `SkeletonView` 를 `Package.swift` 파일에 있는 `dependencies`에 추가하시면 됩니다.
```swift
dependencies: [
.package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.6")
]
```
## 🐒 어떻게 사용하나요?
`SkeletonView` 를 이용하기 위해서는 딱 **3** 단계만 기억하세요:
**1.** 사용하고자 하는 파일에서 `SkeletonView` 를 `Import` 합니다.
```swift
import SkeletonView
```
**2.** 자, 그렇다면 UIView 속성에 `skeletonables` 를 이용하실 수 있습니다. 두가지 옵션이 있습니다
**코드로 사용하는 방법:**
```swift
avatarImageView.isSkeletonable = true
```
**인터페이스빌더 / 스토리보드를 이용하는 방법:**
![](Assets/storyboard.png)
**3.** 당신이 뷰를 세팅할때, **skeleton** 옵션을 사용 할 수 있습니다. 총 **4** 가지 옵션을 지원합니다:
```swift
(1) view.showSkeleton() // Solid
(2) view.showGradientSkeleton() // Gradient
(3) view.showAnimatedSkeleton() // Solid animated
(4) view.showAnimatedGradientSkeleton() // Gradient animated
```
**미리보기**
<table>
<tr>
<td width="25%">
<center>Solid</center>
</td>
<td width="25%">
<center>Gradient</center>
</td>
<td width="25%">
<center>Solid Animated</center>
</td>
<td width="25%">
<center>Gradient Animated</center>
</td>
</tr>
<tr>
<td width="25%">
<img src="Assets/solid.png"></img>
</td>
<td width="25%">
<img src="Assets/gradient.png"></img>
</td>
<td width="25%">
<img src="Assets/solid_animated.gif"></img>
</td>
<td width="25%">
<img src="Assets/gradient_animated.gif"></img>
</td>
</tr>
</table>
> **중요!**
>>```SkeletonView``` 는 재귀적으로 되어있습니다, 만약 모든 뷰에 대해서 skeleton을 호출하고 싶다면, 메인 컨테이너 뷰에서 show `method`를 호출하여야 합니다. 예를 들자면 UIViewControllers가 있습니다.
### 🌿 Collections
현재, ```SkeletonView``` 는 ```UITableView``` 와 ```UICollectionView```에서 호환됩니다.
#### UITableView
만약 ```UITableView```에서 skeleton을 호출하고 싶다면, ```SkeletonTableViewDataSource``` protocol 을 구현하여야 합니다.
``` swift
public protocol SkeletonTableViewDataSource: UITableViewDataSource {
func numSections(in collectionSkeletonView: UITableView) -> Int
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
}
```
해당 프로토클은 보시다시피 ```UITableViewDataSource```를 상속받아 구현하였으므로, skeleton의 protocol과 대체 가능합니다.
프로토콜의 기본 구현은 다음과 같습니다:
``` swift
func numSections(in collectionSkeletonView: UITableView) -> Int
// Default: 1
```
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
// Default:
// 전체 테이블 뷰를 채우는데 필요한 셀 수를 계산합니다
```
해당 메소드는 당신이 구현하여야할 cell identifier을 아는 경우에만 사용합니다, 해당 메소드는 기본으로 구현하지 않아도됩니다 :
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
```
**Example**
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
return "CellIdentifier"
}
```
> **중요!**
> 만약 사이즈가 변하는 셀을 사용한다면 (`tableView.rowHeight = UITableViewAutomaticDimension` ),`estimatedRowHeight`를 무조건 정의해주세요.
👩🏼‍🏫 **어떻게 특정 요소에 skeleton 을 지정할까요?**
아래의 그림은 `UITableView` 에서 특정한 요소에 skeleton 을 지정하는 방법을 보여주는 이미지 입니다:
![](Assets/tableview_scheme.png)
위의 이미지에서 보이듯, 테이블 뷰와 셀에 들어가는 UI 요소들에는 적용을 해야하지만, `contentView`에 skeleton을 적용할 필요는 없습니다.
#### UICollectionView
```UICollectionView``` 에 적용을 하기 위해서는, ```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
}
```
```UITableView``` 와 사용방법은 같습니다.
### 📰 Multiline text
![](Assets/multilines2.png)
텍스트가 들어있는 요소를 사용한다면, ```SkeletonView``` 에서 텍스트의 라인을 그려줍니다.
그리고, 원하는 라인 수를 설정할 수 있습니다. 만약 ```numberOfLines``` 을 0으로 설정한다면, 자동으로 필요한 라인수를 계산해서 그려줍니다. 대신 값이 설정되어있다면 설정된 수만큼의 라인이 그려집니다.
##### 🎛 Customize
당신은 멀티라인을 위해 몇가지 옵션을 설정할 수 있습니다.
| 속성 | 값 | 기본값 | 미리보기 |
| ----------------------------------------------- | --------- | ----- | ---------------------------------- |
| 마지막 라인의 **퍼센트** 를 지정 할 수 있습니다. | `0...100` | `70%` | ![](Assets/multiline_lastline.png) |
| 라인의 **Corner radius** 를 지정할 수 있습니다. (**새로운기능**) | `0...10` | `0` | ![](Assets/multiline_corner.png) |
라인의 radius를 지정하기 위해서는 **코드** 를 이용합니다, 아래 처럼 코드를 작성합니다:
```swift
descriptionTextView.lastLineFillPercent = 50
descriptionTextView.linesCornerRadius = 5
```
혹은 **IB/Storyboard** 를 이용하실 수 있습니다:
![](Assets/multiline_customize.png)
### 🎨 Custom colors
당신은 skeleton의 색상을 지정 할 수 있습니다. 간단하게 원하는 색상을 파라미터로 넘겨주시면 됩니다.
**단색 이용방법**
``` swift
view.showSkeleton(usingColor: UIColor.gray) // Solid
// or
view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0))
```
**그라디언트 이용 방법**
``` swift
let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue)
view.showGradientSkeleton(usingGradient: gradient) // Gradient
```
게다가, ```SkeletonView``` 에서는 20가지의 기본 컬러를 지원합니다 🤙🏼
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
![](Assets/flatcolors.png)
###### 위 이미지는 [https://flatuicolors.com](https://flatuicolors.com) 사이트에서 발췌했습니다.
### 🦋 Appearance
**새로운 사항** skeleton 은 기본설정 값이 정해져 있습니다. 만약 커스텀 컬러를 사용할 필요가 없다면, `SkeletonView` 에 지정 되어있는 기본설정을 사용하시면 됩니다.
기본 설정값:
- **tintColor**: UIColor
- *기본값: .clouds*
- **gradient**: SkeletonGradient
- *기본값: SkeletonGradient(baseColor: .clouds)*
- **multilineHeight**: CGFloat
- *기본값: 15*
- **multilineSpacing**: CGFloat
- *기본값: 10*
- **multilineLastLineFillPercent**: Int
- *기본값: 70*
- **multilineCornerRadius**: Int
- *기본값: 0*
`SkeletonAppearance.default` 에는 사용 되어지는 기본 값들이 설정되어 있습니다 . 아래의 코드와 같이 사용할 수 있습니다:
```Swift
SkeletonAppearance.default.multilineHeight = 20
SkeletonAppearance.default.tintColor = .green
```
### 🤓 커스텀 애니메이션
```SkeletonView``` 에는 두가지 애니메이션이 내장되어 있습니다, 단색 *바운스* 애니메이션과 그라디언트 *슬라이드* 애니메이션 입니다 .
게다가, 직접 애니메이션을 추가하고 싶다면 정말 간단합니다.
Skeleton 에서는 `showAnimatedSkeleton` 함수를 ```SkeletonLayerAnimation```에 정의하여 맞춤형 애니메이션을 정의할 수 있도록 되어 있습니다.
```swift
public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation
```
함수는 이렇게 호출 가능합니다:
```swift
view.showAnimatedSkeleton { (layer) -> CAAnimation in
let animation = CAAnimation()
// Customize here your animation
return animation
}
```
```SkeletonAnimationBuilder```의 사용이 가능합니다. ```SkeletonLayerAnimation```을 만들기 위해 사용됩니다.
이제, 그라디언트를 위한 **슬라이딩 애니메이션** 을 만들 수 있습니다, 애니메이션을 위한 **방향** 과 **지속시간** 을 설정 할 수 있습니다. (기본값 = 1.5초).
```swift
// func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation
let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight)
view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
```
```GradientDirection``` 는 enum 으로 정의 되어있습니다., 아래의 케이스를 참조하세요:
| 방향 | 미리보기 |
| ------------------- | ---------------------------------------------- |
| .leftRight | ![](Assets/sliding_left_to_right.gif) |
| .rightLeft | ![](Assets/sliding_right_to_left.gif) |
| .topBottom | ![](Assets/sliding_top_to_bottom.gif) |
| .bottomTop | ![](Assets/sliding_bottom_to_top.gif) |
| .topLeftBottomRight | ![](Assets/sliding_topLeft_to_bottomRight.gif) |
| .bottomRightTopLeft | ![](Assets/sliding_bottomRight_to_topLeft.gif) |
> **😉 꿀팁!**
슬라이딩 애니메이션을 만들기 위한 또다른 방법이 있습니다, 아래의 코드를 참조하세요:
>>```let animation = GradientDirection.leftToRight.slidingAnimation()```
### 👨‍👧‍👦 계층 구조
```SkeletonView```는 재귀적입니다 , 그리고 우리는 skeleton이 효율적으로 작동하기를 원하기 때문에, 가능한 빨리 재귀작업을 중단하기를 원합니다. 이러한 이유때문에 반드시 컨테이너 뷰를 `Skeletonable` 로 설정해야 합니다, `skeletonable` 되지 않는 뷰를 만나는 순간 재귀 작업을 중단하기 떄문입니다.
아래의 이미지를 참고하세요 이미지는 한눈에 이해되실겁니다:
> ```ìsSkeletonable```= ☠️
| 설정값 | 결과 |
| ----------------------------------------- | --------------------------------------------- |
| ![](Assets/no_skeletonable.png) | ![](Assets/no_skeletonables_result.png) |
| ![](Assets/container_no_skeletonable.png) | ![](Assets/no_skeletonables_result.png) |
| ![](Assets/container_skeletonable.png) | ![](Assets/container_skeletonable_result.png) |
| ![](Assets/all_skeletonables.png) | ![](Assets/all_skeletonables_result.png) |
### 🔬 디버그
**새로운소식** 어떤것들이 잘 동작 하지 않을때를 위해 디버그 작업을 용이하게 하기 위해서 `SkeletonView` 에는 몇가지 새로운 것들이 있습니다.
첫번쨰로, `UIView` 에서 skeleton 정보를 보기위해 다음과 같이 지원하고 있습니다:
```swift
var skeletonDescription: String
```
skeleton은 이렇게 생겼습니다:
![](Assets/debug_description.png)
그리고, 새로운 **디버그 모드**를 활성화 시킬 수 있습니다. 간단하게 `SKELETON_DEBUG` 이라는 환경 변수를 추가해 활성화 하면 됩니다.
![](Assets/debug_mode.png)
그런 이후 skeleton이 나오면 Xcode 콘솔창에서 계층 구조를 볼 수 있습니다.
<details>
<summary>예제를 확인해보세요. </summary>
<img src="Assets/hierarchy_output.png" />
</details>
### 📚 문서화
조금만 기다려주세요...😅
### 📋 지원 가능한 OS & SDK 버전
* iOS 9.0+
* tvOS 9.0+
* Swift 4.2
## 📬 예정된 기능들
* [x] 멀티라인 에서의 마지막 라인의 채우기 비율 설정
* [x] 더많은 그라디언트 애니메이션
* [x] resizable cells 지원
* [x] CollectionView 호환
* [x] tvOS 호환
* [x] recovery state 추가
* [x] Custom default appearance
* [x] 디버그 모드
* [ ] Custom collections 호환
* [ ] skeletons 가 보이거나 가려질때 애니메이션 추가
* [ ] MacOS 와 WatchOS 호환
## ❤️ 기여하기
이 프로젝트는 오픈소스 프로젝트 입니다, 마음편하게 기여해주시면 됩니다 어떻게 하냐구요?
- 새로운 [이슈](https://github.com/Juanpe/SkeletonView/issues/new)를 등록합니다.
- [email](mailto://juanpecatalan.com)을 보냅니다.
- 당신의 수정을 제안합니다, pull request를 포함한 수정을 권장합니다.
전체 [기여자목록](https://github.com/Juanpe/SkeletonView/graphs/contributors)
###### [SwiftPlate](https://github.com/JohnSundell/SwiftPlate)를 통해 프로젝트가 생성되었습니다
## 📢 소식들
- [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start)
- [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful)
- [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65)
- [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9)
- [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899)
- [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d)
- [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/)
- [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204)
- [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759)
- [CocoaControls](https://www.cocoacontrols.com/controls/skeletonview)
- [Awesome iOS Newsletter #74](https://ios.libhunt.com/newsletter/74)
- [Swift News #36](https://www.youtube.com/watch?v=mAGpsQiy6so)
## 👨🏻‍💻 개발자
[1.1]: http://i.imgur.com/tXSoThF.png
[1]: http://www.twitter.com/JuanpeCatalan
* Juanpe Catalán [![alt text][1.1]][1]
<a class="bmc-button" target="_blank" href="https://www.buymeacoffee.com/CDou4xtIK"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy me a coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;"><span style="margin-left:5px"></span></a>
## 👮🏻 라이센스
```
MIT License
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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
+2 -2
View File
@@ -70,8 +70,8 @@
### 📋 版本要求
* iOS 9.0+
* tvOS 9.0+
* iOS 10.0+
* tvOS 10.0+
* Swift 4.2
### 🔮 示例
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "SkeletonView"
s.version = "1.6"
s.version = "1.8.2"
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.
+44 -18
View File
@@ -17,7 +17,6 @@
17DD0E0F207FB28C00C56334 /* CALayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622421FAC81FD007C062A /* CALayer+Extensions.swift */; };
17DD0E10207FB28C00C56334 /* UIColor+Skeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */; };
17DD0E11207FB28C00C56334 /* UIView+Frame.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */; };
17DD0E12207FB28C00C56334 /* UIView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */; };
17DD0E13207FB28C00C56334 /* UIView+UIApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */; };
17DD0E14207FB28F00C56334 /* SkeletonAnimationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */; };
17DD0E15207FB28F00C56334 /* SkeletonAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2F1FB0EC9D00EE67C5 /* SkeletonAppearance.swift */; };
@@ -29,7 +28,8 @@
17DD0E1D207FB32100C56334 /* ContainsMultilineText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E3A1FB123C100EE67C5 /* ContainsMultilineText.swift */; };
17DD0E1E207FB32100C56334 /* PrepareForSkeletonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */; };
17DD0E1F207FB32100C56334 /* RecursiveProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */; };
3B83EE4820C41488005178A4 /* UIView+IBInspectable.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3B83EE4620C41488005178A4 /* UIView+IBInspectable.swift */; settings = {ATTRIBUTES = (Public, ); }; };
1E6C67A2230E76CC0019D87B /* SkeletonTransitionStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4CE587C22EEE65200333067 /* SkeletonTransitionStyle.swift */; };
1E6C67A3230E76CE0019D87B /* UIView+Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4CE587B22EEE63100333067 /* UIView+Transitions.swift */; };
42ABD063210B548200BEEFF4 /* SkeletonView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* SkeletonView.framework */; };
42ABD069210B548200BEEFF4 /* SkeletonView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* SkeletonView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
42ABD078210B54E200BEEFF4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD070210B54E100BEEFF4 /* AppDelegate.swift */; };
@@ -38,13 +38,14 @@
42ABD07B210B54E200BEEFF4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD073210B54E100BEEFF4 /* ViewController.swift */; };
42ABD07C210B54E200BEEFF4 /* Base.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 42ABD074210B54E100BEEFF4 /* Base.lproj */; };
42ABD07F210B54E200BEEFF4 /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */; };
870F4E4321CAC07300B9233B /* SkeletonConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 870F4E4221CAC07300B9233B /* SkeletonConfig.swift */; };
870F4E4421CAC07300B9233B /* SkeletonConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 870F4E4221CAC07300B9233B /* SkeletonConfig.swift */; };
872D5A5621C177E20037D763 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5521C177E20037D763 /* UIView+Extension.swift */; };
872D5A5721C177E20037D763 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5521C177E20037D763 /* UIView+Extension.swift */; };
872D5A5C21C24EDD0037D763 /* UILabel+Multiline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5B21C24EDD0037D763 /* UILabel+Multiline.swift */; };
872D5A5D21C24EDD0037D763 /* UILabel+Multiline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5B21C24EDD0037D763 /* UILabel+Multiline.swift */; };
872D5A5F21C24F8E0037D763 /* UITextView+Multiline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5E21C24F8E0037D763 /* UITextView+Multiline.swift */; };
872D5A6021C24F8E0037D763 /* UITextView+Multiline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5E21C24F8E0037D763 /* UITextView+Multiline.swift */; };
8748240D20C6A88A00E92179 /* UIView+IBInspectable.swift in Headers */ = {isa = PBXBuildFile; fileRef = F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */; settings = {ATTRIBUTES = (Public, ); }; };
8748240E20C6A88E00E92179 /* ContainsMultilineText.swift in Headers */ = {isa = PBXBuildFile; fileRef = F5307E3A1FB123C100EE67C5 /* ContainsMultilineText.swift */; settings = {ATTRIBUTES = (Public, ); }; };
877EFA4521BEE9D80031FC00 /* SkeletonLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877EFA4421BEE9D80031FC00 /* SkeletonLayerBuilder.swift */; };
877EFA4621BEE9D80031FC00 /* SkeletonLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877EFA4421BEE9D80031FC00 /* SkeletonLayerBuilder.swift */; };
@@ -55,6 +56,8 @@
8785E3A3211C9CE800CC9DFD /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DEA97D1FCDBD1F006C80EF /* Constants.swift */; };
88DEA97F1FCDBD78006C80EF /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DEA97D1FCDBD1F006C80EF /* Constants.swift */; };
8933C7851EB5B820000D00A4 /* SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* SkeletonView.swift */; };
E4CE587D22EEE65200333067 /* SkeletonTransitionStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4CE587C22EEE65200333067 /* SkeletonTransitionStyle.swift */; };
E4CE588B22EEF70D00333067 /* UIView+Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4CE587B22EEE63100333067 /* UIView+Transitions.swift */; };
F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */; };
F51DF871206E91B300D23301 /* SkeletonReusableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DF870206E91B300D23301 /* SkeletonReusableCell.swift */; };
F51DF873206E91FB00D23301 /* GenericCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DF872206E91FB00D23301 /* GenericCollectionView.swift */; };
@@ -75,6 +78,10 @@
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 */; };
F570ABF42314629700390248 /* Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F570ABF32314629700390248 /* Swizzling.swift */; };
F570ABF52314629700390248 /* Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F570ABF32314629700390248 /* Swizzling.swift */; };
F5804772230ECD0000066D02 /* UIView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5804771230ECD0000066D02 /* UIView+IBInspectable.swift */; };
F5804773230ECD0000066D02 /* UIView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5804771230ECD0000066D02 /* UIView+IBInspectable.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 */; };
F58A6E6B20A8C54100612494 /* RecoverableViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F58A6E6A20A8C54100612494 /* RecoverableViewState.swift */; };
@@ -88,7 +95,6 @@
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 */; };
F5F899D01FAA6A4D002E8FDA /* UIView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */; };
F5F899D21FAB9630002E8FDA /* AssociationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899D11FAB9630002E8FDA /* AssociationPolicy.swift */; };
F5F899E91FAB9D2B002E8FDA /* SkeletonLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */; };
F5F899EB1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899EA1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift */; };
@@ -146,7 +152,6 @@
17DD0E00207FB27400C56334 /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
17DD0E1A207FB2C200C56334 /* SkeletonView-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SkeletonView-tvOS.plist"; sourceTree = "<group>"; };
17DD0E1B207FB2C200C56334 /* SkeletonView-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SkeletonView-iOS.plist"; sourceTree = "<group>"; };
3B83EE4620C41488005178A4 /* UIView+IBInspectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIView+IBInspectable.swift"; path = "Sources/Extensions/UIView+IBInspectable.swift"; sourceTree = "<group>"; };
42ABD06D210B548200BEEFF4 /* SkeletonViewExampleUICollectionView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SkeletonViewExampleUICollectionView.app; sourceTree = BUILT_PRODUCTS_DIR; };
42ABD070210B54E100BEEFF4 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
42ABD071210B54E100BEEFF4 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
@@ -156,6 +161,7 @@
42ABD076210B54E200BEEFF4 /* SkeletonViewExampleCollectionview-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SkeletonViewExampleCollectionview-Info.plist"; sourceTree = "<group>"; };
42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = "<group>"; };
52D6D97C1BEFF229002C0205 /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
870F4E4221CAC07300B9233B /* SkeletonConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonConfig.swift; sourceTree = "<group>"; };
872D5A5521C177E20037D763 /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = "<group>"; };
872D5A5B21C24EDD0037D763 /* UILabel+Multiline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Multiline.swift"; sourceTree = "<group>"; };
872D5A5E21C24F8E0037D763 /* UITextView+Multiline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+Multiline.swift"; sourceTree = "<group>"; };
@@ -164,6 +170,8 @@
8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonDebug.swift; sourceTree = "<group>"; };
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>"; };
E4CE587B22EEE63100333067 /* UIView+Transitions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Transitions.swift"; sourceTree = "<group>"; };
E4CE587C22EEE65200333067 /* SkeletonTransitionStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTransitionStyle.swift; sourceTree = "<group>"; };
F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonAnimationBuilder.swift; sourceTree = "<group>"; };
F51DF870206E91B300D23301 /* SkeletonReusableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonReusableCell.swift; sourceTree = "<group>"; };
F51DF872206E91FB00D23301 /* GenericCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCollectionView.swift; sourceTree = "<group>"; };
@@ -179,6 +187,8 @@
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>"; };
F570ABF32314629700390248 /* Swizzling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swizzling.swift; sourceTree = "<group>"; };
F5804771230ECD0000066D02 /* UIView+IBInspectable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+IBInspectable.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>"; };
F58A6E6A20A8C54100612494 /* RecoverableViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoverableViewState.swift; sourceTree = "<group>"; };
@@ -188,7 +198,6 @@
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>"; };
F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+IBInspectable.swift"; sourceTree = "<group>"; };
F5F899D11FAB9630002E8FDA /* AssociationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssociationPolicy.swift; sourceTree = "<group>"; };
F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonLayer.swift; sourceTree = "<group>"; };
F5F899EA1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCollectionDataSource.swift; sourceTree = "<group>"; };
@@ -239,7 +248,6 @@
52D6D9721BEFF229002C0205 = {
isa = PBXGroup;
children = (
3B83EE4620C41488005178A4 /* UIView+IBInspectable.swift */,
F5F899F31FABA607002E8FDA /* Example */,
8933C7811EB5B7E0000D00A4 /* Sources */,
52D6D99C1BEFF38C002C0205 /* Configs */,
@@ -333,6 +341,7 @@
8933C7811EB5B7E0000D00A4 /* Sources */ = {
isa = PBXGroup;
children = (
E4CE587A22EEE62300333067 /* Transitions */,
872D5A5A21C24E7C0037D763 /* Multilines */,
877EFA4321BEE9C40031FC00 /* Builders */,
8785E39F211C9C7C00CC9DFD /* Debug */,
@@ -347,6 +356,7 @@
F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */,
8933C7841EB5B820000D00A4 /* SkeletonView.swift */,
F5D3FB0A209DCAA300003FCF /* SubviewsSkeletonables.swift */,
870F4E4221CAC07300B9233B /* SkeletonConfig.swift */,
);
path = Sources;
sourceTree = "<group>";
@@ -360,6 +370,15 @@
name = Frameworks;
sourceTree = "<group>";
};
E4CE587A22EEE62300333067 /* Transitions */ = {
isa = PBXGroup;
children = (
E4CE587B22EEE63100333067 /* UIView+Transitions.swift */,
E4CE587C22EEE65200333067 /* SkeletonTransitionStyle.swift */,
);
path = Transitions;
sourceTree = "<group>";
};
F51DF874206E94D300D23301 /* CollectionViews */ = {
isa = PBXGroup;
children = (
@@ -405,12 +424,12 @@
isa = PBXGroup;
children = (
F5F622421FAC81FD007C062A /* CALayer+Extensions.swift */,
F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */,
F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */,
F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */,
872D5A5521C177E20037D763 /* UIView+Extension.swift */,
F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */,
F5A5E8B3211DCE7000FD20D0 /* Int+Whitespaces.swift */,
F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */,
872D5A5521C177E20037D763 /* UIView+Extension.swift */,
F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */,
F5804771230ECD0000066D02 /* UIView+IBInspectable.swift */,
F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */,
);
path = Extensions;
sourceTree = "<group>";
@@ -421,6 +440,7 @@
F5F899D11FAB9630002E8FDA /* AssociationPolicy.swift */,
F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */,
F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */,
F570ABF32314629700390248 /* Swizzling.swift */,
);
path = Helpers;
sourceTree = "<group>";
@@ -466,7 +486,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
8748240D20C6A88A00E92179 /* UIView+IBInspectable.swift in Headers */,
8748240E20C6A88E00E92179 /* ContainsMultilineText.swift in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -475,7 +494,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
3B83EE4820C41488005178A4 /* UIView+IBInspectable.swift in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -658,6 +676,7 @@
17DD0E08207FB28900C56334 /* CollectionSkeletonProtocol.swift in Sources */,
F58A6E6C20A8C54100612494 /* RecoverableViewState.swift in Sources */,
17DD0E0B207FB28900C56334 /* SkeletonTableViewProtocols.swift in Sources */,
1E6C67A2230E76CC0019D87B /* SkeletonTransitionStyle.swift in Sources */,
17DD0E0D207FB28900C56334 /* UITableView+CollectionSkeleton.swift in Sources */,
17DD0E0E207FB28900C56334 /* UIView+CollectionSkeleton.swift in Sources */,
877EFA4921BEED760031FC00 /* SkeletonMultilineLayerBuilder.swift in Sources */,
@@ -667,9 +686,10 @@
17DD0E19207FB28F00C56334 /* SkeletonFlow.swift in Sources */,
17DD0E09207FB28900C56334 /* SkeletonCollectionDataSource.swift in Sources */,
17DD0E0C207FB28900C56334 /* UICollectionView+CollectionSkeleton.swift in Sources */,
F5804773230ECD0000066D02 /* UIView+IBInspectable.swift in Sources */,
17DD0E13207FB28C00C56334 /* UIView+UIApplicationDelegate.swift in Sources */,
F570ABF52314629700390248 /* Swizzling.swift in Sources */,
F5D3FB0C209DCAA300003FCF /* SubviewsSkeletonables.swift in Sources */,
17DD0E12207FB28C00C56334 /* UIView+IBInspectable.swift in Sources */,
17DD0E14207FB28F00C56334 /* SkeletonAnimationBuilder.swift in Sources */,
17DD0E11207FB28C00C56334 /* UIView+Frame.swift in Sources */,
17DD0E15207FB28F00C56334 /* SkeletonAppearance.swift in Sources */,
@@ -680,9 +700,11 @@
17DD0E1C207FB32100C56334 /* AssociationPolicy.swift in Sources */,
877EFA4621BEE9D80031FC00 /* SkeletonLayerBuilder.swift in Sources */,
F5A5E8B5211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */,
870F4E4421CAC07300B9233B /* SkeletonConfig.swift in Sources */,
17DD0E1D207FB32100C56334 /* ContainsMultilineText.swift in Sources */,
17DD0E0F207FB28C00C56334 /* CALayer+Extensions.swift in Sources */,
17DD0E16207FB28F00C56334 /* SkeletonGradient.swift in Sources */,
1E6C67A3230E76CE0019D87B /* UIView+Transitions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -701,8 +723,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E4CE588B22EEF70D00333067 /* UIView+Transitions.swift in Sources */,
872D5A5621C177E20037D763 /* UIView+Extension.swift in Sources */,
F5F899D01FAA6A4D002E8FDA /* UIView+IBInspectable.swift in Sources */,
872D5A5C21C24EDD0037D763 /* UILabel+Multiline.swift in Sources */,
F54CF5E42024CEB000330B0D /* UIView+CollectionSkeleton.swift in Sources */,
F5307E371FB1076E00EE67C5 /* SkeletonTableViewProtocols.swift in Sources */,
@@ -714,13 +736,16 @@
F5307E321FB0F42F00EE67C5 /* RecursiveProtocol.swift in Sources */,
F5F622431FAC81FD007C062A /* CALayer+Extensions.swift in Sources */,
877EFA4821BEED760031FC00 /* SkeletonMultilineLayerBuilder.swift in Sources */,
E4CE587D22EEE65200333067 /* SkeletonTransitionStyle.swift in Sources */,
F5F899ED1FAB9F04002E8FDA /* CollectionSkeletonProtocol.swift in Sources */,
F58A6E6E20A8C66300612494 /* Recoverable.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 */,
F5804772230ECD0000066D02 /* UIView+IBInspectable.swift in Sources */,
F587FB84202CBFC8002DB5FE /* SkeletonFlow.swift in Sources */,
F570ABF42314629700390248 /* Swizzling.swift in Sources */,
F5D3FB0B209DCAA300003FCF /* SubviewsSkeletonables.swift in Sources */,
F5F899D21FAB9630002E8FDA /* AssociationPolicy.swift in Sources */,
F54CF5E62024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift in Sources */,
@@ -733,6 +758,7 @@
F5307E2E1FB0E5E400EE67C5 /* UIView+Frame.swift in Sources */,
877EFA4521BEE9D80031FC00 /* SkeletonLayerBuilder.swift in Sources */,
F5A5E8B4211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */,
870F4E4321CAC07300B9233B /* SkeletonConfig.swift in Sources */,
F51DF871206E91B300D23301 /* SkeletonReusableCell.swift in Sources */,
F51DF879206E9F5500D23301 /* SkeletonCollectionDelegate.swift in Sources */,
F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */,
@@ -1018,7 +1044,7 @@
52D6D9911BEFF229002C0205 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
APPLICATION_EXTENSION_API_ONLY = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -1043,7 +1069,7 @@
52D6D9921BEFF229002C0205 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
APPLICATION_EXTENSION_API_ONLY = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "42ABD05A210B548200BEEFF4"
BuildableName = "SkeletonViewExampleUICollectionView.app"
BlueprintName = "SkeletonViewExampleUICollectionView"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "42ABD05A210B548200BEEFF4"
BuildableName = "SkeletonViewExampleUICollectionView.app"
BlueprintName = "SkeletonViewExampleUICollectionView"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "42ABD05A210B548200BEEFF4"
BuildableName = "SkeletonViewExampleUICollectionView.app"
BlueprintName = "SkeletonViewExampleUICollectionView"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "42ABD05A210B548200BEEFF4"
BuildableName = "SkeletonViewExampleUICollectionView.app"
BlueprintName = "SkeletonViewExampleUICollectionView"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -17,7 +17,6 @@ public enum SkeletonAppearance {
// codebeat:disable[TOO_MANY_IVARS]
class SkeletonViewAppearance: Appearance {
static var shared = SkeletonViewAppearance()
var tintColor: UIColor = .clouds
+11 -12
View File
@@ -5,7 +5,6 @@ import UIKit
/// Object that facilitates the creation of skeleton layers,
/// based on the builder pattern
class SkeletonLayerBuilder {
var skeletonType: SkeletonType?
var colors: [UIColor] = []
var holder: UIView?
@@ -27,15 +26,15 @@ class SkeletonLayerBuilder {
func setHolder(_ holder: UIView) -> SkeletonLayerBuilder {
self.holder = holder
return self
}
func build() -> SkeletonLayer? {
guard let type = skeletonType,
let holder = holder
else { return nil }
return SkeletonLayer(withType: type,
usingColors: colors,
andSkeletonHolder: holder)
}
}
func build() -> SkeletonLayer? {
guard let type = skeletonType,
let holder = holder
else { return nil }
return SkeletonLayer(type: type,
colors: colors,
skeletonHolder: holder)
}
}
@@ -5,7 +5,6 @@ import UIKit
/// Object that facilitates the creation of skeleton layers for multiline
/// elements, based on the builder pattern
class SkeletonMultilineLayerBuilder {
var skeletonType: SkeletonType?
var index: Int?
var width: CGFloat?
@@ -38,11 +37,10 @@ class SkeletonMultilineLayerBuilder {
let radius = cornerRadius
else { return nil }
let spaceRequiredForEachLine = SkeletonAppearance.default.multilineHeight + SkeletonAppearance.default.multilineSpacing
let layer = type.layer
layer.anchorPoint = .zero
layer.name = CALayer.skeletonSubLayersName
layer.frame = CGRect(x: 0.0, y: CGFloat(index) * spaceRequiredForEachLine, width: width, height: SkeletonAppearance.default.multilineHeight)
layer.updateLayerFrame(for: index, width: width)
layer.cornerRadius = CGFloat(radius)
layer.masksToBounds = true
@@ -19,6 +19,7 @@ protocol CollectionSkeleton {
var estimatedNumberOfRows: Int { get }
func addDummyDataSource()
func updateDummyDataSource()
func removeDummyDataSource(reloadAfter: Bool)
func disableUserInteraction()
func enableUserInteraction()
@@ -28,6 +29,6 @@ extension CollectionSkeleton where Self: UIScrollView {
var estimatedNumberOfRows: Int { return 0 }
func addDummyDataSource() {}
func removeDummyDataSource(reloadAfter: Bool) {}
func disableUserInteraction() { isScrollEnabled = false }
func enableUserInteraction() { isScrollEnabled = true }
func disableUserInteraction() { isUserInteractionEnabled = false; isScrollEnabled = false }
func enableUserInteraction() { isUserInteractionEnabled = true; isScrollEnabled = true }
}
@@ -13,14 +13,20 @@ 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
func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier?
}
public extension SkeletonCollectionViewDataSource {
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return skeletonView.estimatedNumberOfRows
}
func collectionSkeletonView(_ skeletonView: UICollectionView,
supplementaryViewIdentifierOfKind: String,
at indexPath: IndexPath) -> ReusableCellIdentifier? {
return nil
}
func numSections(in collectionSkeletonView: UICollectionView) -> Int { return 1 }
}
@@ -9,7 +9,6 @@
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))
@@ -41,6 +40,14 @@ extension UICollectionView: CollectionSkeleton {
reloadData()
}
func updateDummyDataSource() {
if (dataSource as? SkeletonCollectionDataSource) != nil {
reloadData()
} else {
addDummyDataSource()
}
}
func removeDummyDataSource(reloadAfter: Bool) {
guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
self.skeletonDataSource = nil
@@ -11,7 +11,6 @@ import UIKit
public typealias ReusableCellIdentifier = String
class SkeletonCollectionDataSource: NSObject {
weak var originalTableViewDataSource: SkeletonTableViewDataSource?
weak var originalCollectionViewDataSource: SkeletonCollectionViewDataSource?
var rowHeight: CGFloat = 0.0
@@ -26,25 +25,24 @@ class SkeletonCollectionDataSource: NSObject {
// MARK: - UITableViewDataSource
extension SkeletonCollectionDataSource: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return originalTableViewDataSource?.numSections(in:tableView) ?? 0
return originalTableViewDataSource?.numSections(in: tableView) ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return originalTableViewDataSource?.collectionSkeletonView(tableView, numberOfRowsInSection:section) ?? 0
return originalTableViewDataSource?.collectionSkeletonView(tableView, numberOfRowsInSection: section) ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = originalTableViewDataSource?.collectionSkeletonView(tableView, cellIdentifierForRowAt: indexPath) ?? ""
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
skeletonCellIfContainerSkeletonIsActive(container: tableView, cell: cell)
return cell
}
}
// MARK: - UICollectionViewDataSource
extension SkeletonCollectionDataSource: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return originalCollectionViewDataSource?.numSections(in: collectionView) ?? 0
}
@@ -56,6 +54,31 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, cellIdentifierForItemAt: indexPath) ?? ""
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
skeletonCellIfContainerSkeletonIsActive(container: collectionView, cell: cell)
return cell
}
func collectionView(_ collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String,
at indexPath: IndexPath) -> UICollectionReusableView {
if let viewIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, supplementaryViewIdentifierOfKind: kind, at: indexPath) {
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: viewIdentifier, for: indexPath)
}
return UICollectionReusableView()
}
}
extension SkeletonCollectionDataSource {
private func skeletonCellIfContainerSkeletonIsActive(container: UIView, cell: UIView) {
guard container.isSkeletonActive,
let skeletonConfig = container.currentSkeletonConfig else {
return
}
cell.showSkeleton(skeletonConfig: skeletonConfig)
}
}
@@ -9,7 +9,6 @@
import UIKit
class SkeletonCollectionDelegate: NSObject {
weak var originalTableViewDelegate: SkeletonTableViewDelegate?
weak var originalCollectionViewDelegate: SkeletonCollectionViewDelegate?
@@ -15,7 +15,6 @@ public protocol SkeletonTableViewDataSource: UITableViewDataSource {
}
public extension SkeletonTableViewDataSource {
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int {
return skeletonView.estimatedNumberOfRows
}
@@ -30,5 +29,4 @@ public extension SkeletonTableViewDataSource {
}
}
public protocol SkeletonTableViewDelegate: UITableViewDelegate {
}
public protocol SkeletonTableViewDelegate: UITableViewDelegate { }
@@ -9,7 +9,6 @@
import UIKit
extension UITableView: CollectionSkeleton {
var estimatedNumberOfRows: Int {
return Int(ceil(frame.height/rowHeight))
}
@@ -41,6 +40,14 @@ extension UITableView: CollectionSkeleton {
reloadData()
}
func updateDummyDataSource() {
if (dataSource as? SkeletonCollectionDataSource) != nil {
reloadData()
} else {
addDummyDataSource()
}
}
func removeDummyDataSource(reloadAfter: Bool) {
guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
restoreRowHeight()
@@ -11,12 +11,19 @@ import UIKit
extension UIView {
func addDummyDataSourceIfNeeded() {
guard let collection = self as? CollectionSkeleton else { return }
status = .on
collection.addDummyDataSource()
collection.disableUserInteraction()
}
func updateDummyDataSourceIfNeeded() {
guard let collection = self as? CollectionSkeleton else { return }
collection.updateDummyDataSource()
}
func removeDummyDataSourceIfNeeded(reloadAfter reload: Bool = true) {
guard let collection = self as? CollectionSkeleton else { return }
status = .off
collection.removeDummyDataSource(reloadAfter: reload)
collection.enableUserInteraction()
}
-1
View File
@@ -25,7 +25,6 @@ func skeletonLog(_ message: String) {
}
extension UIView {
public var skeletonDescription: String {
var description = "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())"
let subSkeletons = subviewsSkeletonables
+46 -11
View File
@@ -31,7 +31,6 @@ extension CAGradientLayer {
// MARK: Skeleton sublayers
extension CALayer {
static let skeletonSubLayersName = "SkeletonSubLayersName"
var skeletonSublayers: [CALayer] {
@@ -46,11 +45,7 @@ extension CALayer {
.setCornerRadius(multilineCornerRadius)
(0..<numberOfSublayers).forEach { index in
var width = bounds.width
if index == numberOfSublayers - 1 && numberOfSublayers != 1 {
width = width * CGFloat(lastLineFillPercent) / 100;
}
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: lastLineFillPercent)
if let layer = layerBuilder
.setIndex(index)
.setWidth(width)
@@ -60,9 +55,31 @@ extension CALayer {
}
}
func updateMultilinesLayers(lastLineFillPercent: Int) {
let currentSkeletonSublayers = skeletonSublayers
let numberOfSublayers = currentSkeletonSublayers.count
for (index, layer) in currentSkeletonSublayers.enumerated() {
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: lastLineFillPercent)
layer.updateLayerFrame(for: index, width: width)
}
}
private func calculatedWidthForLine(at index: Int, totalLines: Int, lastLineFillPercent: Int) -> CGFloat {
var width = bounds.width
if index == totalLines - 1 && totalLines != 1 {
width = width * CGFloat(lastLineFillPercent) / 100
}
return width
}
func updateLayerFrame(for index: Int, width: CGFloat) {
let spaceRequiredForEachLine = SkeletonAppearance.default.multilineHeight + SkeletonAppearance.default.multilineSpacing
frame = CGRect(x: 0.0, y: CGFloat(index) * spaceRequiredForEachLine, width: width, height: SkeletonAppearance.default.multilineHeight)
}
private func calculateNumLines(maxLines: Int) -> Int {
let spaceRequitedForEachLine = SkeletonAppearance.default.multilineHeight + SkeletonAppearance.default.multilineSpacing
var numberOfSublayers = Int(round(CGFloat(bounds.height)/CGFloat(spaceRequitedForEachLine)))
let requiredSpaceForEachLine = SkeletonAppearance.default.multilineHeight + SkeletonAppearance.default.multilineSpacing
var numberOfSublayers = Int(round(CGFloat(bounds.height)/CGFloat(requiredSpaceForEachLine)))
if maxLines != 0, maxLines <= numberOfSublayers { numberOfSublayers = maxLines }
return numberOfSublayers
}
@@ -70,7 +87,6 @@ extension CALayer {
// MARK: Animations
public extension CALayer {
var pulse: CAAnimation {
let pulseAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.backgroundColor))
pulseAnimation.fromValue = backgroundColor
@@ -79,6 +95,7 @@ public extension CALayer {
pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
pulseAnimation.autoreverses = true
pulseAnimation.repeatCount = .infinity
pulseAnimation.isRemovedOnCompletion = false
return pulseAnimation
}
@@ -96,15 +113,19 @@ public extension CALayer {
animGroup.duration = 1.5
animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
animGroup.repeatCount = .infinity
animGroup.isRemovedOnCompletion = false
return animGroup
}
func playAnimation(_ anim: SkeletonLayerAnimation, key: String) {
func playAnimation(_ anim: SkeletonLayerAnimation, key: String, completion: (() -> Void)? = nil) {
skeletonSublayers.recursiveSearch(leafBlock: {
DispatchQueue.main.async { CATransaction.begin() }
DispatchQueue.main.async { CATransaction.setCompletionBlock(completion) }
add(anim(self), forKey: key)
DispatchQueue.main.async { CATransaction.commit() }
}) {
$0.playAnimation(anim, key: key)
$0.playAnimation(anim, key: key, completion: completion)
}
}
@@ -116,3 +137,17 @@ public extension CALayer {
}
}
}
extension CALayer {
func setOpacity(from: Int, to: Int, duration: TimeInterval, completion: (() -> Void)?) {
DispatchQueue.main.async { CATransaction.begin() }
let animation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
animation.fromValue = from
animation.toValue = to
animation.duration = duration
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
DispatchQueue.main.async { CATransaction.setCompletionBlock(completion) }
add(animation, forKey: "setOpacityAnimation")
DispatchQueue.main.async { CATransaction.commit() }
}
}
-1
View File
@@ -3,7 +3,6 @@
import Foundation
extension Int {
var whitespace: String {
return whitespaces
}
@@ -4,7 +4,6 @@ import UIKit
// codebeat:disable[TOO_MANY_IVARS]
extension UIColor {
convenience init(_ hex: UInt) {
self.init(
red: CGFloat((hex & 0xFF0000) >> 16) / 255.0,
+9 -2
View File
@@ -10,11 +10,13 @@ enum ViewAssociatedKeys {
static var flowDelegate = "flowDelegate"
static var isSkeletonAnimated = "isSkeletonAnimated"
static var viewState = "viewState"
static var labelViewState = "labelViewState"
static var imageViewState = "imageViewState"
static var currentSkeletonConfig = "currentSkeletonConfig"
}
// codebeat:enable[TOO_MANY_IVARS]
extension UIView {
enum Status {
case on
case off
@@ -30,12 +32,17 @@ extension UIView {
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.skeletonLayer) }
}
var currentSkeletonConfig: SkeletonConfig? {
get { return ao_get(pkey: &ViewAssociatedKeys.currentSkeletonConfig) as? SkeletonConfig }
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.currentSkeletonConfig) }
}
var status: Status! {
get { return ao_get(pkey: &ViewAssociatedKeys.status) as? Status ?? .off }
set { ao_set(newValue ?? .off, pkey: &ViewAssociatedKeys.status) }
}
var skeletonIsAnimated: Bool! {
var isSkeletonAnimated: Bool! {
get { return ao_get(pkey: &ViewAssociatedKeys.isSkeletonAnimated) as? Bool ?? false }
set { ao_set(newValue ?? false, pkey: &ViewAssociatedKeys.isSkeletonAnimated) }
}
-1
View File
@@ -10,7 +10,6 @@ import UIKit
// MARK: Frame
extension UIView {
var maxBoundsEstimated: CGRect {
if let parentStackView = (superview as? UIStackView) {
var origin: CGPoint = .zero
@@ -3,7 +3,6 @@
import UIKit
public extension UIView {
@IBInspectable
var isSkeletonable: Bool {
get { return skeletonable }
@@ -14,7 +13,7 @@ public extension UIView {
return status == .on || (subviewsSkeletonables.first(where: { $0.isSkeletonActive }) != nil)
}
fileprivate var skeletonable: Bool! {
private var skeletonable: Bool! {
get { return ao_get(pkey: &ViewAssociatedKeys.skeletonable) as? Bool ?? false }
set { ao_set(newValue ?? false, pkey: &ViewAssociatedKeys.skeletonable) }
}
@@ -9,16 +9,17 @@
import UIKit
extension UIView {
enum Constants {
static let becomeActiveNotification = UIApplication.didBecomeActiveNotification
static let enterForegroundNotification = UIApplication.didEnterBackgroundNotification
static let willTerminateNotification = UIApplication.willTerminateNotification
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)
NotificationCenter.default.addObserver(self, selector: #selector(willTerminateNotification), name: Constants.enterForegroundNotification, object: nil)
}
func removeAppNoticationsObserver() {
@@ -33,6 +34,10 @@ extension UIView {
}
@objc func appDidEnterBackground() {
UserDefaults.standard.set((isSkeletonActive && skeletonIsAnimated), forKey: Constants.needAnimatedSkeletonKey)
UserDefaults.standard.set((isSkeletonActive && isSkeletonAnimated), forKey: Constants.needAnimatedSkeletonKey)
}
@objc func willTerminateNotification() {
UserDefaults.standard.set(false, forKey: Constants.needAnimatedSkeletonKey)
}
}
-1
View File
@@ -20,7 +20,6 @@ protocol AssociatedObjects: class { }
// transparent wrappers
extension AssociatedObjects {
/// wrapper around `objc_getAssociatedObject`
func ao_get(pkey: UnsafeRawPointer) -> Any? {
return objc_getAssociatedObject(self, pkey)
+24 -11
View File
@@ -8,26 +8,39 @@
import UIKit
protocol PrepareForSkeleton {
func prepareViewForSkeleton()
extension UIView {
@objc func prepareViewForSkeleton() {
startTransition { [weak self] in
self?.backgroundColor = .clear
}
}
}
extension UILabel: PrepareForSkeleton {
func prepareViewForSkeleton() {
text = " "
extension UILabel {
override func prepareViewForSkeleton() {
backgroundColor = .clear
resignFirstResponder()
startTransition { [weak self] in
self?.textColor = .clear
}
}
}
extension UITextView: PrepareForSkeleton {
func prepareViewForSkeleton() {
text = " "
extension UITextView {
override func prepareViewForSkeleton() {
backgroundColor = .clear
resignFirstResponder()
startTransition { [weak self] in
self?.textColor = .clear
}
}
}
extension UIImageView: PrepareForSkeleton {
func prepareViewForSkeleton() {
image = nil
extension UIImageView {
override func prepareViewForSkeleton() {
backgroundColor = .clear
startTransition { [weak self] in
self?.image = nil
}
}
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright © 2019 SkeletonView. All rights reserved.
import Foundation
extension DispatchQueue {
private static var _onceTracker = [String]()
class func once(token: String, block:()->Void) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
guard !_onceTracker.contains(token) else { return }
_onceTracker.append(token)
block()
}
}
func swizzle(selector originalSelector: Selector, with swizzledSelector: Selector, inClass: AnyClass, usingClass: AnyClass) {
guard let originalMethod = class_getInstanceMethod(inClass, originalSelector),
let swizzledMethod = class_getInstanceMethod(usingClass, swizzledSelector)
else { return }
if (class_addMethod(inClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))) {
class_replaceMethod(inClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
@@ -3,7 +3,6 @@
import UIKit
public extension UITextView {
@IBInspectable
var lastLineFillPercent: Int {
get { return lastLineFillingPercent }
+38 -15
View File
@@ -9,13 +9,11 @@
import UIKit
protocol Recoverable {
var viewState: RecoverableViewState? { get set }
func saveViewState()
func recoverViewState(forced: Bool)
}
extension UIView: Recoverable {
var viewState: RecoverableViewState? {
get { return ao_get(pkey: &ViewAssociatedKeys.viewState) as? RecoverableViewState }
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.viewState) }
@@ -28,47 +26,72 @@ extension UIView: Recoverable {
@objc func recoverViewState(forced: Bool) {
guard let safeViewState = viewState else { return }
layer.cornerRadius = safeViewState.cornerRadius
layer.masksToBounds = safeViewState.clipToBounds
if safeViewState.backgroundColor != backgroundColor || forced {
backgroundColor = safeViewState.backgroundColor
startTransition { [weak self] in
self?.layer.cornerRadius = safeViewState.cornerRadius
self?.layer.masksToBounds = safeViewState.clipToBounds
if safeViewState.backgroundColor != self?.backgroundColor || forced {
self?.backgroundColor = safeViewState.backgroundColor
}
}
}
}
extension UILabel {
extension UILabel{
var labelState: RecoverableTextViewState? {
get { return ao_get(pkey: &ViewAssociatedKeys.labelViewState) as? RecoverableTextViewState }
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.labelViewState) }
}
override func saveViewState() {
super.saveViewState()
viewState?.text = text
labelState = RecoverableTextViewState(view: self)
}
override func recoverViewState(forced: Bool) {
super.recoverViewState(forced: forced)
text = text == " " || forced ? viewState?.text : text
startTransition { [weak self] in
self?.textColor = self?.labelState?.textColor
self?.text = self?.labelState?.text
}
}
}
extension UITextView {
extension UITextView{
var textState: RecoverableTextViewState? {
get { return ao_get(pkey: &ViewAssociatedKeys.labelViewState) as? RecoverableTextViewState }
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.labelViewState) }
}
override func saveViewState() {
super.saveViewState()
viewState?.text = text
textState = RecoverableTextViewState(view: self)
}
override func recoverViewState(forced: Bool) {
super.recoverViewState(forced: forced)
text = text == " " || forced ? viewState?.text : text
startTransition { [weak self] in
self?.textColor = self?.textState?.textColor
self?.text = self?.textState?.text
}
}
}
extension UIImageView {
var imageState: RecoverableImageViewState? {
get { return ao_get(pkey: &ViewAssociatedKeys.imageViewState) as? RecoverableImageViewState }
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.imageViewState) }
}
override func saveViewState() {
super.saveViewState()
viewState?.image = image
imageState = RecoverableImageViewState(view: self)
}
override func recoverViewState(forced: Bool) {
super.recoverViewState(forced: forced)
image = image == nil || forced ? viewState?.image : image
startTransition { [weak self] in
self?.image = self?.image == nil || forced ? self?.imageState?.image : self?.image
}
}
}
+23 -8
View File
@@ -13,17 +13,32 @@ struct RecoverableViewState {
var cornerRadius: CGFloat
var clipToBounds: Bool
// UI text
var text: String?
// UI image
var image: UIImage?
}
extension RecoverableViewState {
init(view: UIView) {
self.backgroundColor = view.backgroundColor
self.clipToBounds = view.layer.masksToBounds
self.cornerRadius = view.layer.cornerRadius
}
}
struct RecoverableTextViewState {
var text: String?
var textColor: UIColor?
init(view: UILabel) {
self.textColor = view.textColor
self.text = view.text
}
init(view: UITextView) {
self.textColor = view.textColor
self.text = view.text
}
}
struct RecoverableImageViewState {
var image: UIImage?
init(view: UIImageView) {
self.image = view.image
}
}
+2 -3
View File
@@ -60,9 +60,7 @@ public enum GradientDirection {
}
public class SkeletonAnimationBuilder {
public init() {
}
public init() { }
public func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation {
return { layer in
@@ -80,6 +78,7 @@ public class SkeletonAnimationBuilder {
animGroup.duration = duration
animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
animGroup.repeatCount = .infinity
animGroup.isRemovedOnCompletion = false
return animGroup
}
+40
View File
@@ -0,0 +1,40 @@
// Copyright © 2018 SkeletonView. All rights reserved.
import UIKit
/// Used to store all config needed to activate the skeleton layer.
struct SkeletonConfig {
/// Type of skeleton layer
let type: SkeletonType
/// Colors used in skeleton layer
let colors: [UIColor]
/// If type is gradient, which gradient direction
let gradientDirection: GradientDirection?
/// Specify if skeleton is animated or not
let animated: Bool
/// Used to execute a custom animation
let animation: SkeletonLayerAnimation?
/// Transition style
var transition: SkeletonTransitionStyle
init(
type: SkeletonType,
colors: [UIColor],
gradientDirection: GradientDirection? = nil,
animated: Bool = false,
animation: SkeletonLayerAnimation? = nil,
transition: SkeletonTransitionStyle = .none
) {
self.type = type
self.colors = colors
self.gradientDirection = gradientDirection
self.animated = animated
self.animation = animation
self.transition = transition
}
}
+24 -9
View File
@@ -3,27 +3,42 @@
import UIKit
protocol SkeletonFlowDelegate {
func willBeginShowingSkeletons(withRootView rootView: UIView)
func didShowSkeletons(withRootView rootView: UIView)
func willBeginHidingSkeletons(withRootView rootView: UIView)
func didHideSkeletons(withRootView rootView: UIView)
func willBeginShowingSkeletons(rootView: UIView)
func didShowSkeletons(rootView: UIView)
func willBeginUpdatingSkeletons(rootView: UIView)
func didUpdateSkeletons(rootView: UIView)
func willBeginLayingSkeletonsIfNeeded(rootView: UIView)
func didLayoutSkeletonsIfNeeded(rootView: UIView)
func willBeginHidingSkeletons(rootView: UIView)
func didHideSkeletons(rootView: UIView)
}
class SkeletonFlowHandler: SkeletonFlowDelegate {
func willBeginShowingSkeletons(withRootView rootView: UIView) {
func willBeginShowingSkeletons(rootView: UIView) {
rootView.addAppNotificationsObservers()
}
func didShowSkeletons(withRootView rootView: UIView) {
func didShowSkeletons(rootView: UIView) {
printSkeletonHierarchy(in: rootView)
}
func willBeginHidingSkeletons(withRootView rootView: UIView) {
func willBeginUpdatingSkeletons(rootView: UIView) {
}
func didUpdateSkeletons(rootView: UIView) {
}
func willBeginLayingSkeletonsIfNeeded(rootView: UIView) {
}
func didLayoutSkeletonsIfNeeded(rootView: UIView) {
}
func willBeginHidingSkeletons(rootView: UIView) {
rootView.removeAppNoticationsObserver()
}
func didHideSkeletons(withRootView rootView: UIView) {
func didHideSkeletons(rootView: UIView) {
rootView.flowDelegate = nil
}
}
+1 -2
View File
@@ -9,8 +9,7 @@
import UIKit
public struct SkeletonGradient {
private var gradientColors: [UIColor]
private let gradientColors: [UIColor]
public var colors: [UIColor] {
return gradientColors
Regular → Executable
+40 -8
View File
@@ -34,7 +34,6 @@ public enum SkeletonType {
}
struct SkeletonLayer {
private var maskLayer: CALayer
private weak var holder: UIView?
@@ -46,7 +45,7 @@ struct SkeletonLayer {
return maskLayer
}
init(withType type: SkeletonType, usingColors colors: [UIColor], andSkeletonHolder holder: UIView) {
init(type: SkeletonType, colors: [UIColor], skeletonHolder holder: UIView) {
self.holder = holder
self.maskLayer = type.layer
self.maskLayer.anchorPoint = .zero
@@ -55,21 +54,54 @@ struct SkeletonLayer {
self.maskLayer.tint(withColors: colors)
}
func removeLayer() {
maskLayer.removeFromSuperlayer()
func update(usingColors colors: [UIColor]) {
layoutIfNeeded()
maskLayer.tint(withColors: colors)
}
func layoutIfNeeded() {
if let bounds = holder?.maxBoundsEstimated {
maskLayer.bounds = bounds
}
updateMultilinesIfNeeded()
}
func removeLayer(transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
switch transition {
case .none:
maskLayer.removeFromSuperlayer()
completion?()
case .crossDissolve(let duration):
maskLayer.setOpacity(from: 1, to: 0, duration: duration) {
self.maskLayer.removeFromSuperlayer()
completion?()
}
}
}
/// Returns a multiLineViewHolder only if the current holder implements the `ContainsMultilineText` protocol,
/// and actually displays multiple lines of text.
private var multiLineViewHolder: ContainsMultilineText? {
guard let multiLineView = holder as? ContainsMultilineText,
multiLineView.numLines != 1 else { return nil }
return multiLineView
}
func addMultilinesIfNeeded() {
guard let multiLineView = holder as? ContainsMultilineText else { return }
guard let multiLineView = multiLineViewHolder else { return }
maskLayer.addMultilinesLayers(lines: multiLineView.numLines, type: type, lastLineFillPercent: multiLineView.lastLineFillingPercent, multilineCornerRadius: multiLineView.multilineCornerRadius)
}
func updateMultilinesIfNeeded() {
guard let multiLineView = multiLineViewHolder else { return }
maskLayer.updateMultilinesLayers(lastLineFillPercent: multiLineView.lastLineFillingPercent)
}
}
extension SkeletonLayer {
func start(_ anim: SkeletonLayerAnimation? = nil) {
func start(_ anim: SkeletonLayerAnimation? = nil, completion: (() -> Void)? = nil) {
let animation = anim ?? type.layerAnimation
contentLayer.playAnimation(animation, key: "skeletonAnimation")
contentLayer.playAnimation(animation, key: "skeletonAnimation", completion: completion)
}
func stopAnimation() {
Regular → Executable
+193 -49
View File
@@ -3,37 +3,89 @@
import UIKit
public extension UIView {
func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor) {
showSkeleton(withType: .solid, usingColors: [color])
/// Shows the skeleton without animation using the view that calls this method as root view.
///
/// - Parameters:
/// - color: The color of the skeleton. Defaults to `SkeletonAppearance.default.tintColor`.
/// - transition: The style of the transition when the skeleton appears. Defaults to `.none`.
func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, transition: SkeletonTransitionStyle = .none) {
let config = SkeletonConfig(type: .solid, colors: [color], transition: transition)
showSkeleton(skeletonConfig: config)
}
func showGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient) {
showSkeleton(withType: .gradient, usingColors: gradient.colors)
/// Shows the gradient skeleton without animation using the view that calls this method as root view.
///
/// - Parameters:
/// - gradient: The gradient of the skeleton. Defaults to `SkeletonAppearance.default.gradient`.
/// - transition: The style of the transition when the skeleton appears. Defaults to `.none`.
func showGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, transition: SkeletonTransitionStyle = .none) {
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, transition: transition)
showSkeleton(skeletonConfig: config)
}
func showAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil) {
showSkeleton(withType: .solid, usingColors: [color], animated: true, animation: animation)
/// Shows the animated skeleton using the view that calls this method as root view.
///
/// If animation is nil, sliding animation will be used, with direction left to right.
///
/// - Parameters:
/// - color: The color of skeleton. Defaults to `SkeletonAppearance.default.tintColor`.
/// - animation: The animation of the skeleton. Defaults to `nil`.
/// - transition: The style of the transition when the skeleton appears. Defaults to `.none`.
func showAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .none) {
let config = SkeletonConfig(type: .solid, colors: [color], animated: true, animation: animation, transition: transition)
showSkeleton(skeletonConfig: config)
}
func showAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil) {
showSkeleton(withType: .gradient, usingColors: gradient.colors, animated: true, animation: animation)
/// Shows the gradient skeleton without animation using the view that calls this method as root view.
///
/// If animation is nil, sliding animation will be used, with direction left to right.
///
/// - Parameters:
/// - gradient: The gradient of the skeleton. Defaults to `SkeletonAppearance.default.gradient`.
/// - animation: The animation of the skeleton. Defaults to `nil`.
/// - transition: The style of the transition when the skeleton appears. Defaults to `.none`.
func showAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .none) {
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: true, animation: animation, transition: transition)
showSkeleton(skeletonConfig: config)
}
func updateSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor) {
let config = SkeletonConfig(type: .solid, colors: [color])
updateSkeleton(skeletonConfig: config)
}
func updateGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient) {
let config = SkeletonConfig(type: .gradient, colors: gradient.colors)
updateSkeleton(skeletonConfig: config)
}
func updateAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil) {
let config = SkeletonConfig(type: .solid, colors: [color], animated: true, animation: animation)
updateSkeleton(skeletonConfig: config)
}
func updateAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil) {
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: true, animation: animation)
updateSkeleton(skeletonConfig: config)
}
func layoutSkeletonIfNeeded() {
flowDelegate?.willBeginLayingSkeletonsIfNeeded(rootView: self)
recursiveLayoutSkeletonIfNeeded(root: self)
}
func hideSkeleton(reloadDataAfter reload: Bool = true) {
flowDelegate?.willBeginHidingSkeletons(withRootView: self)
recursiveHideSkeleton(reloadDataAfter: reload, root: self)
func hideSkeleton(reloadDataAfter reload: Bool = true, transition: SkeletonTransitionStyle = .none) {
flowDelegate?.willBeginHidingSkeletons(rootView: self)
recursiveHideSkeleton(reloadDataAfter: reload, transition: transition, root: self)
}
func startSkeletonAnimation(_ anim: SkeletonLayerAnimation? = nil) {
skeletonIsAnimated = true
subviewsSkeletonables.recursiveSearch(leafBlock: startSkeletonLayerAnimationBlock(anim)) { subview in
subview.startSkeletonAnimation(anim)
}
}
func stopSkeletonAnimation() {
skeletonIsAnimated = false
subviewsSkeletonables.recursiveSearch(leafBlock: stopSkeletonLayerAnimationBlock) { subview in
subview.stopSkeletonAnimation()
}
@@ -41,84 +93,176 @@ 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, root: self)
@objc func skeletonLayoutSubviews() {
guard isSkeletonActive else { return }
layoutSkeletonIfNeeded()
}
fileprivate func recursiveShowSkeleton(withType type: SkeletonType, usingColors colors: [UIColor], animated: Bool, animation: SkeletonLayerAnimation?, root: UIView? = nil) {
addDummyDataSourceIfNeeded()
func showSkeleton(skeletonConfig config: SkeletonConfig) {
isSkeletonAnimated = config.animated
flowDelegate = SkeletonFlowHandler()
flowDelegate?.willBeginShowingSkeletons(rootView: self)
recursiveShowSkeleton(skeletonConfig: config, root: self)
}
private func recursiveShowSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
guard !isSkeletonActive else { return }
currentSkeletonConfig = config
swizzleLayoutSubviews()
addDummyDataSourceIfNeeded()
subviewsSkeletonables.recursiveSearch(leafBlock: {
guard !isSkeletonActive else { return }
isUserInteractionEnabled = false
saveViewState()
(self as? PrepareForSkeleton)?.prepareViewForSkeleton()
addSkeletonLayer(withType: type, usingColors: colors, animated: animated, animation: animation)
}) { subview in
subview.recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation)
showSkeletonIfNotActive(skeletonConfig: config)
}){ subview in
subview.recursiveShowSkeleton(skeletonConfig: config)
}
if let root = root {
flowDelegate?.didShowSkeletons(withRootView: root)
flowDelegate?.didShowSkeletons(rootView: root)
}
}
fileprivate func recursiveHideSkeleton(reloadDataAfter reload: Bool, root: UIView? = nil) {
private func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
guard !isSkeletonActive else { return }
isUserInteractionEnabled = false
saveViewState()
prepareViewForSkeleton()
addSkeletonLayer(skeletonConfig: config)
}
func updateSkeleton(skeletonConfig config: SkeletonConfig) {
isSkeletonAnimated = config.animated
flowDelegate?.willBeginUpdatingSkeletons(rootView: self)
recursiveUpdateSkeleton(skeletonConfig: config, root: self)
}
private func recursiveUpdateSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
guard isSkeletonActive else { return }
currentSkeletonConfig = config
updateDummyDataSourceIfNeeded()
subviewsSkeletonables.recursiveSearch(leafBlock: {
if skeletonLayer?.type != config.type {
removeSkeletonLayer()
addSkeletonLayer(skeletonConfig: config)
} else {
updateSkeletonLayer(skeletonConfig: config)
}
}) { subview in
subview.recursiveUpdateSkeleton(skeletonConfig: config)
}
if let root = root {
flowDelegate?.didUpdateSkeletons(rootView: root)
}
}
private func recursiveLayoutSkeletonIfNeeded(root: UIView? = nil) {
subviewsSkeletonables.recursiveSearch(leafBlock: {
guard isSkeletonable, isSkeletonActive else { return }
layoutSkeletonLayerIfNeeded()
if let config = currentSkeletonConfig, config.animated, !isSkeletonAnimated {
startSkeletonAnimation(config.animation)
}
}) { subview in
subview.recursiveLayoutSkeletonIfNeeded()
}
if let root = root {
flowDelegate?.didLayoutSkeletonsIfNeeded(rootView: root)
}
}
private func recursiveHideSkeleton(reloadDataAfter reload: Bool, transition: SkeletonTransitionStyle, root: UIView? = nil) {
guard isSkeletonActive else { return }
removeDummyDataSourceIfNeeded(reloadAfter: reload)
currentSkeletonConfig?.transition = transition
isUserInteractionEnabled = true
subviewsSkeletonables.recursiveSearch(leafBlock: {
recoverViewState(forced: false)
removeSkeletonLayer()
}) { subview in
subview.recursiveHideSkeleton(reloadDataAfter: reload)
subview.recursiveHideSkeleton(reloadDataAfter: reload, transition: transition)
}
if let root = root {
flowDelegate?.didHideSkeletons(withRootView: root)
flowDelegate?.didHideSkeletons(rootView: root)
}
}
fileprivate func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock {
private func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock {
return {
self.isSkeletonAnimated = true
guard let layer = self.skeletonLayer else { return }
layer.start(anim)
layer.start(anim) { [weak self] in
self?.isSkeletonAnimated = false
}
}
}
fileprivate var stopSkeletonLayerAnimationBlock: VoidBlock {
private var stopSkeletonLayerAnimationBlock: VoidBlock {
return {
self.isSkeletonAnimated = false
guard let layer = self.skeletonLayer else { return }
layer.stopAnimation()
}
}
private func swizzleLayoutSubviews() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
DispatchQueue.once(token: "UIView.SkeletonView.swizzle") {
swizzle(selector: #selector(UIView.layoutSubviews),
with: #selector(UIView.skeletonLayoutSubviews),
inClass: UIView.self,
usingClass: UIView.self)
self.layoutSkeletonIfNeeded()
}
}
}
}
extension UIView {
func addSkeletonLayer(withType type: SkeletonType, usingColors colors: [UIColor], gradientDirection direction: GradientDirection? = nil, animated: Bool, animation: SkeletonLayerAnimation? = nil) {
func addSkeletonLayer(skeletonConfig config: SkeletonConfig) {
guard let skeletonLayer = SkeletonLayerBuilder()
.setSkeletonType(type)
.addColors(colors)
.setSkeletonType(config.type)
.addColors(config.colors)
.setHolder(self)
.build()
else { return }
self.skeletonLayer = skeletonLayer
layer.insertSublayer(skeletonLayer.contentLayer, at: UInt32.max)
if animated { skeletonLayer.start(animation) }
layer.insertSublayer(skeletonLayer,
at: UInt32.max,
transition: config.transition) { [weak self] in
if config.animated {
self?.startSkeletonAnimation(config.animation)
}
}
status = .on
}
func updateSkeletonLayer(skeletonConfig config: SkeletonConfig) {
guard let skeletonLayer = skeletonLayer else { return }
skeletonLayer.update(usingColors: config.colors)
if config.animated {
startSkeletonAnimation(config.animation)
} else {
skeletonLayer.stopAnimation()
}
}
func layoutSkeletonLayerIfNeeded() {
guard let skeletonLayer = skeletonLayer else { return }
skeletonLayer.layoutIfNeeded()
}
func removeSkeletonLayer() {
guard isSkeletonActive,
let layer = skeletonLayer else { return }
layer.stopAnimation()
layer.removeLayer()
skeletonLayer = nil
status = .off
let skeletonLayer = skeletonLayer,
let transitionStyle = currentSkeletonConfig?.transition else { return }
skeletonLayer.stopAnimation()
skeletonLayer.removeLayer(transition: transitionStyle) {
self.skeletonLayer = nil
self.status = .off
self.currentSkeletonConfig = nil
}
}
}
-5
View File
@@ -13,35 +13,30 @@ extension UIView {
}
extension UITableView {
override var subviewsToSkeleton: [UIView] {
return visibleCells
}
}
extension UITableViewCell {
override var subviewsToSkeleton: [UIView] {
return contentView.subviews
}
}
extension UICollectionView {
override var subviewsToSkeleton: [UIView] {
return subviews
}
}
extension UICollectionViewCell {
override var subviewsToSkeleton: [UIView] {
return contentView.subviews
}
}
extension UIStackView {
override var subviewsToSkeleton: [UIView] {
return arrangedSubviews
}
@@ -0,0 +1,8 @@
// Copyright © 2019 SkeletonView. All rights reserved.
import UIKit
public enum SkeletonTransitionStyle: Equatable {
case none
case crossDissolve(TimeInterval)
}
@@ -0,0 +1,35 @@
// Copyright © 2019 SkeletonView. All rights reserved.
import UIKit
extension CALayer {
func insertSublayer(_ layer: SkeletonLayer, at idx: UInt32, transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
insertSublayer(layer.contentLayer, at: idx)
switch transition {
case .none:
completion?()
break
case .crossDissolve(let duration):
layer.contentLayer.setOpacity(from: 0, to: 1, duration: duration, completion: completion)
}
}
}
extension UIView {
func startTransition(transitionBlock: @escaping () -> Void) {
guard let transitionStyle = currentSkeletonConfig?.transition,
transitionStyle != .none
else {
transitionBlock()
return
}
if case let .crossDissolve(duration) = transitionStyle {
UIView.transition(with: self,
duration: duration,
options: .transitionCrossDissolve,
animations: transitionBlock,
completion: nil)
}
}
}