Compare commits

..

36 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 978fd553e1 Merge branch 'master' into develop 2019-08-22 15:11:15 +02:00
Tom Izaks 5b3bc204bf Adjust multiline behaviour to properly handle single lines 2019-07-09 14:00:48 +10:00
37 changed files with 350 additions and 188 deletions
+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.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 KiB

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
@@ -81,7 +81,7 @@ class ViewController: UIViewController {
}
func hideSkeleton() {
view.hideSkeleton(transition: .crossDisolve(transitionDurationStepper.value))
view.hideSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
}
func refreshSkeleton() {
+116 -79
View File
@@ -1,16 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<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="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="14490.49"/>
<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">
@@ -18,74 +16,8 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="UCB-SP-lQk">
<rect key="frame" x="0.0" y="263" width="375" height="244"/>
<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>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="CellIdentifier" rowHeight="120" id="2dN-Bd-tdy" customClass="Cell" customModule="SkeletonViewExample" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="375" height="120"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="2dN-Bd-tdy" id="7IN-F3-Mr6">
<rect key="frame" x="0.0" y="0.0" width="375" height="120"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="oiE-tt-nc2">
<rect key="frame" x="15" y="18" width="82" height="82"/>
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="width" constant="82" id="4j0-PU-CmN"/>
<constraint firstAttribute="height" constant="82" id="iqE-Lc-FOj"/>
</constraints>
<userDefinedRuntimeAttributes>
<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">
<rect key="frame" x="118" y="29" width="237" height="20.5"/>
<constraints>
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="71" id="HRL-cI-ieC"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="linesCornerRadius">
<integer key="value" value="0"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<constraints>
<constraint firstItem="oiE-tt-nc2" firstAttribute="leading" secondItem="7IN-F3-Mr6" secondAttribute="leadingMargin" id="1be-ak-AH1"/>
<constraint firstItem="oiE-tt-nc2" firstAttribute="top" secondItem="7IN-F3-Mr6" secondAttribute="topMargin" constant="7" id="EKn-ST-LDX"/>
<constraint firstAttribute="trailingMargin" secondItem="VhU-1t-AaI" secondAttribute="trailing" constant="5" id="I7C-Bq-mfK"/>
<constraint firstItem="VhU-1t-AaI" firstAttribute="leading" secondItem="oiE-tt-nc2" secondAttribute="trailing" constant="21" id="Ojr-Kz-1k6"/>
<constraint firstItem="VhU-1t-AaI" firstAttribute="top" secondItem="7IN-F3-Mr6" secondAttribute="topMargin" constant="18" id="ZW6-JY-S4c"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</tableViewCellContentView>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="avatar" destination="oiE-tt-nc2" id="Dkh-R5-Qhu"/>
<outlet property="label1" destination="VhU-1t-AaI" id="kUW-HV-KrD"/>
</connections>
</tableViewCell>
</prototypes>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="NO"/>
</userDefinedRuntimeAttributes>
<connections>
<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"/>
<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"/>
@@ -132,11 +64,80 @@
<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="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>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="CellIdentifier" rowHeight="120" id="2dN-Bd-tdy" customClass="Cell" customModule="SkeletonViewExample" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="375" height="120"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="2dN-Bd-tdy" id="7IN-F3-Mr6">
<rect key="frame" x="0.0" y="0.0" width="375" height="120"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="oiE-tt-nc2">
<rect key="frame" x="15" y="18" width="82" height="82"/>
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="width" constant="82" id="4j0-PU-CmN"/>
<constraint firstAttribute="height" constant="82" id="iqE-Lc-FOj"/>
</constraints>
<userDefinedRuntimeAttributes>
<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="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"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="linesCornerRadius">
<integer key="value" value="5"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="lastLineFillPercent">
<integer key="value" value="20"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<constraints>
<constraint firstItem="oiE-tt-nc2" firstAttribute="leading" secondItem="7IN-F3-Mr6" secondAttribute="leadingMargin" id="1be-ak-AH1"/>
<constraint firstItem="oiE-tt-nc2" firstAttribute="top" secondItem="7IN-F3-Mr6" secondAttribute="topMargin" constant="7" id="EKn-ST-LDX"/>
<constraint firstAttribute="trailingMargin" secondItem="VhU-1t-AaI" secondAttribute="trailing" constant="5" id="I7C-Bq-mfK"/>
<constraint firstItem="VhU-1t-AaI" firstAttribute="leading" secondItem="oiE-tt-nc2" secondAttribute="trailing" constant="21" id="Ojr-Kz-1k6"/>
<constraint firstItem="VhU-1t-AaI" firstAttribute="top" secondItem="7IN-F3-Mr6" secondAttribute="topMargin" constant="18" id="ZW6-JY-S4c"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</tableViewCellContentView>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="avatar" destination="oiE-tt-nc2" id="Dkh-R5-Qhu"/>
<outlet property="label1" destination="VhU-1t-AaI" id="kUW-HV-KrD"/>
</connections>
</tableViewCell>
</prototypes>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="NO"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="dataSource" destination="BYZ-38-t0r" id="Hxi-nC-gbY"/>
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XgY-1a-UGc">
<rect key="frame" x="0.0" y="507" width="375" height="160"/>
<rect key="frame" x="0.0" y="458" width="375" height="160"/>
<subviews>
<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="135" height="29"/>
<rect key="frame" x="20" y="23" width="145" height="32"/>
<segments>
<segment title="Solid"/>
<segment title="Gradient"/>
@@ -189,13 +190,13 @@
</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="128.5" width="141.5" height="18"/>
<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="29"/>
<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>
@@ -244,6 +245,7 @@
<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"/>
@@ -258,7 +260,42 @@
</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>
+1 -5
View File
@@ -45,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) {
+20 -7
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>
@@ -174,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:
@@ -184,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:
@@ -433,14 +438,21 @@ Since ```SkeletonView``` is recursive, and we want skeleton to be very efficient
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
@@ -516,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.8"
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.
+8 -2
View File
@@ -78,6 +78,8 @@
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 */; };
@@ -185,6 +187,7 @@
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>"; };
@@ -437,6 +440,7 @@
F5F899D11FAB9630002E8FDA /* AssociationPolicy.swift */,
F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */,
F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */,
F570ABF32314629700390248 /* Swizzling.swift */,
);
path = Helpers;
sourceTree = "<group>";
@@ -684,6 +688,7 @@
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 */,
17DD0E14207FB28F00C56334 /* SkeletonAnimationBuilder.swift in Sources */,
17DD0E11207FB28C00C56334 /* UIView+Frame.swift in Sources */,
@@ -740,6 +745,7 @@
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 */,
@@ -1038,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*]" = "";
@@ -1063,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*]" = "";
@@ -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 }
}
@@ -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()
}
+17 -18
View File
@@ -45,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)
@@ -63,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
}
@@ -83,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
}
@@ -100,6 +95,7 @@ public extension CALayer {
pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
pulseAnimation.autoreverses = true
pulseAnimation.repeatCount = .infinity
pulseAnimation.isRemovedOnCompletion = false
return pulseAnimation
}
@@ -117,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)
}
}
@@ -140,15 +140,14 @@ public extension CALayer {
extension CALayer {
func setOpacity(from: Int, to: Int, duration: TimeInterval, completion: (() -> Void)?) {
CATransaction.begin()
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)
// layer.contentLayer.opacity = 1
CATransaction.setCompletionBlock(completion)
DispatchQueue.main.async { CATransaction.setCompletionBlock(completion) }
add(animation, forKey: "setOpacityAnimation")
CATransaction.commit()
DispatchQueue.main.async { CATransaction.commit() }
}
}
+1 -1
View File
@@ -42,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) }
}
@@ -13,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) }
}
@@ -12,12 +12,14 @@ 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() {
@@ -32,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)
}
}
+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)
}
}
+1
View File
@@ -78,6 +78,7 @@ public class SkeletonAnimationBuilder {
animGroup.duration = duration
animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
animGroup.repeatCount = .infinity
animGroup.isRemovedOnCompletion = false
return animGroup
}
+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
}
}
+14 -5
View File
@@ -70,6 +70,7 @@ struct SkeletonLayer {
switch transition {
case .none:
maskLayer.removeFromSuperlayer()
completion?()
case .crossDissolve(let duration):
maskLayer.setOpacity(from: 1, to: 0, duration: duration) {
self.maskLayer.removeFromSuperlayer()
@@ -77,22 +78,30 @@ struct SkeletonLayer {
}
}
}
/// 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() {
+85 -48
View File
@@ -3,21 +3,47 @@
import UIKit
public extension UIView {
/// 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)
}
/// 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)
}
/// 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)
}
/// 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)
@@ -44,29 +70,22 @@ public extension UIView {
}
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, transition: SkeletonTransitionStyle = .none) {
if var config = currentSkeletonConfig {
config.transition = transition
updateSkeleton(skeletonConfig: config)
}
flowDelegate?.willBeginHidingSkeletons(withRootView: self)
recursiveHideSkeleton(reloadDataAfter: reload, root: self)
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()
}
@@ -74,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)
@@ -95,11 +117,11 @@ extension UIView {
}
if let root = root {
flowDelegate?.didShowSkeletons(withRootView: root)
flowDelegate?.didShowSkeletons(rootView: root)
}
}
fileprivate func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
private func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
guard !isSkeletonActive else { return }
isUserInteractionEnabled = false
saveViewState()
@@ -108,81 +130,93 @@ extension UIView {
}
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 {
@@ -197,9 +231,9 @@ extension UIView {
self.skeletonLayer = skeletonLayer
layer.insertSublayer(skeletonLayer,
at: UInt32.max,
transition: config.transition) {
transition: config.transition) { [weak self] in
if config.animated {
skeletonLayer.start(config.animation)
self?.startSkeletonAnimation(config.animation)
}
}
status = .on
@@ -208,8 +242,11 @@ extension UIView {
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() {
@@ -7,6 +7,7 @@ extension CALayer {
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)