Compare commits

...

32 Commits

Author SHA1 Message Date
Juanpe Catalán 8f5d409304 feat: revert last changes in README file 2018-08-10 17:43:15 +02:00
Juanpe Catalán bc3258b8a2 feat: update README 2018-08-10 17:42:24 +02:00
Juanpe Catalán fd4683f555 Merge pull request #90 from Juanpe/debug_system
Debug system
2018-08-10 17:40:11 +02:00
Juanpe Catalán 97ebbcc917 feat: update README 2018-08-10 17:36:49 +02:00
Juanpe Catalán a02922dba6 feat: Update README file 2018-08-10 17:30:43 +02:00
Juanpe Catalán 4ac1abe50e feat: BUMP version 1.4 2018-08-10 17:28:31 +02:00
Juanpe Catalán 25e313850e feat: Update CHANGELOG file 2018-08-10 17:28:12 +02:00
Juanpe Catalán 88914f7816 feat: Update README file explaining debug mode 2018-08-10 17:10:49 +02:00
Juanpe Catalán 14ab00d13a feat: update README file 2018-08-10 14:23:48 +02:00
Juanpe Catalán 90b993a16b feat: Create skeletonHierarchy 2018-08-10 14:17:30 +02:00
Juanpe Catalán 413867718e feat: Improve recursive protocol 2018-08-10 13:23:24 +02:00
Juanpe Catalán e43b3f3750 feat: Add skeleton hierarchy 2018-08-10 08:27:51 +02:00
Juanpe Catalán b9b368b7e8 feat: Create Environment variable 2018-08-09 18:45:03 +02:00
Juanpe Catalán fc22d58f89 Merge pull request #87 from reececomo/alternate-user-interaction
#86 disableUserInteraction() on UIScrollView just to prevent scroll
2018-08-06 10:24:18 +02:00
Reece Como 8988b22784 style consistency 2018-08-06 11:09:20 +08:00
Reece Como 3cb1602e11 Merge https://github.com/Juanpe/SkeletonView into alternate-user-interaction 2018-08-06 11:08:31 +08:00
Reece Como 649672182e allow UIScrollViews to accept button taps 2018-08-06 10:58:10 +08:00
Juanpe Catalán 3c16416dbb feat: update README 2018-08-02 17:55:14 +02:00
Juanpe Catalán b90e97592a feat: BUMP version 1.3 2018-08-02 17:32:31 +02:00
Juanpe Catalán 9fbaaa02f5 feat: Update README and CHANGELOG file 2018-08-02 17:32:23 +02:00
Juanpe Catalán 3f7f2ec95c feat: Remove useless code 2018-08-02 16:33:20 +02:00
Juanpe Catalán 26da7e3fc0 feat: Rename SkeletonAppearance 2018-08-02 16:32:57 +02:00
Juanpe Catalán 804390e98a feat: Create SkeletonViewAppearance 2018-08-02 16:18:41 +02:00
Juanpe Catalán 3bf613da0d Merge pull request #84 from reececomo/83-Customisable-Defaults
#83 Customisable Defaults
2018-08-02 15:41:40 +02:00
Reece Como 0eff052314 Merge pull request #1 from reececomo/83-Customisable-Defaults
#83 Customisable Defaults
2018-08-02 16:32:39 +08:00
Reece Como af91af3716 #83 Customisable Defaults 2018-08-02 16:08:31 +08:00
Juanpe Catalán 2fe2323cc1 Merge pull request #42 from kjoneandrei/feature/fix_skeleton_collectionView
Fix: Skeleton loading for UICollectionView
2018-07-31 09:54:00 +02:00
anho 024171a993 fixed collectionview xib and skeleton 2018-07-27 15:28:59 +02:00
Juanpe Catalán 726817cfe5 feat: Add credits 2018-07-27 13:05:14 +02:00
Juanpe Catalán 9684817956 feat: Change README path 2018-07-27 13:00:36 +02:00
Juanpe Catalán 3a847ece3d fix: Link to Chinese README file 2018-07-27 12:43:11 +02:00
Juanpe Catalán a0fb735c3e feat: Add Chinese README file 2018-07-27 12:38:16 +02:00
35 changed files with 1525 additions and 150 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

