Compare commits

..

38 Commits

Author SHA1 Message Date
Guglielmo Faglioni 795fd7d0f1 Fallback to non-skeleton Header (#416)
Sometimes my collectionview's header doesn't need to be 'skeletoned' while the content does.  

When  collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? returns nil, it's best if we fallback to the non-skeleton header.
2021-07-02 16:05:16 +02:00
Juanpe Catalán 6cc6f5aa80 Update README.md 2021-06-29 11:08:52 +02:00
Juanpe Catalán 3ecc3c3b39 Update README.md 2021-06-29 11:06:05 +02:00
Juanpe Catalán 511535921f Update README.md 2021-06-29 11:05:20 +02:00
Juanpe a6d1ae0b95 Bump version 1.20.0 2021-06-28 15:47:21 +00:00
Juanpe Catalán 220fc4016d Fixed background color warning in UITableViewHeaderFooterView (#417) 2021-06-28 17:45:59 +02:00
Juanpe Catalán ee94dd8aec Update README.md 2021-06-24 16:00:38 +02:00
Juanpe Catalán be2aa4f4ab create CD workflow 2021-06-24 13:26:25 +02:00
Juanpe 55f16d9d51 Bump version 1.19.0 2021-06-24 06:21:47 +00:00
Nemanja Markicevic 9fccaf4fbd Add prepareCellForSkeleton for UITableView and UICollectionView (#415)
* Add prepareCellForSkeleton for UITableView and UICollectionView

* Update README to add collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath)

* Update README.md

Co-authored-by: Juanpe Catalán <juanpecm@gmail.com>
2021-06-24 08:09:36 +02:00
Juanpe Catalán 58959a5f9b Add both skeletonCellForRowAt and skeletonCellForItemAt methods (#414)
* add skeletonCellForIndexPath

* included in README file the new data source methods

* fix typo in the README file
2021-06-23 17:49:31 +02:00
Juanpe Catalán 5838f7881b update README 2021-06-22 13:09:46 +02:00
Juanpe 41173471f6 Bump version 1.18.0 2021-06-22 10:59:04 +00:00
Juanpe Catalán 12e5688b31 Delay the presentation of the skeletons (#411) 2021-06-22 12:54:33 +02:00
Juanpe Catalán 816b2965ff Improved the algorithm that calculates the number of skeleton lines for UITextViews (#410) 2021-06-22 11:23:08 +02:00
Juanpe e12e4a0fd1 Bump version 1.17.2 2021-06-11 20:24:41 +00:00
Richard L Zarth III 6f78f5c378 Replace SkeletonCollectionDataSource.automaticNumberOfRows with UITableView.automaticNumberOfSkeletonRows and UICollectionView.automaticNumberOfSkeletonItems (#409) 2021-06-11 22:21:27 +02:00
Juanpe Catalán c8fdd6998d fix bug remove constraints wrongly (#406)
* check if the constraints were modified and then restore the original

* identify constraints added by SkeletonView

* move removal of skeleton constraint to recoverable protocol implementation
2021-06-11 11:33:23 +02:00
Juanpe Catalán 134463e529 Update README.md 2021-06-10 19:40:41 +02:00
Juanpe ee59239c59 Bump version 1.17.1 2021-06-10 17:36:12 +00:00
Juanpe Catalán f1e61aa9c0 fix typo isUserInteractionDisabledWhenSkeletonIsActive (#404)
* fix typo

* update README
2021-06-10 19:34:18 +02:00
Juanpe 135778aa1a Bump version 1.17.0 2021-06-10 17:24:00 +00:00
Juanpe Catalán e8d5eb61d8 update README file 2021-06-10 19:19:29 +02:00
Juanpe Catalán c2a029ed51 create disableWhenSkeletonIsActive property 2021-06-10 19:14:05 +02:00
Sam Harrison a1c8276980 Make CI action build targets, not just clean (#403) 2021-06-10 19:01:01 +02:00
Sam Harrison e9ac3a5ab3 Fix TableViewCell skeleton not being removed & automaticNumberOfRows reference (#402)
* Change TableView subviewsToSkeleton to all subviews

* Fix reference to automaticNumberOfRows from pr #401
2021-06-10 18:59:21 +02:00
Richard L Zarth III fb83a62f7b Add SkeletonCollectionDataSource.automaticNumberOfRows Constant (#401)
* Add automaticNumberOfRows constant to SkeletonCollectionDataSource.

* Update tableView(_:numberOfRowsInSection:) and collectionView(_:numberOfItemsInSection:) to use the new automaticNumberOfRows constant.

* Update README.

* Update README.md

Add an **IMPORTANT!** header to the automaticNumberOfRows mention in the README.

Co-authored-by: Juanpe Catalán <juanpecm@gmail.com>

Co-authored-by: Juanpe Catalán <juanpecm@gmail.com>
2021-06-09 18:45:44 +02:00
Juanpe Catalán 12521c1d87 update Info.plist 2021-05-31 17:20:11 +02:00
Juanpe c266035888 Bump version 1.16.0 2021-05-31 15:09:22 +00:00
Juanpe Catalán 74b5172ea5 use the right delegate method for footers (#394) 2021-05-11 19:15:41 +02:00
Michael Henry 318e629d04 update the UILabel placeholder to use an empty space #388 (#389) 2021-05-11 19:02:41 +02:00
StasMalinovsky 62193db76f Added estimated number of rows for UICollectionViewFlowLayout with vertical scroll direction. (#385) 2021-04-30 13:26:25 +02:00
Juanpe 19e7866d3d Bump version 1.15.0 2021-04-13 06:50:02 +00:00
Corey Davis 36668f450b [BUG] Fix crashing on NaN value (#382) 2021-04-12 18:25:43 +02:00
Michael Henry 1bdb3b9c72 fix #383 UITableViewAlertForLayoutOutsideViewHierarchy warning (#384) 2021-04-12 11:09:48 +02:00
Juanpe 6c4e9091a7 Bump version 1.14.0 2021-04-08 10:03:16 +00:00
Alex Lykesas 61a6efbc7b UnSwizzle methods when SkeletonView is hidden (#381)
* Add InputAccessoryView to textField

* Add method to removeOnce Add unSwizzleMethods that are called when recursiveHideSkeleton

Co-authored-by: Alexandros Lykesas <alexandros.lykesas@untis.at>
2021-04-08 11:59:32 +02:00
Juanpe Catalán 4143a6065c feat: add workaround to simulate the content of labels contained in a stack view (#380) 2021-03-29 18:00:34 +02:00
72 changed files with 3708 additions and 970 deletions
+64
View File
@@ -0,0 +1,64 @@
name: CD
on:
pull_request:
branches: [main]
types: [closed]
jobs:
release_version:
if: github.event.pull_request.milestone == null && github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup fastlane
run: brew install fastlane
- name: Publish release
id: publish_release
uses: release-drafter/release-drafter@v5
with:
publish: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update podspec
run: fastlane bump_version next_version:${{ steps.publish_release.outputs.tag_name }}
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
branch: 'main'
commit_message: 'Bump version ${{ steps.publish_release.outputs.tag_name }}'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to Cocoapods
env:
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
run: |
set -eo pipefail
pod lib lint --allow-warnings
pod trunk push --allow-warnings
- name: Communicate on PR released
uses: unsplash/comment-on-pr@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
msg: |
Congratulations! 🎉 This was released as part of [SkeletonView ${{ steps.publish_release.outputs.tag_name }}](${{ steps.publish_release.outputs.html_url }}) 🚀
- name: Tweet the release
uses: ethomson/send-tweet-action@v1
with:
consumer-key: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
consumer-secret: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
status: |
🎉 New release ${{ steps.publish_release.outputs.tag_name }} is out 🚀
Check out all the changes here:
${{ steps.publish_release.outputs.html_url }}
+1 -1
View File
@@ -16,4 +16,4 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Build
run: xcodebuild clean -target '${{ matrix.build-config['target'] }}' -sdk '${{ matrix.build-config['sdk'] }}' -destination '${{ matrix.build-config['destination'] }}'
run: xcodebuild clean build -target '${{ matrix.build-config['target'] }}' -sdk '${{ matrix.build-config['sdk'] }}' -destination '${{ matrix.build-config['destination'] }}'
+1 -1
View File
@@ -1 +1 @@
5.3
5.0
+24
View File
@@ -0,0 +1,24 @@
<?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>en</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>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>
+24
View File
@@ -0,0 +1,24 @@
<?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>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
@@ -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>
+45
View File
@@ -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.3</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>
+40
View File
@@ -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: [UIApplication.LaunchOptionsKey: 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,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "avatar.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

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

After

Width:  |  Height:  |  Size: 8.0 KiB

@@ -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,55 @@
// Copyright © 2018 SkeletonView. All rights reserved.
import UIKit
import SkeletonView
class CollectionViewCell: UICollectionViewCell {
var label: UILabel!
var imageView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
isSkeletonable = true
createLabel()
createImageView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func createImageView() {
imageView = UIImageView(image: UIImage(named: "picture"))
imageView.isSkeletonable = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
addSubview(imageView)
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
imageView.topAnchor.constraint(equalTo: topAnchor),
imageView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75),
imageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.75)
])
}
private func createLabel() {
label = UILabel()
label.isSkeletonable = true
label.text = "Lorem ipsum"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.bottomAnchor.constraint(equalTo: bottomAnchor),
label.heightAnchor.constraint(equalToConstant: 40),
label.widthAnchor.constraint(equalToConstant: frame.width)
])
}
}
+212
View File
@@ -0,0 +1,212 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="irH-dz-xqL">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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="SkeletonViewExampleUICollectionView" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Fso-nq-n6t">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<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="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="Ql9-Jy-aWM">
<rect key="frame" x="141" y="20" width="93" height="93"/>
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
<constraints>
<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>
<viewLayoutGuide key="safeArea" id="36i-gK-pIa"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
<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="264"/>
<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="507" width="375" height="160"/>
<subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="fMR-vj-7de">
<rect key="frame" x="20" y="21" width="145" height="32"/>
<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" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KBe-RM-BG8">
<rect key="frame" x="308" y="21" width="51" height="31"/>
<connections>
<action selector="changeAnimated:" destination="irH-dz-xqL" eventType="valueChanged" id="dlH-KK-iee"/>
</connections>
</switch>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Animated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GSj-Ze-UIK">
<rect key="frame" x="229" y="26" width="73" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Color" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4Hm-fj-45V">
<rect key="frame" x="20" y="61" width="41.5" height="52"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HBJ-nh-56V">
<rect key="frame" x="69.5" y="72" width="30" height="30"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="DVW-Tc-XEQ"/>
<constraint firstAttribute="height" constant="30" id="JfP-3b-yqX"/>
</constraints>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aUR-Qo-gHK">
<rect key="frame" x="20" y="61" width="100" height="52"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="GvX-hq-2Qn"/>
<constraint firstAttribute="height" constant="52" id="UQe-Cf-riE"/>
</constraints>
<connections>
<action selector="btnChangeColorTouchUpInside:" destination="irH-dz-xqL" eventType="touchUpInside" id="Xca-QC-htl"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nPs-17-vfs">
<rect key="frame" x="263" y="72" width="94" height="30"/>
<state key="normal" title="Hide skeleton"/>
<connections>
<action selector="showOrHideSkeleton:" destination="irH-dz-xqL" eventType="touchUpInside" id="lHc-k2-OgV"/>
</connections>
</button>
<stepper opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.25" maximumValue="5" stepValue="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="3mz-9M-e7Q">
<rect key="frame" x="263" y="123" width="94" height="32"/>
<connections>
<action selector="transitionDurationStepperAction:" destination="irH-dz-xqL" eventType="valueChanged" id="Ll0-Pr-d0V"/>
</connections>
</stepper>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fade Duration: 0.25 sec" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5gN-Jz-44y">
<rect key="frame" x="92.5" y="130" width="162.5" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="3mz-9M-e7Q" firstAttribute="leading" secondItem="5gN-Jz-44y" secondAttribute="trailing" constant="8" id="65e-Nj-bKG"/>
<constraint firstItem="HBJ-nh-56V" firstAttribute="centerY" secondItem="aUR-Qo-gHK" secondAttribute="centerY" id="81M-Wq-Avl"/>
<constraint firstItem="fMR-vj-7de" firstAttribute="leading" secondItem="JjA-MK-YzZ" secondAttribute="leading" constant="20" id="AyG-hI-tte"/>
<constraint firstItem="aUR-Qo-gHK" firstAttribute="leading" secondItem="fMR-vj-7de" secondAttribute="leading" id="C1b-Hl-jEg"/>
<constraint firstItem="3mz-9M-e7Q" firstAttribute="trailing" secondItem="nPs-17-vfs" secondAttribute="trailing" id="KuK-fc-jOQ"/>
<constraint firstItem="nPs-17-vfs" firstAttribute="centerY" secondItem="aUR-Qo-gHK" secondAttribute="centerY" id="MQX-E5-IDE"/>
<constraint firstItem="HBJ-nh-56V" firstAttribute="leading" secondItem="4Hm-fj-45V" secondAttribute="trailing" constant="8" id="MhM-jY-LIA"/>
<constraint firstItem="4Hm-fj-45V" firstAttribute="height" secondItem="aUR-Qo-gHK" secondAttribute="height" id="OSn-RA-wQL"/>
<constraint firstItem="4Hm-fj-45V" firstAttribute="leading" secondItem="aUR-Qo-gHK" secondAttribute="leading" id="PwQ-UR-iMq"/>
<constraint firstAttribute="height" constant="160" id="QDV-wu-e3I"/>
<constraint firstItem="5gN-Jz-44y" firstAttribute="centerY" secondItem="3mz-9M-e7Q" secondAttribute="centerY" id="TGP-Ep-0ob"/>
<constraint firstItem="nPs-17-vfs" firstAttribute="top" secondItem="KBe-RM-BG8" secondAttribute="bottom" constant="20" id="TPg-wY-9bc"/>
<constraint firstItem="4Hm-fj-45V" firstAttribute="centerY" secondItem="aUR-Qo-gHK" secondAttribute="centerY" id="V4i-bF-Jed"/>
<constraint firstItem="KBe-RM-BG8" firstAttribute="leading" secondItem="GSj-Ze-UIK" secondAttribute="trailing" constant="6" id="ehg-tW-7kq"/>
<constraint firstItem="GSj-Ze-UIK" firstAttribute="centerY" secondItem="KBe-RM-BG8" secondAttribute="centerY" id="esk-GV-DBS"/>
<constraint firstAttribute="trailing" secondItem="KBe-RM-BG8" secondAttribute="trailing" constant="18" id="hhE-rV-dV7"/>
<constraint firstItem="KBe-RM-BG8" firstAttribute="top" secondItem="JjA-MK-YzZ" secondAttribute="top" constant="21" id="pBQ-H8-xTK"/>
<constraint firstAttribute="bottom" secondItem="3mz-9M-e7Q" secondAttribute="bottom" constant="5" id="pQ9-a6-hM4"/>
<constraint firstItem="fMR-vj-7de" firstAttribute="centerY" secondItem="GSj-Ze-UIK" secondAttribute="centerY" id="q2v-t1-Zu0"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="160" id="qR5-cz-YAm"/>
<constraint firstItem="nPs-17-vfs" firstAttribute="trailing" secondItem="KBe-RM-BG8" secondAttribute="trailing" id="yls-k6-ZfC"/>
</constraints>
</view>
</subviews>
<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="eHI-ka-8vS" firstAttribute="top" secondItem="2Gq-Y8-1TU" secondAttribute="top" id="Ux2-GF-HLK"/>
<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"/>
</constraints>
<viewLayoutGuide key="safeArea" id="2Gq-Y8-1TU"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</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="showOrHideSkeletonButton" destination="nPs-17-vfs" id="vw4-fW-QoD"/>
<outlet property="skeletonTypeSelector" destination="fMR-vj-7de" id="CgX-3A-weo"/>
<outlet property="switchAnimated" destination="KBe-RM-BG8" id="emU-g9-NHT"/>
<outlet property="transitionDurationLabel" destination="5gN-Jz-44y" id="69y-iR-mbi"/>
<outlet property="transitionDurationStepper" destination="3mz-9M-e7Q" id="tzK-W7-A4D"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="PkM-Y0-M5i" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-972" y="-209.14542728635683"/>
</scene>
</scenes>
<resources>
<image name="avatar" width="215" height="211"/>
</resources>
</document>
+199
View File
@@ -0,0 +1,199 @@
// 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!
@IBOutlet weak var showOrHideSkeletonButton: UIButton!
@IBOutlet weak var transitionDurationLabel: UILabel!
@IBOutlet weak var transitionDurationStepper: UIStepper!
var type: SkeletonType {
return skeletonTypeSelector.selectedSegmentIndex == 0 ? .solid : .gradient
}
override func viewDidLoad() {
super.viewDidLoad()
transitionDurationStepper.value = 0.25
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()
}
@IBAction func showOrHideSkeleton(_ sender: Any) {
showOrHideSkeletonButton.setTitle((view.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal)
view.isSkeletonActive ? hideSkeleton() : showSkeleton()
}
@IBAction func transitionDurationStepperAction(_ sender: Any) {
transitionDurationLabel.text = "transition duration: \(transitionDurationStepper.value) sec"
}
func showSkeleton() {
refreshSkeleton()
}
func hideSkeleton() {
view.hideSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
}
func refreshSkeleton() {
self.view.hideSkeleton()
if type == .gradient { showGradientSkeleton() }
else { showSolidSkeleton() }
}
func showSolidSkeleton() {
if switchAnimated.isOn {
view.showAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!, transition: .crossDissolve(transitionDurationStepper.value))
} else {
view.showSkeleton(usingColor: colorSelectedView.backgroundColor!, transition: .crossDissolve(transitionDurationStepper.value))
}
}
func showGradientSkeleton() {
let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!)
if switchAnimated.isOn {
view.showAnimatedGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
} else {
view.showGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
}
}
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
}
func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as? CollectionViewCell
cell?.isSkeletonable = indexPath.row != 0
return cell
}
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
return cell
}
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) {
let cell = cell as? CollectionViewCell
cell?.isSkeletonable = indexPath.row != 0
}
}
+46
View File
@@ -0,0 +1,46 @@
//
// AppDelegate.swift
// SkeletonViewExample
//
// Created by Juanpe Catalán on 02/11/2017.
// Copyright © 2017 SkeletonView. All rights reserved.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: 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,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "avatar.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

@@ -0,0 +1,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,344 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
<device id="retina5_9" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Item-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="SkeletonViewExample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="F9K-jU-100" userLabel="ContainerView">
<rect key="frame" x="0.0" y="44" 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"/>
<constraints>
<constraint firstAttribute="height" constant="78" id="gF5-G1-lKI"/>
</constraints>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </string>
<color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="lastLineFillPercent">
<integer key="value" value="40"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="linesCornerRadius">
<integer key="value" value="6"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</textView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="nMj-pU-5wJ">
<rect key="frame" x="45" y="20" width="93" height="93"/>
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="93" id="gw9-nu-cKo"/>
<constraint firstAttribute="width" constant="93" id="zB6-Lp-IUt"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CJW-A4-Fb8">
<rect key="frame" x="166" y="27" width="166" height="12"/>
<color key="backgroundColor" red="0.92156862750000001" green="0.16862745100000001" blue="0.54901960780000003" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="10"/>
<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="skeletonLineSpacing">
<real key="value" value="8"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="CJW-A4-Fb8" firstAttribute="leading" secondItem="nMj-pU-5wJ" secondAttribute="trailing" constant="28" id="Drg-cD-6E8"/>
<constraint firstItem="e9V-mk-xH0" firstAttribute="leading" secondItem="F9K-jU-100" secondAttribute="leading" constant="45" id="HvQ-HY-zYU"/>
<constraint firstItem="e9V-mk-xH0" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" constant="1" id="KcB-tG-NXa"/>
<constraint firstAttribute="height" constant="243" id="MIj-xq-gr1"/>
<constraint firstItem="nMj-pU-5wJ" firstAttribute="leading" secondItem="F9K-jU-100" secondAttribute="leading" constant="45" id="TK6-Ws-2xY"/>
<constraint firstItem="e9V-mk-xH0" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="142" id="Wcx-nZ-1lR"/>
<constraint firstAttribute="trailing" secondItem="e9V-mk-xH0" secondAttribute="trailing" constant="43" id="XbU-Og-rht"/>
<constraint firstItem="CJW-A4-Fb8" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="27" id="ceh-gB-7Et"/>
<constraint firstItem="nMj-pU-5wJ" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="20" id="hQL-cr-MaN"/>
<constraint firstAttribute="trailing" secondItem="CJW-A4-Fb8" secondAttribute="trailing" constant="43" id="nfT-a5-z36"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="UCB-SP-lQk">
<rect key="frame" x="0.0" y="287" width="375" height="282"/>
<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="16" 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="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhU-1t-AaI" userLabel="Label">
<rect key="frame" x="119" y="29" width="235" height="20"/>
<color key="backgroundColor" red="0.92156862750000001" green="0.16862745100000001" blue="0.54901960780000003" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="HRL-cI-ieC"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<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>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="placeholder" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="dha-bH-Ipf">
<rect key="frame" x="119" y="57" width="235" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</textField>
</subviews>
<constraints>
<constraint firstItem="dha-bH-Ipf" firstAttribute="top" secondItem="VhU-1t-AaI" secondAttribute="bottom" constant="8" symbolic="YES" id="1Ek-1L-ZVs"/>
<constraint firstItem="oiE-tt-nc2" firstAttribute="leading" secondItem="7IN-F3-Mr6" secondAttribute="leadingMargin" id="1be-ak-AH1"/>
<constraint firstAttribute="bottom" secondItem="oiE-tt-nc2" secondAttribute="bottom" constant="20" id="CKt-oA-eBI"/>
<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"/>
<constraint firstItem="dha-bH-Ipf" firstAttribute="trailing" secondItem="VhU-1t-AaI" secondAttribute="trailing" id="baX-Nw-8sB"/>
<constraint firstItem="dha-bH-Ipf" firstAttribute="leading" secondItem="VhU-1t-AaI" secondAttribute="leading" id="kzA-mV-IDt"/>
</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"/>
<outlet property="textField" destination="dha-bH-Ipf" id="OHI-6P-tuU"/>
</connections>
</tableViewCell>
</prototypes>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="NO"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="dataSource" destination="BYZ-38-t0r" id="Hxi-nC-gbY"/>
<outlet property="delegate" destination="BYZ-38-t0r" id="Z10-Nx-iGb"/>
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XgY-1a-UGc">
<rect key="frame" x="0.0" y="569" 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="145" height="32"/>
<segments>
<segment title="Solid"/>
<segment title="Gradient"/>
</segments>
<connections>
<action selector="changeSkeletonType:" destination="BYZ-38-t0r" eventType="valueChanged" id="iAS-ab-0jP"/>
</connections>
</segmentedControl>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vz0-qg-GcZ">
<rect key="frame" x="308" y="21" width="51" height="31"/>
<connections>
<action selector="changeAnimated:" destination="BYZ-38-t0r" eventType="valueChanged" id="w1G-gZ-RE0"/>
</connections>
</switch>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Animated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WHN-wR-TKt">
<rect key="frame" x="229" y="26" width="73" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Color" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7CF-rV-pK2">
<rect key="frame" x="20" y="73.666666666666629" width="90" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="iGp-rp-t1d">
<rect key="frame" x="130" y="69" width="30" height="30"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="Q3k-B1-E88"/>
<constraint firstAttribute="height" constant="30" id="xOD-RY-U4u"/>
</constraints>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Mde-Cm-CoS">
<rect key="frame" x="20" y="58" width="140" height="52"/>
<constraints>
<constraint firstAttribute="height" constant="52" id="3GX-2y-eQj"/>
<constraint firstAttribute="width" constant="140" id="6cC-Y1-RKs"/>
</constraints>
<connections>
<action selector="btnChangeColorTouchUpInside:" destination="BYZ-38-t0r" eventType="touchUpInside" id="cB8-Ik-LIJ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Tdu-YQ-saq">
<rect key="frame" x="263" y="69" width="94" height="30"/>
<state key="normal" title="Hide skeleton"/>
<connections>
<action selector="showOrHideSkeleton:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Ma1-WX-Dzy"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fade Duration: 0.25 sec" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mrw-PM-jJJ">
<rect key="frame" x="92.333333333333329" y="130" width="162.66666666666669" 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" value="0.25" maximumValue="5" stepValue="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="l4N-LL-ZrJ">
<rect key="frame" x="263" y="123" width="94" height="32"/>
<connections>
<action selector="transitionDurationStepperAction:" destination="BYZ-38-t0r" eventType="valueChanged" id="jPN-df-fNs"/>
</connections>
</stepper>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="leading" secondItem="mrw-PM-jJJ" secondAttribute="trailing" constant="8" id="5iU-dO-qVc"/>
<constraint firstItem="mrw-PM-jJJ" firstAttribute="centerY" secondItem="l4N-LL-ZrJ" secondAttribute="centerY" id="9OM-mx-4Jo"/>
<constraint firstAttribute="trailing" secondItem="Tdu-YQ-saq" secondAttribute="trailing" constant="18" id="BQ0-S0-YMp"/>
<constraint firstItem="iGp-rp-t1d" firstAttribute="trailing" secondItem="Mde-Cm-CoS" secondAttribute="trailing" id="IJ3-CC-5M7"/>
<constraint firstAttribute="height" constant="160" id="OH5-ja-ZlB"/>
<constraint firstItem="Mde-Cm-CoS" firstAttribute="leading" secondItem="XgY-1a-UGc" secondAttribute="leading" constant="20" id="Rek-hz-pDw"/>
<constraint firstItem="xOL-Sq-r4i" firstAttribute="top" secondItem="XgY-1a-UGc" secondAttribute="top" constant="23" id="Udf-0g-bZm"/>
<constraint firstItem="Tdu-YQ-saq" firstAttribute="top" secondItem="vz0-qg-GcZ" secondAttribute="bottom" constant="17" id="WiR-yP-dyv"/>
<constraint firstItem="Mde-Cm-CoS" firstAttribute="centerY" secondItem="7CF-rV-pK2" secondAttribute="centerY" id="eaN-FA-4mF"/>
<constraint firstItem="iGp-rp-t1d" firstAttribute="centerY" secondItem="7CF-rV-pK2" secondAttribute="centerY" id="hBb-mp-AjF"/>
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="top" secondItem="Tdu-YQ-saq" secondAttribute="bottom" constant="24" id="iA5-RB-pW2"/>
<constraint firstItem="vz0-qg-GcZ" firstAttribute="top" secondItem="XgY-1a-UGc" secondAttribute="top" constant="21" id="iad-6N-wNf"/>
<constraint firstItem="vz0-qg-GcZ" firstAttribute="leading" secondItem="WHN-wR-TKt" secondAttribute="trailing" constant="6" id="jgu-tV-gqO"/>
<constraint firstItem="WHN-wR-TKt" firstAttribute="centerY" secondItem="vz0-qg-GcZ" secondAttribute="centerY" id="kTu-fb-Bc8"/>
<constraint firstAttribute="trailing" secondItem="vz0-qg-GcZ" secondAttribute="trailing" constant="18" id="ktq-JT-uoR"/>
<constraint firstItem="xOL-Sq-r4i" firstAttribute="leading" secondItem="XgY-1a-UGc" secondAttribute="leading" constant="20" id="pY0-qd-xmK"/>
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="trailing" secondItem="Tdu-YQ-saq" secondAttribute="trailing" id="ql2-Z9-dnv"/>
<constraint firstItem="iGp-rp-t1d" firstAttribute="leading" secondItem="7CF-rV-pK2" secondAttribute="trailing" constant="20" id="vWD-0m-dMp"/>
<constraint firstItem="7CF-rV-pK2" firstAttribute="centerY" secondItem="Tdu-YQ-saq" secondAttribute="centerY" id="x0d-LB-A4q"/>
<constraint firstItem="7CF-rV-pK2" firstAttribute="leading" secondItem="xOL-Sq-r4i" secondAttribute="leading" id="yEL-Nv-z76"/>
</constraints>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="F9K-jU-100" firstAttribute="trailing" secondItem="6Tk-OE-BBY" secondAttribute="trailing" id="1es-nY-bd3"/>
<constraint firstItem="F9K-jU-100" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="A3E-iv-1qp"/>
<constraint firstItem="XgY-1a-UGc" firstAttribute="trailing" secondItem="6Tk-OE-BBY" secondAttribute="trailing" id="Luk-cg-4Ez"/>
<constraint firstItem="UCB-SP-lQk" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="Qbw-Rq-Rhw"/>
<constraint firstItem="F9K-jU-100" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" id="VLb-cX-ZHC"/>
<constraint firstItem="XgY-1a-UGc" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="Y8A-sq-fmq"/>
<constraint firstItem="XgY-1a-UGc" firstAttribute="top" secondItem="UCB-SP-lQk" secondAttribute="bottom" id="eof-MM-DrW"/>
<constraint firstItem="UCB-SP-lQk" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="bottom" id="hwL-ec-fKo"/>
<constraint firstItem="UCB-SP-lQk" firstAttribute="trailing" secondItem="6Tk-OE-BBY" secondAttribute="trailing" id="o6z-Dj-ppC"/>
<constraint firstItem="XgY-1a-UGc" firstAttribute="bottom" secondItem="6Tk-OE-BBY" secondAttribute="bottom" id="vnZ-9k-MfI"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
<tabBarItem key="tabBarItem" title="Item" id="wQY-ap-3n3"/>
<navigationItem key="navigationItem" id="BEI-dU-kr2"/>
<connections>
<outlet property="avatarImage" destination="nMj-pU-5wJ" id="9fa-Z7-vYi"/>
<outlet property="colorSelectedView" destination="iGp-rp-t1d" id="0Zm-9d-jRU"/>
<outlet property="showOrHideSkeletonButton" destination="Tdu-YQ-saq" id="gkm-mB-zYD"/>
<outlet property="skeletonTypeSelector" destination="xOL-Sq-r4i" id="yTr-8L-I4Y"/>
<outlet property="switchAnimated" destination="vz0-qg-GcZ" id="d2R-8x-lRb"/>
<outlet property="tableview" destination="UCB-SP-lQk" id="XV0-KX-lAN"/>
<outlet property="transitionDurationLabel" destination="mrw-PM-jJJ" id="BVK-Kl-5Q3"/>
<outlet property="transitionDurationStepper" destination="l4N-LL-ZrJ" id="OJr-Ul-7XR"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-2682.4000000000001" y="339.90147783251234"/>
</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="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="Ao1-hk-zrH"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</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>
<image name="avatar" width="215" height="211"/>
<systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
+33
View File
@@ -0,0 +1,33 @@
//
// Cell.swift
// SkeletonViewExample
//
// Created by Juanpe Catalán on 03/11/2017.
// Copyright © 2017 SkeletonView. All rights reserved.
//
import UIKit
class Cell: UITableViewCell {
@IBOutlet weak var avatar: UIImageView!
@IBOutlet weak var label1: UILabel!
@IBOutlet weak var textField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
setUpInputAccessoryView()
}
func setUpInputAccessoryView() {
let bar = UIToolbar()
let reset = UIBarButtonItem(title: "InputAccessoryView", style: .plain, target: self, action: #selector(resetTapped))
bar.items = [reset]
bar.sizeToFit()
textField.inputAccessoryView = bar
}
@objc func resetTapped() {
}
}
+5
View File
@@ -0,0 +1,5 @@
// Copyright © 2018 SkeletonView. All rights reserved.
import UIKit
let colors = [(UIColor.skeletonDefault,"skeletonDefault"),(UIColor.turquoise,"turquoise"), (UIColor.emerald,"emerald"), (UIColor.peterRiver,"peterRiver"), (UIColor.amethyst,"amethyst"),(UIColor.wetAsphalt,"wetAsphalt"), (UIColor.nephritis,"nephritis"), (UIColor.belizeHole,"belizeHole"), (UIColor.wisteria,"wisteria"), (UIColor.midnightBlue,"midnightBlue"), (UIColor.sunFlower,"sunFlower"), (UIColor.carrot,"carrot"), (UIColor.alizarin,"alizarin"),(UIColor.clouds,"clouds"), (UIColor.concrete,"concrete"), (UIColor.flatOrange,"flatOrange"), (UIColor.pumpkin,"pumpkin"), (UIColor.pomegranate,"pomegranate"), (UIColor.silver,"silver"), (UIColor.asbestos,"asbestos")]
@@ -0,0 +1,44 @@
// Copyright © 2020 SkeletonView. All rights reserved.
import UIKit
class HeaderFooterSection: UITableViewHeaderFooterView {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = " "
label.isSkeletonable = true
label.linesCornerRadius = 5
return label
}()
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
isSkeletonable = true
contentView.addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10)
])
backgroundView = UIView()
if #available(iOS 13.0, *) {
backgroundView?.backgroundColor = .systemBackground
} else {
backgroundView?.backgroundColor = .white
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
+221
View File
@@ -0,0 +1,221 @@
//
// ViewController.swift
// SkeletonViewExample
//
// Created by Juanpe Catalán on 02/11/2017.
// Copyright © 2017 SkeletonView. All rights reserved.
//
import UIKit
import SkeletonView
class ViewController: UIViewController {
@IBOutlet weak var tableview: UITableView! {
didSet {
tableview.rowHeight = UITableView.automaticDimension
tableview.sectionHeaderHeight = UITableView.automaticDimension
tableview.sectionFooterHeight = UITableView.automaticDimension
tableview.estimatedRowHeight = 120.0
tableview.estimatedSectionFooterHeight = 20.0
tableview.estimatedSectionHeaderHeight = 20.0
tableview.register(HeaderFooterSection.self, forHeaderFooterViewReuseIdentifier: "HeaderIdentifier")
tableview.register(HeaderFooterSection.self, forHeaderFooterViewReuseIdentifier: "FooterIdentifier")
}
}
@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!
@IBOutlet weak var showOrHideSkeletonButton: UIButton!
@IBOutlet weak var transitionDurationLabel: UILabel!
@IBOutlet weak var transitionDurationStepper: UIStepper!
var type: SkeletonType {
return skeletonTypeSelector.selectedSegmentIndex == 0 ? .solid : .gradient
}
override func viewDidLoad() {
super.viewDidLoad()
tableview.isSkeletonable = true
transitionDurationStepper.value = 0.25
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
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()
}
@IBAction func showOrHideSkeleton(_ sender: Any) {
showOrHideSkeletonButton.setTitle((view.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal)
view.isSkeletonActive ? hideSkeleton() : showSkeleton()
}
@IBAction func transitionDurationStepperAction(_ sender: Any) {
transitionDurationLabel.text = "Transition duration: \(transitionDurationStepper.value) sec"
}
func showSkeleton() {
if type == .gradient {
let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!)
if switchAnimated.isOn {
view.showAnimatedGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
}
else {
view.showGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
}
}
else {
if switchAnimated.isOn {
view.showAnimatedSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
}
else {
view.showSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
}
}
}
func hideSkeleton() {
view.hideSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
}
func refreshSkeleton() {
if type == .gradient { showOrUpdateGradientSkeleton() }
else { showOrUpdatepdateSolidSkeleton() }
}
func showOrUpdatepdateSolidSkeleton() {
if switchAnimated.isOn {
view.updateAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!)
} else {
view.updateSkeleton(usingColor: colorSelectedView.backgroundColor!)
}
}
func showOrUpdateGradientSkeleton() {
let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!)
if switchAnimated.isOn {
view.updateAnimatedGradientSkeleton(usingGradient: gradient)
} else {
view.updateGradientSkeleton(usingGradient: gradient)
}
}
func showAlertPicker() {
let alertView = UIAlertController(title: "Select color", message: "\n\n\n\n\n\n", preferredStyle: .alert)
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 50, width: 260, height: 115))
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
})
}
}
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
}
}
extension ViewController: SkeletonTableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
return "CellIdentifier"
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as! Cell
cell.label1.text = "cell -> \(indexPath.row)"
return cell
}
func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? {
let cell = skeletonView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as? Cell
cell?.textField.isHidden = indexPath.row == 0
return cell
}
func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) {
let cell = cell as? Cell
cell?.textField.isHidden = indexPath.row == 0
}
}
extension ViewController: SkeletonTableViewDelegate {
func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? {
return "HeaderIdentifier"
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView
.dequeueReusableHeaderFooterView(withIdentifier: "HeaderIdentifier") as! HeaderFooterSection
header.titleLabel.text = "header -> \(section)"
return header
}
func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? {
return "FooterIdentifier"
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footer = tableView
.dequeueReusableHeaderFooterView(withIdentifier: "FooterIdentifier") as! HeaderFooterSection
footer.titleLabel.text = "footer -> \(section)"
return footer
}
}
-16
View File
@@ -1,16 +0,0 @@
{
"object": {
"pins": [
{
"package": "SnapshotTesting",
"repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing.git",
"state": {
"branch": null,
"revision": "c466812aa2e22898f27557e2e780d3aad7a27203",
"version": "1.8.2"
}
}
]
},
"version": 1
}
+14 -31
View File
@@ -1,40 +1,23 @@
// swift-tools-version:5.3
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "SkeletonView",
platforms: [
.iOS(.v11),
.tvOS(.v11)
],
platforms: [
.iOS(.v9),
.tvOS(.v9)
],
products: [
.library(
name: "SkeletonView",
targets: ["SkeletonView"]
)
],
dependencies: [
.package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing.git", .exact("1.8.2"))
],
targets: [
.target(
name: "SkeletonView",
path: "Sources",
exclude: [
"*.md"
]
),
.testTarget(
name: "SkeletonViewTests",
dependencies: [
"SkeletonView",
"SnapshotTesting"
],
path: "Tests/SkeletonViewTests",
exclude: [
"UI/__Snapshots__"
]
)
]
targets: ["SkeletonView"])
],
targets: [
.target(
name: "SkeletonView",
dependencies: [],
path: "Sources")
],
swiftLanguageVersions: [.v5]
)
+71 -20
View File
@@ -4,10 +4,12 @@
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
<img src="https://github.com/Juanpe/SkeletonView/workflows/CI/badge.svg">
</a>
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-main"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
<img src="http://img.shields.io/badge/dependency%20manager-swiftpm%2Bcocoapods%2Bcarthage-green" />
<img src="https://img.shields.io/badge/platforms-ios%2Btvos-green" />
<a href="https://badge.bow-swift.io/recipe?name=SkeletonView&description=An%20elegant%20way%20to%20show%20users%20that%20something%20is%20happening%20and%20also%20prepare%20them%20to%20which%20contents%20he%20is%20waiting&url=https://github.com/juanpe/skeletonview&owner=Juanpe&avatar=https://avatars0.githubusercontent.com/u/1409041?v=4&tag=1.8.7"><img src="https://raw.githubusercontent.com/bow-swift/bow-art/main/badges/nef-playgrounds-badge.svg" alt="SkeletonView Playground" style="height:20px"></a>
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-main"><img alt="codebeat badge" src="https://codebeat.co/badges/1f37bbab-a1c8-4a4a-94d7-f21740d461e9" /></a>
<a href="https://cocoapods.org/pods/SkeletonView"><img src="https://img.shields.io/cocoapods/v/SkeletonView.svg?style=flat"></a>
<a href="https://github.com/Carthage/Carthage/"><img src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat"></a>
<a href="https://swift.org/package-manager/"><img src="https://img.shields.io/badge/SPM-supported-Green.svg?style=flat"></a>
<img src="https://img.shields.io/badge/platforms-iOS_tvOS-green" />
<a href="https://badge.bow-swift.io/recipe?name=SkeletonView&description=An%20elegant%20way%20to%20show%20users%20that%20something%20is%20happening%20and%20also%20prepare%20them%20to%20which%20contents%20he%20is%20waiting&url=https://github.com/juanpe/skeletonview&owner=Juanpe&avatar=https://avatars0.githubusercontent.com/u/1409041?v=4&tag=1.20.0"><img src="https://raw.githubusercontent.com/bow-swift/bow-art/master/badges/nef-playgrounds-badge.svg" alt="SkeletonView Playground" style="height:20px"></a>
</p>
<p align="center">
@@ -19,7 +21,7 @@
• <a href="#-contributing">Contributing</a>
</p>
**🌎 README is available in other languages: [🇪🇸](https://github.com/Juanpe/SkeletonView/blob/main/README_es.md) . [🇨🇳](https://github.com/Juanpe/SkeletonView/blob/main/README_zh.md) . [🇧🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_pt-br.md) . [🇰🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_ko.md) . [🇫🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_fr.md)**
**🌎 README is available in other languages: [🇪🇸](https://github.com/Juanpe/SkeletonView/blob/main/README_es.md) . [🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) . [🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) . [🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) . [🇫🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_fr.md)**
Today almost all apps have async processes, such as API requests, long running processes, etc. While the processes are working, usually developers place a loading view to show users that something is going on.
@@ -43,6 +45,7 @@ Enjoy it! 🙂
- [✨ Miscellaneous](#-miscellaneous)
- [❤️ Contributing](#-contributing)
- [📢 Mentions](#-mentions)
- [🏆 Sponsors](#-sponsors)
- [👨🏻‍💻 Author](#-author)
- [👮🏻 License](#-license)
@@ -169,19 +172,16 @@ If you want to show the skeleton in a ```UITableView```, you need to conform to
``` swift
public protocol SkeletonTableViewDataSource: UITableViewDataSource {
func numSections(in collectionSkeletonView: UITableView) -> Int
func numSections(in collectionSkeletonView: UITableView) -> Int // Default: 1
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? // Default: nil
func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath)
}
```
As you can see, this protocol inherits from ```UITableViewDataSource```, so you can replace this protocol with the skeleton protocol.
This protocol has a default implementation:
``` swift
func numSections(in collectionSkeletonView: UITableView) -> Int
// Default: 1
```
This protocol has a default implementation for some methods. For example, the number of rows for each section is calculated in runtime:
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
@@ -189,18 +189,35 @@ func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection s
// It calculates how many cells need to populate whole tableview
```
There is only one method you need to implement to let Skeleton know the cell identifier. This method doesn't have default implementation:
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
```
> 📣 **IMPORTANT!**
>
> If you return `UITableView.automaticNumberOfSkeletonRows` in the above method, it acts like the default behavior (i.e. it calculates how many cells needed to populate the whole tableview).
**Example**
There is only one method you need to implement to let Skeleton know the cell identifier. This method doesn't have default implementation:
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
return "CellIdentifier"
}
```
By default, the library dequeues the cells from each indexPath, but you can also do this if you want to make some changes before the skeleton appears:
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? {
let cell = skeletonView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as? Cell
cell?.textField.isHidden = indexPath.row == 0
return cell
}
```
If you prefer to leave the deque part to the library you can configure the cell using this method:
``` swift
func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) {
let cell = cell as? Cell
cell?.textField.isHidden = indexPath.row == 0
}
```
Besides, you can skeletonize both the headers and footers. You need to conform to `SkeletonTableViewDelegate` protocol.
```swift
@@ -228,10 +245,12 @@ For `UICollectionView`, you need to conform to `SkeletonCollectionViewDataSource
``` swift
public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
func numSections(in collectionSkeletonView: UICollectionView) -> Int // default: 1
func numSections(in collectionSkeletonView: UICollectionView) -> Int // default: 1
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? // default: nil
func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? // default: nil
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath)
}
```
@@ -491,6 +510,33 @@ Sometimes you wanna hide some view when the animation starts, so there is a quic
view.isHiddenWhenSkeletonIsActive = true // This works only when isSkeletonable = true
```
**Don't modify user interaction when the skeleton is active**
By default, the user interaction is disabled for skeletonized items, but if you don't want to modify the user interaction indicator when skeleton is active, you can use the `isUserInteractionDisabledWhenSkeletonIsActive` property:
```swift
view.isUserInteractionDisabledWhenSkeletonIsActive = false // The view will be active when the skeleton will be active.
```
**Delayed show skeleton**
You can delay the presentation of the skeleton if the views update quickly.
```swift
func showSkeleton(usingColor: UIColor,
animated: Bool,
delay: TimeInterval,
transition: SkeletonTransitionStyle)
```
```swift
func showGradientSkeleton(usingGradient: SkeletonGradient,
animated: Bool,
delay: TimeInterval,
transition: SkeletonTransitionStyle)
```
**Debug**
To facilitate the debug tasks when something is not working fine. **`SkeletonView`** has some new tools.
@@ -520,11 +566,11 @@ Then, when the skeleton appears, you can see the view hierarchy in the Xcode con
* iOS 9.0+
* tvOS 9.0+
* Swift 5
* Swift 5.3
## ❤️ Contributing
This is an open source project, so feel free to contribute. How?
- Open an [issue](https://github.com/Juanpe/SkeletonView/issues/new).
- Send feedback via [email](mailto://juanpecatalan.com).
- Propose your own fixes, suggestions and open a pull request with the changes.
@@ -550,7 +596,12 @@ For more information, please read the [contributing guidelines](https://github.c
- [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)
## 🏆 Sponsors
Open-source projects cannot live long without your help. If you find **SkeletonView** is useful, please consider supporting this
project by becoming a sponsor.
Become a sponsor through [GitHub Sponsors](https://github.com/sponsors/Juanpe) :heart:
## 👨🏻‍💻 Author
+30 -30
View File
@@ -1,4 +1,4 @@
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/header2.jpg)
![](Assets/header2.jpg)
<p align="center">
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
@@ -104,7 +104,7 @@ avatarImageView.isSkeletonable = true
```
**Con IB/Storyboards:**
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/storyboard.png)
![](Assets/storyboard.png)
3️⃣ Una vez indicado, solo tienes que mostrar el **skeleton**. Tienes **4** opciones:
@@ -134,16 +134,16 @@ avatarImageView.isSkeletonable = true
</tr>
<tr>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/solid.png"></img>
<img src="Assets/solid.png"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/gradient.png"></img>
<img src="Assets/gradient.png"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/solid_animated.gif"></img>
<img src="Assets/solid_animated.gif"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/gradient_animated.gif"></img>
<img src="Assets/gradient_animated.gif"></img>
</td>
</tr>
</table>
@@ -225,7 +225,7 @@ El resto del proceso es exactamente igual que con las `UITableView`.
### 🔠 Textos
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multilines2.png)
![](Assets/multilines2.png)
Cuando usas elementos que contienen texto,`SkeletonView` dibujo líneas para simular el texto.
@@ -236,8 +236,8 @@ Puedes especificar algunos atributos para estos elementos:
| Atributo | Valores | Por defecto | Vista previa
| ------- | ------- |------- | -------
| **Porcentaje de relleno** de la última línea. | `0...100` | `70%` | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_lastline.png)
| **Radio de las esquinas** de las líneas. | `0...10` | `0` | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_corner.png)
| **Porcentaje de relleno** de la última línea. | `0...100` | `70%` | ![](Assets/multiline_lastline.png)
| **Radio de las esquinas** de las líneas. | `0...10` | `0` | ![](Assets/multiline_corner.png)
Para modificar alguno de los valores lo puedes hacer **con código**::
```swift
@@ -247,7 +247,7 @@ descriptionTextView.linesCornerRadius = 5
O usando **IB/Storyboards**:
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_customize.png)
![](Assets/multiline_customize.png)
### 🦋 Apariencia
@@ -297,7 +297,7 @@ Además, **SkeletonView** añade 20 colores flat 🤙🏼
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/flatcolors.png)
![](Assets/flatcolors.png)
###### Imagen extraída de la web [https://flatuicolors.com](https://flatuicolors.com)
@@ -338,12 +338,12 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
| Dirección | Vista previa
|------- | -------
| .leftRight | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_left_to_right.gif)
| .rightLeft | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_right_to_left.gif)
| .topBottom | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_top_to_bottom.gif)
| .bottomTop | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_bottom_to_top.gif)
| .topLeftBottomRight | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_topLeft_to_bottomRight.gif)
| .bottomRightTopLeft | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_bottomRight_to_topLeft.gif)
| .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)
> **😉 ¡Truco!**
>
@@ -379,10 +379,10 @@ La transición por defecto es `crossDissolve(0.25)`
</tr>
<tr>
<td width="50%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/skeleton_transition_nofade.gif"></img>
<img src="Assets/skeleton_transition_nofade.gif"></img>
</td>
<td width="50%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/skeleton_transition_fade.gif"></img>
<img src="Assets/skeleton_transition_fade.gif"></img>
</td>
</tr>
</table>
@@ -408,12 +408,12 @@ view.showSkeleton()
| Configuración | Resultado|
|:-------:|:-------:|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonable.jpg" width="350"/> | <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonables_result.png" width="350"/>|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_no_skeletonable.jpg" width="350"/> | <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonables_result.png" width="350"/>|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_skeletonable.jpg" width="350"/> | <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_skeletonable_result.png" width="350"/>|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/all_skeletonables.jpg" width="350"/>| <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/all_skeletonables_result.png" width="350"/>|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_no_skeletonable.jpg" width="350"/> | <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_no_skeletonable_result.png" height="350"/>|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_skeletonable.jpg" width="350"/> | <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_skeletonable_result.png" height="350"/>|
|<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"/>|
@@ -421,7 +421,7 @@ view.showSkeleton()
Esta ilustración muestra como deberías específicar qué elementos son skeletonables cuando estás usando una `UITableView`:
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_scheme.png" width="700px">
<img src="Assets/tableview_scheme.png" width="700px">
**Actualiza el skeleton**
@@ -447,17 +447,17 @@ var skeletonDescription: String
```
Y es representada de la siguiente manera:
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/debug_description.png)
![](Assets/debug_description.png)
Para activar el **modo debug**. Solo tienes que añadir una variable de entorno con esta clave `SKELETON_DEBUG` y activarla.
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/debug_mode.png)
![](Assets/debug_mode.png)
Entonces, cuando el skeleton aparece, tu podrás ver la jerarquía de vistas en la consola de Xcode.
<details>
<summary>Abre para ver un ejemplo </summary>
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/hierarchy_output.png" />
<img src="Assets/hierarchy_output.png" />
</details>
@@ -502,7 +502,7 @@ Para más información, por favor, lee la [guía de contribución](https://githu
[Juanpe Catalán](http://www.twitter.com/JuanpeCatalan)
<a class="bmc-button" target="_blank" href="https://www.buymeacoffee.com/CDou4xtIK"><img src="https://www.buymeacoffee.com/https://github.com/Juanpe/SkeletonView/blob/main/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>
<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>
## 👮🏻 Licencia
+35 -35
View File
@@ -1,4 +1,4 @@
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/header2.jpg)
![](Assets/header2.jpg)
<p align="center">
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
@@ -26,10 +26,10 @@
🌎 Traductions : </br>
[Original](https://github.com/Juanpe/SkeletonView) </br>
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/main/README_zh.md) [@WhatsXie](https://twitter.com/WhatsXie) </br>
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_pt-br.md) [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_ko.md) [@techinpark](https://twitter.com/techinpark) </br>
[🇫🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_fr.md) [@OmarJalil](https://github.com/OmarJalil)
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) [@WhatsXie](https://twitter.com/WhatsXie) </br>
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) [@techinpark](https://twitter.com/techinpark) </br>
[🇫🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_fr.md) [@OmarJalil](https://github.com/OmarJalil)
Aujourd'hui, presque toutes les applications ont des processus asynchrones, tels que les requêtes Api, les processus de longue durée, etc. Et pendant que les processus fonctionnent, les développeurs affichent généralement une vue de chargement pour montrer aux utilisateurs que quelque chose se passe.
@@ -80,7 +80,7 @@ Profitez-en! 🙂
## 🎬 Guides
[<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/thumb_getting_started.png">](https://youtu.be/75kgOhWsPNA)
[<img src="Assets/thumb_getting_started.png">](https://youtu.be/75kgOhWsPNA)
## 📲 Installation
@@ -127,7 +127,7 @@ avatarImageView.isSkeletonable = true
```
**Utilisation des IB/Storyboards:**
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/storyboard.png)
![](Assets/storyboard.png)
**Une fois que vous avez défini les vues, vous pouvez montrer le **squelette**. Pour le faire, vous avez quatre choix :
@@ -157,16 +157,16 @@ avatarImageView.isSkeletonable = true
</tr>
<tr>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/solid.png"></img>
<img src="Assets/solid.png"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/gradient.png"></img>
<img src="Assets/gradient.png"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/solid_animated.gif"></img>
<img src="Assets/solid_animated.gif"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/gradient_animated.gif"></img>
<img src="Assets/gradient_animated.gif"></img>
</td>
</tr>
</table>
@@ -263,7 +263,7 @@ Il n'y a qu'une seule méthode à mettre en œuvre pour faire connaître au Sque
Voici une illustration qui montre comment vous devez spécifier quels éléments sont squelettisables lorsque vous utilisez une `UITableView` :
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_scheme.png)
![](Assets/tableview_scheme.png)
Comme vous pouvez le voir, nous devons faire `skeletonable` la tableview, la cellule et les éléments de l'interface visuelle, mais nous n'avons pas besoin de faire `skeletonable` le `contentView`.
@@ -283,7 +283,7 @@ Le reste du processus ressemble à une `UITableView`.
### 📰 Texte multiligne
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multilines2.png)
![](Assets/multilines2.png)
Lorsqu'on utilise des éléments avec du texte, `SkeletonView` dessine des lignes pour simuler le texte.
En outre, vous pouvez décider combien de lignes vous voulez. Si `numberOfLines` est réglé à zéro, il calculera le combien de lignes sont nécessaires pour remplir tout le squelette et il sera dessiné. Au contraire, si vous le réglez sur un, deux ou tout autre nombre supérieur à zéro, il ne dessinera que ce nombre de lignes.
@@ -294,8 +294,8 @@ Vous pouvez définir certaines propriétés pour les éléments multilignes.
| Propriété | Valeurs | Par défaut | Aperçu
| ------- | ------- |------- | -------
| **Pourcentage de remplissage** de la dernière ligne. | `0...100` | `70%` | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_lastline.png)
| **Corner radius** des lignes. (**NEW**) | `0...10` | `0` | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_corner.png)
| **Pourcentage de remplissage** de la dernière ligne. | `0...100` | `70%` | ![](Assets/multiline_lastline.png)
| **Corner radius** des lignes. (**NEW**) | `0...10` | `0` | ![](Assets/multiline_corner.png)
Pour modifier le pourcentage ou le rayon **à l'aide du code**, définissez les propriétés :
@@ -306,7 +306,7 @@ descriptionTextView.linesCornerRadius = 5
Ou, si vous préférez, utilisez l'**IB/Storyboard** :
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_customize.png)
![](Assets/multiline_customize.png)
### 🎨 Couleurs personnalisées
@@ -328,7 +328,7 @@ En outre, `SkeletonView` dispose de 20 couleurs unies 🤙🏼
`UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...`
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/flatcolors.png)
![](Assets/flatcolors.png)
###### Image tirée du site web [https://flatuicolors.com](https://flatuicolors.com)
### 🦋 Présentation
@@ -396,12 +396,12 @@ view.showAnimatedGradientSkeleton(usingGradient : gradient, animation : animatio
| Direction | Aperçu
|------- | -------
| .leftRight | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_left_to_right.gif)
| .rightLeft | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_right_to_left.gif)
| .topBottom | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_top_to_bottom.gif)
| .bottomTop | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_bottom_to_top.gif)
| .topLeftBottomRight | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_topLeft_to_bottomRight.gif)
| .bottomRightTopLeft | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_bottomRight_to_topLeft.gif)
| .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)
> **😉 TRICK!**
Il existe une autre façon de créer des animations de glissement, en utilisant simplement ce raccourci :
@@ -433,10 +433,10 @@ La valeur par défaut est `crossDissolve(0.25)`.
</tr>
<tr>
<td width="50%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/skeleton_transition_nofade.gif"></img>
<img src="Assets/skeleton_transition_nofade.gif"></img>
</td>
<td width="50%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/skeleton_transition_fade.gif"></img>
<img src="Assets/skeleton_transition_fade.gif"></img>
</td>
</tr>
</table>
@@ -456,12 +456,12 @@ view.showSkeleton()
| Configuration | Résultat|
|:-------:|:-------:|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonable.jpg" width="350"/> | <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonables_result.png" width="350"/>|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_no_skeletonable.jpg" width="350"/> | <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonables_result.png" width="350"/>|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_skeletonable.jpg" width="350"/> | <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_skeletonable_result.png" width="350"/>|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/all_skeletonables.jpg" width="350"/>| <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/all_skeletonables_result.png" width="350"/>|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_no_skeletonable.jpg" width="350"/> | <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_no_skeletonable_result.png" height="350"/>|
|<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_skeletonable.jpg" width="350"/> | <img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_skeletonable_result.png" height="350"/>|
|<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"/>|
### 🔬 Débugger
@@ -473,17 +473,17 @@ var skeletonDescription : String
```
La représentation du squelette ressemble à ceci :
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/debug_description.png)
![](Assets/debug_description.png)
En outre, vous pouvez activer le nouveau mode **debug**. Il suffit d'ajouter la variable d'environnement `SKELETON_DEBUG` et de l'activer.
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/debug_mode.png)
![](Assets/debug_mode.png)
Ensuite, lorsque le squelette apparaît, vous pouvez voir la hiérarchie des vues dans la console Xcode.
<details>
<summary>Ouvrez pour voir un exemple de sortie</summary>
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/hierarchy_output.png" />
<img src="Assets/hierarchy_output.png" />
</details>
### 📚 Documentation
@@ -541,7 +541,7 @@ Voir [tous les contributeurs](https://github.com/Juanpe/SkeletonView/graphs/cont
* 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/https://github.com/Juanpe/SkeletonView/blob/main/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>
<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>
## 👮🏻 Licence
+31 -31
View File
@@ -1,4 +1,4 @@
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/header2.jpg)
![](Assets/header2.jpg)
<p align="center">
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
@@ -25,10 +25,10 @@
🌎 번역에 도움을 주신분들: </br>
[Original](https://github.com/Juanpe/SkeletonView) </br>
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/main/README_zh.md) [@WhatsXie](https://twitter.com/WhatsXie) </br>
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_pt-br.md) [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_ko.md) [@techinpark](https://twitter.com/techinpark) </br>
[🇫🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_fr.md) [@OmarJalil](https://github.com/OmarJalil)
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) [@WhatsXie](https://twitter.com/WhatsXie) </br>
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) [@techinpark](https://twitter.com/techinpark) </br>
[🇫🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_fr.md) [@OmarJalil](https://github.com/OmarJalil)
오늘날 거의 대부분의 앱들은 비동기 방식의 API 호출을 사용하는 프로세스를 가지고 있습니다.
프로세스가 작동하는동안 개발자들은 작업이 실행되고 있다는것을 사용자들에게 보여주기 위해서 로딩 뷰를 배치합니다.
@@ -72,7 +72,7 @@
## 🎬 사용가이드
[<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/thumb_getting_started.png">](https://youtu.be/75kgOhWsPNA)
[<img src="Assets/thumb_getting_started.png">](https://youtu.be/75kgOhWsPNA)
## 📲 설치 방법
@@ -123,7 +123,7 @@ avatarImageView.isSkeletonable = true
```
**인터페이스빌더 / 스토리보드를 이용하는 방법:**
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/storyboard.png)
![](Assets/storyboard.png)
**3.** 당신이 뷰를 세팅할때, **skeleton** 옵션을 사용 할 수 있습니다. 총 **4** 가지 옵션을 지원합니다:
@@ -153,16 +153,16 @@ avatarImageView.isSkeletonable = true
</tr>
<tr>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/solid.png"></img>
<img src="Assets/solid.png"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/gradient.png"></img>
<img src="Assets/gradient.png"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/solid_animated.gif"></img>
<img src="Assets/solid_animated.gif"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/gradient_animated.gif"></img>
<img src="Assets/gradient_animated.gif"></img>
</td>
</tr>
</table>
@@ -224,7 +224,7 @@ func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection s
아래의 그림은 `UITableView` 에서 특정한 요소에 skeleton 을 지정하는 방법을 보여주는 이미지 입니다:
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/tableview_scheme.png)
![](Assets/tableview_scheme.png)
위의 이미지에서 보이듯, 테이블 뷰와 셀에 들어가는 UI 요소들에는 적용을 해야하지만, `contentView`에 skeleton을 적용할 필요는 없습니다.
@@ -245,7 +245,7 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
### 📰 Multiline text
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multilines2.png)
![](Assets/multilines2.png)
텍스트가 들어있는 요소를 사용한다면, ```SkeletonView``` 에서 텍스트의 라인을 그려줍니다.
그리고, 원하는 라인 수를 설정할 수 있습니다. 만약 ```numberOfLines``` 을 0으로 설정한다면, 자동으로 필요한 라인수를 계산해서 그려줍니다. 대신 값이 설정되어있다면 설정된 수만큼의 라인이 그려집니다.
@@ -257,8 +257,8 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
| 속성 | 값 | 기본값 | 미리보기 |
| ----------------------------------------------- | --------- | ----- | ---------------------------------- |
| 마지막 라인의 **퍼센트** 를 지정 할 수 있습니다. | `0...100` | `70%` | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_lastline.png) |
| 라인의 **Corner radius** 를 지정할 수 있습니다. (**새로운기능**) | `0...10` | `0` | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_corner.png) |
| 마지막 라인의 **퍼센트** 를 지정 할 수 있습니다. | `0...100` | `70%` | ![](Assets/multiline_lastline.png) |
| 라인의 **Corner radius** 를 지정할 수 있습니다. (**새로운기능**) | `0...10` | `0` | ![](Assets/multiline_corner.png) |
@@ -270,7 +270,7 @@ descriptionTextView.linesCornerRadius = 5
혹은 **IB/Storyboard** 를 이용하실 수 있습니다:
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_customize.png)
![](Assets/multiline_customize.png)
### 🎨 Custom colors
@@ -292,7 +292,7 @@ view.showGradientSkeleton(usingGradient: gradient) // Gradient
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/flatcolors.png)
![](Assets/flatcolors.png)
###### 위 이미지는 [https://flatuicolors.com](https://flatuicolors.com) 사이트에서 발췌했습니다.
### 🦋 Appearance
@@ -360,12 +360,12 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
| 방향 | 미리보기 |
| ------------------- | ---------------------------------------------- |
| .leftRight | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_left_to_right.gif) |
| .rightLeft | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_right_to_left.gif) |
| .topBottom | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_top_to_bottom.gif) |
| .bottomTop | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_bottom_to_top.gif) |
| .topLeftBottomRight | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_topLeft_to_bottomRight.gif) |
| .bottomRightTopLeft | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_bottomRight_to_topLeft.gif) |
| .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) |
> **😉 꿀팁!**
슬라이딩 애니메이션을 만들기 위한 또다른 방법이 있습니다, 아래의 코드를 참조하세요:
@@ -381,10 +381,10 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
| 설정값 | 결과 |
| ----------------------------------------- | --------------------------------------------- |
| ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonable.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonables_result.png) |
| ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_no_skeletonable.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonables_result.png) |
| ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_skeletonable.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_skeletonable_result.png) |
| ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/all_skeletonables.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/all_skeletonables_result.png) |
| ![](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) |
### 🔬 디버그
@@ -398,17 +398,17 @@ var skeletonDescription: String
```
skeleton은 이렇게 생겼습니다:
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/debug_description.png)
![](Assets/debug_description.png)
그리고, 새로운 **디버그 모드**를 활성화 시킬 수 있습니다. 간단하게 `SKELETON_DEBUG` 이라는 환경 변수를 추가해 활성화 하면 됩니다.
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/debug_mode.png)
![](Assets/debug_mode.png)
그런 이후 skeleton이 나오면 Xcode 콘솔창에서 계층 구조를 볼 수 있습니다.
<details>
<summary>예제를 확인해보세요. </summary>
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/hierarchy_output.png" />
<img src="Assets/hierarchy_output.png" />
</details>
@@ -469,7 +469,7 @@ skeleton은 이렇게 생겼습니다:
* 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/https://github.com/Juanpe/SkeletonView/blob/main/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>
<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>
## 👮🏻 라이센스
@@ -1,4 +1,4 @@
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/header2.jpg)
![](Assets/header2.jpg)
<p align="center">
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
@@ -25,10 +25,10 @@
🌎 Traduções: </br>
[Original](https://github.com/Juanpe/SkeletonView) </br>
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/main/README_zh.md) [@WhatsXie](https://twitter.com/WhatsXie) </br>
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_pt-br.md) [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_ko.md) [@techinpark](https://twitter.com/techinpark) </br>
[🇫🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_fr.md) [@OmarJalil](https://github.com/OmarJalil)
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) [@WhatsXie](https://twitter.com/WhatsXie) </br>
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) [@techinpark](https://twitter.com/techinpark) </br>
[🇫🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_fr.md) [@OmarJalil](https://github.com/OmarJalil)
Hoje, quase todos os apps têm processos assíncronos, como requisições de API, processos longos, etc. E enquanto os processos estão ocorrendo, normalmente os desenvolvedores usam uma view que mostra os usuarios que algo está ocorrendo.
@@ -117,7 +117,7 @@ avatarImageView.isSkeletonable = true
```
**Usando IB/Storyboards:**
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/storyboard.png)
![](Assets/storyboard.png)
**3.** Uma vez que você setou as views, você pode mostrar o **skeleton**. Para fazê-lo, você tem **4** escolhas:
@@ -147,16 +147,16 @@ avatarImageView.isSkeletonable = true
</tr>
<tr>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/solid.png"></img>
<img src="Assets/solid.png"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/gradient.png"></img>
<img src="Assets/gradient.png"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/solid_animated.gif"></img>
<img src="Assets/solid_animated.gif"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/gradient_animated.gif"></img>
<img src="Assets/gradient_animated.gif"></img>
</td>
</tr>
</table>
@@ -226,7 +226,7 @@ O resto do processo é o mesmo da ```UITableView```
### 📰 Texto de várias linhas
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multilines2.png)
![](Assets/multilines2.png)
Quando você usar elementos com texto, ```SkeletonView``` desenha linhas para simular o texto.
Além disso, você pode decidir quantas linhas você quer. Se ```numberOfLines``` está setado para zero (0), haverá um cálculo para saber quantas linhas são necessárias para preencher o skeleton inteiro e será desenhado. Caso contrário, se você setar para um (1) ou qualquer outro número maior que zero, só serão desenhadas aquele número de linhas.
@@ -238,8 +238,8 @@ Você pode setar algumas propriedades para elementos de várias linhas.
| Property | Values | Default | Preview
| ------- | ------- |------- | -------
| **Filling percent** of the last line. | `0...100` | `70%` | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_lastline.png)
| **Corner radius** of lines. (**NEW**) | `0...10` | `0` | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_corner.png)
| **Filling percent** of the last line. | `0...100` | `70%` | ![](Assets/multiline_lastline.png)
| **Corner radius** of lines. (**NEW**) | `0...10` | `0` | ![](Assets/multiline_corner.png)
@@ -251,7 +251,7 @@ descriptionTextView.linesCornerRadius = 5
Ou, se você preferir use **IB/Storyboard**:
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_customize.png)
![](Assets/multiline_customize.png)
### 🎨 Cores customizadas
@@ -273,7 +273,7 @@ Além do mais, ```SkeletonView``` tem 20 cores flat 🤙🏼
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/flatcolors.png)
![](Assets/flatcolors.png)
###### Imagem capturada do site [https://flatuicolors.com](https://flatuicolors.com)
### 🦋 Aparência
@@ -341,12 +341,12 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
| Direction | Preview
|------- | -------
| .leftRight | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_left_to_right.gif)
| .rightLeft | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_right_to_left.gif)
| .topBottom | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_top_to_bottom.gif)
| .bottomTop | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_bottom_to_top.gif)
| .topLeftBottomRight | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_topLeft_to_bottomRight.gif)
| .bottomRightTopLeft | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_bottomRight_to_topLeft.gif)
| .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)
> **😉 TRUQUE!**
Existe outra forma de criar sliding animations, apenas usando este atalho:
@@ -362,10 +362,10 @@ Porque uma imagem vale mais que mil palavras:
| Configuration | Result
|------- | -------
|![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonable.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonables_result.png)
|![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_no_skeletonable.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonables_result.png)
|![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_skeletonable.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_skeletonable_result.png)
|![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/all_skeletonables.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/all_skeletonables_result.png)
|![](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)
@@ -417,7 +417,7 @@ Ver [todos os contribuidores](https://github.com/Juanpe/SkeletonView/graphs/cont
* 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/https://github.com/Juanpe/SkeletonView/blob/main/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>
<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>
## 👮🏻 Licença
+26 -26
View File
@@ -1,4 +1,4 @@
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/header2.jpg)
![](Assets/header2.jpg)
<p align="center">
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
@@ -25,10 +25,10 @@
🌎 翻译: [ [原版的](https://github.com/Juanpe/SkeletonView) ] </br>
[Original](https://github.com/Juanpe/SkeletonView) </br>
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/main/README_zh.md) [@WhatsXie](https://twitter.com/WhatsXie) </br>
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_pt-br.md) [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_ko.md) [@techinpark](https://twitter.com/techinpark) </br>
[🇫🇷](https://github.com/Juanpe/SkeletonView/blob/main/README_fr.md) [@OmarJalil](https://github.com/OmarJalil)
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) [@WhatsXie](https://twitter.com/WhatsXie) </br>
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) [@techinpark](https://twitter.com/techinpark) </br>
[🇫🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_fr.md) [@OmarJalil](https://github.com/OmarJalil)
今天,几乎所有的应用程序都有异步流程,例如:Api请求、长时间运行的流程等。虽然流程正在运行,但通常开发人员会设置一个加载视图来向用户显示正在发生的事情。
@@ -116,7 +116,7 @@ avatarImageView.isSkeletonable = true
```
**使用 IB/Storyboards**
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/storyboard.png)
![](Assets/storyboard.png)
**3.** 设置视图后,可以显示 **skeleton**. 并且您有 **4** 种效果可供选择:
@@ -146,16 +146,16 @@ avatarImageView.isSkeletonable = true
</tr>
<tr>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/solid.png"></img>
<img src="Assets/solid.png"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/gradient.png"></img>
<img src="Assets/gradient.png"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/solid_animated.gif"></img>
<img src="Assets/solid_animated.gif"></img>
</td>
<td width="25%">
<img src="https://github.com/Juanpe/SkeletonView/blob/main/Assets/gradient_animated.gif"></img>
<img src="Assets/gradient_animated.gif"></img>
</td>
</tr>
</table>
@@ -226,7 +226,7 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
### 📰 多行文字
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multilines2.png)
![](Assets/multilines2.png)
使用带有文本的元素时, ```SkeletonView``` 绘制线条以模拟文本。此外,您可以决定您想要多少行。如果 ```numberOfLines``` 设置为零,它将计算填充整个骨架所需的行数,并将绘制它。相反,如果将其设置为一,二或任何大于零的数字,它将只绘制此行数。
@@ -237,8 +237,8 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
| 属性 | 值范围 | 默认 | 延时
| ------- | ------- |------- | -------
| **Filling percent** 最后一行的长度百分比 | `0...100` | `70%` | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_lastline.png)
| **Corner radius** 条目圆角半径. (**新**) | `0...10` | `0` | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_corner.png)
| **Filling percent** 最后一行的长度百分比 | `0...100` | `70%` | ![](Assets/multiline_lastline.png)
| **Corner radius** 条目圆角半径. (**新**) | `0...10` | `0` | ![](Assets/multiline_corner.png)
@@ -251,7 +251,7 @@ descriptionTextView.linesCornerRadius = 5
或者,如果您更喜欢使用 **IB/Storyboard**:
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/multiline_customize.png)
![](Assets/multiline_customize.png)
### 🎨 自定义颜色
@@ -273,7 +273,7 @@ view.showGradientSkeleton(usingGradient: gradient) // 梯度效果
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/flatcolors.png)
![](Assets/flatcolors.png)
###### 从网站 [https://flatuicolors.com](https://flatuicolors.com)捕获的图像
### 🤓 自定义动画
@@ -316,12 +316,12 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
| 方向 | 效果
|------- | -------
| .leftRight | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_left_to_right.gif)
| .rightLeft | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_right_to_left.gif)
| .topBottom | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_top_to_bottom.gif)
| .bottomTop | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_bottom_to_top.gif)
| .topLeftBottomRight | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_topLeft_to_bottomRight.gif)
| .bottomRightTopLeft | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/sliding_bottomRight_to_topLeft.gif)
| .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)
> **😉 技巧!**
存在另一种创建滑动动画的方法,只需使用此快捷方式:
@@ -337,10 +337,10 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
| 分组 | 结果
|------- | -------
|![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonable.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonables_result.png)
|![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_no_skeletonable.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/no_skeletonables_result.png)
|![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_skeletonable.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/container_skeletonable_result.png)
|![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/all_skeletonables.png) | ![](https://github.com/Juanpe/SkeletonView/blob/main/Assets/all_skeletonables_result.png)
|![](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)
@@ -392,7 +392,7 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
* 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/https://github.com/Juanpe/SkeletonView/blob/main/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>
<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>
## 👮🏻 许可证
+2 -2
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "SkeletonView"
s.version = "1.13.0"
s.version = "1.20.0"
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.
@@ -12,7 +12,7 @@ Pod::Spec.new do |s|
s.social_media_url = "https://twitter.com/JuanpeCatalan"
s.ios.deployment_target = "9.0"
s.tvos.deployment_target = "9.0"
s.swift_version = "5.3"
s.swift_version = "5.0"
s.source = { :git => "https://github.com/Juanpe/SkeletonView.git", :tag => s.version.to_s }
s.source_files = "Sources/**/*"
end
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</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>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</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>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</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>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
File diff suppressed because it is too large Load Diff
@@ -4,4 +4,4 @@
<FileRef
location = "self:">
</FileRef>
</Workspace>
</Workspace>
@@ -2,7 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
<false/>
<key>FILEHEADER</key>
<string> ___COPYRIGHT___</string>
</dict>
</plist>
</plist>
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
BuildableName = "SkeletonView.framework"
BlueprintName = "SkeletonView-iOS"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
BuildableName = "SkeletonView.framework"
BlueprintName = "SkeletonView-iOS"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
BuildableName = "SkeletonView.framework"
BlueprintName = "SkeletonView-iOS"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
BuildableName = "SkeletonView.framework"
BlueprintName = "SkeletonView-iOS"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "9999"
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -14,9 +14,9 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SkeletonView::SkeletonView"
BlueprintIdentifier = "17DD0DFF207FB27400C56334"
BuildableName = "SkeletonView.framework"
BlueprintName = "SkeletonView"
BlueprintName = "SkeletonView-tvOS"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -28,17 +28,9 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SkeletonView::SkeletonViewTests"
BuildableName = "SkeletonViewTests.xctest"
BlueprintName = "SkeletonViewTests"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -50,6 +42,17 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "17DD0DFF207FB27400C56334"
BuildableName = "SkeletonView.framework"
BlueprintName = "SkeletonView-tvOS"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -57,6 +60,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "17DD0DFF207FB27400C56334"
BuildableName = "SkeletonView.framework"
BlueprintName = "SkeletonView-tvOS"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F5F899F11FABA607002E8FDA"
BuildableName = "SkeletonViewExample.app"
BlueprintName = "SkeletonViewExample"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F5F899F11FABA607002E8FDA"
BuildableName = "SkeletonViewExample.app"
BlueprintName = "SkeletonViewExample"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F5F899F11FABA607002E8FDA"
BuildableName = "SkeletonViewExample.app"
BlueprintName = "SkeletonViewExample"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F5F899F11FABA607002E8FDA"
BuildableName = "SkeletonViewExample.app"
BlueprintName = "SkeletonViewExample"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "42ABD05A210B548200BEEFF4"
BuildableName = "SkeletonViewExampleUICollectionView.app"
BlueprintName = "SkeletonViewExampleUICollectionView"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "42ABD05A210B548200BEEFF4"
BuildableName = "SkeletonViewExampleUICollectionView.app"
BlueprintName = "SkeletonViewExampleUICollectionView"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "42ABD05A210B548200BEEFF4"
BuildableName = "SkeletonViewExampleUICollectionView.app"
BlueprintName = "SkeletonViewExampleUICollectionView"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "42ABD05A210B548200BEEFF4"
BuildableName = "SkeletonViewExampleUICollectionView.app"
BlueprintName = "SkeletonViewExampleUICollectionView"
ReferencedContainer = "container:SkeletonView.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -31,12 +31,16 @@ extension CollectionSkeleton where Self: UIScrollView {
func removeDummyDataSource(reloadAfter: Bool) {}
func disableUserInteraction() {
isUserInteractionEnabled = false
isScrollEnabled = false
if isUserInteractionDisabledWhenSkeletonIsActive {
isUserInteractionEnabled = false
isScrollEnabled = false
}
}
func enableUserInteraction() {
isUserInteractionEnabled = true
isScrollEnabled = true
if isUserInteractionDisabledWhenSkeletonIsActive {
isUserInteractionEnabled = true
isScrollEnabled = true
}
}
}
@@ -13,20 +13,28 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier?
func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell?
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath)
}
public extension SkeletonCollectionViewDataSource {
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return skeletonView.estimatedNumberOfRows
UICollectionView.automaticNumberOfSkeletonItems
}
func collectionSkeletonView(_ skeletonView: UICollectionView,
supplementaryViewIdentifierOfKind: String,
at indexPath: IndexPath) -> ReusableCellIdentifier? {
return nil
func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? {
nil
}
func numSections(in collectionSkeletonView: UICollectionView) -> Int { return 1 }
func numSections(in collectionSkeletonView: UICollectionView) -> Int {
1
}
func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? {
nil
}
func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) { }
}
public protocol SkeletonCollectionViewDelegate: UICollectionViewDelegate { }
@@ -7,11 +7,20 @@
//
import UIKit
extension UICollectionView: CollectionSkeleton {
public static let automaticNumberOfSkeletonItems = -1
var estimatedNumberOfRows: Int {
guard let flowlayout = collectionViewLayout as? UICollectionViewFlowLayout else { return 0 }
return Int(ceil(frame.height / flowlayout.itemSize.height))
switch flowlayout.scrollDirection {
case .vertical:
return Int(ceil(frame.height / flowlayout.itemSize.height))
case .horizontal:
return Int(ceil(frame.width / flowlayout.itemSize.width))
default:
return 0
}
}
var skeletonDataSource: SkeletonCollectionDataSource? {
@@ -32,12 +32,31 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
originalTableViewDataSource?.collectionSkeletonView(tableView, numberOfRowsInSection: section) ?? 0
guard let originalTableViewDataSource = originalTableViewDataSource else {
return 0
}
let numberOfRows = originalTableViewDataSource.collectionSkeletonView(tableView, numberOfRowsInSection: section)
if numberOfRows == UITableView.automaticNumberOfSkeletonRows {
return tableView.estimatedNumberOfRows
} else {
return numberOfRows
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = originalTableViewDataSource?.collectionSkeletonView(tableView, cellIdentifierForRowAt: indexPath) ?? ""
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
guard let cell = originalTableViewDataSource?.collectionSkeletonView(tableView, skeletonCellForRowAt: indexPath) else {
let cellIdentifier = originalTableViewDataSource?.collectionSkeletonView(tableView, cellIdentifierForRowAt: indexPath) ?? ""
let fakeCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: fakeCell, at: indexPath)
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: fakeCell)
return fakeCell
}
originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: cell, at: indexPath)
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: cell)
return cell
}
@@ -50,12 +69,31 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, numberOfItemsInSection: section) ?? 0
guard let originalCollectionViewDataSource = originalCollectionViewDataSource else {
return 0
}
let numberOfItems = originalCollectionViewDataSource.collectionSkeletonView(collectionView, numberOfItemsInSection: section)
if numberOfItems == UICollectionView.automaticNumberOfSkeletonItems {
return collectionView.estimatedNumberOfRows
} else {
return numberOfItems
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, cellIdentifierForItemAt: indexPath) ?? ""
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
guard let cell = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, skeletonCellForItemAt: indexPath) else {
let cellIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, cellIdentifierForItemAt: indexPath) ?? ""
let fakeCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: fakeCell, at: indexPath)
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: fakeCell)
return fakeCell
}
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: cell, at: indexPath)
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: cell)
return cell
}
@@ -69,7 +107,7 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
return view
}
return UICollectionReusableView()
return originalCollectionViewDataSource?.collectionView?(collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath) ?? UICollectionReusableView()
}
}
@@ -25,7 +25,7 @@ extension SkeletonCollectionDelegate: UITableViewDelegate {
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForHeaderInSection: section))
headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForFooterInSection: section))
}
func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) {
@@ -12,11 +12,13 @@ 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
func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell?
func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath)
}
public extension SkeletonTableViewDataSource {
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int {
return skeletonView.estimatedNumberOfRows
return UITableView.automaticNumberOfSkeletonRows
}
func numSections(in collectionSkeletonView: UITableView) -> Int { return 1 }
@@ -27,6 +29,12 @@ public extension SkeletonTableViewDataSource {
func collectionSkeletonView(_ skeletonView: UITableView, cellIdenfierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
return collectionSkeletonView(skeletonView, cellIdentifierForRowAt: indexPath)
}
func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? {
nil
}
func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) { }
}
public protocol SkeletonTableViewDelegate: UITableViewDelegate {
@@ -11,6 +11,8 @@ import UIKit
public typealias ReusableHeaderFooterIdentifier = String
extension UITableView: CollectionSkeleton {
public static let automaticNumberOfSkeletonRows = -1
var estimatedNumberOfRows: Int {
return Int(ceil(frame.height / rowHeight))
}
+6 -2
View File
@@ -125,8 +125,12 @@ extension CALayer {
private func calculateNumLines(for config: SkeletonMultilinesLayerConfig) -> Int {
let definedNumberOfLines = config.lines
let requiredSpaceForEachLine = config.lineHeight + config.multilineSpacing
let calculatedNumberOfLines = Int(round(CGFloat(bounds.height - config.paddingInsets.top - config.paddingInsets.bottom) / CGFloat(requiredSpaceForEachLine)))
let neededLines = round(CGFloat(bounds.height - config.paddingInsets.top - config.paddingInsets.bottom) / CGFloat(requiredSpaceForEachLine))
guard neededLines.isNormal else {
return 0
}
let calculatedNumberOfLines = Int(neededLines)
guard calculatedNumberOfLines > 0 else {
return 1
}
@@ -12,9 +12,17 @@ extension UIView {
nonContentSizeLayoutConstraints.filter { $0.firstAttribute == NSLayoutConstraint.Attribute.height }
}
var skeletonHeightConstraints: [NSLayoutConstraint] {
nonContentSizeLayoutConstraints.filter {
$0.firstAttribute == NSLayoutConstraint.Attribute.height
&& $0.identifier?.contains("SkeletonView.Constraint.Height") ?? false
}
}
@discardableResult
func setHeight(equalToConstant constant: CGFloat) -> NSLayoutConstraint {
let heightConstraint = heightAnchor.constraint(equalToConstant: constant)
heightConstraint.identifier = "SkeletonView.Constraint.Height.\(constant)"
NSLayoutConstraint.activate([heightConstraint])
return heightConstraint
}
+12
View File
@@ -14,8 +14,11 @@ enum ViewAssociatedKeys {
static var labelViewState = "labelViewState"
static var imageViewState = "imageViewState"
static var buttonViewState = "buttonViewState"
static var headerFooterViewState = "headerFooterViewState"
static var currentSkeletonConfig = "currentSkeletonConfig"
static var skeletonCornerRadius = "skeletonCornerRadius"
static var disabledWhenSkeletonIsActive = "disabledWhenSkeletonIsActive"
static var delayedShowSkeletonWorkItem = "delayedShowSkeletonWorkItem"
}
// codebeat:enable[TOO_MANY_IVARS]
@@ -49,4 +52,13 @@ extension UIView {
get { return ao_get(pkey: &ViewAssociatedKeys.isSkeletonAnimated) as? Bool ?? false }
set { ao_set(newValue, pkey: &ViewAssociatedKeys.isSkeletonAnimated) }
}
var isSuperviewAStackView: Bool {
superview is UIStackView
}
var delayedShowSkeletonWorkItem: DispatchWorkItem? {
get { return ao_get(pkey: &ViewAssociatedKeys.delayedShowSkeletonWorkItem) as? DispatchWorkItem }
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.delayedShowSkeletonWorkItem) }
}
}
@@ -14,6 +14,12 @@ public extension UIView {
get { return hiddenWhenSkeletonIsActive }
set { hiddenWhenSkeletonIsActive = newValue }
}
@IBInspectable
var isUserInteractionDisabledWhenSkeletonIsActive: Bool {
get { return disabledWhenSkeletonIsActive }
set { disabledWhenSkeletonIsActive = newValue }
}
@IBInspectable
var skeletonCornerRadius: Float {
@@ -34,6 +40,11 @@ public extension UIView {
get { return ao_get(pkey: &ViewAssociatedKeys.hiddenWhenSkeletonIsActive) as? Bool ?? false }
set { ao_set(newValue, pkey: &ViewAssociatedKeys.hiddenWhenSkeletonIsActive) }
}
private var disabledWhenSkeletonIsActive: Bool {
get { return ao_get(pkey: &ViewAssociatedKeys.disabledWhenSkeletonIsActive) as? Bool ?? true }
set { ao_set(newValue, pkey: &ViewAssociatedKeys.disabledWhenSkeletonIsActive) }
}
private var skeletonableCornerRadius: Float {
get { return ao_get(pkey: &ViewAssociatedKeys.skeletonCornerRadius) as? Float ?? 0.0 }
@@ -10,7 +10,10 @@ import UIKit
extension UIView {
@objc func prepareViewForSkeleton() {
isUserInteractionEnabled = false
if isUserInteractionDisabledWhenSkeletonIsActive {
isUserInteractionEnabled = false
}
startTransition { [weak self] in
self?.backgroundColor = .clear
}
@@ -19,7 +22,6 @@ extension UIView {
extension UILabel {
var desiredHeightBasedOnNumberOfLines: CGFloat {
let lineHeight = constraintHeight ?? SkeletonAppearance.default.multilineHeight
let spaceNeededForEachLine = lineHeight * CGFloat(numberOfLines)
let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(numberOfLines - 1)
let padding = paddingInsets.top + paddingInsets.bottom
@@ -28,7 +30,13 @@ extension UILabel {
}
func updateHeightConstraintsIfNeeded() {
guard numberOfLines > 1 else { return }
guard numberOfLines > 1 || numberOfLines == 0 else { return }
// Workaround to simulate content when the label is contained in a `UIStackView`.
if isSuperviewAStackView, bounds.height == 0 {
// This is a placeholder text to simulate content because it's contained in a stack view in order to prevent that the content size will be zero.
text = " "
}
let desiredHeight = desiredHeightBasedOnNumberOfLines
if desiredHeight > definedMaxHeight {
@@ -38,10 +46,7 @@ extension UILabel {
}
}
func restoreBackupHeightConstraints() {
heightConstraints.forEach {
removeConstraint($0)
}
func restoreBackupHeightConstraintsIfNeeded() {
guard !backupHeightConstraints.isEmpty else { return }
NSLayoutConstraint.activate(backupHeightConstraints)
backupHeightConstraints.removeAll()
@@ -49,7 +54,11 @@ extension UILabel {
override func prepareViewForSkeleton() {
backgroundColor = .clear
isUserInteractionEnabled = false
if isUserInteractionDisabledWhenSkeletonIsActive {
isUserInteractionEnabled = false
}
resignFirstResponder()
startTransition { [weak self] in
self?.updateHeightConstraintsIfNeeded()
@@ -61,7 +70,11 @@ extension UILabel {
extension UITextView {
override func prepareViewForSkeleton() {
backgroundColor = .clear
isUserInteractionEnabled = false
if isUserInteractionDisabledWhenSkeletonIsActive {
isUserInteractionEnabled = false
}
resignFirstResponder()
startTransition { [weak self] in
self?.textColor = .clear
@@ -84,7 +97,11 @@ extension UITextField {
extension UIImageView {
override func prepareViewForSkeleton() {
backgroundColor = .clear
isUserInteractionEnabled = false
if isUserInteractionDisabledWhenSkeletonIsActive {
isUserInteractionEnabled = false
}
startTransition { [weak self] in
self?.image = nil
}
@@ -94,9 +111,23 @@ extension UIImageView {
extension UIButton {
override func prepareViewForSkeleton() {
backgroundColor = .clear
isUserInteractionEnabled = false
if isUserInteractionDisabledWhenSkeletonIsActive {
isUserInteractionEnabled = false
}
startTransition { [weak self] in
self?.setTitle(nil, for: .normal)
}
}
}
extension UITableViewHeaderFooterView {
override func prepareViewForSkeleton() {
backgroundView?.backgroundColor = .clear
if isUserInteractionDisabledWhenSkeletonIsActive {
isUserInteractionEnabled = false
}
}
}
+10 -3
View File
@@ -4,21 +4,28 @@ 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()
}
class func removeOnce(token: String, block: () -> Void) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
guard let index = _onceTracker.firstIndex(of: token) else { return }
_onceTracker.remove(at: index)
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 {
@@ -11,8 +11,8 @@ enum MultilineAssociatedKeys {
}
protocol ContainsMultilineText {
var constraintHeight: CGFloat? { get }
var numLines: Int { get }
var lineHeight: CGFloat { get }
var numberOfLines: Int { get }
var lastLineFillingPercent: Int { get }
var multilineCornerRadius: Int { get }
var multilineSpacing: CGFloat { get }
+4 -8
View File
@@ -27,15 +27,11 @@ public extension UILabel {
}
}
extension UILabel: ContainsMultilineText {
var constraintHeight: CGFloat? {
backupHeightConstraints.first?.constant
extension UILabel: ContainsMultilineText {
var lineHeight: CGFloat {
backupHeightConstraints.first?.constant ?? SkeletonAppearance.default.multilineHeight
}
var numLines: Int {
return numberOfLines
}
var lastLineFillingPercent: Int {
get { return ao_get(pkey: &MultilineAssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonAppearance.default.multilineLastLineFillPercent }
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.lastLineFillingPercent) }
+11 -3
View File
@@ -28,11 +28,19 @@ public extension UITextView {
}
extension UITextView: ContainsMultilineText {
var constraintHeight: CGFloat? {
heightConstraints.first?.constant
var lineHeight: CGFloat {
if let fontLineHeight = font?.lineHeight {
if let heightConstraints = heightConstraints.first?.constant {
return (fontLineHeight > heightConstraints) ? heightConstraints : fontLineHeight
}
return fontLineHeight
}
return SkeletonAppearance.default.multilineHeight
}
var numLines: Int {
var numberOfLines: Int {
-1
}
+37 -9
View File
@@ -27,12 +27,17 @@ extension UIView: Recoverable {
guard let storedViewState = viewState else { return }
startTransition { [weak self] in
self?.layer.cornerRadius = storedViewState.cornerRadius
self?.layer.masksToBounds = storedViewState.clipToBounds
self?.isUserInteractionEnabled = storedViewState.isUserInteractionsEnabled
guard let self = self else { return }
if self?.backgroundColor == .clear || forced {
self?.backgroundColor = storedViewState.backgroundColor
self.layer.cornerRadius = storedViewState.cornerRadius
self.layer.masksToBounds = storedViewState.clipToBounds
if self.isUserInteractionDisabledWhenSkeletonIsActive {
self.isUserInteractionEnabled = storedViewState.isUserInteractionsEnabled
}
if self.backgroundColor == .clear || forced {
self.backgroundColor = storedViewState.backgroundColor
}
}
}
@@ -52,12 +57,16 @@ extension UILabel {
override func recoverViewState(forced: Bool) {
super.recoverViewState(forced: forced)
startTransition { [weak self] in
guard let storedLabelState = self?.labelState else { return }
guard let self = self,
let storedLabelState = self.labelState else {
return
}
self?.restoreBackupHeightConstraints()
NSLayoutConstraint.deactivate(self.skeletonHeightConstraints)
self.restoreBackupHeightConstraintsIfNeeded()
if self?.textColor == .clear || forced {
self?.textColor = storedLabelState.textColor
if self.textColor == .clear || forced {
self.textColor = storedLabelState.textColor
}
}
}
@@ -152,3 +161,22 @@ extension UIButton {
}
}
}
extension UITableViewHeaderFooterView {
var headerFooterState: RecoverableTableViewHeaderFooterViewState? {
get { return ao_get(pkey: &ViewAssociatedKeys.headerFooterViewState) as? RecoverableTableViewHeaderFooterViewState }
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.headerFooterViewState) }
}
override func saveViewState() {
super.saveViewState()
headerFooterState = RecoverableTableViewHeaderFooterViewState(view: self)
}
override func recoverViewState(forced: Bool) {
super.recoverViewState(forced: forced)
startTransition { [weak self] in
self?.backgroundView?.backgroundColor = self?.headerFooterState?.backgroundViewColor
}
}
}
@@ -59,3 +59,11 @@ struct RecoverableButtonViewState {
self.title = view.titleLabel?.text
}
}
struct RecoverableTableViewHeaderFooterViewState {
var backgroundViewColor: UIColor?
init(view: UITableViewHeaderFooterView) {
self.backgroundViewColor = view.backgroundView?.backgroundColor
}
}
+5 -7
View File
@@ -83,9 +83,8 @@ struct SkeletonLayer {
/// If there is more than one line, or custom preferences have been set for a single line, draw custom layers
func addTextLinesIfNeeded() {
guard let textView = holderAsTextView else { return }
let lineHeight = textView.constraintHeight ?? SkeletonAppearance.default.multilineHeight
let config = SkeletonMultilinesLayerConfig(lines: textView.numLines,
lineHeight: lineHeight,
let config = SkeletonMultilinesLayerConfig(lines: textView.numberOfLines,
lineHeight: textView.lineHeight,
type: type,
lastLineFillPercent: textView.lastLineFillingPercent,
multilineCornerRadius: textView.multilineCornerRadius,
@@ -98,9 +97,8 @@ struct SkeletonLayer {
func updateLinesIfNeeded() {
guard let textView = holderAsTextView else { return }
let lineHeight = textView.constraintHeight ?? SkeletonAppearance.default.multilineHeight
let config = SkeletonMultilinesLayerConfig(lines: textView.numLines,
lineHeight: lineHeight,
let config = SkeletonMultilinesLayerConfig(lines: textView.numberOfLines,
lineHeight: textView.lineHeight,
type: type,
lastLineFillPercent: textView.lastLineFillingPercent,
multilineCornerRadius: textView.multilineCornerRadius,
@@ -113,7 +111,7 @@ struct SkeletonLayer {
var holderAsTextView: ContainsMultilineText? {
guard let textView = holder as? ContainsMultilineText,
(textView.numLines == -1 || textView.numLines == 0 || textView.numLines > 1 || textView.numLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
(textView.numberOfLines == -1 || textView.numberOfLines == 0 || textView.numberOfLines > 1 || textView.numberOfLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
return nil
}
return textView
+85 -7
View File
@@ -9,20 +9,63 @@ public extension UIView {
/// - color: The color of the skeleton. Defaults to `SkeletonAppearance.default.tintColor`.
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
delayedShowSkeletonWorkItem?.cancel()
let config = SkeletonConfig(type: .solid, colors: [color], transition: transition)
showSkeleton(skeletonConfig: config)
}
/// Shows the skeleton using the view that calls this method as root view.
///
/// - Parameters:
/// - color: The color of the skeleton. Defaults to `SkeletonAppearance.default.tintColor`.
/// - animated: If the skeleton is animated or not. Defaults to `true`.
/// - delay: The amount of time (measured in seconds) to wait before show the skeleton.
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animated: Bool = true, delay: TimeInterval, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
delayedShowSkeletonWorkItem?.cancel()
delayedShowSkeletonWorkItem = DispatchWorkItem { [weak self] in
let config = SkeletonConfig(type: .solid, colors: [color], animated: animated, transition: transition)
self?.showSkeleton(skeletonConfig: config)
}
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: delayedShowSkeletonWorkItem!)
}
/// 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 `.crossDissolve(0.25)`.
func showGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
delayedShowSkeletonWorkItem?.cancel()
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, transition: transition)
showSkeleton(skeletonConfig: config)
}
/// Shows the gradient skeleton using the view that calls this method as root view.
///
/// - Parameters:
/// - gradient: The gradient of the skeleton. Defaults to `SkeletonAppearance.default.gradient`.
/// - animated: If the skeleton is animated or not. Defaults to `true`.
/// - delay: The amount of time (measured in seconds) to wait before show the skeleton.
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
func showGradientSkeleton(
usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient,
animated: Bool = true,
delay: TimeInterval,
transition: SkeletonTransitionStyle = .crossDissolve(0.25)
) {
delayedShowSkeletonWorkItem?.cancel()
delayedShowSkeletonWorkItem = DispatchWorkItem { [weak self] in
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: animated, transition: transition)
self?.showSkeleton(skeletonConfig: config)
}
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: delayedShowSkeletonWorkItem!)
}
/// 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.
@@ -32,6 +75,7 @@ public extension UIView {
/// - animation: The animation of the skeleton. Defaults to `nil`.
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
func showAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
delayedShowSkeletonWorkItem?.cancel()
let config = SkeletonConfig(type: .solid, colors: [color], animated: true, animation: animation, transition: transition)
showSkeleton(skeletonConfig: config)
}
@@ -45,6 +89,7 @@ public extension UIView {
/// - animation: The animation of the skeleton. Defaults to `nil`.
/// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`.
func showAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
delayedShowSkeletonWorkItem?.cancel()
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: true, animation: animation, transition: transition)
showSkeleton(skeletonConfig: config)
}
@@ -75,6 +120,7 @@ public extension UIView {
}
func hideSkeleton(reloadDataAfter reload: Bool = true, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
delayedShowSkeletonWorkItem?.cancel()
flowDelegate?.willBeginHidingSkeletons(rootView: self)
recursiveHideSkeleton(reloadDataAfter: reload, transition: transition, root: self)
}
@@ -190,6 +236,8 @@ extension UIView {
isHidden = false
}
currentSkeletonConfig?.transition = transition
unSwizzleLayoutSubviews()
unSwizzleTraitCollectionDidChange()
removeDummyDataSourceIfNeeded(reloadAfter: reload)
subviewsSkeletonables.recursiveSearch(leafBlock: {
recoverViewState(forced: false)
@@ -233,6 +281,17 @@ extension UIView {
}
}
private func unSwizzleLayoutSubviews() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
DispatchQueue.removeOnce(token: "UIView.SkeletonView.swizzleLayoutSubviews") {
swizzle(selector: #selector(UIView.skeletonLayoutSubviews),
with: #selector(UIView.layoutSubviews),
inClass: UIView.self,
usingClass: UIView.self)
}
}
}
private func swizzleTraitCollectionDidChange() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
DispatchQueue.once(token: "UIView.SkeletonView.swizzleTraitCollectionDidChange") {
@@ -243,6 +302,17 @@ extension UIView {
}
}
}
private func unSwizzleTraitCollectionDidChange() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
DispatchQueue.removeOnce(token: "UIView.SkeletonView.swizzleTraitCollectionDidChange") {
swizzle(selector: #selector(UIView.skeletonTraitCollectionDidChange(_:)),
with: #selector(UIView.traitCollectionDidChange(_:)),
inClass: UIView.self,
usingClass: UIView.self)
}
}
}
}
extension UIView {
@@ -253,14 +323,22 @@ extension UIView {
.setHolder(self)
.build()
else { return }
self.skeletonLayer = skeletonLayer
layer.insertSublayer(skeletonLayer,
at: UInt32.max,
transition: config.transition) { [weak self] in
if config.animated {
self?.startSkeletonAnimation(config.animation)
}
layer.insertSkeletonLayer(
skeletonLayer,
atIndex: UInt32.max,
transition: config.transition
) { [weak self] in
guard let self = self else { return }
/// Workaround to fix the problem when inserting a sublayer and
/// the content offset is modified by the system.
(self as? UITextView)?.setContentOffset(.zero, animated: false)
if config.animated {
self.startSkeletonAnimation(config.animation)
}
}
status = .on
}
+5 -1
View File
@@ -14,7 +14,11 @@ extension UIView {
extension UITableView {
override var subviewsToSkeleton: [UIView] {
visibleCells + visibleSectionHeaders + visibleSectionFooters
// on `UIViewController'S onViewDidLoad`, the window is still nil.
// Some developer trying to call `view.showAnimatedSkeleton()`
// when the request or data is loading which sometimes happens before the ViewDidAppear
guard window != nil else { return [] }
return subviews
}
}
+3 -3
View File
@@ -3,13 +3,13 @@
import UIKit
extension CALayer {
func insertSublayer(_ layer: SkeletonLayer, at idx: UInt32, transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
insertSublayer(layer.contentLayer, at: idx)
func insertSkeletonLayer(_ sublayer: SkeletonLayer, atIndex index: UInt32, transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
insertSublayer(sublayer.contentLayer, at: index)
switch transition {
case .none:
completion?()
case .crossDissolve(let duration):
layer.contentLayer.setOpacity(from: 0, to: 1, duration: duration, completion: completion)
sublayer.contentLayer.setOpacity(from: 0, to: 1, duration: duration, completion: completion)
}
}
}
@@ -1,6 +0,0 @@
import XCTest
@testable import SkeletonView
final class SkeletonViewTests: XCTestCase {
}
+17
View File
@@ -0,0 +1,17 @@
default_platform(:ios)
podspec_name = "SkeletonView.podspec"
lane :bump_version do |options|
version_bump_podspec(path: @podspec_name, version_number: options[:next_version])
end
lane :release_current do
version = version_get_podspec(path: @podspec_name)
if git_tag_exists(tag: version)
UI.user_error!("The tag #{version} already exists on the repo. To release a new version of the library bump the version on #{@podspec_name}")
end
pod_lib_lint
add_git_tag(tag: "#{version}")
push_git_tags
pod_push
end