Compare commits

...

79 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
69 changed files with 909 additions and 387 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
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

+14
View File
@@ -3,8 +3,22 @@ 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).
- 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)
+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>
+38 -10
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,15 +45,11 @@ class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableview.isSkeletonable = true
}
override func viewDidLayoutSubviews() {
view.layoutSkeletonIfNeeded()
view.showAnimatedSkeleton()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.showAnimatedSkeleton()
}
@IBAction func changeAnimated(_ sender: Any) {
@@ -70,6 +68,39 @@ 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() {
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 hideSkeleton() {
view.hideSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
}
func refreshSkeleton() {
if type == .gradient { showOrUpdateGradientSkeleton() }
else { showOrUpdatepdateSolidSkeleton() }
@@ -93,7 +124,6 @@ class ViewController: UIViewController {
}
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))
@@ -119,7 +149,6 @@ class ViewController: UIViewController {
}
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
@@ -134,9 +163,8 @@ extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
}
extension ViewController: SkeletonTableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
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
+60 -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>
@@ -50,6 +53,7 @@ Enjoy it! 🙂
* [Custom colors](#-custom-colors)
* [Appearance](#-appearance)
* [Custom animations](#-custom-animations)
* [Transitions](#-transitions)
* [Hierarchy](#-hierarchy)
* [Debug](#-debug)
* [Documentation](#-documentation)
@@ -99,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.7")
.package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0")
]
```
@@ -173,7 +177,7 @@ avatarImageView.isSkeletonable = true
#### Skeleton views layout
Sometimes skeleton layout may not fit your layout because the parent view bounds have changed. For example, rotating the device.
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:
@@ -183,6 +187,8 @@ override func viewDidLayoutSubviews() {
}
```
⚠️⚠️ 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:
@@ -390,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
@@ -439,7 +488,7 @@ Coming soon...😅
* iOS 9.0+
* tvOS 9.0+
* Swift 4.2
* Swift 5
## 📬 Next steps
@@ -451,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
@@ -479,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)
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "SkeletonView"
s.version = "1.7"
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.
+38 -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 */; };
@@ -46,7 +46,6 @@
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 */; };
@@ -57,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 */; };
@@ -77,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 */; };
@@ -90,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 */; };
@@ -148,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>"; };
@@ -167,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>"; };
@@ -182,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>"; };
@@ -191,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>"; };
@@ -242,7 +248,6 @@
52D6D9721BEFF229002C0205 = {
isa = PBXGroup;
children = (
3B83EE4620C41488005178A4 /* UIView+IBInspectable.swift */,
F5F899F31FABA607002E8FDA /* Example */,
8933C7811EB5B7E0000D00A4 /* Sources */,
52D6D99C1BEFF38C002C0205 /* Configs */,
@@ -336,6 +341,7 @@
8933C7811EB5B7E0000D00A4 /* Sources */ = {
isa = PBXGroup;
children = (
E4CE587A22EEE62300333067 /* Transitions */,
872D5A5A21C24E7C0037D763 /* Multilines */,
877EFA4321BEE9C40031FC00 /* Builders */,
8785E39F211C9C7C00CC9DFD /* Debug */,
@@ -364,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 = (
@@ -409,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>";
@@ -425,6 +440,7 @@
F5F899D11FAB9630002E8FDA /* AssociationPolicy.swift */,
F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */,
F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */,
F570ABF32314629700390248 /* Swizzling.swift */,
);
path = Helpers;
sourceTree = "<group>";
@@ -470,7 +486,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
8748240D20C6A88A00E92179 /* UIView+IBInspectable.swift in Headers */,
8748240E20C6A88E00E92179 /* ContainsMultilineText.swift in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -479,7 +494,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
3B83EE4820C41488005178A4 /* UIView+IBInspectable.swift in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -662,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 */,
@@ -671,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 */,
@@ -688,6 +704,7 @@
17DD0E1D207FB32100C56334 /* ContainsMultilineText.swift in Sources */,
17DD0E0F207FB28C00C56334 /* CALayer+Extensions.swift in Sources */,
17DD0E16207FB28F00C56334 /* SkeletonGradient.swift in Sources */,
1E6C67A3230E76CE0019D87B /* UIView+Transitions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -706,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 */,
@@ -719,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 */,
@@ -1024,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*]" = "";
@@ -1049,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*]" = "";
@@ -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?
@@ -29,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 }
}
@@ -17,7 +17,6 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
}
public extension SkeletonCollectionViewDataSource {
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return skeletonView.estimatedNumberOfRows
}
@@ -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))
@@ -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,13 +25,12 @@ 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 {
@@ -45,7 +43,6 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
// MARK: - UICollectionViewDataSource
extension SkeletonCollectionDataSource: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return originalCollectionViewDataSource?.numSections(in: collectionView) ?? 0
}
@@ -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))
}
@@ -11,6 +11,7 @@ import UIKit
extension UIView {
func addDummyDataSourceIfNeeded() {
guard let collection = self as? CollectionSkeleton else { return }
status = .on
collection.addDummyDataSource()
collection.disableUserInteraction()
}
@@ -22,6 +23,7 @@ extension UIView {
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
+28 -16
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 = getLineWidth(index: index, numberOfSublayers: numberOfSublayers, lastLineFillPercent: lastLineFillPercent)
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)
@@ -64,17 +59,16 @@ extension CALayer {
let currentSkeletonSublayers = skeletonSublayers
let numberOfSublayers = currentSkeletonSublayers.count
for (index, layer) in currentSkeletonSublayers.enumerated() {
let width = getLineWidth(index: index, numberOfSublayers: numberOfSublayers, lastLineFillPercent: lastLineFillPercent)
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: lastLineFillPercent)
layer.updateLayerFrame(for: index, width: width)
}
}
private func getLineWidth(index: Int, numberOfSublayers: Int, lastLineFillPercent: Int) -> CGFloat {
private func calculatedWidthForLine(at index: Int, totalLines: Int, lastLineFillPercent: Int) -> CGFloat {
var width = bounds.width
if index == numberOfSublayers - 1 && numberOfSublayers != 1 {
width = width * CGFloat(lastLineFillPercent) / 100;
if index == totalLines - 1 && totalLines != 1 {
width = width * CGFloat(lastLineFillPercent) / 100
}
return width
}
@@ -84,8 +78,8 @@ extension CALayer {
}
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
}
@@ -93,7 +87,6 @@ extension CALayer {
// MARK: Animations
public extension CALayer {
var pulse: CAAnimation {
let pulseAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.backgroundColor))
pulseAnimation.fromValue = backgroundColor
@@ -102,6 +95,7 @@ public extension CALayer {
pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
pulseAnimation.autoreverses = true
pulseAnimation.repeatCount = .infinity
pulseAnimation.isRemovedOnCompletion = false
return pulseAnimation
}
@@ -119,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)
}
}
@@ -139,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,
+3 -2
View File
@@ -10,12 +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
@@ -41,7 +42,7 @@ extension UIView {
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
}
+16 -7
View File
@@ -6,26 +6,35 @@ import UIKit
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
) {
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
}
}
+16 -17
View File
@@ -3,43 +3,42 @@
import UIKit
protocol SkeletonFlowDelegate {
func willBeginShowingSkeletons(withRootView rootView: UIView)
func didShowSkeletons(withRootView rootView: UIView)
func willBeginUpdatingSkeletons(withRootView rootView: UIView)
func didUpdateSkeletons(withRootView rootView: UIView)
func willBeginLayingSkeletonsIfNeeded(withRootView: UIView)
func didLayoutSkeletonsIfNeeded(withRootView: 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 willBeginUpdatingSkeletons(withRootView rootView: UIView) {
func willBeginUpdatingSkeletons(rootView: UIView) {
}
func didUpdateSkeletons(withRootView rootView: UIView) {
func didUpdateSkeletons(rootView: UIView) {
}
func willBeginLayingSkeletonsIfNeeded(withRootView: UIView) {
func willBeginLayingSkeletonsIfNeeded(rootView: UIView) {
}
func didLayoutSkeletonsIfNeeded(withRootView: UIView) {
func didLayoutSkeletonsIfNeeded(rootView: UIView) {
}
func willBeginHidingSkeletons(withRootView 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
+28 -13
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
@@ -57,36 +56,52 @@ struct SkeletonLayer {
func update(usingColors colors: [UIColor]) {
layoutIfNeeded()
self.maskLayer.tint(withColors: colors)
maskLayer.tint(withColors: colors)
}
func layoutIfNeeded() {
if let bounds = self.holder?.maxBoundsEstimated {
self.maskLayer.bounds = bounds
if let bounds = holder?.maxBoundsEstimated {
maskLayer.bounds = bounds
}
updateMultilinesIfNeeded()
}
func removeLayer() {
maskLayer.removeFromSuperlayer()
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 = holder as? ContainsMultilineText else { return }
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
+115 -70
View File
@@ -3,67 +3,89 @@
import UIKit
public extension UIView {
func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor) {
let config: SkeletonConfig = SkeletonConfig(type: .solid, colors: [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) {
let config: SkeletonConfig = SkeletonConfig(type: .gradient, colors: 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) {
let config: SkeletonConfig = SkeletonConfig(type: .solid, colors: [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) {
let config: SkeletonConfig = SkeletonConfig(type: .gradient, colors: 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 = SkeletonConfig(type: .solid, colors: [color])
let config = SkeletonConfig(type: .solid, colors: [color])
updateSkeleton(skeletonConfig: config)
}
func updateGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient) {
let config: SkeletonConfig = SkeletonConfig(type: .gradient, colors: gradient.colors)
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 = SkeletonConfig(type: .solid, colors: [color], animated: true, animation: animation)
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 = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: true, animation: animation)
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: true, animation: animation)
updateSkeleton(skeletonConfig: config)
}
func layoutSkeletonIfNeeded() {
guard let flowDelegate = flowDelegate else { return }
flowDelegate.willBeginLayingSkeletonsIfNeeded(withRootView: self)
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()
}
@@ -71,19 +93,22 @@ public extension UIView {
}
extension UIView {
@objc func skeletonLayoutSubviews() {
guard isSkeletonActive else { return }
layoutSkeletonIfNeeded()
}
func showSkeleton(skeletonConfig config: SkeletonConfig) {
skeletonIsAnimated = config.animated
isSkeletonAnimated = config.animated
flowDelegate = SkeletonFlowHandler()
flowDelegate?.willBeginShowingSkeletons(withRootView: self)
flowDelegate?.willBeginShowingSkeletons(rootView: self)
recursiveShowSkeleton(skeletonConfig: config, root: self)
}
fileprivate func recursiveShowSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
layoutIfNeeded()
private func recursiveShowSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
guard !isSkeletonActive else { return }
currentSkeletonConfig = config
swizzleLayoutSubviews()
addDummyDataSourceIfNeeded()
subviewsSkeletonables.recursiveSearch(leafBlock: {
showSkeletonIfNotActive(skeletonConfig: config)
@@ -92,98 +117,109 @@ extension UIView {
}
if let root = root {
flowDelegate?.didShowSkeletons(withRootView: root)
flowDelegate?.didShowSkeletons(rootView: root)
}
}
fileprivate func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
guard !self.isSkeletonActive else { return }
self.isUserInteractionEnabled = false
self.saveViewState()
(self as? PrepareForSkeleton)?.prepareViewForSkeleton()
self.addSkeletonLayer(skeletonConfig: config)
private func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
guard !isSkeletonActive else { return }
isUserInteractionEnabled = false
saveViewState()
prepareViewForSkeleton()
addSkeletonLayer(skeletonConfig: config)
}
func updateSkeleton(skeletonConfig config: SkeletonConfig) {
guard let flowDelegate = flowDelegate else { return }
skeletonIsAnimated = config.animated
flowDelegate.willBeginUpdatingSkeletons(withRootView: self)
isSkeletonAnimated = config.animated
flowDelegate?.willBeginUpdatingSkeletons(rootView: self)
recursiveUpdateSkeleton(skeletonConfig: config, root: self)
}
fileprivate func recursiveUpdateSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
layoutIfNeeded()
private func recursiveUpdateSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
guard isSkeletonActive else { return }
currentSkeletonConfig = config
updateDummyDataSourceIfNeeded()
subviewsSkeletonables.recursiveSearch(leafBlock: {
guard isSkeletonActive else { return }
if skeletonLayer?.type != config.type {
hideSkeleton()
}
if isSkeletonActive {
updateSkeletonLayer(skeletonConfig: config)
removeSkeletonLayer()
addSkeletonLayer(skeletonConfig: config)
} else {
showSkeletonIfNotActive(skeletonConfig: config)
updateSkeletonLayer(skeletonConfig: config)
}
}) { subview in
subview.recursiveUpdateSkeleton(skeletonConfig: config)
}
if let root = root {
flowDelegate?.didUpdateSkeletons(withRootView: root)
flowDelegate?.didUpdateSkeletons(rootView: root)
}
}
fileprivate func recursiveLayoutSkeletonIfNeeded(root: UIView? = nil) {
layoutIfNeeded()
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(withRootView: root)
flowDelegate?.didLayoutSkeletonsIfNeeded(rootView: root)
}
}
fileprivate func recursiveHideSkeleton(reloadDataAfter reload: Bool, root: UIView? = nil) {
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(skeletonConfig config: SkeletonConfig) {
guard let skeletonLayer = SkeletonLayerBuilder()
.setSkeletonType(config.type)
@@ -193,16 +229,24 @@ extension UIView {
else { return }
self.skeletonLayer = skeletonLayer
layer.insertSublayer(skeletonLayer.contentLayer, at: UInt32.max)
if config.animated { skeletonLayer.start(config.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 { skeletonLayer.start(config.animation) }
else { skeletonLayer.stopAnimation() }
if config.animated {
startSkeletonAnimation(config.animation)
} else {
skeletonLayer.stopAnimation()
}
}
func layoutSkeletonLayerIfNeeded() {
@@ -212,12 +256,13 @@ extension UIView {
func removeSkeletonLayer() {
guard isSkeletonActive,
let skeletonLayer = skeletonLayer else { return }
let skeletonLayer = skeletonLayer,
let transitionStyle = currentSkeletonConfig?.transition else { return }
skeletonLayer.stopAnimation()
skeletonLayer.removeLayer()
self.skeletonLayer = nil
status = .off
currentSkeletonConfig = nil
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)
}
}
}