+24
View File
@@ -1,6 +1,30 @@
# Change Log
All notable changes to this project will be documented in this file
## [Debug (1.4)](https://github.com/Juanpe/SkeletonView/releases/tag/1.4)
### New
- Create `skeletonDescription` print a skeleton representation of the view.
- Create `SKELETON_DEBUG` environment variable, in order to print the view hierarchy when the skeleton appears.
### Improvements
- Add two new methods to `SkeletonFlowDelegate` protocol. Now you can know when the skeleton did show and when it did hide.
- `Recursive` protocol
### Bug fixes
- Solved issue [#86](https://github.com/Juanpe/SkeletonView/issues/86) (thanks @reececomo)
## [Custom defaults (1.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.3)
### New
- Default values customizables. Now you can set the default values of Skeleton appearance.(thanks @reececomo)
- issues: [[#50](https://github.com/Juanpe/SkeletonView/issues/50), [#83](https://github.com/Juanpe/SkeletonView/issues/83)]
### Bug fixes
- Solved issue [#41](https://github.com/Juanpe/SkeletonView/issues/41). Now, Skeleton works if UICollectionView cell's Nib is registered in code. (thanks @kjoneandrei)
## [Typo (1.2.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2.3)
### Fixes
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.2.1</string>
<string>1.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.2.1</string>
<string>1.3</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
@@ -0,0 +1,40 @@
// Copyright © 2018 SkeletonView. All rights reserved.
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<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-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,35 @@
// Copyright © 2018 SkeletonView. All rights reserved.
import UIKit
import SkeletonView
class CollectionViewCell: UICollectionViewCell {
var label: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
isSkeletonable = true
createLabel()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func createLabel() {
label = UILabel()
label.isSkeletonable = true
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)
])
}
}
+167
View File
@@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="irH-dz-xqL">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<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-->
<scene sceneID="qda-qV-vJk">
<objects>
<viewController id="irH-dz-xqL" customClass="ViewController" customModule="SkeletonView" customModuleProvider="target" 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"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="HKL-L0-T2w">
<rect key="frame" x="0.0" y="243" width="375" height="284"/>
<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"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells/>
<connections>
<outlet property="dataSource" destination="irH-dz-xqL" id="wya-hE-ovQ"/>
</connections>
</collectionView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JjA-MK-YzZ">
<rect key="frame" x="0.0" y="527" width="375" height="140"/>
<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"/>
<segments>
<segment title="Solid"/>
<segment title="Gradient"/>
</segments>
<connections>
<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"/>
<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"/>
<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"/>
<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"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</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"/>
<connections>
<action selector="btnChangeColorTouchUpInside:" destination="irH-dz-xqL" eventType="touchUpInside" id="Xca-QC-htl"/>
</connections>
</button>
</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"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eHI-ka-8vS">
<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="obr-b6-dib">
<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="jx6-c1-U0j"/>
</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>
</userDefinedRuntimeAttributes>
</textView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" 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>
<constraint firstAttribute="height" constant="93" id="jlG-7K-wMd"/>
<constraint firstAttribute="width" constant="93" id="xHX-Y1-dvi"/>
</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 firstAttribute="height" relation="greaterThanOrEqual" constant="243" id="0g6-3g-uII"/>
<constraint firstAttribute="trailing" secondItem="obr-b6-dib" secondAttribute="trailing" constant="43" id="3ms-Wk-qcn"/>
<constraint firstItem="obr-b6-dib" firstAttribute="centerX" secondItem="eHI-ka-8vS" secondAttribute="centerX" constant="1" id="B5s-DM-eR8"/>
<constraint firstAttribute="height" constant="243" id="GX5-3W-tUt"/>
<constraint firstItem="Ql9-Jy-aWM" firstAttribute="centerX" secondItem="eHI-ka-8vS" secondAttribute="centerX" id="HsA-ID-oSK"/>
<constraint firstItem="Ql9-Jy-aWM" firstAttribute="top" secondItem="eHI-ka-8vS" secondAttribute="top" constant="20" id="Hxu-ae-hXQ"/>
<constraint firstItem="obr-b6-dib" firstAttribute="leading" secondItem="eHI-ka-8vS" secondAttribute="leading" constant="45" id="eop-Gq-7mO"/>
<constraint firstItem="obr-b6-dib" firstAttribute="top" secondItem="eHI-ka-8vS" secondAttribute="top" constant="142" id="inJ-75-hGX"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="eHI-ka-8vS" firstAttribute="trailing" secondItem="2Gq-Y8-1TU" secondAttribute="trailing" id="0dc-Vd-yJY"/>
<constraint firstItem="JjA-MK-YzZ" firstAttribute="leading" secondItem="2Gq-Y8-1TU" secondAttribute="leading" id="57i-UV-Wqd"/>
<constraint firstItem="eHI-ka-8vS" firstAttribute="leading" secondItem="2Gq-Y8-1TU" secondAttribute="leading" id="5tt-Ne-67Y"/>
<constraint firstItem="JjA-MK-YzZ" firstAttribute="bottom" secondItem="2Gq-Y8-1TU" secondAttribute="bottom" id="AAr-ke-R7M"/>
<constraint firstItem="JjA-MK-YzZ" firstAttribute="trailing" secondItem="2Gq-Y8-1TU" secondAttribute="trailing" id="DtS-9c-zBC"/>
<constraint firstItem="HKL-L0-T2w" firstAttribute="top" secondItem="eHI-ka-8vS" secondAttribute="bottom" id="Jgf-jS-PLT"/>
<constraint firstItem="JjA-MK-YzZ" firstAttribute="top" secondItem="HKL-L0-T2w" secondAttribute="bottom" id="XEd-Gf-KFI"/>
<constraint firstItem="2Gq-Y8-1TU" firstAttribute="trailing" secondItem="HKL-L0-T2w" secondAttribute="trailing" id="bNo-98-pE4"/>
<constraint firstItem="HKL-L0-T2w" firstAttribute="leading" secondItem="2Gq-Y8-1TU" secondAttribute="leading" id="iIq-cx-paX"/>
<constraint firstItem="eHI-ka-8vS" firstAttribute="top" secondItem="Fso-nq-n6t" secondAttribute="top" id="tlZ-Wl-lvE"/>
</constraints>
<viewLayoutGuide key="safeArea" id="2Gq-Y8-1TU"/>
</view>
<connections>
<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="skeletonTypeSelector" destination="fMR-vj-7de" id="CgX-3A-weo"/>
<outlet property="switchAnimated" destination="KBe-RM-BG8" id="emU-g9-NHT"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="PkM-Y0-M5i" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-972" y="-209.14542728635683"/>
</scene>
</scenes>
<resources>
<image name="avatar" width="215" height="211"/>
</resources>
</document>
@@ -0,0 +1,173 @@
// Copyright © 2018 SkeletonView. All rights reserved.
import UIKit
import SkeletonView
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView! {
didSet {
collectionView.isSkeletonable = true
collectionView.backgroundColor = .clear
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
}
}
@IBOutlet weak var avatarImage: UIImageView! {
didSet {
avatarImage.layer.cornerRadius = avatarImage.frame.width/2
avatarImage.layer.masksToBounds = true
}
}
@IBOutlet weak var colorSelectedView: UIView! {
didSet {
colorSelectedView.layer.cornerRadius = 5
colorSelectedView.layer.masksToBounds = true
colorSelectedView.backgroundColor = SkeletonAppearance.default.tintColor
}
}
@IBOutlet weak var switchAnimated: UISwitch!
@IBOutlet weak var skeletonTypeSelector: UISegmentedControl!
var type: SkeletonType {
return skeletonTypeSelector.selectedSegmentIndex == 0 ? .solid : .gradient
}
override func viewDidLoad() {
super.viewDidLoad()
view.isSkeletonable = true
collectionView.prepareSkeleton(completion: { done in
self.view.showAnimatedSkeleton()
})
}
@IBAction func changeAnimated(_ sender: Any) {
if switchAnimated.isOn {
view.startSkeletonAnimation()
} else {
view.stopSkeletonAnimation()
}
}
@IBAction func changeSkeletonType(_ sender: Any) {
refreshSkeleton()
}
@IBAction func btnChangeColorTouchUpInside(_ sender: Any) {
showAlertPicker()
}
func refreshSkeleton() {
self.view.hideSkeleton()
if type == .gradient { showGradientSkeleton() }
else { showSolidSkeleton() }
}
func showSolidSkeleton() {
if switchAnimated.isOn {
view.showAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!)
} else {
view.showSkeleton(usingColor: colorSelectedView.backgroundColor!)
}
}
func showGradientSkeleton() {
let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!)
if switchAnimated.isOn {
view.showAnimatedGradientSkeleton(usingGradient: gradient)
} else {
view.showGradientSkeleton(usingGradient: gradient)
}
}
func showAlertPicker() {
let alertView = UIAlertController(title: "Select color", message: "\n\n\n\n\n\n", preferredStyle: .alert)
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 50, width: 260, height: 115))
pickerView.dataSource = self
pickerView.delegate = self
alertView.view.addSubview(pickerView)
let action = UIAlertAction(title: "OK", style: .default) { [unowned pickerView, unowned self] _ in
let row = pickerView.selectedRow(inComponent: 0)
self.colorSelectedView.backgroundColor = colors[row].0
self.refreshSkeleton()
}
alertView.addAction(action)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertView.addAction(cancelAction)
present(alertView, animated: false, completion: {
pickerView.frame.size.width = alertView.view.frame.size.width
})
}
}
// MARK: - UIPickerViewDelegate, UIPickerViewDataSource
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return colors.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return colors[row].1
}
}
// 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)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
}
// MARK: - SkeletonCollectionViewDataSource
extension ViewController: SkeletonCollectionViewDataSource {
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier {
return "CollectionViewCell"
}
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
return cell
}
}
+3
View File
@@ -201,6 +201,9 @@
<constraint firstItem="XgY-1a-UGc" firstAttribute="bottom" secondItem="6Tk-OE-BBY" secondAttribute="bottom" id="vnZ-9k-MfI"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="NO"/>
</userDefinedRuntimeAttributes>
</view>
<navigationItem key="navigationItem" id="BEI-dU-kr2"/>
<connections>
+1 -7
View File
@@ -1,10 +1,4 @@
//
// Constants.swift
// SkeletonView-iOS
//
// Created by Renato Mendes on 28/11/2017.
// Copyright © 2017 SkeletonView. All rights reserved.
//
// Copyright © 2018 SkeletonView. All rights reserved.
import UIKit
+1 -1
View File
@@ -29,7 +29,7 @@ class ViewController: UIViewController {
didSet {
colorSelectedView.layer.cornerRadius = 5
colorSelectedView.layer.masksToBounds = true
colorSelectedView.backgroundColor = SkeletonDefaultConfig.tintColor
colorSelectedView.backgroundColor = SkeletonAppearance.default.tintColor
}
}
+58 -2
View File
@@ -31,6 +31,8 @@
</a>
</p>
🌎 Translations: [ [🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) by [@WhatsXie](https://twitter.com/WhatsXie) ]
Today almost all apps have async processes, such as Api requests, long running processes, etc. And while the processes are working, usually developers place a loading view to show users that something is going on.
```SkeletonView``` has been conceived to address this need, an elegant way to show users that something is happening and also prepare them to which contents he is waiting.
@@ -47,8 +49,10 @@ Enjoy it! 🙂
* [Collections](#-collections)
* [Multiline text](#-multiline-text)
* [Custom colors](#-custom-colors)
* [Appearance](#-appearance)
* [Custom animations](#-custom-animations)
* [Hierarchy](#-hierarchy)
* [Debug](#-debug)
* [Documentation](#-documentation)
* [Next steps](#-next-steps)
* [Contributing](#-contributing)
@@ -271,9 +275,34 @@ Besides, ```SkeletonView``` features 20 flat colors 🤙🏼
![](Assets/flatcolors.png)
###### Image captured from website [https://flatuicolors.com](https://flatuicolors.com)
### 🦋 Appearance
**NEW** The skeletons have a default appearance. So, when you don't specify the color, gradient or multilines properties, `SkeletonView` uses the default values.
Default values:
- **tintColor**: UIColor
- *default: .clouds*
- **gradient**: SkeletonGradient
- *default: SkeletonGradient(baseColor: .clouds)*
- **multilineHeight**: CGFloat
- *default: 15*
- **multilineSpacing**: CGFloat
- *default: 10*
- **multilineLastLineFillPercent**: Int
- *default: 70*
- **multilineCornerRadius**: Int
- *default: 0*
To get these default values you can use `SkeletonAppearance.default`. Using this property you can set the values as well:
```Swift
SkeletonAppearance.default.multilineHeight = 20
SkeletonAppearance.default.tintColor = .green
```
### 🤓 Custom animations
Now, ```SkeletonView``` has two built-in animations, *pulse* for solid skeletons and *sliding* for gradients.
```SkeletonView``` has two built-in animations, *pulse* for solid skeletons and *sliding* for gradients.
Besides, if you want to do your own skeleton animation, it's really easy.
@@ -295,7 +324,7 @@ view.showAnimatedSkeleton { (layer) -> CAAnimation in
}
```
**NEW** It's available ```SkeletonAnimationBuilder```. It's a builder to make ```SkeletonLayerAnimation```.
It's available ```SkeletonAnimationBuilder```. It's a builder to make ```SkeletonLayerAnimation```.
Today, you can create **sliding animations** for gradients, deciding the **direction** and setting the **duration** of the animation (default = 1.5s).
@@ -338,6 +367,31 @@ Because an image is worth a thousand words:
|![](Assets/all_skeletonables.png) | ![](Assets/all_skeletonables_result.png)
### 🔬 Debug
**NEW** In order to facilitate the debug tasks when something is not working fine. `SkeletonView` has some new tools.
First, `UIView` has available a new property with his skeleton info:
```swift
var skeletonDescription: String
```
The skeleton representation looks like this:
![](Assets/debug_description.png)
Besides, you can activate the new **debug mode**. You just add the environment variable `SKELETON_DEBUG` and activate it.
![](Assets/debug_mode.png)
Then, when the skeleton appears, you can see the view hierarchy in the Xcode console.
<details>
<summary>Open to see an output example </summary>
<img src="Assets/hierarchy_output.png" />
</details>
### 📚 Documentation
Coming soon...😅
@@ -350,6 +404,8 @@ Coming soon...😅
* [x] CollectionView compatible
* [x] tvOS compatible
* [x] Add recovery state
* [x] Custom default appearance
* [x] Debug mode
* [ ] Custom collections compatible
* [ ] Add animations when it shows/hides the skeletons
* [ ] MacOS and WatchOS compatible
Executable
+419
View File
@@ -0,0 +1,419 @@
![](Assets/header2.jpg)
<p align="center">
<a href="https://travis-ci.org/Juanpe/SkeletonView">
<img src="https://img.shields.io/travis/Juanpe/SkeletonView.svg">
</a>
<a href="https://instagram.github.io/IGListKit/">
<img src="https://img.shields.io/cocoapods/p/SkeletonView.svg" alt="Platforms">
</a>
<img src="https://img.shields.io/badge/Swift-4.1-orange.svg" />
<a href="https://cocoapods.org/pods/SkeletonView">
<img src="https://img.shields.io/cocoapods/v/SkeletonView.svg" alt="CocoaPods" />
</a>
<a href="https://github.com/Carthage/Carthage">
<img src="https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat" alt="Carthage" />
</a>
<a href="https://cocoapods.org/pods/SkeletonView">
<img src="https://img.shields.io/cocoapods/dt/SkeletonView.svg?style=flat" alt="CocoaPods downloads" />
</a>
<a href="https://twitter.com/JuanpeCatalan">
<img src="https://img.shields.io/badge/contact-@JuanpeCatalan-blue.svg?style=flat" alt="Twitter: @JuanpeCatalan" />
</a>
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=MJ4Y2D9DEX6FL&lc=ES&item_name=SkeletonView&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted">
<img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Paypal" />
</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>
<a href="https://twitter.com/JuanpeCatalan">
<img src="https://img.shields.io/twitter/follow/JuanpeCatalan.svg?style=social&label=Follow" alt="Twitter" />
</a>
</p>
🌎 翻译: [ [原版的](https://github.com/Juanpe/SkeletonView) ]
今天,几乎所有的应用程序都有异步流程,例如:Api请求、长时间运行的流程等。虽然流程正在运行,但通常开发人员会设置一个加载视图来向用户显示正在发生的事情。
```SkeletonView``` 已经构想出来满足这种需求,这是一种优雅的方式,向用户展示正在发生的事情,并为他们等待的内容做好准备。
好好享受! 🙂
* [特征](#-特征)
* [版本要求](#-版本要求)
* [示例项目](#-示例)
* [安装](#-安装)
* [Cocoapods](#使用-cocoapods)
* [Carthage](#使用-carthage)
* [如何使用](#-如何使用)
* [集合](#-集合)
* [多行文字](#-多行文字)
* [自定义颜色](#-自定义颜色)
* [自定义动画](#-自定义动画)
* [等级制度](#-等级制度)
* [文档](#-文档)
* [下一步](#-下一步)
* [特约](#-特约)
* [提及](#-提及)
* [作者](#-作者)
* [许可证](#-许可证)
## 🌟 特征
- [x] 使用方便
- [x] 支持所有 UIView
- [x] 完全可定制
- [x] 通用(iPhone和iPad
- [x] Interface Builder 友好
- [x] 简单的 Swift 语法
- [x] 轻量级可读代码库
### 📋 版本要求
* iOS 9.0+
* tvOS 9.0+
* Swift 4
### 🔮 示例
要运行示例项目,请克隆并运行 `SkeletonViewExample` 项目。
## 📲 安装
#### 使用 [CocoaPods](https://cocoapods.org)
使用 CocoaPods 编辑您的 Podfile 并指定依赖项:
```ruby
pod "SkeletonView"
```
#### 使用 [Carthage](https://github.com/carthage)
编辑您的 Cartfile 并指定依赖项:
```bash
github "Juanpe/SkeletonView"
```
## 🐒 如何使用
只需 **3** 个步骤即可使用 `SkeletonView`:
**1.** 在适当的位置导入SkeletonView
```swift
import SkeletonView
```
**2.** 现在,您可以通过两种设置方式实现 `SkeletonView` 效果
**使用纯代码:**
```swift
avatarImageView.isSkeletonable = true
```
**使用 IB/Storyboards**
![](Assets/storyboard.png)
**3.** 设置视图后,可以显示 **skeleton**. 并且您有 **4** 种效果可供选择:
```swift
(1) view.showSkeleton() // 固体
(2) view.showGradientSkeleton() // 渐变
(3) view.showAnimatedSkeleton() // 纯色动画
(4) view.showAnimatedGradientSkeleton() // 渐变动画
```
**Preview**
<table>
<tr>
<td width="25%">
<center>固体</center>
</td>
<td width="25%">
<center>渐变</center>
</td>
<td width="25%">
<center>纯色动画</center>
</td>
<td width="25%">
<center>渐变动画</center>
</td>
</tr>
<tr>
<td width="25%">
<img src="Assets/solid.png"></img>
</td>
<td width="25%">
<img src="Assets/gradient.png"></img>
</td>
<td width="25%">
<img src="Assets/solid_animated.gif"></img>
</td>
<td width="25%">
<img src="Assets/gradient_animated.gif"></img>
</td>
</tr>
</table>
> **重要!**
>>```SkeletonView``` 是递归的,所以如果你想在所有可骨架化的视图中显示骨架,你只需要在主容器视图中调用show方法。例如,使用UIViewControllers
### 🌿 集合
现在,```SkeletonView``` 兼容 ```UITableView``` 和 ```UICollectionView```。
###### UITableView
如果你要显示 skeleton 在一个 ```UITableView```上,你需要符合 ```SkeletonTableViewDataSource``` 协议。
``` swift
public protocol SkeletonTableViewDataSource: UITableViewDataSource {
func numSections(in collectionSkeletonView: UITableView) -> Int
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
}
```
如您所见,此协议继承自 UITableViewDataSource,因此您可以使用骨架协议替换此协议。
该协议具有默认实现:
``` swift
func numSections(in collectionSkeletonView: UITableView) -> Int
// 默认值:1
```
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
// 默认值:
// 它计算填充整个tableview需要多少个单元格
```
为了让Skeleton知道单元标识符,您只需要实现一种方法。此方法没有默认实现:
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
```
**示例**
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
return "CellIdentifier"
}
```
> **重要!**
> 如果您使用可调整大小的单元格 (`tableView.rowHeight = UITableViewAutomaticDimension` ),则必须定义 `estimatedRowHeight`。
###### UICollectionView
要为 ```UICollectionView``` 设置效果, 您需要符合 ```SkeletonCollectionViewDataSource``` 协议。
``` swift
public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
func numSections(in collectionSkeletonView: UICollectionView) -> Int
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
}
```
其余操作与 ```UITableView``` 相同。
### 📰 多行文字
![](Assets/multilines2.png)
使用带有文本的元素时, ```SkeletonView``` 绘制线条以模拟文本。此外,您可以决定您想要多少行。如果 ```numberOfLines``` 设置为零,它将计算填充整个骨架所需的行数,并将绘制它。相反,如果将其设置为一,二或任何大于零的数字,它将只绘制此行数。
##### 🎛 定制
您可以为多行元素设置一些属性。
| 属性 | 值范围 | 默认 | 延时
| ------- | ------- |------- | -------
| **Filling percent** 最后一行的长度百分比 | `0...100` | `70%` | ![](Assets/multiline_lastline.png)
| **Corner radius** 条目圆角半径. (**新**) | `0...10` | `0` | ![](Assets/multiline_corner.png)
**纯代码**修改百分比或半径:
```swift
descriptionTextView.lastLineFillPercent = 50
descriptionTextView.linesCornerRadius = 5
```
或者,如果您更喜欢使用 **IB/Storyboard**:
![](Assets/multiline_customize.png)
### 🎨 自定义颜色
您可以决定 ```SkeletonView``` 的显示颜色。您只需要传递颜色或渐变的参数。
**使用纯色**
``` swift
view.showSkeleton(usingColor: UIColor.gray) // 固体效果
// 或者
view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0))
```
**使用渐变色**
``` swift
let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue)
view.showGradientSkeleton(usingGradient: gradient) // 梯度效果
```
此外, ```SkeletonView``` 附带的 20 种颜色 🤙🏼
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
![](Assets/flatcolors.png)
###### 从网站 [https://flatuicolors.com](https://flatuicolors.com)捕获的图像
### 🤓 自定义动画
现在,```SkeletonView``` 有两个内置动画,*pulse* 脉冲效果和 *sliding* 渐变滑动效果。
此外,如果你想做自己的 skeleton 动画,那真的很容易。
Skeleton 提供了 `showAnimatedSkeleton` 一个具有 ```SkeletonLayerAnimation``` 闭包的功能,您可以在其中定义自定义动画。
```swift
public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation
```
您可以像这样调用函数:
```swift
view.showAnimatedSkeleton { (layer) -> CAAnimation in
let animation = CAAnimation()
// 在这里自定义你的动画
return animation
}
```
**新** 它可用 ```SkeletonAnimationBuilder```。这是一个 ```SkeletonLayerAnimation```的衍生。
今天,您可以为渐变创建 **滑动动画**,确定 **方向** 并设置动画的 **持续时间** (默认值 = 1.5s)。
```swift
// func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation
let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight)
view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
```
```GradientDirection``` 是一个枚举,在这种情况下:
| 方向 | 效果
|------- | -------
| .leftRight | ![](Assets/sliding_left_to_right.gif)
| .rightLeft | ![](Assets/sliding_right_to_left.gif)
| .topBottom | ![](Assets/sliding_top_to_bottom.gif)
| .bottomTop | ![](Assets/sliding_bottom_to_top.gif)
| .topLeftBottomRight | ![](Assets/sliding_topLeft_to_bottomRight.gif)
| .bottomRightTopLeft | ![](Assets/sliding_bottomRight_to_topLeft.gif)
> **😉 技巧!**
存在另一种创建滑动动画的方法,只需使用此快捷方式:
>>```let animation = GradientDirection.leftToRight.slidingAnimation()```
### 👨‍👧‍👦 等级制度
由于 ```SkeletonView``` 是递归的,我们希望 skeleton 效率高效, 我们希望尽快停止递归。因此,您必须将容器视图设置为 `Skeletonable` ,因为`skeletonable` 一旦视图不是 Skeletonable, Skeleton 将停止查找子视图,然后断开递归。
一图胜千言:
> 设置 ```ìsSkeletonable```= ☠️
| 分组 | 结果
|------- | -------
|![](Assets/no_skeletonable.png) | ![](Assets/no_skeletonables_result.png)
|![](Assets/container_no_skeletonable.png) | ![](Assets/no_skeletonables_result.png)
|![](Assets/container_skeletonable.png) | ![](Assets/container_skeletonable_result.png)
|![](Assets/all_skeletonables.png) | ![](Assets/all_skeletonables_result.png)
### 📚 文档
快出来...😅
## 📬 下一步
* [x] 设置多行元素中最后一行的填充百分比
* [x] 添加更多渐变动画
* [x] 支持可调整大小的单元
* [x] CollectionView 兼容
* [x] tvOS 兼容
* [x] 添加恢复状态
* [ ] 自定义集合兼容
* [ ] 在显示/隐藏骨架时添加动画
* [ ] MacOS 和 WatchOS兼容
## ❤️ 特约
这是一个开源项目,所以请随时贡献。怎么样?
- 打开一个 [issue](https://github.com/Juanpe/SkeletonView/issues/new)
- 反馈通过发送 [email](mailto://juanpecatalan.com)
- 提出您自己的修复和建议,并带有拉取的请求。
查看 [所有贡献者](https://github.com/Juanpe/SkeletonView/graphs/contributors)
###### 使用 [SwiftPlate](https://github.com/JohnSundell/SwiftPlate) 生成的项目
## 📢 提及
- [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start)
- [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful)
- [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65)
- [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9)
- [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899)
- [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d)
- [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/)
- [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204)
- [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759)
- [CocoaControls](https://www.cocoacontrols.com/controls/skeletonview)
- [Awesome iOS Newsletter #74](https://ios.libhunt.com/newsletter/74)
## 👨🏻‍💻 作者
[1.1]: http://i.imgur.com/tXSoThF.png
[1]: http://www.twitter.com/JuanpeCatalan
* Juanpe Catalán [![alt text][1.1]][1]
<a class="bmc-button" target="_blank" href="https://www.buymeacoffee.com/CDou4xtIK"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy me a coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;"><span style="margin-left:5px"></span></a>
## 👮🏻 许可证
```
MIT License
Copyright (c) 2017 Juanpe Catalán
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "SkeletonView"
s.version = "1.2.3"
s.version = "1.4"
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.
+210 -9
View File
@@ -20,7 +20,7 @@
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 /* SkeletonDefaultConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2F1FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift */; };
17DD0E15207FB28F00C56334 /* SkeletonAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2F1FB0EC9D00EE67C5 /* SkeletonAppearance.swift */; };
17DD0E16207FB28F00C56334 /* SkeletonGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2B1FAF6BC900EE67C5 /* SkeletonGradient.swift */; };
17DD0E17207FB28F00C56334 /* SkeletonLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */; };
17DD0E18207FB28F00C56334 /* SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* SkeletonView.swift */; };
@@ -31,8 +31,20 @@
17DD0E1F207FB32100C56334 /* RecursiveProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */; };
3B83EE4720C41488005178A4 /* ContainsMultilineText.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3B83EE4520C41488005178A4 /* ContainsMultilineText.swift */; settings = {ATTRIBUTES = (Public, ); }; };
3B83EE4820C41488005178A4 /* UIView+IBInspectable.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3B83EE4620C41488005178A4 /* UIView+IBInspectable.swift */; settings = {ATTRIBUTES = (Public, ); }; };
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 */; };
42ABD079210B54E200BEEFF4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 42ABD071210B54E100BEEFF4 /* Main.storyboard */; };
42ABD07A210B54E200BEEFF4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 42ABD072210B54E100BEEFF4 /* Assets.xcassets */; };
42ABD07B210B54E200BEEFF4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD073210B54E100BEEFF4 /* ViewController.swift */; };
42ABD07C210B54E200BEEFF4 /* Base.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 42ABD074210B54E100BEEFF4 /* Base.lproj */; };
42ABD07E210B54E200BEEFF4 /* SkeletonViewExampleCollectionview-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 42ABD076210B54E200BEEFF4 /* SkeletonViewExampleCollectionview-Info.plist */; };
42ABD07F210B54E200BEEFF4 /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD077210B54E200BEEFF4 /* CollectionViewCell.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, ); }; };
8785E3A1211C9C9800CC9DFD /* SkeletonDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */; };
8785E3A2211C9CA500CC9DFD /* SkeletonDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */; };
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 */; };
F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */; };
@@ -44,7 +56,7 @@
F51ED28520973CC9008B2434 /* SkeletonReusableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DF870206E91B300D23301 /* SkeletonReusableCell.swift */; };
F5307E2C1FAF6BC900EE67C5 /* SkeletonGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2B1FAF6BC900EE67C5 /* SkeletonGradient.swift */; };
F5307E2E1FB0E5E400EE67C5 /* UIView+Frame.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */; };
F5307E301FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2F1FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift */; };
F5307E301FB0EC9D00EE67C5 /* SkeletonAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2F1FB0EC9D00EE67C5 /* SkeletonAppearance.swift */; };
F5307E321FB0F42F00EE67C5 /* RecursiveProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */; };
F5307E371FB1076E00EE67C5 /* SkeletonTableViewProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E361FB1076E00EE67C5 /* SkeletonTableViewProtocols.swift */; };
F5307E391FB1078E00EE67C5 /* SkeletonCollectionViewProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E381FB1078E00EE67C5 /* SkeletonCollectionViewProtocols.swift */; };
@@ -61,6 +73,8 @@
F58A6E6C20A8C54100612494 /* RecoverableViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F58A6E6A20A8C54100612494 /* RecoverableViewState.swift */; };
F58A6E6E20A8C66300612494 /* Recoverable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F58A6E6D20A8C66300612494 /* Recoverable.swift */; };
F58A6E6F20A8C66300612494 /* Recoverable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F58A6E6D20A8C66300612494 /* Recoverable.swift */; };
F5A5E8B4211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5A5E8B3211DCE7000FD20D0 /* Int+Whitespaces.swift */; };
F5A5E8B5211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5A5E8B3211DCE7000FD20D0 /* Int+Whitespaces.swift */; };
F5D3FB0B209DCAA300003FCF /* SubviewsSkeletonables.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D3FB0A209DCAA300003FCF /* SubviewsSkeletonables.swift */; };
F5D3FB0C209DCAA300003FCF /* SubviewsSkeletonables.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D3FB0A209DCAA300003FCF /* SubviewsSkeletonables.swift */; };
F5F622411FAC6E31007C062A /* UIColor+Skeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */; };
@@ -79,6 +93,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
42ABD05C210B548200BEEFF4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 52D6D9731BEFF229002C0205 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 52D6D97B1BEFF229002C0205;
remoteInfo = "SkeletonView-iOS";
};
F5307E431FB3B84500EE67C5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 52D6D9731BEFF229002C0205 /* Project object */;
@@ -89,6 +110,17 @@
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
42ABD068210B548200BEEFF4 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
42ABD069210B548200BEEFF4 /* SkeletonView.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
F5307E451FB3B84600EE67C5 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -108,7 +140,16 @@
17DD0E1B207FB2C200C56334 /* SkeletonView-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SkeletonView-iOS.plist"; sourceTree = "<group>"; };
3B83EE4520C41488005178A4 /* ContainsMultilineText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContainsMultilineText.swift; path = Sources/Helpers/ContainsMultilineText.swift; 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>"; };
42ABD072210B54E100BEEFF4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
42ABD073210B54E100BEEFF4 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
42ABD074210B54E100BEEFF4 /* Base.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base.lproj; sourceTree = "<group>"; };
42ABD076210B54E200BEEFF4 /* SkeletonViewExampleCollectionview-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SkeletonViewExampleCollectionview-Info.plist"; sourceTree = "<group>"; };
42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = "<group>"; };
52D6D97C1BEFF229002C0205 /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonAnimationBuilder.swift; sourceTree = "<group>"; };
@@ -117,7 +158,7 @@
F51DF878206E9F5500D23301 /* SkeletonCollectionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCollectionDelegate.swift; sourceTree = "<group>"; };
F5307E2B1FAF6BC900EE67C5 /* SkeletonGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonGradient.swift; sourceTree = "<group>"; };
F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Frame.swift"; sourceTree = "<group>"; };
F5307E2F1FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonDefaultConfig.swift; sourceTree = "<group>"; };
F5307E2F1FB0EC9D00EE67C5 /* SkeletonAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonAppearance.swift; sourceTree = "<group>"; };
F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecursiveProtocol.swift; sourceTree = "<group>"; };
F5307E361FB1076E00EE67C5 /* SkeletonTableViewProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTableViewProtocols.swift; sourceTree = "<group>"; };
F5307E381FB1078E00EE67C5 /* SkeletonCollectionViewProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCollectionViewProtocols.swift; sourceTree = "<group>"; };
@@ -130,6 +171,7 @@
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>"; };
F58A6E6D20A8C66300612494 /* Recoverable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recoverable.swift; sourceTree = "<group>"; };
F5A5E8B3211DCE7000FD20D0 /* Int+Whitespaces.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Whitespaces.swift"; sourceTree = "<group>"; };
F5D3FB0A209DCAA300003FCF /* SubviewsSkeletonables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubviewsSkeletonables.swift; sourceTree = "<group>"; };
F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Skeleton.swift"; sourceTree = "<group>"; };
F5F622421FAC81FD007C062A /* CALayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+Extensions.swift"; sourceTree = "<group>"; };
@@ -156,6 +198,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
42ABD062210B548200BEEFF4 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
42ABD063210B548200BEEFF4 /* SkeletonView.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
52D6D9781BEFF229002C0205 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -174,11 +224,25 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
42ABD06F210B54BC00BEEFF4 /* Example UICollectionView */ = {
isa = PBXGroup;
children = (
42ABD070210B54E100BEEFF4 /* AppDelegate.swift */,
42ABD072210B54E100BEEFF4 /* Assets.xcassets */,
42ABD074210B54E100BEEFF4 /* Base.lproj */,
42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */,
42ABD071210B54E100BEEFF4 /* Main.storyboard */,
42ABD073210B54E100BEEFF4 /* ViewController.swift */,
);
path = "Example UICollectionView";
sourceTree = "<group>";
};
52D6D9721BEFF229002C0205 = {
isa = PBXGroup;
children = (
3B83EE4520C41488005178A4 /* ContainsMultilineText.swift */,
3B83EE4620C41488005178A4 /* UIView+IBInspectable.swift */,
42ABD06F210B54BC00BEEFF4 /* Example UICollectionView */,
F5F899F31FABA607002E8FDA /* Example */,
8933C7811EB5B7E0000D00A4 /* Sources */,
52D6D99C1BEFF38C002C0205 /* Configs */,
@@ -193,6 +257,7 @@
52D6D97C1BEFF229002C0205 /* SkeletonView.framework */,
F5F899F21FABA607002E8FDA /* SkeletonViewExample.app */,
17DD0E00207FB27400C56334 /* SkeletonView.framework */,
42ABD06D210B548200BEEFF4 /* SkeletonViewExampleUICollectionView.app */,
);
name = Products;
sourceTree = "<group>";
@@ -206,15 +271,32 @@
path = Configs;
sourceTree = "<group>";
};
8785E39E211C9C6D00CC9DFD /* Appearance */ = {
isa = PBXGroup;
children = (
F5307E2F1FB0EC9D00EE67C5 /* SkeletonAppearance.swift */,
);
path = Appearance;
sourceTree = "<group>";
};
8785E39F211C9C7C00CC9DFD /* Debug */ = {
isa = PBXGroup;
children = (
8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */,
);
path = Debug;
sourceTree = "<group>";
};
8933C7811EB5B7E0000D00A4 /* Sources */ = {
isa = PBXGroup;
children = (
8785E39F211C9C7C00CC9DFD /* Debug */,
8785E39E211C9C6D00CC9DFD /* Appearance */,
F58A6E7020A8C87100612494 /* Recoverable */,
F5307E331FB1068500EE67C5 /* Collections */,
F5307E341FB106A500EE67C5 /* Extensions */,
F5307E351FB106BF00EE67C5 /* Helpers */,
F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */,
F5307E2F1FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift */,
F587FB83202CBFC8002DB5FE /* SkeletonFlow.swift */,
F5307E2B1FAF6BC900EE67C5 /* SkeletonGradient.swift */,
F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */,
@@ -282,6 +364,7 @@
F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */,
F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */,
F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */,
F5A5E8B3211DCE7000FD20D0 /* Int+Whitespaces.swift */,
);
path = Extensions;
sourceTree = "<group>";
@@ -330,6 +413,7 @@
F5F89A061FABA725002E8FDA /* Example */ = {
isa = PBXGroup;
children = (
42ABD076210B54E200BEEFF4 /* SkeletonViewExampleCollectionview-Info.plist */,
F5F89A001FABA607002E8FDA /* SkeletonViewExampleInfo.plist */,
);
name = Example;
@@ -377,6 +461,25 @@
productReference = 17DD0E00207FB27400C56334 /* SkeletonView.framework */;
productType = "com.apple.product-type.framework";
};
42ABD05A210B548200BEEFF4 /* SkeletonViewExampleUICollectionView */ = {
isa = PBXNativeTarget;
buildConfigurationList = 42ABD06A210B548200BEEFF4 /* Build configuration list for PBXNativeTarget "SkeletonViewExampleUICollectionView" */;
buildPhases = (
42ABD05D210B548200BEEFF4 /* Sources */,
42ABD062210B548200BEEFF4 /* Frameworks */,
42ABD064210B548200BEEFF4 /* Resources */,
42ABD068210B548200BEEFF4 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
42ABD05B210B548200BEEFF4 /* PBXTargetDependency */,
);
name = SkeletonViewExampleUICollectionView;
productName = SkeletonViewExample;
productReference = 42ABD06D210B548200BEEFF4 /* SkeletonViewExampleUICollectionView.app */;
productType = "com.apple.product-type.application";
};
52D6D97B1BEFF229002C0205 /* SkeletonView-iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "SkeletonView-iOS" */;
@@ -428,9 +531,13 @@
CreatedOnToolsVersion = 9.3;
ProvisioningStyle = Automatic;
};
42ABD05A210B548200BEEFF4 = {
DevelopmentTeam = KWEMDK92F4;
LastSwiftMigration = 0940;
ProvisioningStyle = Automatic;
};
52D6D97B1BEFF229002C0205 = {
CreatedOnToolsVersion = 7.1;
DevelopmentTeam = KWEMDK92F4;
LastSwiftMigration = 0800;
};
F5F899F11FABA607002E8FDA = {
@@ -456,6 +563,7 @@
52D6D97B1BEFF229002C0205 /* SkeletonView-iOS */,
17DD0DFF207FB27400C56334 /* SkeletonView-tvOS */,
F5F899F11FABA607002E8FDA /* SkeletonViewExample */,
42ABD05A210B548200BEEFF4 /* SkeletonViewExampleUICollectionView */,
);
};
/* End PBXProject section */
@@ -468,6 +576,17 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
42ABD064210B548200BEEFF4 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
42ABD07E210B54E200BEEFF4 /* SkeletonViewExampleCollectionview-Info.plist in Resources */,
42ABD07A210B54E200BEEFF4 /* Assets.xcassets in Resources */,
42ABD07C210B54E200BEEFF4 /* Base.lproj in Resources */,
42ABD079210B54E200BEEFF4 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
52D6D97A1BEFF229002C0205 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -496,6 +615,7 @@
17DD0E1E207FB32100C56334 /* PrepareForSkeletonProtocol.swift in Sources */,
F51ED28520973CC9008B2434 /* SkeletonReusableCell.swift in Sources */,
17DD0E17207FB28F00C56334 /* SkeletonLayer.swift in Sources */,
8785E3A2211C9CA500CC9DFD /* SkeletonDebug.swift in Sources */,
17DD0E08207FB28900C56334 /* CollectionSkeletonProtocol.swift in Sources */,
F58A6E6C20A8C54100612494 /* RecoverableViewState.swift in Sources */,
17DD0E0B207FB28900C56334 /* SkeletonTableViewProtocols.swift in Sources */,
@@ -512,17 +632,29 @@
17DD0E12207FB28C00C56334 /* UIView+IBInspectable.swift in Sources */,
17DD0E14207FB28F00C56334 /* SkeletonAnimationBuilder.swift in Sources */,
17DD0E11207FB28C00C56334 /* UIView+Frame.swift in Sources */,
17DD0E15207FB28F00C56334 /* SkeletonDefaultConfig.swift in Sources */,
17DD0E15207FB28F00C56334 /* SkeletonAppearance.swift in Sources */,
17DD0E10207FB28C00C56334 /* UIColor+Skeleton.swift in Sources */,
F51ED28420973CC6008B2434 /* GenericCollectionView.swift in Sources */,
17DD0E0A207FB28900C56334 /* SkeletonCollectionViewProtocols.swift in Sources */,
17DD0E1C207FB32100C56334 /* AssociationPolicy.swift in Sources */,
F5A5E8B5211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */,
17DD0E1D207FB32100C56334 /* ContainsMultilineText.swift in Sources */,
17DD0E0F207FB28C00C56334 /* CALayer+Extensions.swift in Sources */,
17DD0E16207FB28F00C56334 /* SkeletonGradient.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
42ABD05D210B548200BEEFF4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
42ABD07B210B54E200BEEFF4 /* ViewController.swift in Sources */,
42ABD078210B54E200BEEFF4 /* AppDelegate.swift in Sources */,
42ABD07F210B54E200BEEFF4 /* CollectionViewCell.swift in Sources */,
8785E3A3211C9CE800CC9DFD /* Constants.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
52D6D9771BEFF229002C0205 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -531,7 +663,8 @@
F54CF5E42024CEB000330B0D /* UIView+CollectionSkeleton.swift in Sources */,
F5307E371FB1076E00EE67C5 /* SkeletonTableViewProtocols.swift in Sources */,
8933C7851EB5B820000D00A4 /* SkeletonView.swift in Sources */,
F5307E301FB0EC9D00EE67C5 /* SkeletonDefaultConfig.swift in Sources */,
8785E3A1211C9C9800CC9DFD /* SkeletonDebug.swift in Sources */,
F5307E301FB0EC9D00EE67C5 /* SkeletonAppearance.swift in Sources */,
F58A6E6B20A8C54100612494 /* RecoverableViewState.swift in Sources */,
F54CF5E52024CEB000330B0D /* UITableView+CollectionSkeleton.swift in Sources */,
F5307E321FB0F42F00EE67C5 /* RecursiveProtocol.swift in Sources */,
@@ -552,6 +685,7 @@
F5307E3B1FB123C100EE67C5 /* ContainsMultilineText.swift in Sources */,
F5F899E91FAB9D2B002E8FDA /* SkeletonLayer.swift in Sources */,
F5307E2E1FB0E5E400EE67C5 /* UIView+Frame.swift in Sources */,
F5A5E8B4211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */,
F51DF871206E91B300D23301 /* SkeletonReusableCell.swift in Sources */,
F51DF879206E9F5500D23301 /* SkeletonCollectionDelegate.swift in Sources */,
F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */,
@@ -572,6 +706,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
42ABD05B210B548200BEEFF4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 52D6D97B1BEFF229002C0205 /* SkeletonView-iOS */;
targetProxy = 42ABD05C210B548200BEEFF4 /* PBXContainerItemProxy */;
};
F5307E441FB3B84500EE67C5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 52D6D97B1BEFF229002C0205 /* SkeletonView-iOS */;
@@ -663,6 +802,59 @@
};
name = Release;
};
42ABD06B210B548200BEEFF4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = KWEMDK92F4;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "SkeletonViewExample copy-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.juanpecatalan.SkeletonViewExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
42ABD06C210B548200BEEFF4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = KWEMDK92F4;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "SkeletonViewExample copy-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.juanpecatalan.SkeletonViewExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
52D6D98E1BEFF229002C0205 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -782,7 +974,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = KWEMDK92F4;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -807,7 +999,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = KWEMDK92F4;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -885,6 +1077,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
42ABD06A210B548200BEEFF4 /* Build configuration list for PBXNativeTarget "SkeletonViewExampleUICollectionView" */ = {
isa = XCConfigurationList;
buildConfigurations = (
42ABD06B210B548200BEEFF4 /* Debug */,
42ABD06C210B548200BEEFF4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
52D6D9761BEFF229002C0205 /* Build configuration list for PBXProject "SkeletonView" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>FILEHEADER</key>
<string> ___COPYRIGHT___</string>
</dict>
</plist>
@@ -0,0 +1,33 @@
// Copyright © 2017 SkeletonView. All rights reserved.
import UIKit
public protocol Appearance {
var tintColor: UIColor { get set }
var gradient: SkeletonGradient { get set }
var multilineHeight: CGFloat { get set }
var multilineSpacing: CGFloat { get set }
var multilineLastLineFillPercent: Int { get set }
var multilineCornerRadius: Int { get set }
}
public enum SkeletonAppearance {
public static var `default`: Appearance = SkeletonViewAppearance.shared
}
class SkeletonViewAppearance: Appearance {
static var shared = SkeletonViewAppearance()
var tintColor: UIColor = .clouds
var gradient: SkeletonGradient = SkeletonGradient(baseColor: .clouds)
var multilineHeight: CGFloat = 15
var multilineSpacing: CGFloat = 10
var multilineLastLineFillPercent: Int = 70
var multilineCornerRadius: Int = 0
}
@@ -28,6 +28,6 @@ extension CollectionSkeleton where Self: UIScrollView {
var estimatedNumberOfRows: Int { return 0 }
func addDummyDataSource() {}
func removeDummyDataSource(reloadAfter: Bool) {}
func disableUserInteraction() { isUserInteractionEnabled = false }
func enableUserInteraction() { isUserInteractionEnabled = true }
func disableUserInteraction() { isScrollEnabled = false }
func enableUserInteraction() { isScrollEnabled = true }
}
@@ -52,3 +52,20 @@ extension UICollectionView: CollectionSkeleton {
extension UICollectionView: GenericCollectionView {
var scrollView: UIScrollView { return self }
}
public extension UICollectionView {
func prepareSkeleton(completion: @escaping (Bool) -> Void) {
guard let originalDataSource = self.dataSource as? SkeletonCollectionViewDataSource,
!(originalDataSource is SkeletonCollectionDataSource)
else { return }
let dataSource = SkeletonCollectionDataSource(collectionViewDataSource: originalDataSource, rowHeight: 0.0)
self.skeletonDataSource = dataSource
performBatchUpdates({
self.reloadData()
}) { (done) in
completion(done)
}
}
}
@@ -58,6 +58,3 @@ extension UITableView: CollectionSkeleton {
return estimatedRowHeight
}
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright © 2018 SkeletonView. All rights reserved.
import Foundation
import UIKit
enum SkeletonEnvironmentKey: String {
case debugMode = "SKELETON_DEBUG"
}
extension Dictionary {
subscript (_ key: SkeletonEnvironmentKey) -> Value? {
return self[key.rawValue as! Key]
}
}
func printSkeletonHierarchy(in view: UIView) {
skeletonLog(view.skeletonHierarchy())
}
func skeletonLog(_ message: String) {
if let _ = ProcessInfo.processInfo.environment[.debugMode] {
print(message)
}
}
extension UIView {
public var skeletonDescription: String {
var description = "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())"
let subSkeletons = subviewsSkeletonables
if subSkeletons.count != 0 {
description += " | (\(subSkeletons.count)) subSkeletons"
}
if isSkeletonable {
description += " | ☠️ "
}
return description + ">"
}
public func skeletonHierarchy(index: Int = 0) -> String {
var description = index == 0 ? "\n ⬇⬇ ☠️ Root view hierarchy with Skeletons ⬇⬇ \n" : ""
description += "\(index == 0 ? "\n" : 3.whitespaces) \(skeletonDescription) \n"
subviewsToSkeleton.forEach {
description += (index + 2).whitespaces
description += $0.skeletonHierarchy(index: index + 1)
}
return description
}
}
+17 -13
View File
@@ -10,18 +10,20 @@ import UIKit
extension CALayer {
@objc func tint(withColors colors: [UIColor]) {
recursiveSearch(inArray: skeletonSublayers,
leafBlock: { backgroundColor = colors.first?.cgColor }) {
$0.tint(withColors: colors)
skeletonSublayers.recursiveSearch(leafBlock: {
backgroundColor = colors.first?.cgColor
}) {
$0.tint(withColors: colors)
}
}
}
extension CAGradientLayer {
override func tint(withColors colors: [UIColor]) {
recursiveSearch(inArray: skeletonSublayers,
leafBlock: { self.colors = colors.map { $0.cgColor } }) {
$0.tint(withColors: colors)
skeletonSublayers.recursiveSearch(leafBlock: {
self.colors = colors.map { $0.cgColor }
}) {
$0.tint(withColors: colors)
}
}
}
@@ -51,7 +53,7 @@ extension CALayer {
}
private func calculateNumLines(maxLines: Int) -> Int {
let spaceRequitedForEachLine = SkeletonDefaultConfig.multilineHeight + SkeletonDefaultConfig.multilineSpacing
let spaceRequitedForEachLine = SkeletonAppearance.default.multilineHeight + SkeletonAppearance.default.multilineSpacing
var numberOfSublayers = Int(round(CGFloat(bounds.height)/CGFloat(spaceRequitedForEachLine)))
if maxLines != 0, maxLines <= numberOfSublayers { numberOfSublayers = maxLines }
return numberOfSublayers
@@ -91,16 +93,18 @@ public extension CALayer {
}
func playAnimation(_ anim: SkeletonLayerAnimation, key: String) {
recursiveSearch(inArray: skeletonSublayers,
leafBlock: { add(anim(self), forKey: key) }) {
$0.playAnimation(anim, key: key)
skeletonSublayers.recursiveSearch(leafBlock: {
add(anim(self), forKey: key)
}) {
$0.playAnimation(anim, key: key)
}
}
func stopAnimation(forKey key: String) {
recursiveSearch(inArray: skeletonSublayers,
leafBlock: { removeAnimation(forKey: key) }) {
$0.stopAnimation(forKey: key)
skeletonSublayers.recursiveSearch(leafBlock: {
removeAnimation(forKey: key)
}) {
$0.stopAnimation(forKey: key)
}
}
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright © 2018 SkeletonView. All rights reserved.
import Foundation
extension Int {
var whitespace: String {
return whitespaces
}
var whitespaces: String {
return String(repeating: " ", count: self)
}
}
+4 -4
View File
@@ -59,11 +59,11 @@ extension UILabel: ContainsMultilineText {
}
var lastLineFillingPercent: Int {
get { return objc_getAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonDefaultConfig.multilineLastLineFillPercent }
get { return objc_getAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonAppearance.default.multilineLastLineFillPercent }
set { objc_setAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent, newValue, AssociationPolicy.retain.objc) }
}
var multilineCornerRadius: Int {
get { return objc_getAssociatedObject(self, &AssociatedKeys.multilineCornerRadius) as? Int ?? SkeletonDefaultConfig.multilineCornerRadius }
get { return objc_getAssociatedObject(self, &AssociatedKeys.multilineCornerRadius) as? Int ?? SkeletonAppearance.default.multilineCornerRadius }
set { objc_setAssociatedObject(self, &AssociatedKeys.multilineCornerRadius, newValue, AssociationPolicy.retain.objc) }
}
@@ -71,12 +71,12 @@ extension UILabel: ContainsMultilineText {
extension UITextView: ContainsMultilineText {
var lastLineFillingPercent: Int {
get { return objc_getAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonDefaultConfig.multilineLastLineFillPercent }
get { return objc_getAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonAppearance.default.multilineLastLineFillPercent }
set { objc_setAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent, newValue, AssociationPolicy.retain.objc) }
}
var multilineCornerRadius: Int {
get { return objc_getAssociatedObject(self, &AssociatedKeys.multilineCornerRadius) as? Int ?? SkeletonDefaultConfig.multilineCornerRadius }
get { return objc_getAssociatedObject(self, &AssociatedKeys.multilineCornerRadius) as? Int ?? SkeletonAppearance.default.multilineCornerRadius }
set { objc_setAssociatedObject(self, &AssociatedKeys.multilineCornerRadius, newValue, AssociationPolicy.retain.objc) }
}
}
+10 -19
View File
@@ -1,37 +1,28 @@
//
// HelperProtocols.swift
// SkeletonView-iOS
//
// Created by Juanpe Catalán on 06/11/2017.
// Copyright © 2017 SkeletonView. All rights reserved.
//
import UIKit
typealias VoidBlock = () -> Void
typealias RecursiveBlock<T> = (T) -> Void
protocol IterableElement {}
extension UIView: IterableElement {}
extension CALayer: IterableElement {}
//MARK: Recursive
protocol Recursive {
associatedtype Element
func recursiveSearch(inArray array:[Element], leafBlock: VoidBlock, recursiveBlock: RecursiveBlock<Element>)
associatedtype Element: IterableElement
func recursiveSearch(leafBlock: VoidBlock, recursiveBlock: RecursiveBlock<Element>)
}
extension Recursive {
func recursiveSearch(inArray array:[Element], leafBlock: VoidBlock, recursiveBlock: RecursiveBlock<Element>) {
guard array.count > 0 else {
extension Array: Recursive where Element: IterableElement {
func recursiveSearch(leafBlock: VoidBlock, recursiveBlock: RecursiveBlock<Element>) {
guard count > 0 else {
leafBlock()
return
}
array.forEach { recursiveBlock($0) }
forEach { recursiveBlock($0) }
}
}
extension UIView: Recursive {
typealias Element = UIView
}
extension CALayer: Recursive {
typealias Element = CALayer
}
-24
View File
@@ -1,24 +0,0 @@
//
// SkeletonDefaultConfig.swift
// SkeletonView-iOS
//
// Created by Juanpe Catalán on 06/11/2017.
// Copyright © 2017 SkeletonView. All rights reserved.
//
import UIKit
public enum SkeletonDefaultConfig {
public static let tintColor = UIColor.clouds
public static let gradient = SkeletonGradient(baseColor: tintColor)
public static let multilineHeight: CGFloat = 15
public static let multilineSpacing: CGFloat = 10
public static let multilineLastLineFillPercent = 70
public static let multilineCornerRadius = 0
}
+11 -7
View File
@@ -1,26 +1,30 @@
//
// SkeletonFlow.swift
// SkeletonView-iOS
//
// Created by Juanpe Catalán on 08/02/2018.
// Copyright © 2018 SkeletonView. All rights reserved.
//
import UIKit
protocol SkeletonFlowDelegate {
func willBeginShowingSkeletons(withRootView rootView: UIView)
func didShowSkeletons(withRootView rootView: UIView)
func willBeginHidingSkeletons(withRootView rootView: UIView)
func didHideSkeletons(withRootView rootView: UIView)
}
class SkeletonFlowHandler: SkeletonFlowDelegate {
func willBeginShowingSkeletons(withRootView rootView: UIView) {
rootView.addAppNotificationsObservers()
}
func didShowSkeletons(withRootView rootView: UIView) {
printSkeletonHierarchy(in: rootView)
}
func willBeginHidingSkeletons(withRootView rootView: UIView) {
rootView.removeAppNoticationsObserver()
}
func didHideSkeletons(withRootView rootView: UIView) {
rootView.flowDelegate = nil
}
}
+2 -2
View File
@@ -15,11 +15,11 @@ class SkeletonLayerFactory {
}
func makeMultilineLayer(withType type: SkeletonType, for index: Int, width: CGFloat, multilineCornerRadius: Int) -> CALayer {
let spaceRequiredForEachLine = SkeletonDefaultConfig.multilineHeight + SkeletonDefaultConfig.multilineSpacing
let spaceRequiredForEachLine = SkeletonAppearance.default.multilineHeight + SkeletonAppearance.default.multilineSpacing
let layer = type.layer
layer.anchorPoint = .zero
layer.name = CALayer.skeletonSubLayersName
layer.frame = CGRect(x: 0.0, y: CGFloat(index) * spaceRequiredForEachLine, width: width, height: SkeletonDefaultConfig.multilineHeight)
layer.frame = CGRect(x: 0.0, y: CGFloat(index) * spaceRequiredForEachLine, width: width, height: SkeletonAppearance.default.multilineHeight)
layer.cornerRadius = CGFloat(multilineCornerRadius)
layer.masksToBounds = true
+35 -37
View File
@@ -1,49 +1,41 @@
//
// SkeletonView.swift
// SkeletonView
//
// Created by Juanpe Catalán on 01/11/2017.
// Copyright © 2017 SkeletonView. All rights reserved.
//
import UIKit
public extension UIView {
func showSkeleton(usingColor color: UIColor = SkeletonDefaultConfig.tintColor) {
func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor) {
showSkeleton(withType: .solid, usingColors: [color])
}
func showGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonDefaultConfig.gradient) {
func showGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient) {
showSkeleton(withType: .gradient, usingColors: gradient.colors)
}
func showAnimatedSkeleton(usingColor color: UIColor = SkeletonDefaultConfig.tintColor, animation: SkeletonLayerAnimation? = nil) {
func showAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil) {
showSkeleton(withType: .solid, usingColors: [color], animated: true, animation: animation)
}
func showAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonDefaultConfig.gradient, animation: SkeletonLayerAnimation? = nil) {
func showAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil) {
showSkeleton(withType: .gradient, usingColors: gradient.colors, animated: true, animation: animation)
}
func hideSkeleton(reloadDataAfter reload: Bool = true) {
flowDelegate?.willBeginHidingSkeletons(withRootView: self)
recursiveHideSkeleton(reloadDataAfter: reload)
recursiveHideSkeleton(reloadDataAfter: reload, root: self)
}
func startSkeletonAnimation(_ anim: SkeletonLayerAnimation? = nil) {
skeletonIsAnimated = true
recursiveSearch(inArray: subviewsSkeletonables,
leafBlock: startSkeletonLayerAnimationBlock(anim)) {
$0.startSkeletonAnimation(anim)
}
subviewsSkeletonables.recursiveSearch(leafBlock: startSkeletonLayerAnimationBlock(anim)) { subview in
subview.startSkeletonAnimation(anim)
}
}
func stopSkeletonAnimation() {
skeletonIsAnimated = false
recursiveSearch(inArray: subviewsSkeletonables,
leafBlock: stopSkeletonLayerAnimationBlock) {
$0.stopSkeletonAnimation()
subviewsSkeletonables.recursiveSearch(leafBlock: stopSkeletonLayerAnimationBlock) { subview in
subview.stopSkeletonAnimation()
}
}
}
@@ -54,33 +46,39 @@ extension UIView {
skeletonIsAnimated = animated
flowDelegate = SkeletonFlowHandler()
flowDelegate?.willBeginShowingSkeletons(withRootView: self)
recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation)
recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation, root: self)
}
fileprivate func recursiveShowSkeleton(withType type: SkeletonType, usingColors colors: [UIColor], animated: Bool, animation: SkeletonLayerAnimation?) {
fileprivate func recursiveShowSkeleton(withType type: SkeletonType, usingColors colors: [UIColor], animated: Bool, animation: SkeletonLayerAnimation?, root: UIView? = nil) {
addDummyDataSourceIfNeeded()
recursiveSearch(inArray: subviewsSkeletonables,
leafBlock: {
guard !isSkeletonActive else { return }
isUserInteractionEnabled = false
saveViewState()
(self as? PrepareForSkeleton)?.prepareViewForSkeleton()
addSkeletonLayer(withType: type, usingColors: colors, animated: animated, animation: animation)
}) {
$0.recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation)
subviewsSkeletonables.recursiveSearch(leafBlock: {
guard !isSkeletonActive else { return }
isUserInteractionEnabled = false
saveViewState()
(self as? PrepareForSkeleton)?.prepareViewForSkeleton()
addSkeletonLayer(withType: type, usingColors: colors, animated: animated, animation: animation)
}) { subview in
subview.recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation)
}
if let root = root {
flowDelegate?.didShowSkeletons(withRootView: root)
}
}
fileprivate func recursiveHideSkeleton(reloadDataAfter reload: Bool) {
fileprivate func recursiveHideSkeleton(reloadDataAfter reload: Bool, root: UIView? = nil) {
removeDummyDataSourceIfNeeded()
isUserInteractionEnabled = true
recursiveSearch(inArray: subviewsSkeletonables,
leafBlock: {
recoverViewState(forced: false)
removeSkeletonLayer()
}, recursiveBlock: {
$0.recursiveHideSkeleton(reloadDataAfter: reload)
})
subviewsSkeletonables.recursiveSearch(leafBlock: {
recoverViewState(forced: false)
removeSkeletonLayer()
}) { subview in
subview.recursiveHideSkeleton(reloadDataAfter: reload)
}
if let root = root {
flowDelegate?.didHideSkeletons(withRootView: root)
}
}
fileprivate func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock {
+20 -17
View File
@@ -1,45 +1,48 @@
//
// SubviewsSkeletonables.swift
// SkeletonView
//
// Created by Juanpe Catalán on 05/05/2018.
// Copyright © 2018 SkeletonView. All rights reserved.
//
import UIKit
extension UIView {
@objc var subviewsSkeletonables: [UIView] {
return subviews.filter { $0.isSkeletonable }
return subviewsToSkeleton.filter { $0.isSkeletonable }
}
@objc var subviewsToSkeleton: [UIView] {
return subviews
}
}
extension UITableView {
override var subviewsSkeletonables: [UIView] {
return visibleCells.filter { $0.isSkeletonable }
override var subviewsToSkeleton: [UIView] {
return visibleCells
}
}
extension UITableViewCell {
override var subviewsSkeletonables: [UIView] {
return contentView.subviews.filter { $0.isSkeletonable }
override var subviewsToSkeleton: [UIView] {
return contentView.subviews
}
}
extension UICollectionView {
override var subviewsSkeletonables: [UIView] {
return subviews.filter { $0.isSkeletonable }
override var subviewsToSkeleton: [UIView] {
return subviews
}
}
extension UICollectionViewCell {
override var subviewsSkeletonables: [UIView] {
return contentView.subviews.filter { $0.isSkeletonable }
override var subviewsToSkeleton: [UIView] {
return contentView.subviews
}
}
extension UIStackView {
override var subviewsSkeletonables: [UIView] {
return arrangedSubviews.filter { $0.isSkeletonable }
override var subviewsToSkeleton: [UIView] {
return arrangedSubviews
}
}