Compare commits
233 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a106d1af2 | |||
| 5544acc63e | |||
| 65299378c4 | |||
| d8f1b4c53b | |||
| d2d8c1f5db | |||
| ab385e5afb | |||
| 5d8cd9432e | |||
| fbaf2e7b4b | |||
| 97d83c7038 | |||
| ce83713240 | |||
| 56d3156f8e | |||
| aeb9dcf2c7 | |||
| 9914be0f5b | |||
| 7d0609098c | |||
| b431aabddb | |||
| 0091ffc08b | |||
| e948e313a1 | |||
| a7ae5f0f9f | |||
| 85cce0cd7c | |||
| 3afe7286a2 | |||
| 6f1db7e303 | |||
| c1815642db | |||
| a6a168a919 | |||
| d0dbd1e004 | |||
| 4ade8f8797 | |||
| 264195bf16 | |||
| 11cc9a3ff7 | |||
| 37ac7d6e5d | |||
| a1e183a5e1 | |||
| 420785502b | |||
| eed18e5372 | |||
| 0a86ac4a9c | |||
| 264b0a70c2 | |||
| 01d68190ab | |||
| 2d57efd9cb | |||
| da51a6f673 | |||
| f6d4343cc6 | |||
| c6e20aa1a0 | |||
| 809544066f | |||
| 9406e3ef62 | |||
| 6d4c7d76c3 | |||
| f580fdaac2 | |||
| 4be93db383 | |||
| ae4ecfa760 | |||
| f60e5cd7ae | |||
| e097385de9 | |||
| 84d8971aa2 | |||
| 5384fd34dd | |||
| 53d965e151 | |||
| 04678fc772 | |||
| 46da5ab6fa | |||
| 45871be409 | |||
| 93f54dcc4d | |||
| 3bf3038941 | |||
| df1454c749 | |||
| 8212fc1a0b | |||
| e4b9416667 | |||
| f6f001068d | |||
| cb4ddd487a | |||
| 18cd0f9aba | |||
| 731509a46f | |||
| 7833c94f2e | |||
| 71d3e72eec | |||
| a0a2ae760b | |||
| 71d40d24b2 | |||
| 4236e9d424 | |||
| 19b88fce3e | |||
| b200a1ff3a | |||
| d9408d59b4 | |||
| 5a0f6e2314 | |||
| 3f7505bed9 | |||
| 5436e44f15 | |||
| 2f2e542d51 | |||
| 15764debf0 | |||
| bcd0fa7983 | |||
| 4cdc5935fc | |||
| 93769902a3 | |||
| a1d54a448d | |||
| cc8d21e7af | |||
| 69a0c8319d | |||
| 3c173c0a23 | |||
| dce910d6d0 | |||
| 2f751b9036 | |||
| fdd17dd4e9 | |||
| 978fd553e1 | |||
| 14ef5012f3 | |||
| e62c7d8c0e | |||
| e7aa9da5ae | |||
| 09a30d36b8 | |||
| d5656b9715 | |||
| 66dc7e89c5 | |||
| 5c815d7647 | |||
| aa60f82c81 | |||
| 6676760860 | |||
| b571988fad | |||
| abd9130ff6 | |||
| fc9dca1730 | |||
| 46a95eecb4 | |||
| e08e563ec0 | |||
| 1c24785ed6 | |||
| bbcaeedd19 | |||
| 6fc8f5caea | |||
| ab6efd1504 | |||
| 6632f5d91e | |||
| c1a47f098b | |||
| 02be363c95 | |||
| cb0e4c63cf | |||
| 26d839387d | |||
| e05d013af8 | |||
| d8a5df75ee | |||
| f7eb048317 | |||
| 4497e5cdb9 | |||
| e83ab2e033 | |||
| d40a877c42 | |||
| 3b6c61cfcb | |||
| f42b77fd4d | |||
| c75df015d8 | |||
| 0393c04aa8 | |||
| c3df0bc5df | |||
| 0f0bb220f2 | |||
| a299c8c0c8 | |||
| aea2ae6793 | |||
| a3ed33f53b | |||
| 6cf19ca50d | |||
| ca0b295a80 | |||
| 60c387b250 | |||
| 5b3bc204bf | |||
| 1eece8e013 | |||
| d665d01469 | |||
| 623b326e7b | |||
| 3a4bbadcbe | |||
| dc92e652cb | |||
| e3f38fdd25 | |||
| 6c1c4dda42 | |||
| 39c60b6da2 | |||
| c75d34e7de | |||
| c5a0bc0050 | |||
| be74f093f3 | |||
| 0f90edfc8e | |||
| 67daffd7c9 | |||
| 961c731888 | |||
| 39fa679ddc | |||
| 40c107af23 | |||
| 0781fceab7 | |||
| eef7a87eb6 | |||
| be25db821b | |||
| 9e014dab0e | |||
| f53b722b52 | |||
| bd02497124 | |||
| 1802e76da9 | |||
| 318b2546c6 | |||
| 37df94b09f | |||
| 81599f2980 | |||
| b28358663b | |||
| b364218213 | |||
| 18048c9bcd | |||
| 510fe34476 | |||
| 932579edc0 | |||
| 96673df005 | |||
| 43c1f2e5ac | |||
| 37f6205421 | |||
| 3471288307 | |||
| b77edf84b2 | |||
| 3e87293a84 | |||
| f637c20ea9 | |||
| 1737536057 | |||
| 31fe04b92e | |||
| f7d577c269 | |||
| 3a7be98057 | |||
| dab24e40a7 | |||
| c729dec667 | |||
| f6f0a11968 | |||
| 6784de9597 | |||
| 6e91b3fa71 | |||
| 4b6f55cf66 | |||
| beaf22d696 | |||
| c62586ccff | |||
| 55cb95e3fa | |||
| d72b28dc53 | |||
| b742179fec | |||
| f2b0329db9 | |||
| 8271110682 | |||
| 096099e2a0 | |||
| 4d4fda2177 | |||
| 4296d1cb04 | |||
| db19eae968 | |||
| 1d2256b4e6 | |||
| 4e5c8627dd | |||
| 6012b0c84b | |||
| 09e1fe31e0 | |||
| 4fb8020c83 | |||
| 303ae5aeb8 | |||
| 4649c7e61f | |||
| 1671af6d28 | |||
| 83e7e48caa | |||
| c03af15070 | |||
| ed82f74ba8 | |||
| 01aba0b3ff | |||
| 6352030012 | |||
| 4b06c30b9d | |||
| 3dcde46fe9 | |||
| c9de28eb37 | |||
| 915e838994 | |||
| 74b44264ec | |||
| a9ad72aa0c | |||
| d535529be8 | |||
| a6bff05ca4 | |||
| bc98cd600e | |||
| 8f1cfc0045 | |||
| 432ec6caa2 | |||
| e18aedbbf8 | |||
| 4dc67ac3d7 | |||
| aa29cd3d6b | |||
| a2def55880 | |||
| 97f5e3e2e1 | |||
| 420cc7a669 | |||
| b9601f56c3 | |||
| fbca1092c3 | |||
| eb90faf413 | |||
| f0cc7125ef | |||
| f5b493d6f0 | |||
| 48bc217413 | |||
| 516fcecce2 | |||
| 9052d5d806 | |||
| 12dff52238 | |||
| f0b4575de6 | |||
| 7be8ddd70a | |||
| a9628c0de8 | |||
| 339191f016 | |||
| 7022942d92 | |||
| 03cf1bfea8 | |||
| 9f46e7a647 | |||
| 674cd1aa9f |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"SWIFT": {
|
||||
"TOO_MANY_FUNCTIONS": [50, 100, 150, 200],
|
||||
"TOTAL_LOC": [200, 400, 500, 600]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
github: [juanpe]
|
||||
@@ -0,0 +1,9 @@
|
||||
issuesOpened: >
|
||||
Thank you for raising an issue. We will try and get back to you as soon as possible.
|
||||
|
||||
Please make sure you have given us as much context as possible.
|
||||
|
||||
pullRequestOpened: >
|
||||
Thank you for raising your pull request.
|
||||
|
||||
Please make sure you have followed our contributing guidelines. We will review it as soon as possible.
|
||||
@@ -0,0 +1,10 @@
|
||||
daysUntilStale: 60
|
||||
daysUntilClose: 7
|
||||
onlyLabels:
|
||||
- awaiting user input
|
||||
staleLabel: given up
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
closeComment: false
|
||||
@@ -0,0 +1,14 @@
|
||||
name: Greetings
|
||||
|
||||
on: [pull_request, issues]
|
||||
|
||||
jobs:
|
||||
greeting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: 'Hi '
|
||||
pr-message: 'Hi! Message that will be displayed on users'
|
||||
|
||||
@@ -46,7 +46,7 @@ playground.xcworkspace
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
# Pods/
|
||||
Pods/
|
||||
|
||||
# Carthage
|
||||
#
|
||||
@@ -66,3 +66,7 @@ fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
|
||||
# JetBrains
|
||||
|
||||
.idea
|
||||
|
||||
@@ -1 +1 @@
|
||||
4.2
|
||||
5.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
language: objective-c
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- xcodebuild -project SkeletonView.xcodeproj -target SkeletonView-iOS -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 7,OS=12'
|
||||
- xcodebuild -project SkeletonView.xcodeproj -target SkeletonView-tvOS -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV,OS=12'
|
||||
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 28 KiB |
@@ -1,6 +1,57 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file
|
||||
|
||||
## Next version
|
||||
|
||||
### New
|
||||
|
||||
- Support for iOS 13 dark mode. (thanks @Wilsonator5000)
|
||||
|
||||
## [1.8.2](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.2)
|
||||
|
||||
### New
|
||||
- Add ability to customize line spacing per label. (thanks @gshahbazian)
|
||||
|
||||
## [LayoutSkeleton (1.8.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.1)
|
||||
|
||||
### Improvements
|
||||
- Fix completion call in .none transition style while hide skeletons. (thanks @aadudyrev)
|
||||
|
||||
### New
|
||||
- Swizzle `layoutSubviews` method.
|
||||
|
||||
### Improvements
|
||||
- Fix completion call in .none transition style while hiding skeletons. (thanks @aadudyrev)
|
||||
- Swift format.
|
||||
|
||||
### Bug fixes
|
||||
- Update layout subviews when the original method is called.
|
||||
- Issues: [#88, #149]
|
||||
|
||||
## [Transitions (1.8)](https://github.com/Juanpe/SkeletonView/releases/tag/1.8)
|
||||
|
||||
### New
|
||||
|
||||
- Adding swift news to mentioned section (thanks @osterbergmarcus).
|
||||
- Create `SkeletonTransitionStyle`. Now, you can animate transition when you show or hide skeletons. (thanks @pontusjacobsson)
|
||||
|
||||
### Improvements
|
||||
- Refactor some methods.
|
||||
|
||||
### Bug fixes
|
||||
- Solved issues.
|
||||
[#175](https://github.com/Juanpe/SkeletonView/issues/175) Swift Package Manager version format
|
||||
|
||||
## [Layout update (1.7)](https://github.com/Juanpe/SkeletonView/releases/tag/1.7)
|
||||
|
||||
### New
|
||||
|
||||
- Allow updating skeleton layout to recalculate skeleton bounds: `layoutSkeletonIfNeeded`. See the examples to know how to use it. (thanks @eduardbosch)
|
||||
|
||||
### Improvements
|
||||
|
||||
- Allow updating skeleton layers without recreating them: `updateSkeleton`, `updateGradientSkeleton`, `updateAnimatedSkeleton`, `updateAnimatedGradientSkeleton`. (thanks @eduardbosch)
|
||||
|
||||
## [Debug (1.4)](https://github.com/Juanpe/SkeletonView/releases/tag/1.4)
|
||||
|
||||
### New
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import SkeletonView
|
||||
|
||||
class CollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var label: UILabel!
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
isSkeletonable = true
|
||||
createLabel()
|
||||
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func createLabel() {
|
||||
label = UILabel()
|
||||
label.isSkeletonable = true
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(label)
|
||||
NSLayoutConstraint.activate([
|
||||
label.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
label.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
label.heightAnchor.constraint(equalToConstant: frame.height / 2),
|
||||
label.widthAnchor.constraint(equalToConstant: frame.width / 2)
|
||||
])
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
Before Width: | Height: | Size: 933 B 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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
@@ -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)
|
||||
])
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="irH-dz-xqL">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="irH-dz-xqL">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -13,7 +13,7 @@
|
||||
<!--View Controller-->
|
||||
<scene sceneID="qda-qV-vJk">
|
||||
<objects>
|
||||
<viewController id="irH-dz-xqL" customClass="ViewController" customModule="SkeletonViewExampleUICollectionView" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController id="irH-dz-xqL" customClass="ViewController" customModule="SkeletonViewExampleUICollectionView" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Fso-nq-n6t">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -37,7 +37,7 @@
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="Ql9-Jy-aWM">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="Ql9-Jy-aWM">
|
||||
<rect key="frame" x="141" y="20" width="93" height="93"/>
|
||||
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
@@ -66,7 +66,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="HKL-L0-T2w">
|
||||
<rect key="frame" x="0.0" y="263" width="375" height="264"/>
|
||||
<rect key="frame" x="0.0" y="263" width="375" height="244"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="mGU-kn-rfE">
|
||||
<size key="itemSize" width="50" height="50"/>
|
||||
@@ -80,11 +80,10 @@
|
||||
</connections>
|
||||
</collectionView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JjA-MK-YzZ">
|
||||
<rect key="frame" x="0.0" y="527" width="375" height="140"/>
|
||||
<rect key="frame" x="0.0" y="507" width="375" height="160"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="fMR-vj-7de">
|
||||
<rect key="frame" x="20" y="23" width="140" height="29"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="fMR-vj-7de">
|
||||
<rect key="frame" x="20" y="22.5" width="135" height="29"/>
|
||||
<segments>
|
||||
<segment title="Solid"/>
|
||||
<segment title="Gradient"/>
|
||||
@@ -93,44 +92,85 @@
|
||||
<action selector="changeSkeletonType:" destination="irH-dz-xqL" eventType="valueChanged" id="lfR-JV-DU4"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KBe-RM-BG8">
|
||||
<rect key="frame" x="310" y="21" width="49" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KBe-RM-BG8">
|
||||
<rect key="frame" x="308" y="21" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="changeAnimated:" destination="irH-dz-xqL" eventType="valueChanged" id="dlH-KK-iee"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Animated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GSj-Ze-UIK">
|
||||
<rect key="frame" x="211" y="28" width="91" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Animated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GSj-Ze-UIK">
|
||||
<rect key="frame" x="229" y="26" width="73" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Color" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4Hm-fj-45V">
|
||||
<rect key="frame" x="32" y="89" width="52" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Color" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4Hm-fj-45V">
|
||||
<rect key="frame" x="20" y="61" width="41.5" height="52"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="HBJ-nh-56V">
|
||||
<rect key="frame" x="92" y="84" width="30" height="30"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HBJ-nh-56V">
|
||||
<rect key="frame" x="69.5" y="72" width="30" height="30"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="30" id="DVW-Tc-XEQ"/>
|
||||
<constraint firstAttribute="height" constant="30" id="JfP-3b-yqX"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aUR-Qo-gHK">
|
||||
<rect key="frame" x="20" y="74" width="140" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aUR-Qo-gHK">
|
||||
<rect key="frame" x="20" y="61" width="100" height="52"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="100" id="GvX-hq-2Qn"/>
|
||||
<constraint firstAttribute="height" constant="52" id="UQe-Cf-riE"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<action selector="btnChangeColorTouchUpInside:" destination="irH-dz-xqL" eventType="touchUpInside" id="Xca-QC-htl"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nPs-17-vfs">
|
||||
<rect key="frame" x="263" y="72" width="94" height="30"/>
|
||||
<state key="normal" title="Hide skeleton"/>
|
||||
<connections>
|
||||
<action selector="showOrHideSkeleton:" destination="irH-dz-xqL" eventType="touchUpInside" id="lHc-k2-OgV"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stepper opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" maximumValue="5" stepValue="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="3mz-9M-e7Q">
|
||||
<rect key="frame" x="263" y="126" width="94" height="29"/>
|
||||
<connections>
|
||||
<action selector="transitionDurationStepperAction:" destination="irH-dz-xqL" eventType="valueChanged" id="Ll0-Pr-d0V"/>
|
||||
</connections>
|
||||
</stepper>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fade Duration: 0 sec" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5gN-Jz-44y">
|
||||
<rect key="frame" x="113.5" y="131.5" width="141.5" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="140" id="QDV-wu-e3I"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="140" id="qR5-cz-YAm"/>
|
||||
<constraint firstItem="3mz-9M-e7Q" firstAttribute="leading" secondItem="5gN-Jz-44y" secondAttribute="trailing" constant="8" id="65e-Nj-bKG"/>
|
||||
<constraint firstItem="HBJ-nh-56V" firstAttribute="centerY" secondItem="aUR-Qo-gHK" secondAttribute="centerY" id="81M-Wq-Avl"/>
|
||||
<constraint firstItem="fMR-vj-7de" firstAttribute="leading" secondItem="JjA-MK-YzZ" secondAttribute="leading" constant="20" id="AyG-hI-tte"/>
|
||||
<constraint firstItem="aUR-Qo-gHK" firstAttribute="leading" secondItem="fMR-vj-7de" secondAttribute="leading" id="C1b-Hl-jEg"/>
|
||||
<constraint firstItem="3mz-9M-e7Q" firstAttribute="trailing" secondItem="nPs-17-vfs" secondAttribute="trailing" id="KuK-fc-jOQ"/>
|
||||
<constraint firstItem="nPs-17-vfs" firstAttribute="centerY" secondItem="aUR-Qo-gHK" secondAttribute="centerY" id="MQX-E5-IDE"/>
|
||||
<constraint firstItem="HBJ-nh-56V" firstAttribute="leading" secondItem="4Hm-fj-45V" secondAttribute="trailing" constant="8" id="MhM-jY-LIA"/>
|
||||
<constraint firstItem="4Hm-fj-45V" firstAttribute="height" secondItem="aUR-Qo-gHK" secondAttribute="height" id="OSn-RA-wQL"/>
|
||||
<constraint firstItem="4Hm-fj-45V" firstAttribute="leading" secondItem="aUR-Qo-gHK" secondAttribute="leading" id="PwQ-UR-iMq"/>
|
||||
<constraint firstAttribute="height" constant="160" id="QDV-wu-e3I"/>
|
||||
<constraint firstItem="5gN-Jz-44y" firstAttribute="centerY" secondItem="3mz-9M-e7Q" secondAttribute="centerY" id="TGP-Ep-0ob"/>
|
||||
<constraint firstItem="nPs-17-vfs" firstAttribute="top" secondItem="KBe-RM-BG8" secondAttribute="bottom" constant="20" id="TPg-wY-9bc"/>
|
||||
<constraint firstItem="4Hm-fj-45V" firstAttribute="centerY" secondItem="aUR-Qo-gHK" secondAttribute="centerY" id="V4i-bF-Jed"/>
|
||||
<constraint firstItem="KBe-RM-BG8" firstAttribute="leading" secondItem="GSj-Ze-UIK" secondAttribute="trailing" constant="6" id="ehg-tW-7kq"/>
|
||||
<constraint firstItem="GSj-Ze-UIK" firstAttribute="centerY" secondItem="KBe-RM-BG8" secondAttribute="centerY" id="esk-GV-DBS"/>
|
||||
<constraint firstAttribute="trailing" secondItem="KBe-RM-BG8" secondAttribute="trailing" constant="18" id="hhE-rV-dV7"/>
|
||||
<constraint firstItem="KBe-RM-BG8" firstAttribute="top" secondItem="JjA-MK-YzZ" secondAttribute="top" constant="21" id="pBQ-H8-xTK"/>
|
||||
<constraint firstAttribute="bottom" secondItem="3mz-9M-e7Q" secondAttribute="bottom" constant="5" id="pQ9-a6-hM4"/>
|
||||
<constraint firstItem="fMR-vj-7de" firstAttribute="centerY" secondItem="GSj-Ze-UIK" secondAttribute="centerY" id="q2v-t1-Zu0"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="160" id="qR5-cz-YAm"/>
|
||||
<constraint firstItem="nPs-17-vfs" firstAttribute="trailing" secondItem="KBe-RM-BG8" secondAttribute="trailing" id="yls-k6-ZfC"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
@@ -148,13 +188,19 @@
|
||||
<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"/>
|
||||
@@ -4,7 +4,6 @@ import UIKit
|
||||
import SkeletonView
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var collectionView: UICollectionView! {
|
||||
didSet {
|
||||
collectionView.isSkeletonable = true
|
||||
@@ -36,6 +35,9 @@ class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var switchAnimated: UISwitch!
|
||||
@IBOutlet weak var skeletonTypeSelector: UISegmentedControl!
|
||||
@IBOutlet weak var showOrHideSkeletonButton: UIButton!
|
||||
@IBOutlet weak var transitionDurationLabel: UILabel!
|
||||
@IBOutlet weak var transitionDurationStepper: UIStepper!
|
||||
|
||||
var type: SkeletonType {
|
||||
return skeletonTypeSelector.selectedSegmentIndex == 0 ? .solid : .gradient
|
||||
@@ -43,7 +45,6 @@ class ViewController: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.isSkeletonable = true
|
||||
collectionView.prepareSkeleton(completion: { done in
|
||||
self.view.showAnimatedSkeleton()
|
||||
})
|
||||
@@ -65,6 +66,23 @@ class ViewController: UIViewController {
|
||||
showAlertPicker()
|
||||
}
|
||||
|
||||
@IBAction func showOrHideSkeleton(_ sender: Any) {
|
||||
showOrHideSkeletonButton.setTitle((view.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal)
|
||||
view.isSkeletonActive ? hideSkeleton() : showSkeleton()
|
||||
}
|
||||
|
||||
@IBAction func transitionDurationStepperAction(_ sender: Any) {
|
||||
transitionDurationLabel.text = "transition duration: \(transitionDurationStepper.value) sec"
|
||||
}
|
||||
|
||||
func showSkeleton() {
|
||||
refreshSkeleton()
|
||||
}
|
||||
|
||||
func hideSkeleton() {
|
||||
view.hideSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
|
||||
}
|
||||
|
||||
func refreshSkeleton() {
|
||||
self.view.hideSkeleton()
|
||||
if type == .gradient { showGradientSkeleton() }
|
||||
@@ -73,23 +91,22 @@ class ViewController: UIViewController {
|
||||
|
||||
func showSolidSkeleton() {
|
||||
if switchAnimated.isOn {
|
||||
view.showAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!)
|
||||
view.showAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!, transition: .crossDissolve(transitionDurationStepper.value))
|
||||
} else {
|
||||
view.showSkeleton(usingColor: colorSelectedView.backgroundColor!)
|
||||
view.showSkeleton(usingColor: colorSelectedView.backgroundColor!, transition: .crossDissolve(transitionDurationStepper.value))
|
||||
}
|
||||
}
|
||||
|
||||
func showGradientSkeleton() {
|
||||
let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!)
|
||||
if switchAnimated.isOn {
|
||||
view.showAnimatedGradientSkeleton(usingGradient: gradient)
|
||||
view.showAnimatedGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
|
||||
} else {
|
||||
view.showGradientSkeleton(usingGradient: gradient)
|
||||
view.showGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
|
||||
}
|
||||
}
|
||||
|
||||
func showAlertPicker() {
|
||||
|
||||
let alertView = UIAlertController(title: "Select color", message: "\n\n\n\n\n\n", preferredStyle: .alert)
|
||||
|
||||
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 50, width: 260, height: 115))
|
||||
@@ -117,7 +134,6 @@ class ViewController: UIViewController {
|
||||
// MARK: - UIPickerViewDelegate, UIPickerViewDataSource
|
||||
|
||||
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
|
||||
|
||||
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
return 1
|
||||
}
|
||||
@@ -134,7 +150,6 @@ class ViewController: UIViewController {
|
||||
// MARK: - UICollectionViewDelegateFlowLayout
|
||||
|
||||
extension ViewController: UICollectionViewDelegateFlowLayout {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
return CGSize(width: view.frame.width/3 - 10, height: view.frame.width/3 - 10)
|
||||
}
|
||||
@@ -162,7 +177,7 @@ extension ViewController: SkeletonCollectionViewDataSource {
|
||||
// MARK: - UICollectionViewDataSource
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return 0
|
||||
return 10
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
let colors = [(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,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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 933 B |
@@ -1,26 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" 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="14088"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<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-->
|
||||
<!--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="667"/>
|
||||
<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" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="lastLineFillPercent">
|
||||
<integer key="value" value="40"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="linesCornerRadius">
|
||||
<integer key="value" value="6"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="nMj-pU-5wJ">
|
||||
<rect key="frame" x="141" y="20" width="93" height="93"/>
|
||||
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="93" id="gw9-nu-cKo"/>
|
||||
<constraint firstAttribute="width" constant="93" id="zB6-Lp-IUt"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="nMj-pU-5wJ" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" id="9X4-2r-AKx"/>
|
||||
<constraint firstItem="e9V-mk-xH0" firstAttribute="leading" secondItem="F9K-jU-100" secondAttribute="leading" constant="45" id="HvQ-HY-zYU"/>
|
||||
<constraint firstItem="e9V-mk-xH0" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" constant="1" id="KcB-tG-NXa"/>
|
||||
<constraint firstAttribute="height" constant="243" id="MIj-xq-gr1"/>
|
||||
<constraint firstItem="e9V-mk-xH0" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="142" id="Wcx-nZ-1lR"/>
|
||||
<constraint firstAttribute="trailing" secondItem="e9V-mk-xH0" secondAttribute="trailing" constant="43" id="XbU-Og-rht"/>
|
||||
<constraint firstItem="nMj-pU-5wJ" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="20" id="hQL-cr-MaN"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="UCB-SP-lQk">
|
||||
<rect key="frame" x="0.0" y="263" width="375" height="264"/>
|
||||
<rect key="frame" x="0.0" y="287" width="375" height="282"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="separatorColor" red="0.1061807256" green="0.84678786989999999" blue="0.031482450150000001" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<prototypes>
|
||||
@@ -42,18 +87,21 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="15" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhU-1t-AaI">
|
||||
<rect key="frame" x="118" y="29" width="237" height="20.5"/>
|
||||
<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="VhU-1t-AaI" userLabel="Label">
|
||||
<rect key="frame" x="118" y="29" width="237" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="71" id="HRL-cI-ieC"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<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="0"/>
|
||||
<integer key="value" value="5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="lastLineFillPercent">
|
||||
<integer key="value" value="20"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
@@ -83,62 +131,14 @@
|
||||
</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="F9K-jU-100">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="243"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="e9V-mk-xH0">
|
||||
<rect key="frame" x="45" y="142" width="287" height="78"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="78" id="gF5-G1-lKI"/>
|
||||
</constraints>
|
||||
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </string>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="lastLineFillPercent">
|
||||
<integer key="value" value="40"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="linesCornerRadius">
|
||||
<integer key="value" value="6"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="nMj-pU-5wJ">
|
||||
<rect key="frame" x="141" y="20" width="93" height="93"/>
|
||||
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="93" id="gw9-nu-cKo"/>
|
||||
<constraint firstAttribute="width" constant="93" id="zB6-Lp-IUt"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="nMj-pU-5wJ" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" id="9X4-2r-AKx"/>
|
||||
<constraint firstItem="e9V-mk-xH0" firstAttribute="leading" secondItem="F9K-jU-100" secondAttribute="leading" constant="45" id="HvQ-HY-zYU"/>
|
||||
<constraint firstItem="e9V-mk-xH0" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" constant="1" id="KcB-tG-NXa"/>
|
||||
<constraint firstAttribute="height" constant="243" id="MIj-xq-gr1"/>
|
||||
<constraint firstItem="e9V-mk-xH0" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="142" id="Wcx-nZ-1lR"/>
|
||||
<constraint firstAttribute="trailing" secondItem="e9V-mk-xH0" secondAttribute="trailing" constant="43" id="XbU-Og-rht"/>
|
||||
<constraint firstItem="nMj-pU-5wJ" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="20" id="hQL-cr-MaN"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XgY-1a-UGc">
|
||||
<rect key="frame" x="0.0" y="527" width="375" height="140"/>
|
||||
<rect key="frame" x="0.0" y="569" width="375" height="160"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="xOL-Sq-r4i">
|
||||
<rect key="frame" x="20" y="23" width="140" height="29"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="xOL-Sq-r4i">
|
||||
<rect key="frame" x="20" y="23" width="145" height="32"/>
|
||||
<segments>
|
||||
<segment title="Solid"/>
|
||||
<segment title="Gradient"/>
|
||||
@@ -147,47 +147,88 @@
|
||||
<action selector="changeSkeletonType:" destination="BYZ-38-t0r" eventType="valueChanged" id="iAS-ab-0jP"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vz0-qg-GcZ">
|
||||
<rect key="frame" x="310" y="21" width="49" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vz0-qg-GcZ">
|
||||
<rect key="frame" x="308" y="21" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="changeAnimated:" destination="BYZ-38-t0r" eventType="valueChanged" id="w1G-gZ-RE0"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Animated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WHN-wR-TKt">
|
||||
<rect key="frame" x="211" y="28" width="91" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Animated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WHN-wR-TKt">
|
||||
<rect key="frame" x="229" y="26" width="73" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Color" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7CF-rV-pK2">
|
||||
<rect key="frame" x="32" y="89" width="52" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Color" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7CF-rV-pK2">
|
||||
<rect key="frame" x="20" y="73.666666666666629" width="90" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iGp-rp-t1d">
|
||||
<rect key="frame" x="92" y="84" width="30" height="30"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="iGp-rp-t1d">
|
||||
<rect key="frame" x="130" y="69" width="30" height="30"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="30" id="Q3k-B1-E88"/>
|
||||
<constraint firstAttribute="height" constant="30" id="xOD-RY-U4u"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Mde-Cm-CoS">
|
||||
<rect key="frame" x="20" y="74" width="140" height="52"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Mde-Cm-CoS">
|
||||
<rect key="frame" x="20" y="58" width="140" height="52"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="52" id="3GX-2y-eQj"/>
|
||||
<constraint firstAttribute="width" constant="140" id="6cC-Y1-RKs"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<action selector="btnChangeColorTouchUpInside:" destination="BYZ-38-t0r" eventType="touchUpInside" id="cB8-Ik-LIJ"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Tdu-YQ-saq">
|
||||
<rect key="frame" x="263" y="69" width="94" height="30"/>
|
||||
<state key="normal" title="Hide skeleton"/>
|
||||
<connections>
|
||||
<action selector="showOrHideSkeleton:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Ma1-WX-Dzy"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fade Duration: 0 sec" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mrw-PM-jJJ">
|
||||
<rect key="frame" x="113.66666666666667" y="130" width="141.33333333333331" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stepper opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" maximumValue="5" stepValue="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="l4N-LL-ZrJ">
|
||||
<rect key="frame" x="263" y="123" width="94" height="32"/>
|
||||
<connections>
|
||||
<action selector="transitionDurationStepperAction:" destination="BYZ-38-t0r" eventType="valueChanged" id="jPN-df-fNs"/>
|
||||
</connections>
|
||||
</stepper>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="140" id="OH5-ja-ZlB"/>
|
||||
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="leading" secondItem="mrw-PM-jJJ" secondAttribute="trailing" constant="8" id="5iU-dO-qVc"/>
|
||||
<constraint firstItem="mrw-PM-jJJ" firstAttribute="centerY" secondItem="l4N-LL-ZrJ" secondAttribute="centerY" id="9OM-mx-4Jo"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Tdu-YQ-saq" secondAttribute="trailing" constant="18" id="BQ0-S0-YMp"/>
|
||||
<constraint firstItem="iGp-rp-t1d" firstAttribute="trailing" secondItem="Mde-Cm-CoS" secondAttribute="trailing" id="IJ3-CC-5M7"/>
|
||||
<constraint firstAttribute="height" constant="160" id="OH5-ja-ZlB"/>
|
||||
<constraint firstItem="Mde-Cm-CoS" firstAttribute="leading" secondItem="XgY-1a-UGc" secondAttribute="leading" constant="20" id="Rek-hz-pDw"/>
|
||||
<constraint firstItem="xOL-Sq-r4i" firstAttribute="top" secondItem="XgY-1a-UGc" secondAttribute="top" constant="23" id="Udf-0g-bZm"/>
|
||||
<constraint firstItem="Tdu-YQ-saq" firstAttribute="top" secondItem="vz0-qg-GcZ" secondAttribute="bottom" constant="17" id="WiR-yP-dyv"/>
|
||||
<constraint firstItem="Mde-Cm-CoS" firstAttribute="centerY" secondItem="7CF-rV-pK2" secondAttribute="centerY" id="eaN-FA-4mF"/>
|
||||
<constraint firstItem="iGp-rp-t1d" firstAttribute="centerY" secondItem="7CF-rV-pK2" secondAttribute="centerY" id="hBb-mp-AjF"/>
|
||||
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="top" secondItem="Tdu-YQ-saq" secondAttribute="bottom" constant="24" id="iA5-RB-pW2"/>
|
||||
<constraint firstItem="vz0-qg-GcZ" firstAttribute="top" secondItem="XgY-1a-UGc" secondAttribute="top" constant="21" id="iad-6N-wNf"/>
|
||||
<constraint firstItem="vz0-qg-GcZ" firstAttribute="leading" secondItem="WHN-wR-TKt" secondAttribute="trailing" constant="6" id="jgu-tV-gqO"/>
|
||||
<constraint firstItem="WHN-wR-TKt" firstAttribute="centerY" secondItem="vz0-qg-GcZ" secondAttribute="centerY" id="kTu-fb-Bc8"/>
|
||||
<constraint firstAttribute="trailing" secondItem="vz0-qg-GcZ" secondAttribute="trailing" constant="18" id="ktq-JT-uoR"/>
|
||||
<constraint firstItem="xOL-Sq-r4i" firstAttribute="leading" secondItem="XgY-1a-UGc" secondAttribute="leading" constant="20" id="pY0-qd-xmK"/>
|
||||
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="trailing" secondItem="Tdu-YQ-saq" secondAttribute="trailing" id="ql2-Z9-dnv"/>
|
||||
<constraint firstItem="iGp-rp-t1d" firstAttribute="leading" secondItem="7CF-rV-pK2" secondAttribute="trailing" constant="20" id="vWD-0m-dMp"/>
|
||||
<constraint firstItem="7CF-rV-pK2" firstAttribute="centerY" secondItem="Tdu-YQ-saq" secondAttribute="centerY" id="x0d-LB-A4q"/>
|
||||
<constraint firstItem="7CF-rV-pK2" firstAttribute="leading" secondItem="xOL-Sq-r4i" secondAttribute="leading" id="yEL-Nv-z76"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<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"/>
|
||||
@@ -202,21 +243,60 @@
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="NO"/>
|
||||
<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="-2582" y="-400"/>
|
||||
<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"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="Ao1-hk-zrH"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" title="Item" id="iKp-9S-aib"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="M03-a6-GOC" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1644" y="340"/>
|
||||
</scene>
|
||||
<!--Tab Bar Controller-->
|
||||
<scene sceneID="U6k-MC-AHH">
|
||||
<objects>
|
||||
<tabBarController automaticallyAdjustsScrollViewInsets="NO" id="Va7-1y-Tel" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="HSI-2O-RyO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tabBar>
|
||||
<connections>
|
||||
<segue destination="BYZ-38-t0r" kind="relationship" relationship="viewControllers" id="dL3-9L-KNU"/>
|
||||
<segue destination="dv8-ph-Ehg" kind="relationship" relationship="viewControllers" id="8QB-uV-gaF"/>
|
||||
</connections>
|
||||
</tabBarController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="huq-Fh-0sW" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-2172" y="-555"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
@@ -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")]
|
||||
@@ -10,11 +10,12 @@ import UIKit
|
||||
import SkeletonView
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var tableview: UITableView! {
|
||||
didSet {
|
||||
tableview.rowHeight = UITableView.automaticDimension
|
||||
tableview.estimatedRowHeight = 120.0
|
||||
tableview.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "HeaderIdentifier")
|
||||
tableview.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "FooterIdentifier")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +36,9 @@ class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var switchAnimated: UISwitch!
|
||||
@IBOutlet weak var skeletonTypeSelector: UISegmentedControl!
|
||||
@IBOutlet weak var showOrHideSkeletonButton: UIButton!
|
||||
@IBOutlet weak var transitionDurationLabel: UILabel!
|
||||
@IBOutlet weak var transitionDurationStepper: UIStepper!
|
||||
|
||||
var type: SkeletonType {
|
||||
return skeletonTypeSelector.selectedSegmentIndex == 0 ? .solid : .gradient
|
||||
@@ -43,11 +47,11 @@ class ViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableview.isSkeletonable = true
|
||||
view.showAnimatedSkeleton()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
view.showAnimatedSkeleton()
|
||||
}
|
||||
|
||||
@IBAction func changeAnimated(_ sender: Any) {
|
||||
@@ -66,31 +70,62 @@ class ViewController: UIViewController {
|
||||
showAlertPicker()
|
||||
}
|
||||
|
||||
func refreshSkeleton() {
|
||||
self.view.hideSkeleton()
|
||||
if type == .gradient { showGradientSkeleton() }
|
||||
else { showSolidSkeleton() }
|
||||
@IBAction func showOrHideSkeleton(_ sender: Any) {
|
||||
showOrHideSkeletonButton.setTitle((view.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal)
|
||||
view.isSkeletonActive ? hideSkeleton() : showSkeleton()
|
||||
}
|
||||
|
||||
func showSolidSkeleton() {
|
||||
if switchAnimated.isOn {
|
||||
view.showAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!)
|
||||
} else {
|
||||
view.showSkeleton(usingColor: colorSelectedView.backgroundColor!)
|
||||
@IBAction func transitionDurationStepperAction(_ sender: Any) {
|
||||
transitionDurationLabel.text = "Transition duration: \(transitionDurationStepper.value) sec"
|
||||
}
|
||||
|
||||
func showSkeleton() {
|
||||
if type == .gradient {
|
||||
let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!)
|
||||
if switchAnimated.isOn {
|
||||
view.showAnimatedGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
|
||||
}
|
||||
else {
|
||||
view.showGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value))
|
||||
}
|
||||
}
|
||||
else {
|
||||
if switchAnimated.isOn {
|
||||
view.showAnimatedSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
|
||||
}
|
||||
else {
|
||||
view.showSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showGradientSkeleton() {
|
||||
func hideSkeleton() {
|
||||
view.hideSkeleton(transition: .crossDissolve(transitionDurationStepper.value))
|
||||
}
|
||||
|
||||
func refreshSkeleton() {
|
||||
if type == .gradient { showOrUpdateGradientSkeleton() }
|
||||
else { showOrUpdatepdateSolidSkeleton() }
|
||||
}
|
||||
|
||||
func showOrUpdatepdateSolidSkeleton() {
|
||||
if switchAnimated.isOn {
|
||||
view.updateAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!)
|
||||
} else {
|
||||
view.updateSkeleton(usingColor: colorSelectedView.backgroundColor!)
|
||||
}
|
||||
}
|
||||
|
||||
func showOrUpdateGradientSkeleton() {
|
||||
let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!)
|
||||
if switchAnimated.isOn {
|
||||
view.showAnimatedGradientSkeleton(usingGradient: gradient)
|
||||
view.updateAnimatedGradientSkeleton(usingGradient: gradient)
|
||||
} else {
|
||||
view.showGradientSkeleton(usingGradient: gradient)
|
||||
view.updateGradientSkeleton(usingGradient: gradient)
|
||||
}
|
||||
}
|
||||
|
||||
func showAlertPicker() {
|
||||
|
||||
let alertView = UIAlertController(title: "Select color", message: "\n\n\n\n\n\n", preferredStyle: .alert)
|
||||
|
||||
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 50, width: 260, height: 115))
|
||||
@@ -116,7 +151,6 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
|
||||
|
||||
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
return 1
|
||||
}
|
||||
@@ -131,9 +165,8 @@ extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
|
||||
}
|
||||
|
||||
extension ViewController: SkeletonTableViewDataSource {
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return 9
|
||||
return 10
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
|
||||
@@ -147,3 +180,24 @@ extension ViewController: SkeletonTableViewDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
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")!
|
||||
header.textLabel?.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")!
|
||||
footer.textLabel?.text = "footer => \(section)"
|
||||
return footer
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
gem 'cocoapods', '~> 1.7.0.beta.2'
|
||||
@@ -0,0 +1,212 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.0)
|
||||
activesupport (4.2.11.1)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.6.0)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
atomos (0.1.3)
|
||||
babosa (1.0.2)
|
||||
claide (1.0.2)
|
||||
cocoapods (1.7.5)
|
||||
activesupport (>= 4.0.2, < 5)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.7.5)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.2.2, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-stats (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.3.1, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.6.6)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (~> 1.4)
|
||||
xcodeproj (>= 1.10.0, < 2.0)
|
||||
cocoapods-core (1.7.5)
|
||||
activesupport (>= 4.0.2, < 6)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.4)
|
||||
cocoapods-downloader (1.2.2)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.0)
|
||||
cocoapods-stats (1.1.0)
|
||||
cocoapods-trunk (1.3.1)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander-fastlane (4.4.6)
|
||||
highline (~> 1.7.2)
|
||||
concurrent-ruby (1.1.5)
|
||||
declarative (0.0.10)
|
||||
declarative-option (0.1.0)
|
||||
digest-crc (0.4.1)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.4)
|
||||
emoji_regex (1.0.1)
|
||||
escape (0.0.4)
|
||||
excon (0.71.0)
|
||||
faraday (0.15.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
faraday (>= 0.7.4)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday_middleware (0.13.1)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
fastimage (2.1.5)
|
||||
fastlane (2.128.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
babosa (>= 1.0.2, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 2.0)
|
||||
excon (>= 0.45.0, < 1.0.0)
|
||||
faraday (~> 0.9)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 0.9)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-api-client (>= 0.21.2, < 0.24.0)
|
||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||
highline (>= 1.7.2, < 2.0.0)
|
||||
json (< 3.0.0)
|
||||
jwt (~> 2.1.0)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multi_xml (~> 0.5)
|
||||
multipart-post (~> 2.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
public_suffix (~> 2.0.0)
|
||||
rubyzip (>= 1.2.2, < 2.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.8.1, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.23.9)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.5, < 0.7.0)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mime-types (~> 3.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.9)
|
||||
google-cloud-core (1.3.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-env (1.2.0)
|
||||
faraday (~> 0.11)
|
||||
google-cloud-storage (1.16.0)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.23)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (>= 0.6.2, < 0.10.0)
|
||||
googleauth (0.6.7)
|
||||
faraday (~> 0.12)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.7)
|
||||
highline (1.7.10)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.2.0)
|
||||
jwt (2.1.0)
|
||||
memoist (0.16.0)
|
||||
mime-types (3.2.2)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2019.0331)
|
||||
mini_magick (4.9.5)
|
||||
minitest (5.11.3)
|
||||
molinillo (0.6.6)
|
||||
multi_json (1.13.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.2.6)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.0)
|
||||
netrc (0.11.0)
|
||||
os (1.0.1)
|
||||
plist (3.5.0)
|
||||
public_suffix (2.0.5)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (1.4.0)
|
||||
rubyzip (1.3.0)
|
||||
security (0.1.3)
|
||||
signet (0.11.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.5)
|
||||
CFPropertyList
|
||||
naturally
|
||||
slack-notifier (2.3.2)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
tty-cursor (0.7.0)
|
||||
tty-screen (0.7.0)
|
||||
tty-spinner (0.9.1)
|
||||
tty-cursor (~> 0.7)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.6)
|
||||
unicode-display_width (1.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.11.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.2.6)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.0)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods (~> 1.7.0.beta.2)
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.0.1
|
||||
@@ -0,0 +1,23 @@
|
||||
// swift-tools-version:5.0
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "SkeletonView",
|
||||
platforms: [
|
||||
.iOS(.v9),
|
||||
.tvOS(.v9)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "SkeletonView",
|
||||
targets: ["SkeletonView"])
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "SkeletonView",
|
||||
dependencies: [],
|
||||
path: "Sources")
|
||||
],
|
||||
swiftLanguageVersions: [.v5]
|
||||
)
|
||||
@@ -1,39 +1,39 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/Juanpe/SkeletonView">
|
||||
<img src="https://img.shields.io/travis/Juanpe/SkeletonView.svg">
|
||||
<a href="https://app.bitrise.io/app/6d289a17e22c8323">
|
||||
<img src="https://app.bitrise.io/app/6d289a17e22c8323/status.svg?token=fI7gKC41XD9-aRXDScCKBw&branch=master">
|
||||
</a>
|
||||
<a href="https://instagram.github.io/IGListKit/">
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<a href="https://github.com/Juanpe/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/p/SkeletonView.svg" alt="Platforms">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/Swift-4.1-orange.svg" />
|
||||
<img src="https://img.shields.io/badge/Swift-5-orange.svg" />
|
||||
<a href="https://cocoapods.org/pods/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/v/SkeletonView.svg" alt="CocoaPods" />
|
||||
</a>
|
||||
<a href="https://github.com/Carthage/Carthage">
|
||||
<img src="https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat" alt="Carthage" />
|
||||
<img src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat" alt="Carthage" />
|
||||
</a>
|
||||
<a href="https://cocoapods.org/pods/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/dt/SkeletonView.svg?style=flat" alt="CocoaPods downloads" />
|
||||
<a href="https://github.com/apple/swift-package-manager">
|
||||
<img src="https://img.shields.io/badge/SPM-compatible-brightgreen.svg" alt="SPM" />
|
||||
</a>
|
||||
<a href="https://twitter.com/JuanpeCatalan">
|
||||
<img src="https://img.shields.io/badge/contact-@JuanpeCatalan-blue.svg?style=flat" alt="Twitter: @JuanpeCatalan" />
|
||||
</a>
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=MJ4Y2D9DEX6FL&lc=ES&item_name=SkeletonView¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted">
|
||||
<img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Paypal" />
|
||||
<br/>
|
||||
<a href="https://gitter.im/SkeletonView/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge">
|
||||
<img src="https://badges.gitter.im/SkeletonView/community.svg?style=flat" />
|
||||
</a>
|
||||
<a href="https://twitter.com/intent/tweet?text=Wow%20This%20library%20is%20awesome:&url=https%3A%2F%2Fgithub.com%2FJuanpe%2FSkeletonView">
|
||||
<img src="https://img.shields.io/twitter/url/https/github.com/Juanpe/SkeletonView.svg?style=social" alt="License" />
|
||||
</a>
|
||||
<a href="https://twitter.com/JuanpeCatalan">
|
||||
<img src="https://img.shields.io/twitter/follow/JuanpeCatalan.svg?style=social&label=Follow" alt="Twitter" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
🌎 Translations: </br>
|
||||
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) by [@WhatsXie](https://twitter.com/WhatsXie) </br>
|
||||
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) by [@brunomunizaf](https://twitter.com/brunomuniz_af)
|
||||
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) by [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
|
||||
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) by [@techinpark](https://twitter.com/techinpark)
|
||||
|
||||
Today almost all apps have async processes, such as Api requests, long running processes, etc. And while the processes are working, usually developers place a loading view to show users that something is going on.
|
||||
|
||||
@@ -42,21 +42,22 @@ Today almost all apps have async processes, such as Api requests, long running p
|
||||
Enjoy it! 🙂
|
||||
|
||||
* [Features](#-features)
|
||||
* [Requirements](#-supported-os--sdk-versions)
|
||||
* [Guides](#-guides)
|
||||
* [Example Project](#-example)
|
||||
* [Installation](#-installation)
|
||||
* [Cocoapods](#using-cocoapods)
|
||||
* [Carthage](#using-carthage)
|
||||
* [SPM](#using-swift-package-manager)
|
||||
* [How to use](#-how-to-use)
|
||||
* [Collections](#-collections)
|
||||
* [Multiline text](#-multiline-text)
|
||||
* [Custom colors](#-custom-colors)
|
||||
* [Appearance](#-appearance)
|
||||
* [Custom animations](#-custom-animations)
|
||||
* [Transitions](#-transitions)
|
||||
* [Hierarchy](#-hierarchy)
|
||||
* [Debug](#-debug)
|
||||
* [Documentation](#-documentation)
|
||||
* [Supported OS & SDK Versions](#-supported-os--sdk-versions)
|
||||
* [Next steps](#-next-steps)
|
||||
* [Contributing](#-contributing)
|
||||
* [Mentions](#-mentions)
|
||||
@@ -74,20 +75,10 @@ Enjoy it! 🙂
|
||||
- [x] Simple Swift syntax
|
||||
- [x] Lightweight readable codebase
|
||||
|
||||
### 📋 Supported OS & SDK Versions
|
||||
|
||||
* iOS 9.0+
|
||||
* tvOS 9.0+
|
||||
* Swift 4
|
||||
|
||||
### 🎬 Guides
|
||||
## 🎬 Guides
|
||||
|
||||
[<img src="Assets/thumb_getting_started.png">](https://youtu.be/75kgOhWsPNA)
|
||||
|
||||
### 🔮 Example
|
||||
|
||||
To run the example project, clone the repo and run `SkeletonViewExample` target.
|
||||
|
||||
## 📲 Installation
|
||||
|
||||
#### Using [CocoaPods](https://cocoapods.org)
|
||||
@@ -106,6 +97,18 @@ Edit your `Cartfile` and specify the dependency:
|
||||
github "Juanpe/SkeletonView"
|
||||
```
|
||||
|
||||
#### Using [Swift Package Manager](https://github.com/apple/swift-package-manager)
|
||||
|
||||
Once you have your Swift package set up, adding `SkeletonView` as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0")
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 🐒 How to use
|
||||
|
||||
Only **3** steps needed to use `SkeletonView`:
|
||||
@@ -170,11 +173,38 @@ avatarImageView.isSkeletonable = true
|
||||
> **IMPORTANT!**
|
||||
>>```SkeletonView``` is recursive, so if you want show the skeleton in all skeletonable views, you only need to call the show method in the main container view. For example, with UIViewControllers
|
||||
|
||||
### Extra
|
||||
|
||||
#### Skeleton views layout
|
||||
|
||||
Sometimes skeleton layout may not fit your layout because the parent view bounds have changed. ~For example, rotating the device.~
|
||||
|
||||
You can relayout the skeleton views like so:
|
||||
|
||||
```swift
|
||||
override func viewDidLayoutSubviews() {
|
||||
view.layoutSkeletonIfNeeded()
|
||||
}
|
||||
```
|
||||
|
||||
⚠️⚠️ You shouldn't call this method. From *version 1.8.1* you don't need to call this method, the library does automatically. So, you can use this method *ONLY* in the cases when you need to update the layout of the skeleton manually.
|
||||
|
||||
#### Update skeleton configuration
|
||||
|
||||
You can change the skeleton configuration at any time like its colour, animation, etc. with the following methods:
|
||||
|
||||
```swift
|
||||
(1) view.updateSkeleton() // Solid
|
||||
(2) view.updateGradientSkeleton() // Gradient
|
||||
(3) view.updateAnimatedSkeleton() // Solid animated
|
||||
(4) view.updateAnimatedGradientSkeleton() // Gradient animated
|
||||
```
|
||||
|
||||
### 🌿 Collections
|
||||
|
||||
Now, ```SkeletonView``` is compatible with ```UITableView``` and ```UICollectionView```.
|
||||
|
||||
###### UITableView
|
||||
#### UITableView
|
||||
|
||||
If you want to show the skeleton in a ```UITableView```, you need to conform to ```SkeletonTableViewDataSource``` protocol.
|
||||
|
||||
@@ -183,6 +213,8 @@ 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, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier?
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier?
|
||||
}
|
||||
```
|
||||
As you can see, this protocol inherits from ```UITableViewDataSource```, so you can replace this protocol with the skeleton protocol.
|
||||
@@ -200,6 +232,16 @@ func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection s
|
||||
// It calculates how many cells need to populate whole tableview
|
||||
```
|
||||
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier?
|
||||
// Default: nil
|
||||
```
|
||||
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier?
|
||||
// Default: nil
|
||||
```
|
||||
|
||||
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
|
||||
@@ -215,7 +257,15 @@ There is only one method you need to implement to let Skeleton know the cell ide
|
||||
> **IMPORTANT!**
|
||||
> If you are using resizable cells (`tableView.rowHeight = UITableViewAutomaticDimension` ), it's mandatory define the `estimatedRowHeight`.
|
||||
|
||||
###### UICollectionView
|
||||
👩🏼🏫 **How specify which elements are skeletonables?**
|
||||
|
||||
Here is an illustration that shows how you should specify which elements are skeletonables when you are using an `UITableView`:
|
||||
|
||||

|
||||
|
||||
As you can see, we have to make skeletonable the tableview, the cell and the UI elements, but we don't need to set as skeletonable the `contentView`
|
||||
|
||||
#### UICollectionView
|
||||
|
||||
For ```UICollectionView```, you need to conform to ```SkeletonCollectionViewDataSource``` protocol.
|
||||
|
||||
@@ -288,9 +338,9 @@ Besides, ```SkeletonView``` features 20 flat colors 🤙🏼
|
||||
|
||||
Default values:
|
||||
- **tintColor**: UIColor
|
||||
- *default: .clouds*
|
||||
- *default: `.skeletonDefault` (same as `.clouds` but adaptive to dark mode)*
|
||||
- **gradient**: SkeletonGradient
|
||||
- *default: SkeletonGradient(baseColor: .clouds)*
|
||||
- *default: `SkeletonGradient(baseColor: .skeletonDefault)`*
|
||||
- **multilineHeight**: CGFloat
|
||||
- *default: 15*
|
||||
- **multilineSpacing**: CGFloat
|
||||
@@ -306,6 +356,12 @@ SkeletonAppearance.default.multilineHeight = 20
|
||||
SkeletonAppearance.default.tintColor = .green
|
||||
```
|
||||
|
||||
You can also specifiy these line appearance properties on a per-label basis:
|
||||
- **lastLineFillPercent**: Int
|
||||
- **linesCornerRadius**: Int
|
||||
- **skeletonLineSpacing**: CGFloat
|
||||
- **skeletonPaddingInsets**: UIEdgeInsets
|
||||
|
||||
|
||||
### 🤓 Custom animations
|
||||
|
||||
@@ -358,20 +414,63 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
|
||||
Exist another way to create sliding animations, just using this shortcut:
|
||||
>>```let animation = GradientDirection.leftToRight.slidingAnimation()```
|
||||
|
||||
|
||||
|
||||
### 🏄 Transitions
|
||||
|
||||
```SkeletonView``` has build-in transitions to **show** or **hide** the skeletons in a *smoother* way 🤙
|
||||
|
||||
To use the transition, simply add the ```transition``` parameter to your ```showSkeleton()``` or ```hideSkeleton()``` function with the transition time, like this:
|
||||
|
||||
```swift
|
||||
view.showSkeleton(transition: .crossDissolve(0.25)) //Show skeleton cross dissolve transition with 0.25 seconds fade time
|
||||
view.hideSkeleton(transition: .crossDissolve(0.25)) //Hide skeleton cross dissolve transition with 0.25 seconds fade time
|
||||
|
||||
```
|
||||
|
||||
**Preview**
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<center>None</center>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<center>Cross dissolve</center>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<img src="Assets/skeleton_transition_nofade.gif"></img>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<img src="Assets/skeleton_transition_fade.gif"></img>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
### 👨👧👦 Hierarchy
|
||||
|
||||
Since ```SkeletonView``` is recursive, and we want skeleton to be very efficient, we want to stop recursion as soon as possible. For this reason, you must set the container view as `Skeletonable`, because Skeleton will stop looking for `skeletonable` subviews as soon as a view is not Skeletonable, breaking then the recursion.
|
||||
|
||||
Because an image is worth a thousand words:
|
||||
|
||||
In this example we have a `UIViewController` with a `ContainerView` and a `UITableView`. When the view is ready, we show the skeleton using this method:
|
||||
```
|
||||
view.showSkeleton()
|
||||
```
|
||||
|
||||
> ```ìsSkeletonable```= ☠️
|
||||
|
||||
| Configuration | Result
|
||||
|------- | -------
|
||||
| | 
|
||||
| | 
|
||||
| | 
|
||||
| | 
|
||||
| Configuration | Result|
|
||||
|:-------:|:-------:|
|
||||
|<img src="Assets/no_skeletonable.jpg" width="350"/> | <img src="Assets/no_skeletonables_result.png" width="350"/>|
|
||||
|<img src="Assets/container_no_skeletonable.jpg" width="350"/> | <img src="Assets/no_skeletonables_result.png" width="350"/>|
|
||||
|<img src="Assets/container_skeletonable.jpg" width="350"/> | <img src="Assets/container_skeletonable_result.png" width="350"/>|
|
||||
|<img src="Assets/all_skeletonables.jpg" width="350"/>| <img src="Assets/all_skeletonables_result.png" width="350"/>|
|
||||
|<img src="Assets/tableview_no_skeletonable.jpg" width="350"/> | <img src="Assets/tableview_no_skeletonable_result.png" height="350"/>|
|
||||
|<img src="Assets/tableview_skeletonable.jpg" width="350"/> | <img src="Assets/tableview_skeletonable_result.png" height="350"/>|
|
||||
|
||||
|
||||
### 🔬 Debug
|
||||
@@ -403,6 +502,12 @@ Then, when the skeleton appears, you can see the view hierarchy in the Xcode con
|
||||
### 📚 Documentation
|
||||
Coming soon...😅
|
||||
|
||||
### 📋 Supported OS & SDK Versions
|
||||
|
||||
* iOS 9.0+
|
||||
* tvOS 9.0+
|
||||
* Swift 5
|
||||
|
||||
## 📬 Next steps
|
||||
|
||||
* [x] Set the filling percent of the last line in multiline elements
|
||||
@@ -413,8 +518,8 @@ Coming soon...😅
|
||||
* [x] Add recovery state
|
||||
* [x] Custom default appearance
|
||||
* [x] Debug mode
|
||||
* [x] Add animations when it shows/hides the skeletons
|
||||
* [ ] Custom collections compatible
|
||||
* [ ] Add animations when it shows/hides the skeletons
|
||||
* [ ] MacOS and WatchOS compatible
|
||||
|
||||
## ❤️ Contributing
|
||||
@@ -440,6 +545,8 @@ See [all contributors](https://github.com/Juanpe/SkeletonView/graphs/contributor
|
||||
- [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759)
|
||||
- [CocoaControls](https://www.cocoacontrols.com/controls/skeletonview)
|
||||
- [Awesome iOS Newsletter #74](https://ios.libhunt.com/newsletter/74)
|
||||
- [Swift News #36](https://www.youtube.com/watch?v=mAGpsQiy6so)
|
||||
- [Best iOS articles, new tools & more](https://medium.com/flawless-app-stories/best-ios-articles-new-tools-more-fcbe673e10d)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,501 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://app.bitrise.io/app/6d289a17e22c8323">
|
||||
<img src="https://app.bitrise.io/app/6d289a17e22c8323/status.svg?token=fI7gKC41XD9-aRXDScCKBw&branch=master">
|
||||
</a>
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<a href="https://github.com/Juanpe/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/p/SkeletonView.svg" alt="Platforms">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/Swift-5-orange.svg" />
|
||||
<a href="https://cocoapods.org/pods/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/v/SkeletonView.svg" alt="CocoaPods" />
|
||||
</a>
|
||||
<a href="https://github.com/Carthage/Carthage">
|
||||
<img src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat" alt="Carthage" />
|
||||
</a>
|
||||
<a href="https://github.com/apple/swift-package-manager">
|
||||
<img src="https://img.shields.io/badge/SPM-compatible-brightgreen.svg" alt="SPM" />
|
||||
</a>
|
||||
<a href="https://twitter.com/JuanpeCatalan">
|
||||
<img src="https://img.shields.io/badge/contact-@JuanpeCatalan-blue.svg?style=flat" alt="Twitter: @JuanpeCatalan" />
|
||||
</a>
|
||||
<br/>
|
||||
<a href="https://twitter.com/intent/tweet?text=Wow%20This%20library%20is%20awesome:&url=https%3A%2F%2Fgithub.com%2FJuanpe%2FSkeletonView">
|
||||
<img src="https://img.shields.io/twitter/url/https/github.com/Juanpe/SkeletonView.svg?style=social" alt="License" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
🌎 번역에 도움을 주신분들: </br>
|
||||
[🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) by [@WhatsXie](https://twitter.com/WhatsXie) </br>
|
||||
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) by [@brunomunizaf](https://twitter.com/brunomuniz_af) </br>
|
||||
[🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) by [@techinpark](https://twitter.com/techinpark)
|
||||
|
||||
|
||||
오늘날 거의 대부분의 앱들은 비동기 방식의 API 호출을 사용하는 프로세스를 가지고 있습니다.
|
||||
프로세스가 작동하는동안 개발자들은 작업이 실행되고 있다는것을 사용자들에게 보여주기 위해서 로딩 뷰를 배치합니다.
|
||||
|
||||
```SkeletonView```는 이러한 필요에 의해 고안되었고, 사용자들에게 무엇인가 로딩이 되고 있다는것을 보여주면서 기다리는 콘텐츠에 대해서도 미리 준비할 수 있게 해주는 우아하게 표현할수 있는 방법입니다
|
||||
|
||||
맘껏 누리세요 🙂
|
||||
|
||||
* [기능](#-features)
|
||||
* [가이드](#-guides)
|
||||
* [설치방법](#-installation)
|
||||
* [Cocoapods](#using-cocoapods)
|
||||
* [Carthage](#using-carthage)
|
||||
* [SPM](#using-swift-package-manager)
|
||||
* [어떻게 사용하나요?](#-how-to-use)
|
||||
* [Collections](#-collections)
|
||||
* [Multiline text](#-multiline-text)
|
||||
* [Custom colors](#-custom-colors)
|
||||
* [Appearance](#-appearance)
|
||||
* [Custom animations](#-custom-animations)
|
||||
* [Hierarchy](#-hierarchy)
|
||||
* [Debug](#-debug)
|
||||
* [문서화](#-documentation)
|
||||
* [지원되는 OS와 SDK 버전](#-supported-os--sdk-versions)
|
||||
* [Next steps](#-next-steps)
|
||||
* [Contributing](#-contributing)
|
||||
* [Mentions](#-mentions)
|
||||
* [개발자](#-author)
|
||||
* [라이센스](#-license)
|
||||
|
||||
|
||||
## 🌟 기능
|
||||
|
||||
- [x] 사용이 쉽습니다
|
||||
- [x] 모든 `UIView`에서 사용가능합니다
|
||||
- [x] 전체 커스터마이징이 가능합니다
|
||||
- [x] 공통으로 이용가능합니다 (iPhone & iPad)
|
||||
- [x] `Interface Builder` 에서 사용 가능합니다.
|
||||
- [x] 간단한 스위프트 문법
|
||||
- [x] 가볍고 가독성 좋은 코드
|
||||
|
||||
## 🎬 사용가이드
|
||||
|
||||
[<img src="Assets/thumb_getting_started.png">](https://youtu.be/75kgOhWsPNA)
|
||||
|
||||
## 📲 설치 방법
|
||||
|
||||
#### [CocoaPods](https://cocoapods.org) 로 사용하기
|
||||
|
||||
당신의 프로젝트 `Podfile` 파일에 아래와 같이 입력합니다:
|
||||
|
||||
```ruby
|
||||
pod "SkeletonView"
|
||||
```
|
||||
|
||||
#### [Carthage](https://github.com/carthage)로 사용하기
|
||||
|
||||
당신의 프로젝트 `Cartfile` 파일에 아래와 같이 입력합니다:
|
||||
|
||||
```bash
|
||||
github "Juanpe/SkeletonView"
|
||||
```
|
||||
|
||||
#### [Swift Package Manager](https://github.com/apple/swift-package-manager)로 사용하기
|
||||
|
||||
|
||||
당신의 프로젝트에 Swift package를 설정한다면, `SkeletonView` 를 `Package.swift` 파일에 있는 `dependencies`에 추가하시면 됩니다.
|
||||
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.6")
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 🐒 어떻게 사용하나요?
|
||||
|
||||
`SkeletonView` 를 이용하기 위해서는 딱 **3** 단계만 기억하세요:
|
||||
|
||||
**1.** 사용하고자 하는 파일에서 `SkeletonView` 를 `Import` 합니다.
|
||||
```swift
|
||||
import SkeletonView
|
||||
```
|
||||
|
||||
**2.** 자, 그렇다면 UIView 속성에 `skeletonables` 를 이용하실 수 있습니다. 두가지 옵션이 있습니다
|
||||
|
||||
**코드로 사용하는 방법:**
|
||||
```swift
|
||||
avatarImageView.isSkeletonable = true
|
||||
```
|
||||
**인터페이스빌더 / 스토리보드를 이용하는 방법:**
|
||||
|
||||

|
||||
|
||||
**3.** 당신이 뷰를 세팅할때, **skeleton** 옵션을 사용 할 수 있습니다. 총 **4** 가지 옵션을 지원합니다:
|
||||
|
||||
```swift
|
||||
(1) view.showSkeleton() // Solid
|
||||
(2) view.showGradientSkeleton() // Gradient
|
||||
(3) view.showAnimatedSkeleton() // Solid animated
|
||||
(4) view.showAnimatedGradientSkeleton() // Gradient animated
|
||||
```
|
||||
|
||||
**미리보기**
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="25%">
|
||||
<center>Solid</center>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<center>Gradient</center>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<center>Solid Animated</center>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<center>Gradient Animated</center>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="25%">
|
||||
<img src="Assets/solid.png"></img>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<img src="Assets/gradient.png"></img>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<img src="Assets/solid_animated.gif"></img>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<img src="Assets/gradient_animated.gif"></img>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> **중요!**
|
||||
>>```SkeletonView``` 는 재귀적으로 되어있습니다, 만약 모든 뷰에 대해서 skeleton을 호출하고 싶다면, 메인 컨테이너 뷰에서 show `method`를 호출하여야 합니다. 예를 들자면 UIViewControllers가 있습니다.
|
||||
|
||||
|
||||
|
||||
### 🌿 Collections
|
||||
|
||||
현재, ```SkeletonView``` 는 ```UITableView``` 와 ```UICollectionView```에서 호환됩니다.
|
||||
|
||||
#### UITableView
|
||||
|
||||
만약 ```UITableView```에서 skeleton을 호출하고 싶다면, ```SkeletonTableViewDataSource``` protocol 을 구현하여야 합니다.
|
||||
|
||||
``` swift
|
||||
public protocol SkeletonTableViewDataSource: UITableViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
}
|
||||
```
|
||||
|
||||
해당 프로토클은 보시다시피 ```UITableViewDataSource```를 상속받아 구현하였으므로, skeleton의 protocol과 대체 가능합니다.
|
||||
|
||||
프로토콜의 기본 구현은 다음과 같습니다:
|
||||
|
||||
``` swift
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int
|
||||
// Default: 1
|
||||
```
|
||||
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
// Default:
|
||||
// 전체 테이블 뷰를 채우는데 필요한 셀 수를 계산합니다
|
||||
```
|
||||
|
||||
해당 메소드는 당신이 구현하여야할 cell identifier을 아는 경우에만 사용합니다, 해당 메소드는 기본으로 구현하지 않아도됩니다 :
|
||||
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
```
|
||||
|
||||
**Example**
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
|
||||
return "CellIdentifier"
|
||||
}
|
||||
```
|
||||
|
||||
> **중요!**
|
||||
> 만약 사이즈가 변하는 셀을 사용한다면 (`tableView.rowHeight = UITableViewAutomaticDimension` ),`estimatedRowHeight`를 무조건 정의해주세요.
|
||||
|
||||
|
||||
👩🏼🏫 **어떻게 특정 요소에 skeleton 을 지정할까요?**
|
||||
|
||||
아래의 그림은 `UITableView` 에서 특정한 요소에 skeleton 을 지정하는 방법을 보여주는 이미지 입니다:
|
||||
|
||||

|
||||
|
||||
위의 이미지에서 보이듯, 테이블 뷰와 셀에 들어가는 UI 요소들에는 적용을 해야하지만, `contentView`에 skeleton을 적용할 필요는 없습니다.
|
||||
|
||||
#### UICollectionView
|
||||
|
||||
```UICollectionView``` 에 적용을 하기 위해서는, ```SkeletonCollectionViewDataSource``` protocol 을 구현할 필요가 있습니다.
|
||||
|
||||
``` swift
|
||||
public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
}
|
||||
```
|
||||
|
||||
```UITableView``` 와 사용방법은 같습니다.
|
||||
|
||||
### 📰 Multiline text
|
||||
|
||||
|
||||

|
||||
|
||||
텍스트가 들어있는 요소를 사용한다면, ```SkeletonView``` 에서 텍스트의 라인을 그려줍니다.
|
||||
그리고, 원하는 라인 수를 설정할 수 있습니다. 만약 ```numberOfLines``` 을 0으로 설정한다면, 자동으로 필요한 라인수를 계산해서 그려줍니다. 대신 값이 설정되어있다면 설정된 수만큼의 라인이 그려집니다.
|
||||
|
||||
##### 🎛 Customize
|
||||
|
||||
당신은 멀티라인을 위해 몇가지 옵션을 설정할 수 있습니다.
|
||||
|
||||
|
||||
| 속성 | 값 | 기본값 | 미리보기 |
|
||||
| ----------------------------------------------- | --------- | ----- | ---------------------------------- |
|
||||
| 마지막 라인의 **퍼센트** 를 지정 할 수 있습니다. | `0...100` | `70%` |  |
|
||||
| 라인의 **Corner radius** 를 지정할 수 있습니다. (**새로운기능**) | `0...10` | `0` |  |
|
||||
|
||||
|
||||
|
||||
라인의 radius를 지정하기 위해서는 **코드** 를 이용합니다, 아래 처럼 코드를 작성합니다:
|
||||
```swift
|
||||
descriptionTextView.lastLineFillPercent = 50
|
||||
descriptionTextView.linesCornerRadius = 5
|
||||
```
|
||||
|
||||
혹은 **IB/Storyboard** 를 이용하실 수 있습니다:
|
||||
|
||||

|
||||
|
||||
### 🎨 Custom colors
|
||||
|
||||
당신은 skeleton의 색상을 지정 할 수 있습니다. 간단하게 원하는 색상을 파라미터로 넘겨주시면 됩니다.
|
||||
|
||||
**단색 이용방법**
|
||||
``` swift
|
||||
view.showSkeleton(usingColor: UIColor.gray) // Solid
|
||||
// or
|
||||
view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0))
|
||||
```
|
||||
**그라디언트 이용 방법**
|
||||
``` swift
|
||||
let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue)
|
||||
view.showGradientSkeleton(usingGradient: gradient) // Gradient
|
||||
```
|
||||
|
||||
게다가, ```SkeletonView``` 에서는 20가지의 기본 컬러를 지원합니다 🤙🏼
|
||||
|
||||
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
|
||||
|
||||

|
||||
###### 위 이미지는 [https://flatuicolors.com](https://flatuicolors.com) 사이트에서 발췌했습니다.
|
||||
|
||||
### 🦋 Appearance
|
||||
|
||||
**새로운 사항** skeleton 은 기본설정 값이 정해져 있습니다. 만약 커스텀 컬러를 사용할 필요가 없다면, `SkeletonView` 에 지정 되어있는 기본설정을 사용하시면 됩니다.
|
||||
|
||||
기본 설정값:
|
||||
- **tintColor**: UIColor
|
||||
- *기본값: .clouds*
|
||||
- **gradient**: SkeletonGradient
|
||||
- *기본값: SkeletonGradient(baseColor: .clouds)*
|
||||
- **multilineHeight**: CGFloat
|
||||
- *기본값: 15*
|
||||
- **multilineSpacing**: CGFloat
|
||||
- *기본값: 10*
|
||||
- **multilineLastLineFillPercent**: Int
|
||||
- *기본값: 70*
|
||||
- **multilineCornerRadius**: Int
|
||||
- *기본값: 0*
|
||||
|
||||
`SkeletonAppearance.default` 에는 사용 되어지는 기본 값들이 설정되어 있습니다 . 아래의 코드와 같이 사용할 수 있습니다:
|
||||
```Swift
|
||||
SkeletonAppearance.default.multilineHeight = 20
|
||||
SkeletonAppearance.default.tintColor = .green
|
||||
```
|
||||
|
||||
|
||||
### 🤓 커스텀 애니메이션
|
||||
|
||||
```SkeletonView``` 에는 두가지 애니메이션이 내장되어 있습니다, 단색 *바운스* 애니메이션과 그라디언트 *슬라이드* 애니메이션 입니다 .
|
||||
|
||||
게다가, 직접 애니메이션을 추가하고 싶다면 정말 간단합니다.
|
||||
|
||||
|
||||
Skeleton 에서는 `showAnimatedSkeleton` 함수를 ```SkeletonLayerAnimation```에 정의하여 맞춤형 애니메이션을 정의할 수 있도록 되어 있습니다.
|
||||
|
||||
```swift
|
||||
public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation
|
||||
```
|
||||
|
||||
함수는 이렇게 호출 가능합니다:
|
||||
|
||||
```swift
|
||||
view.showAnimatedSkeleton { (layer) -> CAAnimation in
|
||||
let animation = CAAnimation()
|
||||
// Customize here your animation
|
||||
|
||||
return animation
|
||||
}
|
||||
```
|
||||
|
||||
```SkeletonAnimationBuilder```의 사용이 가능합니다. ```SkeletonLayerAnimation```을 만들기 위해 사용됩니다.
|
||||
|
||||
이제, 그라디언트를 위한 **슬라이딩 애니메이션** 을 만들 수 있습니다, 애니메이션을 위한 **방향** 과 **지속시간** 을 설정 할 수 있습니다. (기본값 = 1.5초).
|
||||
|
||||
```swift
|
||||
// func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation
|
||||
|
||||
let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight)
|
||||
view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
|
||||
|
||||
```
|
||||
|
||||
```GradientDirection``` 는 enum 으로 정의 되어있습니다., 아래의 케이스를 참조하세요:
|
||||
|
||||
| 방향 | 미리보기 |
|
||||
| ------------------- | ---------------------------------------------- |
|
||||
| .leftRight |  |
|
||||
| .rightLeft |  |
|
||||
| .topBottom |  |
|
||||
| .bottomTop |  |
|
||||
| .topLeftBottomRight |  |
|
||||
| .bottomRightTopLeft |  |
|
||||
|
||||
> **😉 꿀팁!**
|
||||
슬라이딩 애니메이션을 만들기 위한 또다른 방법이 있습니다, 아래의 코드를 참조하세요:
|
||||
>>```let animation = GradientDirection.leftToRight.slidingAnimation()```
|
||||
|
||||
### 👨👧👦 계층 구조
|
||||
|
||||
```SkeletonView```는 재귀적입니다 , 그리고 우리는 skeleton이 효율적으로 작동하기를 원하기 때문에, 가능한 빨리 재귀작업을 중단하기를 원합니다. 이러한 이유때문에 반드시 컨테이너 뷰를 `Skeletonable` 로 설정해야 합니다, `skeletonable` 되지 않는 뷰를 만나는 순간 재귀 작업을 중단하기 떄문입니다.
|
||||
|
||||
아래의 이미지를 참고하세요 이미지는 한눈에 이해되실겁니다:
|
||||
|
||||
> ```ìsSkeletonable```= ☠️
|
||||
|
||||
| 설정값 | 결과 |
|
||||
| ----------------------------------------- | --------------------------------------------- |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
|
||||
### 🔬 디버그
|
||||
|
||||
**새로운소식** 어떤것들이 잘 동작 하지 않을때를 위해 디버그 작업을 용이하게 하기 위해서 `SkeletonView` 에는 몇가지 새로운 것들이 있습니다.
|
||||
|
||||
첫번쨰로, `UIView` 에서 skeleton 정보를 보기위해 다음과 같이 지원하고 있습니다:
|
||||
```swift
|
||||
var skeletonDescription: String
|
||||
|
||||
```
|
||||
skeleton은 이렇게 생겼습니다:
|
||||
|
||||

|
||||
|
||||
그리고, 새로운 **디버그 모드**를 활성화 시킬 수 있습니다. 간단하게 `SKELETON_DEBUG` 이라는 환경 변수를 추가해 활성화 하면 됩니다.
|
||||
|
||||

|
||||
|
||||
그런 이후 skeleton이 나오면 Xcode 콘솔창에서 계층 구조를 볼 수 있습니다.
|
||||
|
||||
<details>
|
||||
<summary>예제를 확인해보세요. </summary>
|
||||
<img src="Assets/hierarchy_output.png" />
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
### 📚 문서화
|
||||
조금만 기다려주세요...😅
|
||||
|
||||
### 📋 지원 가능한 OS & SDK 버전
|
||||
|
||||
* iOS 9.0+
|
||||
* tvOS 9.0+
|
||||
* Swift 4.2
|
||||
|
||||
## 📬 예정된 기능들
|
||||
|
||||
* [x] 멀티라인 에서의 마지막 라인의 채우기 비율 설정
|
||||
* [x] 더많은 그라디언트 애니메이션
|
||||
* [x] resizable cells 지원
|
||||
* [x] CollectionView 호환
|
||||
* [x] tvOS 호환
|
||||
* [x] recovery state 추가
|
||||
* [x] Custom default appearance
|
||||
* [x] 디버그 모드
|
||||
* [ ] Custom collections 호환
|
||||
* [ ] skeletons 가 보이거나 가려질때 애니메이션 추가
|
||||
* [ ] MacOS 와 WatchOS 호환
|
||||
|
||||
## ❤️ 기여하기
|
||||
이 프로젝트는 오픈소스 프로젝트 입니다, 마음편하게 기여해주시면 됩니다 어떻게 하냐구요?
|
||||
- 새로운 [이슈](https://github.com/Juanpe/SkeletonView/issues/new)를 등록합니다.
|
||||
- [email](mailto://juanpecatalan.com)을 보냅니다.
|
||||
- 당신의 수정을 제안합니다, pull request를 포함한 수정을 권장합니다.
|
||||
|
||||
전체 [기여자목록](https://github.com/Juanpe/SkeletonView/graphs/contributors)
|
||||
|
||||
###### [SwiftPlate](https://github.com/JohnSundell/SwiftPlate)를 통해 프로젝트가 생성되었습니다
|
||||
|
||||
## 📢 소식들
|
||||
|
||||
- [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start)
|
||||
- [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful)
|
||||
- [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65)
|
||||
- [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9)
|
||||
- [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899)
|
||||
- [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d)
|
||||
- [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/)
|
||||
- [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204)
|
||||
- [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759)
|
||||
- [CocoaControls](https://www.cocoacontrols.com/controls/skeletonview)
|
||||
- [Awesome iOS Newsletter #74](https://ios.libhunt.com/newsletter/74)
|
||||
- [Swift News #36](https://www.youtube.com/watch?v=mAGpsQiy6so)
|
||||
|
||||
|
||||
|
||||
## 👨🏻💻 개발자
|
||||
[1.1]: http://i.imgur.com/tXSoThF.png
|
||||
[1]: http://www.twitter.com/JuanpeCatalan
|
||||
|
||||
* Juanpe Catalán [![alt text][1.1]][1]
|
||||
|
||||
<a class="bmc-button" target="_blank" href="https://www.buymeacoffee.com/CDou4xtIK"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy me a coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;"><span style="margin-left:5px"></span></a>
|
||||
|
||||
## 👮🏻 라이센스
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Juanpe Catalán
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
@@ -1,19 +1,17 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/Juanpe/SkeletonView">
|
||||
<img src="https://img.shields.io/travis/Juanpe/SkeletonView.svg">
|
||||
<a href="https://app.bitrise.io/app/6d289a17e22c8323">
|
||||
<img src="https://app.bitrise.io/app/6d289a17e22c8323/status.svg?token=fI7gKC41XD9-aRXDScCKBw&branch=master">
|
||||
</a>
|
||||
<a href="https://instagram.github.io/IGListKit/">
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<a href="https://github.com/Juanpe/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/p/SkeletonView.svg" alt="Platforms">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/Swift-4.1-orange.svg" />
|
||||
<img src="https://img.shields.io/badge/Swift-5-orange.svg" />
|
||||
<a href="https://cocoapods.org/pods/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/v/SkeletonView.svg" alt="CocoaPods" />
|
||||
</a>
|
||||
<a href="https://github.com/Carthage/Carthage">
|
||||
<img src="https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat" alt="Carthage" />
|
||||
</a>
|
||||
<a href="https://cocoapods.org/pods/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/dt/SkeletonView.svg?style=flat" alt="CocoaPods downloads" />
|
||||
</a>
|
||||
@@ -23,12 +21,10 @@
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=MJ4Y2D9DEX6FL&lc=ES&item_name=SkeletonView¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted">
|
||||
<img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Paypal" />
|
||||
</a>
|
||||
<br/>
|
||||
<a href="https://twitter.com/intent/tweet?text=Wow%20This%20library%20is%20awesome:&url=https%3A%2F%2Fgithub.com%2FJuanpe%2FSkeletonView">
|
||||
<img src="https://img.shields.io/twitter/url/https/github.com/Juanpe/SkeletonView.svg?style=social" alt="License" />
|
||||
</a>
|
||||
<a href="https://twitter.com/JuanpeCatalan">
|
||||
<img src="https://img.shields.io/twitter/follow/JuanpeCatalan.svg?style=social&label=Follow" alt="Twitter" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
🌎 Traduções: </br>
|
||||
@@ -76,7 +72,7 @@ Aproveite! 🙂
|
||||
|
||||
* iOS 9.0+
|
||||
* tvOS 9.0+
|
||||
* Swift 4
|
||||
* Swift 4.2
|
||||
|
||||
### 🔮 Exemplo
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/Juanpe/SkeletonView">
|
||||
<img src="https://img.shields.io/travis/Juanpe/SkeletonView.svg">
|
||||
<a href="https://app.bitrise.io/app/6d289a17e22c8323">
|
||||
<img src="https://app.bitrise.io/app/6d289a17e22c8323/status.svg?token=fI7gKC41XD9-aRXDScCKBw&branch=master">
|
||||
</a>
|
||||
<a href="https://instagram.github.io/IGListKit/">
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<a href="https://github.com/Juanpe/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/p/SkeletonView.svg" alt="Platforms">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/Swift-4.1-orange.svg" />
|
||||
<img src="https://img.shields.io/badge/Swift-5-orange.svg" />
|
||||
<a href="https://cocoapods.org/pods/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/v/SkeletonView.svg" alt="CocoaPods" />
|
||||
</a>
|
||||
<a href="https://github.com/Carthage/Carthage">
|
||||
<img src="https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat" alt="Carthage" />
|
||||
</a>
|
||||
<a href="https://cocoapods.org/pods/SkeletonView">
|
||||
<img src="https://img.shields.io/cocoapods/dt/SkeletonView.svg?style=flat" alt="CocoaPods downloads" />
|
||||
</a>
|
||||
@@ -23,14 +21,13 @@
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=MJ4Y2D9DEX6FL&lc=ES&item_name=SkeletonView¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted">
|
||||
<img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Paypal" />
|
||||
</a>
|
||||
<br/>
|
||||
<a href="https://twitter.com/intent/tweet?text=Wow%20This%20library%20is%20awesome:&url=https%3A%2F%2Fgithub.com%2FJuanpe%2FSkeletonView">
|
||||
<img src="https://img.shields.io/twitter/url/https/github.com/Juanpe/SkeletonView.svg?style=social" alt="License" />
|
||||
</a>
|
||||
<a href="https://twitter.com/JuanpeCatalan">
|
||||
<img src="https://img.shields.io/twitter/follow/JuanpeCatalan.svg?style=social&label=Follow" alt="Twitter" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
🌎 翻译: [ [原版的](https://github.com/Juanpe/SkeletonView) ] </br>
|
||||
[🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) [@brunomunizaf](https://twitter.com/brunomuniz_af)
|
||||
|
||||
@@ -73,9 +70,9 @@
|
||||
|
||||
### 📋 版本要求
|
||||
|
||||
* iOS 9.0+
|
||||
* tvOS 9.0+
|
||||
* Swift 4
|
||||
* iOS 10.0+
|
||||
* tvOS 10.0+
|
||||
* Swift 4.2
|
||||
|
||||
### 🔮 示例
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.4.1"
|
||||
s.version = "1.8.4"
|
||||
s.summary = "An elegant way to show users that something is happening and also prepare them to which contents he is waiting"
|
||||
s.description = <<-DESC
|
||||
Today almost all apps have async processes, as API requests, long runing processes, etc. And while the processes are working, usually developers place a loading view to show users that something is going on.
|
||||
@@ -12,6 +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.0"
|
||||
s.source = { :git => "https://github.com/Juanpe/SkeletonView.git", :tag => s.version.to_s }
|
||||
s.source_files = "Sources/**/*"
|
||||
end
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
17DD0E0F207FB28C00C56334 /* CALayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622421FAC81FD007C062A /* CALayer+Extensions.swift */; };
|
||||
17DD0E10207FB28C00C56334 /* UIColor+Skeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */; };
|
||||
17DD0E11207FB28C00C56334 /* UIView+Frame.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */; };
|
||||
17DD0E12207FB28C00C56334 /* UIView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */; };
|
||||
17DD0E13207FB28C00C56334 /* UIView+UIApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */; };
|
||||
17DD0E14207FB28F00C56334 /* SkeletonAnimationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */; };
|
||||
17DD0E15207FB28F00C56334 /* SkeletonAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E2F1FB0EC9D00EE67C5 /* SkeletonAppearance.swift */; };
|
||||
@@ -29,8 +28,8 @@
|
||||
17DD0E1D207FB32100C56334 /* ContainsMultilineText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E3A1FB123C100EE67C5 /* ContainsMultilineText.swift */; };
|
||||
17DD0E1E207FB32100C56334 /* PrepareForSkeletonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */; };
|
||||
17DD0E1F207FB32100C56334 /* RecursiveProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */; };
|
||||
3B83EE4720C41488005178A4 /* ContainsMultilineText.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3B83EE4520C41488005178A4 /* ContainsMultilineText.swift */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
3B83EE4820C41488005178A4 /* UIView+IBInspectable.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3B83EE4620C41488005178A4 /* UIView+IBInspectable.swift */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
1E6C67A2230E76CC0019D87B /* SkeletonTransitionStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4CE587C22EEE65200333067 /* SkeletonTransitionStyle.swift */; };
|
||||
1E6C67A3230E76CE0019D87B /* UIView+Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4CE587B22EEE63100333067 /* UIView+Transitions.swift */; };
|
||||
42ABD063210B548200BEEFF4 /* SkeletonView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* SkeletonView.framework */; };
|
||||
42ABD069210B548200BEEFF4 /* SkeletonView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* SkeletonView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
42ABD078210B54E200BEEFF4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD070210B54E100BEEFF4 /* AppDelegate.swift */; };
|
||||
@@ -39,13 +38,26 @@
|
||||
42ABD07B210B54E200BEEFF4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD073210B54E100BEEFF4 /* ViewController.swift */; };
|
||||
42ABD07C210B54E200BEEFF4 /* Base.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 42ABD074210B54E100BEEFF4 /* Base.lproj */; };
|
||||
42ABD07F210B54E200BEEFF4 /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */; };
|
||||
8748240D20C6A88A00E92179 /* UIView+IBInspectable.swift in Headers */ = {isa = PBXBuildFile; fileRef = F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
870F4E4321CAC07300B9233B /* SkeletonConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 870F4E4221CAC07300B9233B /* SkeletonConfig.swift */; };
|
||||
870F4E4421CAC07300B9233B /* SkeletonConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 870F4E4221CAC07300B9233B /* SkeletonConfig.swift */; };
|
||||
872D5A5621C177E20037D763 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5521C177E20037D763 /* UIView+Extension.swift */; };
|
||||
872D5A5721C177E20037D763 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5521C177E20037D763 /* UIView+Extension.swift */; };
|
||||
872D5A5C21C24EDD0037D763 /* UILabel+Multiline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5B21C24EDD0037D763 /* UILabel+Multiline.swift */; };
|
||||
872D5A5D21C24EDD0037D763 /* UILabel+Multiline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5B21C24EDD0037D763 /* UILabel+Multiline.swift */; };
|
||||
872D5A5F21C24F8E0037D763 /* UITextView+Multiline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5E21C24F8E0037D763 /* UITextView+Multiline.swift */; };
|
||||
872D5A6021C24F8E0037D763 /* UITextView+Multiline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872D5A5E21C24F8E0037D763 /* UITextView+Multiline.swift */; };
|
||||
8748240E20C6A88E00E92179 /* ContainsMultilineText.swift in Headers */ = {isa = PBXBuildFile; fileRef = F5307E3A1FB123C100EE67C5 /* ContainsMultilineText.swift */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
877EFA4521BEE9D80031FC00 /* SkeletonLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877EFA4421BEE9D80031FC00 /* SkeletonLayerBuilder.swift */; };
|
||||
877EFA4621BEE9D80031FC00 /* SkeletonLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877EFA4421BEE9D80031FC00 /* SkeletonLayerBuilder.swift */; };
|
||||
877EFA4821BEED760031FC00 /* SkeletonMultilineLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877EFA4721BEED760031FC00 /* SkeletonMultilineLayerBuilder.swift */; };
|
||||
877EFA4921BEED760031FC00 /* SkeletonMultilineLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877EFA4721BEED760031FC00 /* SkeletonMultilineLayerBuilder.swift */; };
|
||||
8785E3A1211C9C9800CC9DFD /* SkeletonDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */; };
|
||||
8785E3A2211C9CA500CC9DFD /* SkeletonDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */; };
|
||||
8785E3A3211C9CE800CC9DFD /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DEA97D1FCDBD1F006C80EF /* Constants.swift */; };
|
||||
88DEA97F1FCDBD78006C80EF /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DEA97D1FCDBD1F006C80EF /* Constants.swift */; };
|
||||
8933C7851EB5B820000D00A4 /* SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* SkeletonView.swift */; };
|
||||
E4CE587D22EEE65200333067 /* SkeletonTransitionStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4CE587C22EEE65200333067 /* SkeletonTransitionStyle.swift */; };
|
||||
E4CE588B22EEF70D00333067 /* UIView+Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4CE587B22EEE63100333067 /* UIView+Transitions.swift */; };
|
||||
F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */; };
|
||||
F51DF871206E91B300D23301 /* SkeletonReusableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DF870206E91B300D23301 /* SkeletonReusableCell.swift */; };
|
||||
F51DF873206E91FB00D23301 /* GenericCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DF872206E91FB00D23301 /* GenericCollectionView.swift */; };
|
||||
@@ -66,6 +78,10 @@
|
||||
F54CF5E52024CEB000330B0D /* UITableView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F54CF5E22024CEAF00330B0D /* UITableView+CollectionSkeleton.swift */; };
|
||||
F54CF5E62024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F54CF5E32024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift */; };
|
||||
F56B94461FAE20AF0095662F /* PrepareForSkeletonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */; };
|
||||
F570ABF42314629700390248 /* Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F570ABF32314629700390248 /* Swizzling.swift */; };
|
||||
F570ABF52314629700390248 /* Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F570ABF32314629700390248 /* Swizzling.swift */; };
|
||||
F5804772230ECD0000066D02 /* UIView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5804771230ECD0000066D02 /* UIView+IBInspectable.swift */; };
|
||||
F5804773230ECD0000066D02 /* UIView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5804771230ECD0000066D02 /* UIView+IBInspectable.swift */; };
|
||||
F587FB84202CBFC8002DB5FE /* SkeletonFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F587FB83202CBFC8002DB5FE /* SkeletonFlow.swift */; };
|
||||
F587FB86202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */; };
|
||||
F58A6E6B20A8C54100612494 /* RecoverableViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F58A6E6A20A8C54100612494 /* RecoverableViewState.swift */; };
|
||||
@@ -79,7 +95,6 @@
|
||||
F5F622411FAC6E31007C062A /* UIColor+Skeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */; };
|
||||
F5F622431FAC81FD007C062A /* CALayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622421FAC81FD007C062A /* CALayer+Extensions.swift */; };
|
||||
F5F622451FACA338007C062A /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622441FACA338007C062A /* Cell.swift */; };
|
||||
F5F899D01FAA6A4D002E8FDA /* UIView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */; };
|
||||
F5F899D21FAB9630002E8FDA /* AssociationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899D11FAB9630002E8FDA /* AssociationPolicy.swift */; };
|
||||
F5F899E91FAB9D2B002E8FDA /* SkeletonLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */; };
|
||||
F5F899EB1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F899EA1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift */; };
|
||||
@@ -137,8 +152,6 @@
|
||||
17DD0E00207FB27400C56334 /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
17DD0E1A207FB2C200C56334 /* SkeletonView-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SkeletonView-tvOS.plist"; sourceTree = "<group>"; };
|
||||
17DD0E1B207FB2C200C56334 /* SkeletonView-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SkeletonView-iOS.plist"; sourceTree = "<group>"; };
|
||||
3B83EE4520C41488005178A4 /* ContainsMultilineText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContainsMultilineText.swift; path = Sources/Helpers/ContainsMultilineText.swift; sourceTree = "<group>"; };
|
||||
3B83EE4620C41488005178A4 /* UIView+IBInspectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIView+IBInspectable.swift"; path = "Sources/Extensions/UIView+IBInspectable.swift"; sourceTree = "<group>"; };
|
||||
42ABD06D210B548200BEEFF4 /* SkeletonViewExampleUICollectionView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SkeletonViewExampleUICollectionView.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
42ABD070210B54E100BEEFF4 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
42ABD071210B54E100BEEFF4 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
||||
@@ -148,9 +161,17 @@
|
||||
42ABD076210B54E200BEEFF4 /* SkeletonViewExampleCollectionview-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SkeletonViewExampleCollectionview-Info.plist"; sourceTree = "<group>"; };
|
||||
42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
52D6D97C1BEFF229002C0205 /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
870F4E4221CAC07300B9233B /* SkeletonConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonConfig.swift; sourceTree = "<group>"; };
|
||||
872D5A5521C177E20037D763 /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = "<group>"; };
|
||||
872D5A5B21C24EDD0037D763 /* UILabel+Multiline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Multiline.swift"; sourceTree = "<group>"; };
|
||||
872D5A5E21C24F8E0037D763 /* UITextView+Multiline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+Multiline.swift"; sourceTree = "<group>"; };
|
||||
877EFA4421BEE9D80031FC00 /* SkeletonLayerBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonLayerBuilder.swift; sourceTree = "<group>"; };
|
||||
877EFA4721BEED760031FC00 /* SkeletonMultilineLayerBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonMultilineLayerBuilder.swift; sourceTree = "<group>"; };
|
||||
8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonDebug.swift; sourceTree = "<group>"; };
|
||||
88DEA97D1FCDBD1F006C80EF /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
8933C7841EB5B820000D00A4 /* SkeletonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkeletonView.swift; sourceTree = "<group>"; };
|
||||
E4CE587B22EEE63100333067 /* UIView+Transitions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Transitions.swift"; sourceTree = "<group>"; };
|
||||
E4CE587C22EEE65200333067 /* SkeletonTransitionStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTransitionStyle.swift; sourceTree = "<group>"; };
|
||||
F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonAnimationBuilder.swift; sourceTree = "<group>"; };
|
||||
F51DF870206E91B300D23301 /* SkeletonReusableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonReusableCell.swift; sourceTree = "<group>"; };
|
||||
F51DF872206E91FB00D23301 /* GenericCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCollectionView.swift; sourceTree = "<group>"; };
|
||||
@@ -166,6 +187,8 @@
|
||||
F54CF5E22024CEAF00330B0D /* UITableView+CollectionSkeleton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+CollectionSkeleton.swift"; sourceTree = "<group>"; };
|
||||
F54CF5E32024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+CollectionSkeleton.swift"; sourceTree = "<group>"; };
|
||||
F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrepareForSkeletonProtocol.swift; sourceTree = "<group>"; };
|
||||
F570ABF32314629700390248 /* Swizzling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swizzling.swift; sourceTree = "<group>"; };
|
||||
F5804771230ECD0000066D02 /* UIView+IBInspectable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+IBInspectable.swift"; sourceTree = "<group>"; };
|
||||
F587FB83202CBFC8002DB5FE /* SkeletonFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonFlow.swift; sourceTree = "<group>"; };
|
||||
F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+UIApplicationDelegate.swift"; sourceTree = "<group>"; };
|
||||
F58A6E6A20A8C54100612494 /* RecoverableViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoverableViewState.swift; sourceTree = "<group>"; };
|
||||
@@ -175,7 +198,6 @@
|
||||
F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Skeleton.swift"; sourceTree = "<group>"; };
|
||||
F5F622421FAC81FD007C062A /* CALayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+Extensions.swift"; sourceTree = "<group>"; };
|
||||
F5F622441FACA338007C062A /* Cell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cell.swift; sourceTree = "<group>"; };
|
||||
F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+IBInspectable.swift"; sourceTree = "<group>"; };
|
||||
F5F899D11FAB9630002E8FDA /* AssociationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssociationPolicy.swift; sourceTree = "<group>"; };
|
||||
F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonLayer.swift; sourceTree = "<group>"; };
|
||||
F5F899EA1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCollectionDataSource.swift; sourceTree = "<group>"; };
|
||||
@@ -223,25 +245,9 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
42ABD06F210B54BC00BEEFF4 /* Example UICollectionView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
42ABD070210B54E100BEEFF4 /* AppDelegate.swift */,
|
||||
42ABD072210B54E100BEEFF4 /* Assets.xcassets */,
|
||||
42ABD074210B54E100BEEFF4 /* Base.lproj */,
|
||||
42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */,
|
||||
42ABD071210B54E100BEEFF4 /* Main.storyboard */,
|
||||
42ABD073210B54E100BEEFF4 /* ViewController.swift */,
|
||||
);
|
||||
path = "Example UICollectionView";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
52D6D9721BEFF229002C0205 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B83EE4520C41488005178A4 /* ContainsMultilineText.swift */,
|
||||
3B83EE4620C41488005178A4 /* UIView+IBInspectable.swift */,
|
||||
42ABD06F210B54BC00BEEFF4 /* Example UICollectionView */,
|
||||
F5F899F31FABA607002E8FDA /* Example */,
|
||||
8933C7811EB5B7E0000D00A4 /* Sources */,
|
||||
52D6D99C1BEFF38C002C0205 /* Configs */,
|
||||
@@ -270,6 +276,52 @@
|
||||
path = Configs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
872D5A5821C17D850037D763 /* CollectionView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
42ABD070210B54E100BEEFF4 /* AppDelegate.swift */,
|
||||
42ABD072210B54E100BEEFF4 /* Assets.xcassets */,
|
||||
42ABD074210B54E100BEEFF4 /* Base.lproj */,
|
||||
42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */,
|
||||
42ABD071210B54E100BEEFF4 /* Main.storyboard */,
|
||||
42ABD073210B54E100BEEFF4 /* ViewController.swift */,
|
||||
);
|
||||
path = CollectionView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
872D5A5921C17D980037D763 /* TableView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F5F899F41FABA607002E8FDA /* AppDelegate.swift */,
|
||||
88DEA97D1FCDBD1F006C80EF /* Constants.swift */,
|
||||
F5F899F61FABA607002E8FDA /* ViewController.swift */,
|
||||
F5F622441FACA338007C062A /* Cell.swift */,
|
||||
F5F899F81FABA607002E8FDA /* Main.storyboard */,
|
||||
F5F899FB1FABA607002E8FDA /* Assets.xcassets */,
|
||||
F5F899FD1FABA607002E8FDA /* LaunchScreen.storyboard */,
|
||||
);
|
||||
path = TableView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
872D5A5A21C24E7C0037D763 /* Multilines */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F5307E3A1FB123C100EE67C5 /* ContainsMultilineText.swift */,
|
||||
872D5A5B21C24EDD0037D763 /* UILabel+Multiline.swift */,
|
||||
872D5A5E21C24F8E0037D763 /* UITextView+Multiline.swift */,
|
||||
);
|
||||
path = Multilines;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
877EFA4321BEE9C40031FC00 /* Builders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
877EFA4421BEE9D80031FC00 /* SkeletonLayerBuilder.swift */,
|
||||
877EFA4721BEED760031FC00 /* SkeletonMultilineLayerBuilder.swift */,
|
||||
);
|
||||
path = Builders;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8785E39E211C9C6D00CC9DFD /* Appearance */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -289,6 +341,9 @@
|
||||
8933C7811EB5B7E0000D00A4 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E4CE587A22EEE62300333067 /* Transitions */,
|
||||
872D5A5A21C24E7C0037D763 /* Multilines */,
|
||||
877EFA4321BEE9C40031FC00 /* Builders */,
|
||||
8785E39F211C9C7C00CC9DFD /* Debug */,
|
||||
8785E39E211C9C6D00CC9DFD /* Appearance */,
|
||||
F58A6E7020A8C87100612494 /* Recoverable */,
|
||||
@@ -301,6 +356,7 @@
|
||||
F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */,
|
||||
8933C7841EB5B820000D00A4 /* SkeletonView.swift */,
|
||||
F5D3FB0A209DCAA300003FCF /* SubviewsSkeletonables.swift */,
|
||||
870F4E4221CAC07300B9233B /* SkeletonConfig.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -314,6 +370,15 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E4CE587A22EEE62300333067 /* Transitions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E4CE587B22EEE63100333067 /* UIView+Transitions.swift */,
|
||||
E4CE587C22EEE65200333067 /* SkeletonTransitionStyle.swift */,
|
||||
);
|
||||
path = Transitions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F51DF874206E94D300D23301 /* CollectionViews */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -359,11 +424,12 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F5F622421FAC81FD007C062A /* CALayer+Extensions.swift */,
|
||||
F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */,
|
||||
F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */,
|
||||
F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */,
|
||||
F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */,
|
||||
F5A5E8B3211DCE7000FD20D0 /* Int+Whitespaces.swift */,
|
||||
F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */,
|
||||
872D5A5521C177E20037D763 /* UIView+Extension.swift */,
|
||||
F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */,
|
||||
F5804771230ECD0000066D02 /* UIView+IBInspectable.swift */,
|
||||
F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -372,9 +438,9 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F5F899D11FAB9630002E8FDA /* AssociationPolicy.swift */,
|
||||
F5307E3A1FB123C100EE67C5 /* ContainsMultilineText.swift */,
|
||||
F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */,
|
||||
F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */,
|
||||
F570ABF32314629700390248 /* Swizzling.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
@@ -391,13 +457,8 @@
|
||||
F5F899F31FABA607002E8FDA /* Example */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F5F899F41FABA607002E8FDA /* AppDelegate.swift */,
|
||||
88DEA97D1FCDBD1F006C80EF /* Constants.swift */,
|
||||
F5F899F61FABA607002E8FDA /* ViewController.swift */,
|
||||
F5F622441FACA338007C062A /* Cell.swift */,
|
||||
F5F899F81FABA607002E8FDA /* Main.storyboard */,
|
||||
F5F899FB1FABA607002E8FDA /* Assets.xcassets */,
|
||||
F5F899FD1FABA607002E8FDA /* LaunchScreen.storyboard */,
|
||||
872D5A5921C17D980037D763 /* TableView */,
|
||||
872D5A5821C17D850037D763 /* CollectionView */,
|
||||
);
|
||||
path = Example;
|
||||
sourceTree = "<group>";
|
||||
@@ -425,7 +486,6 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8748240D20C6A88A00E92179 /* UIView+IBInspectable.swift in Headers */,
|
||||
8748240E20C6A88E00E92179 /* ContainsMultilineText.swift in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -434,8 +494,6 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3B83EE4720C41488005178A4 /* ContainsMultilineText.swift in Headers */,
|
||||
3B83EE4820C41488005178A4 /* UIView+IBInspectable.swift in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -523,7 +581,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0910;
|
||||
LastUpgradeCheck = 1000;
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = SkeletonView;
|
||||
TargetAttributes = {
|
||||
17DD0DFF207FB27400C56334 = {
|
||||
@@ -547,7 +605,7 @@
|
||||
};
|
||||
buildConfigurationList = 52D6D9761BEFF229002C0205 /* Build configuration list for PBXProject "SkeletonView" */;
|
||||
compatibilityVersion = "Xcode 6.3";
|
||||
developmentRegion = English;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
@@ -608,7 +666,9 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
872D5A5721C177E20037D763 /* UIView+Extension.swift in Sources */,
|
||||
17DD0E1F207FB32100C56334 /* RecursiveProtocol.swift in Sources */,
|
||||
872D5A5D21C24EDD0037D763 /* UILabel+Multiline.swift in Sources */,
|
||||
17DD0E1E207FB32100C56334 /* PrepareForSkeletonProtocol.swift in Sources */,
|
||||
F51ED28520973CC9008B2434 /* SkeletonReusableCell.swift in Sources */,
|
||||
17DD0E17207FB28F00C56334 /* SkeletonLayer.swift in Sources */,
|
||||
@@ -616,28 +676,35 @@
|
||||
17DD0E08207FB28900C56334 /* CollectionSkeletonProtocol.swift in Sources */,
|
||||
F58A6E6C20A8C54100612494 /* RecoverableViewState.swift in Sources */,
|
||||
17DD0E0B207FB28900C56334 /* SkeletonTableViewProtocols.swift in Sources */,
|
||||
1E6C67A2230E76CC0019D87B /* SkeletonTransitionStyle.swift in Sources */,
|
||||
17DD0E0D207FB28900C56334 /* UITableView+CollectionSkeleton.swift in Sources */,
|
||||
17DD0E0E207FB28900C56334 /* UIView+CollectionSkeleton.swift in Sources */,
|
||||
877EFA4921BEED760031FC00 /* SkeletonMultilineLayerBuilder.swift in Sources */,
|
||||
F51ED28320973CBB008B2434 /* SkeletonCollectionDelegate.swift in Sources */,
|
||||
F58A6E6F20A8C66300612494 /* Recoverable.swift in Sources */,
|
||||
17DD0E18207FB28F00C56334 /* SkeletonView.swift in Sources */,
|
||||
17DD0E19207FB28F00C56334 /* SkeletonFlow.swift in Sources */,
|
||||
17DD0E09207FB28900C56334 /* SkeletonCollectionDataSource.swift in Sources */,
|
||||
17DD0E0C207FB28900C56334 /* UICollectionView+CollectionSkeleton.swift in Sources */,
|
||||
F5804773230ECD0000066D02 /* UIView+IBInspectable.swift in Sources */,
|
||||
17DD0E13207FB28C00C56334 /* UIView+UIApplicationDelegate.swift in Sources */,
|
||||
F570ABF52314629700390248 /* Swizzling.swift in Sources */,
|
||||
F5D3FB0C209DCAA300003FCF /* SubviewsSkeletonables.swift in Sources */,
|
||||
17DD0E12207FB28C00C56334 /* UIView+IBInspectable.swift in Sources */,
|
||||
17DD0E14207FB28F00C56334 /* SkeletonAnimationBuilder.swift in Sources */,
|
||||
17DD0E11207FB28C00C56334 /* UIView+Frame.swift in Sources */,
|
||||
17DD0E15207FB28F00C56334 /* SkeletonAppearance.swift in Sources */,
|
||||
872D5A6021C24F8E0037D763 /* UITextView+Multiline.swift in Sources */,
|
||||
17DD0E10207FB28C00C56334 /* UIColor+Skeleton.swift in Sources */,
|
||||
F51ED28420973CC6008B2434 /* GenericCollectionView.swift in Sources */,
|
||||
17DD0E0A207FB28900C56334 /* SkeletonCollectionViewProtocols.swift in Sources */,
|
||||
17DD0E1C207FB32100C56334 /* AssociationPolicy.swift in Sources */,
|
||||
877EFA4621BEE9D80031FC00 /* SkeletonLayerBuilder.swift in Sources */,
|
||||
F5A5E8B5211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */,
|
||||
870F4E4421CAC07300B9233B /* SkeletonConfig.swift in Sources */,
|
||||
17DD0E1D207FB32100C56334 /* ContainsMultilineText.swift in Sources */,
|
||||
17DD0E0F207FB28C00C56334 /* CALayer+Extensions.swift in Sources */,
|
||||
17DD0E16207FB28F00C56334 /* SkeletonGradient.swift in Sources */,
|
||||
1E6C67A3230E76CE0019D87B /* UIView+Transitions.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -656,7 +723,9 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F5F899D01FAA6A4D002E8FDA /* UIView+IBInspectable.swift in Sources */,
|
||||
E4CE588B22EEF70D00333067 /* UIView+Transitions.swift in Sources */,
|
||||
872D5A5621C177E20037D763 /* UIView+Extension.swift in Sources */,
|
||||
872D5A5C21C24EDD0037D763 /* UILabel+Multiline.swift in Sources */,
|
||||
F54CF5E42024CEB000330B0D /* UIView+CollectionSkeleton.swift in Sources */,
|
||||
F5307E371FB1076E00EE67C5 /* SkeletonTableViewProtocols.swift in Sources */,
|
||||
8933C7851EB5B820000D00A4 /* SkeletonView.swift in Sources */,
|
||||
@@ -666,23 +735,30 @@
|
||||
F54CF5E52024CEB000330B0D /* UITableView+CollectionSkeleton.swift in Sources */,
|
||||
F5307E321FB0F42F00EE67C5 /* RecursiveProtocol.swift in Sources */,
|
||||
F5F622431FAC81FD007C062A /* CALayer+Extensions.swift in Sources */,
|
||||
877EFA4821BEED760031FC00 /* SkeletonMultilineLayerBuilder.swift in Sources */,
|
||||
E4CE587D22EEE65200333067 /* SkeletonTransitionStyle.swift in Sources */,
|
||||
F5F899ED1FAB9F04002E8FDA /* CollectionSkeletonProtocol.swift in Sources */,
|
||||
F58A6E6E20A8C66300612494 /* Recoverable.swift in Sources */,
|
||||
F5307E2C1FAF6BC900EE67C5 /* SkeletonGradient.swift in Sources */,
|
||||
F587FB86202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift in Sources */,
|
||||
F5F899EB1FAB9DA3002E8FDA /* SkeletonCollectionDataSource.swift in Sources */,
|
||||
F5F622411FAC6E31007C062A /* UIColor+Skeleton.swift in Sources */,
|
||||
F5804772230ECD0000066D02 /* UIView+IBInspectable.swift in Sources */,
|
||||
F587FB84202CBFC8002DB5FE /* SkeletonFlow.swift in Sources */,
|
||||
F570ABF42314629700390248 /* Swizzling.swift in Sources */,
|
||||
F5D3FB0B209DCAA300003FCF /* SubviewsSkeletonables.swift in Sources */,
|
||||
F5F899D21FAB9630002E8FDA /* AssociationPolicy.swift in Sources */,
|
||||
F54CF5E62024CEB000330B0D /* UICollectionView+CollectionSkeleton.swift in Sources */,
|
||||
F56B94461FAE20AF0095662F /* PrepareForSkeletonProtocol.swift in Sources */,
|
||||
F5307E391FB1078E00EE67C5 /* SkeletonCollectionViewProtocols.swift in Sources */,
|
||||
872D5A5F21C24F8E0037D763 /* UITextView+Multiline.swift in Sources */,
|
||||
F51DF873206E91FB00D23301 /* GenericCollectionView.swift in Sources */,
|
||||
F5307E3B1FB123C100EE67C5 /* ContainsMultilineText.swift in Sources */,
|
||||
F5F899E91FAB9D2B002E8FDA /* SkeletonLayer.swift in Sources */,
|
||||
F5307E2E1FB0E5E400EE67C5 /* UIView+Frame.swift in Sources */,
|
||||
877EFA4521BEE9D80031FC00 /* SkeletonLayerBuilder.swift in Sources */,
|
||||
F5A5E8B4211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */,
|
||||
870F4E4321CAC07300B9233B /* SkeletonConfig.swift in Sources */,
|
||||
F51DF871206E91B300D23301 /* SkeletonReusableCell.swift in Sources */,
|
||||
F51DF879206E9F5500D23301 /* SkeletonCollectionDelegate.swift in Sources */,
|
||||
F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */,
|
||||
@@ -761,7 +837,7 @@
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
};
|
||||
@@ -793,7 +869,7 @@
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
};
|
||||
@@ -821,7 +897,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -847,7 +923,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.juanpecatalan.SkeletonViewExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
@@ -856,6 +932,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -904,7 +981,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -915,6 +992,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -955,7 +1033,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -966,7 +1044,7 @@
|
||||
52D6D9911BEFF229002C0205 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
@@ -984,14 +1062,14 @@
|
||||
PRODUCT_NAME = SkeletonView;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
52D6D9921BEFF229002C0205 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
@@ -1008,7 +1086,7 @@
|
||||
PRODUCT_NAME = SkeletonView;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1032,7 +1110,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.juanpecatalan.SkeletonViewExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1057,7 +1135,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.juanpecatalan.SkeletonViewExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1000"
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -29,6 +29,15 @@
|
||||
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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1000"
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -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>
|
||||
@@ -9,19 +9,20 @@ public protocol Appearance {
|
||||
var multilineSpacing: CGFloat { get set }
|
||||
var multilineLastLineFillPercent: Int { get set }
|
||||
var multilineCornerRadius: Int { get set }
|
||||
var renderSingleLineAsView: Bool { get set }
|
||||
}
|
||||
|
||||
public enum SkeletonAppearance {
|
||||
public static var `default`: Appearance = SkeletonViewAppearance.shared
|
||||
}
|
||||
|
||||
// codebeat:disable[TOO_MANY_IVARS]
|
||||
class SkeletonViewAppearance: Appearance {
|
||||
|
||||
static var shared = SkeletonViewAppearance()
|
||||
|
||||
var tintColor: UIColor = .clouds
|
||||
var tintColor: UIColor = .skeletonDefault
|
||||
|
||||
var gradient: SkeletonGradient = SkeletonGradient(baseColor: .clouds)
|
||||
var gradient: SkeletonGradient = SkeletonGradient(baseColor: .skeletonDefault)
|
||||
|
||||
var multilineHeight: CGFloat = 15
|
||||
|
||||
@@ -30,4 +31,7 @@ class SkeletonViewAppearance: Appearance {
|
||||
var multilineLastLineFillPercent: Int = 70
|
||||
|
||||
var multilineCornerRadius: Int = 0
|
||||
|
||||
var renderSingleLineAsView: Bool = false
|
||||
}
|
||||
// codebeat:enable[TOO_MANY_IVARS]
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Object that facilitates the creation of skeleton layers,
|
||||
/// based on the builder pattern
|
||||
class SkeletonLayerBuilder {
|
||||
var skeletonType: SkeletonType?
|
||||
var colors: [UIColor] = []
|
||||
var holder: UIView?
|
||||
|
||||
func setSkeletonType(_ type: SkeletonType) -> SkeletonLayerBuilder {
|
||||
self.skeletonType = type
|
||||
return self
|
||||
}
|
||||
|
||||
func addColor(_ color: UIColor) -> SkeletonLayerBuilder {
|
||||
return addColors([color])
|
||||
}
|
||||
|
||||
func addColors(_ colors: [UIColor]) -> SkeletonLayerBuilder {
|
||||
self.colors.append(contentsOf: colors)
|
||||
return self
|
||||
}
|
||||
|
||||
func setHolder(_ holder: UIView) -> SkeletonLayerBuilder {
|
||||
self.holder = holder
|
||||
return self
|
||||
}
|
||||
|
||||
func build() -> SkeletonLayer? {
|
||||
guard let type = skeletonType,
|
||||
let holder = holder
|
||||
else { return nil }
|
||||
|
||||
return SkeletonLayer(type: type,
|
||||
colors: colors,
|
||||
skeletonHolder: holder)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Object that facilitates the creation of skeleton layers for multiline
|
||||
/// elements, based on the builder pattern
|
||||
class SkeletonMultilineLayerBuilder {
|
||||
var skeletonType: SkeletonType?
|
||||
var index: Int?
|
||||
var height: CGFloat?
|
||||
var width: CGFloat?
|
||||
var cornerRadius: Int?
|
||||
var multilineSpacing: CGFloat = SkeletonAppearance.default.multilineSpacing
|
||||
var paddingInsets: UIEdgeInsets = .zero
|
||||
|
||||
func setSkeletonType(_ type: SkeletonType) -> SkeletonMultilineLayerBuilder {
|
||||
self.skeletonType = type
|
||||
return self
|
||||
}
|
||||
|
||||
func setIndex(_ index: Int) -> SkeletonMultilineLayerBuilder {
|
||||
self.index = index
|
||||
return self
|
||||
}
|
||||
|
||||
func setHeight(_ height: CGFloat) -> SkeletonMultilineLayerBuilder {
|
||||
self.height = height
|
||||
return self
|
||||
}
|
||||
|
||||
func setWidth(_ width: CGFloat) -> SkeletonMultilineLayerBuilder {
|
||||
self.width = width
|
||||
return self
|
||||
}
|
||||
|
||||
func setCornerRadius(_ radius: Int) -> SkeletonMultilineLayerBuilder {
|
||||
self.cornerRadius = radius
|
||||
return self
|
||||
}
|
||||
|
||||
func setMultilineSpacing(_ spacing: CGFloat) -> SkeletonMultilineLayerBuilder {
|
||||
self.multilineSpacing = spacing
|
||||
return self
|
||||
}
|
||||
|
||||
func setPadding(_ insets: UIEdgeInsets) -> SkeletonMultilineLayerBuilder {
|
||||
self.paddingInsets = insets
|
||||
return self
|
||||
}
|
||||
|
||||
func build() -> CALayer? {
|
||||
guard let type = skeletonType,
|
||||
let index = index,
|
||||
let width = width,
|
||||
let height = height,
|
||||
let radius = cornerRadius
|
||||
else { return nil }
|
||||
|
||||
let layer = type.layer
|
||||
layer.anchorPoint = .zero
|
||||
layer.name = CALayer.skeletonSubLayersName
|
||||
layer.updateLayerFrame(for: index, size: CGSize(width: width, height: height), multilineSpacing: self.multilineSpacing, paddingInsets: paddingInsets)
|
||||
|
||||
layer.cornerRadius = CGFloat(radius)
|
||||
layer.masksToBounds = true
|
||||
|
||||
return layer
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ protocol CollectionSkeleton {
|
||||
var estimatedNumberOfRows: Int { get }
|
||||
|
||||
func addDummyDataSource()
|
||||
func updateDummyDataSource()
|
||||
func removeDummyDataSource(reloadAfter: Bool)
|
||||
func disableUserInteraction()
|
||||
func enableUserInteraction()
|
||||
@@ -28,6 +29,6 @@ extension CollectionSkeleton where Self: UIScrollView {
|
||||
var estimatedNumberOfRows: Int { return 0 }
|
||||
func addDummyDataSource() {}
|
||||
func removeDummyDataSource(reloadAfter: Bool) {}
|
||||
func disableUserInteraction() { isScrollEnabled = false }
|
||||
func enableUserInteraction() { isScrollEnabled = true }
|
||||
func disableUserInteraction() { isUserInteractionEnabled = false; isScrollEnabled = false }
|
||||
func enableUserInteraction() { isUserInteractionEnabled = true; isScrollEnabled = true }
|
||||
}
|
||||
|
||||
@@ -13,14 +13,20 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier?
|
||||
}
|
||||
|
||||
public extension SkeletonCollectionViewDataSource {
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return skeletonView.estimatedNumberOfRows
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView,
|
||||
supplementaryViewIdentifierOfKind: String,
|
||||
at indexPath: IndexPath) -> ReusableCellIdentifier? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int { return 1 }
|
||||
}
|
||||
|
||||
|
||||
@@ -9,24 +9,23 @@
|
||||
import UIKit
|
||||
|
||||
extension UICollectionView: CollectionSkeleton {
|
||||
|
||||
var estimatedNumberOfRows: Int {
|
||||
guard let flowlayout = collectionViewLayout as? UICollectionViewFlowLayout else { return 0 }
|
||||
return Int(ceil(frame.height/flowlayout.itemSize.height))
|
||||
}
|
||||
|
||||
var skeletonDataSource: SkeletonCollectionDataSource? {
|
||||
get { return objc_getAssociatedObject(self, &CollectionAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
|
||||
get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CollectionAssociatedKeys.dummyDataSource, newValue, AssociationPolicy.retain.objc)
|
||||
ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDataSource)
|
||||
self.dataSource = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var skeletonDelegate: SkeletonCollectionDelegate? {
|
||||
get { return objc_getAssociatedObject(self, &CollectionAssociatedKeys.dummyDelegate) as? SkeletonCollectionDelegate }
|
||||
get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDelegate) as? SkeletonCollectionDelegate }
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CollectionAssociatedKeys.dummyDelegate, newValue, AssociationPolicy.retain.objc)
|
||||
ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDelegate)
|
||||
self.delegate = newValue
|
||||
}
|
||||
}
|
||||
@@ -41,6 +40,14 @@ extension UICollectionView: CollectionSkeleton {
|
||||
reloadData()
|
||||
}
|
||||
|
||||
func updateDummyDataSource() {
|
||||
if (dataSource as? SkeletonCollectionDataSource) != nil {
|
||||
reloadData()
|
||||
} else {
|
||||
addDummyDataSource()
|
||||
}
|
||||
}
|
||||
|
||||
func removeDummyDataSource(reloadAfter: Bool) {
|
||||
guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
|
||||
self.skeletonDataSource = nil
|
||||
|
||||
@@ -11,7 +11,6 @@ import UIKit
|
||||
public typealias ReusableCellIdentifier = String
|
||||
|
||||
class SkeletonCollectionDataSource: NSObject {
|
||||
|
||||
weak var originalTableViewDataSource: SkeletonTableViewDataSource?
|
||||
weak var originalCollectionViewDataSource: SkeletonCollectionViewDataSource?
|
||||
var rowHeight: CGFloat = 0.0
|
||||
@@ -26,25 +25,24 @@ class SkeletonCollectionDataSource: NSObject {
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
extension SkeletonCollectionDataSource: UITableViewDataSource {
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return originalTableViewDataSource?.numSections(in:tableView) ?? 0
|
||||
return originalTableViewDataSource?.numSections(in: tableView) ?? 0
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return originalTableViewDataSource?.collectionSkeletonView(tableView, numberOfRowsInSection:section) ?? 0
|
||||
return originalTableViewDataSource?.collectionSkeletonView(tableView, numberOfRowsInSection: section) ?? 0
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cellIdentifier = originalTableViewDataSource?.collectionSkeletonView(tableView, cellIdentifierForRowAt: indexPath) ?? ""
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: cell)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDataSource
|
||||
extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
|
||||
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
return originalCollectionViewDataSource?.numSections(in: collectionView) ?? 0
|
||||
}
|
||||
@@ -56,6 +54,32 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cellIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, cellIdentifierForItemAt: indexPath) ?? ""
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: cell)
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView,
|
||||
viewForSupplementaryElementOfKind kind: String,
|
||||
at indexPath: IndexPath) -> UICollectionReusableView {
|
||||
|
||||
if let viewIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, supplementaryViewIdentifierOfKind: kind, at: indexPath) {
|
||||
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: viewIdentifier, for: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: view)
|
||||
return view
|
||||
}
|
||||
|
||||
return UICollectionReusableView()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SkeletonCollectionDataSource {
|
||||
private func skeletonViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
|
||||
guard container.isSkeletonActive,
|
||||
let skeletonConfig = container.currentSkeletonConfig else {
|
||||
return
|
||||
}
|
||||
|
||||
view.showSkeleton(skeletonConfig: skeletonConfig)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import UIKit
|
||||
|
||||
class SkeletonCollectionDelegate: NSObject {
|
||||
|
||||
weak var originalTableViewDelegate: SkeletonTableViewDelegate?
|
||||
weak var originalCollectionViewDelegate: SkeletonCollectionViewDelegate?
|
||||
|
||||
@@ -19,8 +18,41 @@ class SkeletonCollectionDelegate: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
extension SkeletonCollectionDelegate: UITableViewDelegate { }
|
||||
// MARK: - UITableViewDelegate
|
||||
extension SkeletonCollectionDelegate: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
if let viewIdentifier = originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForHeaderInSection: section),
|
||||
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier) {
|
||||
|
||||
// MARK: - UICollectionViewDataSource
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: header)
|
||||
return header
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
if let viewIdentifier = originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForFooterInSection: section),
|
||||
let footer = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier) {
|
||||
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: footer)
|
||||
return footer
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDelegate
|
||||
extension SkeletonCollectionDelegate: UICollectionViewDelegate { }
|
||||
|
||||
extension SkeletonCollectionDelegate {
|
||||
private func skeletonViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
|
||||
guard container.isSkeletonActive,
|
||||
let skeletonConfig = container.currentSkeletonConfig else {
|
||||
return
|
||||
}
|
||||
|
||||
view.showSkeleton(skeletonConfig: skeletonConfig)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ public protocol SkeletonTableViewDataSource: UITableViewDataSource {
|
||||
}
|
||||
|
||||
public extension SkeletonTableViewDataSource {
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return skeletonView.estimatedNumberOfRows
|
||||
}
|
||||
@@ -31,4 +30,16 @@ public extension SkeletonTableViewDataSource {
|
||||
}
|
||||
|
||||
public protocol SkeletonTableViewDelegate: UITableViewDelegate {
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier?
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier?
|
||||
}
|
||||
|
||||
public extension SkeletonTableViewDelegate {
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,25 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public typealias ReusableHeaderFooterIdentifier = String
|
||||
|
||||
extension UITableView: CollectionSkeleton {
|
||||
|
||||
var estimatedNumberOfRows: Int {
|
||||
return Int(ceil(frame.height/rowHeight))
|
||||
}
|
||||
|
||||
var skeletonDataSource: SkeletonCollectionDataSource? {
|
||||
get { return objc_getAssociatedObject(self, &CollectionAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
|
||||
get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CollectionAssociatedKeys.dummyDataSource, newValue, AssociationPolicy.retain.objc)
|
||||
ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDataSource)
|
||||
self.dataSource = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var skeletonDelegate: SkeletonCollectionDelegate? {
|
||||
get { return objc_getAssociatedObject(self, &CollectionAssociatedKeys.dummyDelegate) as? SkeletonCollectionDelegate }
|
||||
get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDelegate) as? SkeletonCollectionDelegate }
|
||||
set {
|
||||
objc_setAssociatedObject(self, &CollectionAssociatedKeys.dummyDelegate, newValue, AssociationPolicy.retain.objc)
|
||||
ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDelegate)
|
||||
self.delegate = newValue
|
||||
}
|
||||
}
|
||||
@@ -34,16 +35,40 @@ extension UITableView: CollectionSkeleton {
|
||||
guard let originalDataSource = self.dataSource as? SkeletonTableViewDataSource,
|
||||
!(originalDataSource is SkeletonCollectionDataSource)
|
||||
else { return }
|
||||
let dataSource = SkeletonCollectionDataSource(tableViewDataSource: originalDataSource, rowHeight: calculateRowHeight())
|
||||
let rowHeight = calculateRowHeight()
|
||||
let dataSource = SkeletonCollectionDataSource(tableViewDataSource: originalDataSource,
|
||||
rowHeight: rowHeight)
|
||||
self.skeletonDataSource = dataSource
|
||||
|
||||
if let originalDelegate = self.delegate as? SkeletonTableViewDelegate,
|
||||
!(originalDelegate is SkeletonCollectionDelegate)
|
||||
{
|
||||
let delegate = SkeletonCollectionDelegate(tableViewDelegate: originalDelegate)
|
||||
self.skeletonDelegate = delegate
|
||||
}
|
||||
|
||||
reloadData()
|
||||
}
|
||||
|
||||
func updateDummyDataSource() {
|
||||
if (dataSource as? SkeletonCollectionDataSource) != nil {
|
||||
reloadData()
|
||||
} else {
|
||||
addDummyDataSource()
|
||||
}
|
||||
}
|
||||
|
||||
func removeDummyDataSource(reloadAfter: Bool) {
|
||||
guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
|
||||
restoreRowHeight()
|
||||
self.skeletonDataSource = nil
|
||||
self.dataSource = dataSource.originalTableViewDataSource
|
||||
|
||||
if let delegate = self.delegate as? SkeletonCollectionDelegate {
|
||||
self.skeletonDelegate = nil
|
||||
self.delegate = delegate.originalTableViewDelegate
|
||||
}
|
||||
|
||||
if reloadAfter { self.reloadData() }
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,19 @@ import UIKit
|
||||
extension UIView {
|
||||
func addDummyDataSourceIfNeeded() {
|
||||
guard let collection = self as? CollectionSkeleton else { return }
|
||||
status = .on
|
||||
collection.addDummyDataSource()
|
||||
collection.disableUserInteraction()
|
||||
}
|
||||
|
||||
func updateDummyDataSourceIfNeeded() {
|
||||
guard let collection = self as? CollectionSkeleton else { return }
|
||||
collection.updateDummyDataSource()
|
||||
}
|
||||
|
||||
func removeDummyDataSourceIfNeeded(reloadAfter reload: Bool = true) {
|
||||
guard let collection = self as? CollectionSkeleton else { return }
|
||||
status = .off
|
||||
collection.removeDummyDataSource(reloadAfter: reload)
|
||||
collection.enableUserInteraction()
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ func skeletonLog(_ message: String) {
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
public var skeletonDescription: String {
|
||||
var description = "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())"
|
||||
let subSkeletons = subviewsSkeletonables
|
||||
|
||||
@@ -28,41 +28,90 @@ extension CAGradientLayer {
|
||||
}
|
||||
}
|
||||
|
||||
struct SkeletonMultilinesLayerConfig {
|
||||
var lines: Int
|
||||
var lineHeight: CGFloat? = nil
|
||||
var type: SkeletonType
|
||||
var lastLineFillPercent: Int
|
||||
var multilineCornerRadius: Int
|
||||
var multilineSpacing: CGFloat
|
||||
var paddingInsets: UIEdgeInsets
|
||||
}
|
||||
|
||||
|
||||
// MARK: Skeleton sublayers
|
||||
extension CALayer {
|
||||
|
||||
static let skeletonSubLayersName = "SkeletonSubLayersName"
|
||||
|
||||
var skeletonSublayers: [CALayer] {
|
||||
return sublayers?.filter { $0.name == CALayer.skeletonSubLayersName } ?? [CALayer]()
|
||||
}
|
||||
|
||||
func addMultilinesLayers(lines: Int, type: SkeletonType, lastLineFillPercent: Int, multilineCornerRadius: Int) {
|
||||
let numberOfSublayers = calculateNumLines(maxLines: lines)
|
||||
for index in 0..<numberOfSublayers {
|
||||
var width = bounds.width
|
||||
|
||||
if index == numberOfSublayers-1 && numberOfSublayers != 1 {
|
||||
width = width * CGFloat(lastLineFillPercent)/100;
|
||||
func addMultilinesLayers(for config: SkeletonMultilinesLayerConfig) {
|
||||
let numberOfSublayers = config.lines == 1 ? 1 : calculateNumLines(for: config)
|
||||
var height = config.lineHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
if numberOfSublayers == 1 {
|
||||
height = bounds.height
|
||||
}
|
||||
|
||||
let layerBuilder = SkeletonMultilineLayerBuilder()
|
||||
.setSkeletonType(config.type)
|
||||
.setCornerRadius(config.multilineCornerRadius)
|
||||
.setMultilineSpacing(config.multilineSpacing)
|
||||
.setPadding(config.paddingInsets)
|
||||
.setHeight(height)
|
||||
|
||||
(0..<numberOfSublayers).forEach { index in
|
||||
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: config.lastLineFillPercent, paddingInsets: config.paddingInsets)
|
||||
if let layer = layerBuilder
|
||||
.setIndex(index)
|
||||
.setWidth(width)
|
||||
.build() {
|
||||
addSublayer(layer)
|
||||
}
|
||||
|
||||
let layer = SkeletonLayerFactory().makeMultilineLayer(withType: type, for: index, width: width, multilineCornerRadius: multilineCornerRadius)
|
||||
addSublayer(layer)
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateNumLines(maxLines: Int) -> Int {
|
||||
let spaceRequitedForEachLine = SkeletonAppearance.default.multilineHeight + SkeletonAppearance.default.multilineSpacing
|
||||
var numberOfSublayers = Int(round(CGFloat(bounds.height)/CGFloat(spaceRequitedForEachLine)))
|
||||
if maxLines != 0, maxLines <= numberOfSublayers { numberOfSublayers = maxLines }
|
||||
|
||||
func updateMultilinesLayers(for config: SkeletonMultilinesLayerConfig) {
|
||||
let currentSkeletonSublayers = skeletonSublayers
|
||||
let numberOfSublayers = currentSkeletonSublayers.count
|
||||
let lastLineFillPercent = config.lastLineFillPercent
|
||||
let paddingInsets = config.paddingInsets
|
||||
let multilineSpacing = config.multilineSpacing
|
||||
var height = config.lineHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
if numberOfSublayers == 1 {
|
||||
height = bounds.height
|
||||
}
|
||||
|
||||
for (index, layer) in currentSkeletonSublayers.enumerated() {
|
||||
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: lastLineFillPercent, paddingInsets: paddingInsets)
|
||||
layer.updateLayerFrame(for: index, size: CGSize(width: width, height: height), multilineSpacing: multilineSpacing, paddingInsets: paddingInsets)
|
||||
}
|
||||
}
|
||||
|
||||
private func calculatedWidthForLine(at index: Int, totalLines: Int, lastLineFillPercent: Int, paddingInsets: UIEdgeInsets) -> CGFloat {
|
||||
var width = bounds.width - paddingInsets.left - paddingInsets.right
|
||||
if index == totalLines - 1 && totalLines != 1 {
|
||||
width = width * CGFloat(lastLineFillPercent) / 100
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
func updateLayerFrame(for index: Int, size: CGSize, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets) {
|
||||
let spaceRequiredForEachLine = SkeletonAppearance.default.multilineHeight + multilineSpacing
|
||||
frame = CGRect(x: paddingInsets.left, y: CGFloat(index) * spaceRequiredForEachLine + paddingInsets.top, width: size.width, height: size.height)
|
||||
}
|
||||
|
||||
private func calculateNumLines(for config: SkeletonMultilinesLayerConfig) -> Int {
|
||||
let requiredSpaceForEachLine = (config.lineHeight ?? SkeletonAppearance.default.multilineHeight) + config.multilineSpacing
|
||||
var numberOfSublayers = Int(round(CGFloat(bounds.height - config.paddingInsets.top - config.paddingInsets.bottom)/CGFloat(requiredSpaceForEachLine)))
|
||||
if config.lines != 0, config.lines <= numberOfSublayers { numberOfSublayers = config.lines }
|
||||
return numberOfSublayers
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Animations
|
||||
public extension CALayer {
|
||||
|
||||
var pulse: CAAnimation {
|
||||
let pulseAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.backgroundColor))
|
||||
pulseAnimation.fromValue = backgroundColor
|
||||
@@ -71,6 +120,7 @@ public extension CALayer {
|
||||
pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
pulseAnimation.autoreverses = true
|
||||
pulseAnimation.repeatCount = .infinity
|
||||
pulseAnimation.isRemovedOnCompletion = false
|
||||
return pulseAnimation
|
||||
}
|
||||
|
||||
@@ -88,15 +138,19 @@ public extension CALayer {
|
||||
animGroup.duration = 1.5
|
||||
animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
|
||||
animGroup.repeatCount = .infinity
|
||||
animGroup.isRemovedOnCompletion = false
|
||||
|
||||
return animGroup
|
||||
}
|
||||
|
||||
func playAnimation(_ anim: SkeletonLayerAnimation, key: String) {
|
||||
func playAnimation(_ anim: SkeletonLayerAnimation, key: String, completion: (() -> Void)? = nil) {
|
||||
skeletonSublayers.recursiveSearch(leafBlock: {
|
||||
DispatchQueue.main.async { CATransaction.begin() }
|
||||
DispatchQueue.main.async { CATransaction.setCompletionBlock(completion) }
|
||||
add(anim(self), forKey: key)
|
||||
DispatchQueue.main.async { CATransaction.commit() }
|
||||
}) {
|
||||
$0.playAnimation(anim, key: key)
|
||||
$0.playAnimation(anim, key: key, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,3 +162,17 @@ public extension CALayer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CALayer {
|
||||
func setOpacity(from: Int, to: Int, duration: TimeInterval, completion: (() -> Void)?) {
|
||||
DispatchQueue.main.async { CATransaction.begin() }
|
||||
let animation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
|
||||
animation.fromValue = from
|
||||
animation.toValue = to
|
||||
animation.duration = duration
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
DispatchQueue.main.async { CATransaction.setCompletionBlock(completion) }
|
||||
add(animation, forKey: "setOpacityAnimation")
|
||||
DispatchQueue.main.async { CATransaction.commit() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import Foundation
|
||||
|
||||
extension Int {
|
||||
|
||||
var whitespace: String {
|
||||
return whitespaces
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
//
|
||||
// UIColor+Skeleton.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 03/11/2017.
|
||||
// Copyright © 2017 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// codebeat:disable[TOO_MANY_IVARS]
|
||||
extension UIColor {
|
||||
|
||||
convenience init(_ hex: UInt) {
|
||||
self.init(
|
||||
red: CGFloat((hex & 0xFF0000) >> 16) / 255.0,
|
||||
@@ -20,15 +14,20 @@ extension UIColor {
|
||||
}
|
||||
|
||||
func isLight() -> Bool {
|
||||
// algorithm from: http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
|
||||
guard let components = cgColor.components,
|
||||
components.count >= 3 else { return false }
|
||||
components.count >= 3 else { return false }
|
||||
let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
|
||||
return !(brightness < 0.5)
|
||||
}
|
||||
|
||||
public var complementaryColor: UIColor {
|
||||
return isLight() ? darker : lighter
|
||||
if #available(iOS 13, tvOS 13, *) {
|
||||
return UIColor { traitCollection in
|
||||
return self.isLight() ? self.darker : self.lighter
|
||||
}
|
||||
} else {
|
||||
return isLight() ? darker : lighter
|
||||
}
|
||||
}
|
||||
|
||||
public var lighter: UIColor {
|
||||
@@ -65,11 +64,25 @@ public extension UIColor {
|
||||
static var carrot = UIColor(0xe67e22)
|
||||
static var alizarin = UIColor(0xe74c3c)
|
||||
static var clouds = UIColor(0xecf0f1)
|
||||
static var darkClouds = UIColor(0x1c2325)
|
||||
static var concrete = UIColor(0x95a5a6)
|
||||
static var flatOrange = UIColor(0xf39c12)
|
||||
static var pumpkin = UIColor(0xd35400)
|
||||
static var pomegranate = UIColor(0xc0392b)
|
||||
static var silver = UIColor(0xbdc3c7)
|
||||
static var asbestos = UIColor(0x7f8c8d)
|
||||
}
|
||||
|
||||
static var skeletonDefault: UIColor {
|
||||
if #available(iOS 13, tvOS 13, *) {
|
||||
return UIColor { traitCollection in
|
||||
switch traitCollection.userInterfaceStyle {
|
||||
case .dark: return .darkClouds
|
||||
default: return .clouds
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .clouds
|
||||
}
|
||||
}
|
||||
}
|
||||
// codebeat:enable[TOO_MANY_IVARS]
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
// codebeat:disable[TOO_MANY_IVARS]
|
||||
enum ViewAssociatedKeys {
|
||||
static var skeletonable = "skeletonable"
|
||||
static var status = "status"
|
||||
static var skeletonLayer = "layer"
|
||||
static var flowDelegate = "flowDelegate"
|
||||
static var isSkeletonAnimated = "isSkeletonAnimated"
|
||||
static var viewState = "viewState"
|
||||
static var labelViewState = "labelViewState"
|
||||
static var imageViewState = "imageViewState"
|
||||
static var currentSkeletonConfig = "currentSkeletonConfig"
|
||||
}
|
||||
// codebeat:enable[TOO_MANY_IVARS]
|
||||
|
||||
extension UIView {
|
||||
enum Status {
|
||||
case on
|
||||
case off
|
||||
}
|
||||
|
||||
var flowDelegate: SkeletonFlowDelegate? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.flowDelegate) as? SkeletonFlowDelegate }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.flowDelegate) }
|
||||
}
|
||||
|
||||
var skeletonLayer: SkeletonLayer? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.skeletonLayer) as? SkeletonLayer }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.skeletonLayer) }
|
||||
}
|
||||
|
||||
var currentSkeletonConfig: SkeletonConfig? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.currentSkeletonConfig) as? SkeletonConfig }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.currentSkeletonConfig) }
|
||||
}
|
||||
|
||||
var status: Status! {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.status) as? Status ?? .off }
|
||||
set { ao_set(newValue ?? .off, pkey: &ViewAssociatedKeys.status) }
|
||||
}
|
||||
|
||||
var isSkeletonAnimated: Bool! {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.isSkeletonAnimated) as? Bool ?? false }
|
||||
set { ao_set(newValue ?? false, pkey: &ViewAssociatedKeys.isSkeletonAnimated) }
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import UIKit
|
||||
|
||||
// MARK: Frame
|
||||
extension UIView {
|
||||
|
||||
var maxBoundsEstimated: CGRect {
|
||||
if let parentStackView = (superview as? UIStackView) {
|
||||
var origin: CGPoint = .zero
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
//
|
||||
// UIView+IBInspectable.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 01/11/2017.
|
||||
// Copyright © 2017 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
private enum AssociatedKeys {
|
||||
static var skeletonable = "skeletonable"
|
||||
static var status = "status"
|
||||
static var skeletonLayer = "layer"
|
||||
static var flowDelegate = "flowDelegate"
|
||||
static var isSkeletonAnimated = "isSkeletonAnimated"
|
||||
static var viewState = "viewState"
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
|
||||
@IBInspectable
|
||||
var isSkeletonable: Bool {
|
||||
get { return skeletonable }
|
||||
@@ -28,43 +12,10 @@ public extension UIView {
|
||||
var isSkeletonActive: Bool {
|
||||
return status == .on || (subviewsSkeletonables.first(where: { $0.isSkeletonActive }) != nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
enum Status {
|
||||
case on
|
||||
case off
|
||||
}
|
||||
|
||||
var flowDelegate: SkeletonFlowDelegate? {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.flowDelegate) as? SkeletonFlowDelegate }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.flowDelegate, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
var skeletonLayer: SkeletonLayer? {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.skeletonLayer) as? SkeletonLayer }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.skeletonLayer, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
var status: Status! {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.status) as? Status ?? .off }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.status, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
var viewState: RecoverableViewState? {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.viewState) as? RecoverableViewState }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.viewState, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
var skeletonIsAnimated: Bool! {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.isSkeletonAnimated) as? Bool ?? false }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.isSkeletonAnimated, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
fileprivate var skeletonable: Bool! {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.skeletonable) as? Bool ?? false }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.skeletonable, newValue, AssociationPolicy.retain.objc) }
|
||||
private var skeletonable: Bool! {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.skeletonable) as? Bool ?? false }
|
||||
set { ao_set(newValue ?? false, pkey: &ViewAssociatedKeys.skeletonable) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,17 @@
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
|
||||
enum Constants {
|
||||
static let becomeActiveNotification = UIApplication.didBecomeActiveNotification
|
||||
static let enterForegroundNotification = UIApplication.didEnterBackgroundNotification
|
||||
static let willTerminateNotification = UIApplication.willTerminateNotification
|
||||
static let needAnimatedSkeletonKey = "needAnimateSkeleton"
|
||||
}
|
||||
|
||||
func addAppNotificationsObservers() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: Constants.becomeActiveNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: Constants.enterForegroundNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(willTerminateNotification), name: Constants.enterForegroundNotification, object: nil)
|
||||
}
|
||||
|
||||
func removeAppNoticationsObserver() {
|
||||
@@ -33,6 +34,10 @@ extension UIView {
|
||||
}
|
||||
|
||||
@objc func appDidEnterBackground() {
|
||||
UserDefaults.standard.set((isSkeletonActive && skeletonIsAnimated), forKey: Constants.needAnimatedSkeletonKey)
|
||||
UserDefaults.standard.set((isSkeletonActive && isSkeletonAnimated), forKey: Constants.needAnimatedSkeletonKey)
|
||||
}
|
||||
|
||||
@objc func willTerminateNotification() {
|
||||
UserDefaults.standard.set(false, forKey: Constants.needAnimatedSkeletonKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
//
|
||||
// AssociationPolicy.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 02/11/2017.
|
||||
// Copyright © 2017 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -21,3 +15,31 @@ enum AssociationPolicy: UInt {
|
||||
return objc_AssociationPolicy(rawValue: rawValue)!
|
||||
}
|
||||
}
|
||||
|
||||
protocol AssociatedObjects: class { }
|
||||
|
||||
// transparent wrappers
|
||||
extension AssociatedObjects {
|
||||
/// wrapper around `objc_getAssociatedObject`
|
||||
func ao_get(pkey: UnsafeRawPointer) -> Any? {
|
||||
return objc_getAssociatedObject(self, pkey)
|
||||
}
|
||||
|
||||
/// wrapper around `objc_setAssociatedObject`
|
||||
func ao_setOptional(_ value: Any?, pkey: UnsafeRawPointer, policy: AssociationPolicy = .retainNonatomic) {
|
||||
guard let value = value else { return }
|
||||
objc_setAssociatedObject(self, pkey, value, policy.objc)
|
||||
}
|
||||
|
||||
/// wrapper around `objc_setAssociatedObject`
|
||||
func ao_set(_ value: Any, pkey: UnsafeRawPointer, policy: AssociationPolicy = .retainNonatomic) {
|
||||
objc_setAssociatedObject(self, pkey, value, policy.objc)
|
||||
}
|
||||
|
||||
/// wrapper around 'objc_removeAssociatedObjects'
|
||||
func ao_removeAll() {
|
||||
objc_removeAssociatedObjects(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSObject: AssociatedObjects { }
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
//
|
||||
// ContainsMultilineText.swift
|
||||
// SkeletonView-iOS
|
||||
//
|
||||
// Created by Juanpe Catalán on 07/11/2017.
|
||||
// Copyright © 2017 SkeletonView. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
private enum AssociatedKeys {
|
||||
static var lastLineFillingPercent = "lastLineFillingPercent"
|
||||
static var multilineCornerRadius = "multilineCornerRadius"
|
||||
|
||||
}
|
||||
|
||||
protocol ContainsMultilineText {
|
||||
var numLines: Int { get }
|
||||
var lastLineFillingPercent: Int { get }
|
||||
var multilineCornerRadius: Int { get }
|
||||
}
|
||||
|
||||
extension ContainsMultilineText {
|
||||
var numLines: Int { return 0 }
|
||||
}
|
||||
|
||||
public extension UILabel {
|
||||
|
||||
@IBInspectable
|
||||
var lastLineFillPercent: Int {
|
||||
get { return lastLineFillingPercent }
|
||||
set { lastLineFillingPercent = min(newValue, 100) }
|
||||
}
|
||||
@IBInspectable
|
||||
var linesCornerRadius: Int {
|
||||
get { return multilineCornerRadius }
|
||||
set { multilineCornerRadius = min(newValue, 10) }
|
||||
}
|
||||
}
|
||||
|
||||
public extension UITextView {
|
||||
|
||||
@IBInspectable
|
||||
var lastLineFillPercent: Int {
|
||||
get { return lastLineFillingPercent }
|
||||
set { lastLineFillingPercent = min(newValue, 100) }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var linesCornerRadius: Int {
|
||||
get { return multilineCornerRadius }
|
||||
set { multilineCornerRadius = min(newValue, 10) }
|
||||
}
|
||||
}
|
||||
|
||||
extension UILabel: ContainsMultilineText {
|
||||
var numLines: Int {
|
||||
return numberOfLines
|
||||
}
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonAppearance.default.multilineLastLineFillPercent }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
var multilineCornerRadius: Int {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.multilineCornerRadius) as? Int ?? SkeletonAppearance.default.multilineCornerRadius }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.multilineCornerRadius, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
}
|
||||
extension UITextView: ContainsMultilineText {
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonAppearance.default.multilineLastLineFillPercent }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.lastLineFillingPercent, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
|
||||
var multilineCornerRadius: Int {
|
||||
get { return objc_getAssociatedObject(self, &AssociatedKeys.multilineCornerRadius) as? Int ?? SkeletonAppearance.default.multilineCornerRadius }
|
||||
set { objc_setAssociatedObject(self, &AssociatedKeys.multilineCornerRadius, newValue, AssociationPolicy.retain.objc) }
|
||||
}
|
||||
}
|
||||
@@ -8,26 +8,39 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol PrepareForSkeleton {
|
||||
func prepareViewForSkeleton()
|
||||
extension UIView {
|
||||
@objc func prepareViewForSkeleton() {
|
||||
startTransition { [weak self] in
|
||||
self?.backgroundColor = .clear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UILabel: PrepareForSkeleton {
|
||||
func prepareViewForSkeleton() {
|
||||
text = " "
|
||||
extension UILabel {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
resignFirstResponder()
|
||||
startTransition { [weak self] in
|
||||
self?.textColor = .clear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UITextView: PrepareForSkeleton {
|
||||
func prepareViewForSkeleton() {
|
||||
text = " "
|
||||
extension UITextView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
resignFirstResponder()
|
||||
startTransition { [weak self] in
|
||||
self?.textColor = .clear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIImageView: PrepareForSkeleton {
|
||||
func prepareViewForSkeleton() {
|
||||
image = nil
|
||||
extension UIImageView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
startTransition { [weak self] in
|
||||
self?.image = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright © 2019 SkeletonView. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension DispatchQueue {
|
||||
private static var _onceTracker = [String]()
|
||||
|
||||
class func once(token: String, block:()->Void) {
|
||||
objc_sync_enter(self); defer { objc_sync_exit(self) }
|
||||
guard !_onceTracker.contains(token) else { return }
|
||||
|
||||
_onceTracker.append(token)
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
func swizzle(selector originalSelector: Selector, with swizzledSelector: Selector, inClass: AnyClass, usingClass: AnyClass) {
|
||||
guard let originalMethod = class_getInstanceMethod(inClass, originalSelector),
|
||||
let swizzledMethod = class_getInstanceMethod(usingClass, swizzledSelector)
|
||||
else { return }
|
||||
|
||||
if (class_addMethod(inClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))) {
|
||||
class_replaceMethod(inClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
|
||||
} else {
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright © 2017 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
enum MultilineAssociatedKeys {
|
||||
static var lastLineFillingPercent = "lastLineFillingPercent"
|
||||
static var multilineCornerRadius = "multilineCornerRadius"
|
||||
static var multilineSpacing = "multilineSpacing"
|
||||
static var paddingInsets = "paddingInsets"
|
||||
}
|
||||
|
||||
protocol ContainsMultilineText {
|
||||
var multilineTextFont: UIFont? { get }
|
||||
var numLines: Int { get }
|
||||
var lastLineFillingPercent: Int { get }
|
||||
var multilineCornerRadius: Int { get }
|
||||
var multilineSpacing: CGFloat { get }
|
||||
var paddingInsets: UIEdgeInsets { get }
|
||||
}
|
||||
|
||||
extension ContainsMultilineText {
|
||||
var numLines: Int { return 0 }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UILabel {
|
||||
@IBInspectable
|
||||
var lastLineFillPercent: Int {
|
||||
get { return lastLineFillingPercent }
|
||||
set { lastLineFillingPercent = min(newValue, 100) }
|
||||
}
|
||||
@IBInspectable
|
||||
var linesCornerRadius: Int {
|
||||
get { return multilineCornerRadius }
|
||||
set { multilineCornerRadius = min(newValue, 10) }
|
||||
}
|
||||
@IBInspectable
|
||||
var skeletonLineSpacing: CGFloat {
|
||||
get { return multilineSpacing }
|
||||
set { multilineSpacing = min(newValue, 10) }
|
||||
}
|
||||
@IBInspectable
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
extension UILabel: ContainsMultilineText {
|
||||
var multilineTextFont: UIFont? {
|
||||
return font
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
|
||||
var multilineCornerRadius: Int {
|
||||
get { return ao_get(pkey: &MultilineAssociatedKeys.multilineCornerRadius) as? Int ?? SkeletonAppearance.default.multilineCornerRadius }
|
||||
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.multilineCornerRadius) }
|
||||
}
|
||||
|
||||
var multilineSpacing: CGFloat {
|
||||
get { return ao_get(pkey: &MultilineAssociatedKeys.multilineSpacing) as? CGFloat ?? SkeletonAppearance.default.multilineSpacing }
|
||||
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.multilineSpacing) }
|
||||
}
|
||||
|
||||
var paddingInsets: UIEdgeInsets {
|
||||
get { return ao_get(pkey: &MultilineAssociatedKeys.paddingInsets) as? UIEdgeInsets ?? .zero }
|
||||
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.paddingInsets) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
public extension UITextView {
|
||||
@IBInspectable
|
||||
var lastLineFillPercent: Int {
|
||||
get { return lastLineFillingPercent }
|
||||
set { lastLineFillingPercent = min(newValue, 100) }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var linesCornerRadius: Int {
|
||||
get { return multilineCornerRadius }
|
||||
set { multilineCornerRadius = min(newValue, 10) }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var skeletonLineSpacing: CGFloat {
|
||||
get { return multilineSpacing }
|
||||
set { multilineSpacing = min(newValue, 10) }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
extension UITextView: ContainsMultilineText {
|
||||
var multilineTextFont: UIFont? {
|
||||
return font
|
||||
}
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
get {
|
||||
let defaultValue = SkeletonAppearance.default.multilineLastLineFillPercent
|
||||
return ao_get(pkey: &MultilineAssociatedKeys.lastLineFillingPercent) as? Int ?? defaultValue
|
||||
}
|
||||
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.lastLineFillingPercent) }
|
||||
}
|
||||
|
||||
var multilineCornerRadius: Int {
|
||||
get {
|
||||
let defaultValue = SkeletonAppearance.default.multilineCornerRadius
|
||||
return ao_get(pkey: &MultilineAssociatedKeys.multilineCornerRadius) as? Int ?? defaultValue
|
||||
}
|
||||
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.multilineCornerRadius) }
|
||||
}
|
||||
|
||||
var multilineSpacing: CGFloat {
|
||||
get { return ao_get(pkey: &MultilineAssociatedKeys.multilineSpacing) as? CGFloat ?? SkeletonAppearance.default.multilineSpacing }
|
||||
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.multilineSpacing) }
|
||||
}
|
||||
|
||||
var paddingInsets: UIEdgeInsets {
|
||||
get { return ao_get(pkey: &MultilineAssociatedKeys.paddingInsets) as? UIEdgeInsets ?? .zero }
|
||||
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.paddingInsets) }
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,15 @@
|
||||
import UIKit
|
||||
|
||||
protocol Recoverable {
|
||||
var viewState: RecoverableViewState? { get set }
|
||||
func saveViewState()
|
||||
func recoverViewState(forced: Bool)
|
||||
}
|
||||
|
||||
extension UIView: Recoverable {
|
||||
var viewState: RecoverableViewState? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.viewState) as? RecoverableViewState }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.viewState) }
|
||||
}
|
||||
|
||||
@objc func saveViewState() {
|
||||
viewState = RecoverableViewState(view: self)
|
||||
@@ -23,47 +26,74 @@ extension UIView: Recoverable {
|
||||
@objc func recoverViewState(forced: Bool) {
|
||||
guard let safeViewState = viewState else { return }
|
||||
|
||||
layer.cornerRadius = safeViewState.cornerRadius
|
||||
layer.masksToBounds = safeViewState.clipToBounds
|
||||
|
||||
if safeViewState.backgroundColor != backgroundColor || forced {
|
||||
backgroundColor = safeViewState.backgroundColor
|
||||
startTransition { [weak self] in
|
||||
self?.layer.cornerRadius = safeViewState.cornerRadius
|
||||
self?.layer.masksToBounds = safeViewState.clipToBounds
|
||||
|
||||
if safeViewState.backgroundColor != self?.backgroundColor || forced {
|
||||
self?.backgroundColor = safeViewState.backgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UILabel {
|
||||
extension UILabel{
|
||||
var labelState: RecoverableTextViewState? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.labelViewState) as? RecoverableTextViewState }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.labelViewState) }
|
||||
}
|
||||
|
||||
override func saveViewState() {
|
||||
super.saveViewState()
|
||||
viewState?.text = text
|
||||
labelState = RecoverableTextViewState(view: self)
|
||||
}
|
||||
|
||||
override func recoverViewState(forced: Bool) {
|
||||
super.recoverViewState(forced: forced)
|
||||
text = text == " " || forced ? viewState?.text : text
|
||||
startTransition { [weak self] in
|
||||
self?.textColor = self?.labelState?.textColor
|
||||
self?.text = self?.labelState?.text
|
||||
self?.isUserInteractionEnabled = self?.labelState?.isUserInteractionsEnabled ?? false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UITextView {
|
||||
extension UITextView{
|
||||
var textState: RecoverableTextViewState? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.labelViewState) as? RecoverableTextViewState }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.labelViewState) }
|
||||
}
|
||||
|
||||
override func saveViewState() {
|
||||
super.saveViewState()
|
||||
viewState?.text = text
|
||||
textState = RecoverableTextViewState(view: self)
|
||||
}
|
||||
|
||||
override func recoverViewState(forced: Bool) {
|
||||
super.recoverViewState(forced: forced)
|
||||
text = text == " " || forced ? viewState?.text : text
|
||||
startTransition { [weak self] in
|
||||
self?.textColor = self?.textState?.textColor
|
||||
self?.text = self?.textState?.text
|
||||
self?.isUserInteractionEnabled = self?.textState?.isUserInteractionsEnabled ?? false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIImageView {
|
||||
var imageState: RecoverableImageViewState? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.imageViewState) as? RecoverableImageViewState }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.imageViewState) }
|
||||
}
|
||||
|
||||
override func saveViewState() {
|
||||
super.saveViewState()
|
||||
viewState?.image = image
|
||||
imageState = RecoverableImageViewState(view: self)
|
||||
}
|
||||
|
||||
override func recoverViewState(forced: Bool) {
|
||||
super.recoverViewState(forced: forced)
|
||||
image = image == nil || forced ? viewState?.image : image
|
||||
startTransition { [weak self] in
|
||||
self?.image = self?.image == nil || forced ? self?.imageState?.image : self?.image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,35 @@ struct RecoverableViewState {
|
||||
var cornerRadius: CGFloat
|
||||
var clipToBounds: Bool
|
||||
|
||||
// UI text
|
||||
var text: String?
|
||||
|
||||
// UI image
|
||||
var image: UIImage?
|
||||
}
|
||||
|
||||
extension RecoverableViewState {
|
||||
init(view: UIView) {
|
||||
self.backgroundColor = view.backgroundColor
|
||||
self.clipToBounds = view.layer.masksToBounds
|
||||
self.cornerRadius = view.layer.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
struct RecoverableTextViewState {
|
||||
var text: String?
|
||||
var textColor: UIColor?
|
||||
var isUserInteractionsEnabled: Bool
|
||||
|
||||
init(view: UILabel) {
|
||||
self.textColor = view.textColor
|
||||
self.text = view.text
|
||||
self.isUserInteractionsEnabled = view.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(view: UITextView) {
|
||||
self.textColor = view.textColor
|
||||
self.text = view.text
|
||||
self.isUserInteractionsEnabled = view.isUserInteractionEnabled
|
||||
}
|
||||
}
|
||||
|
||||
struct RecoverableImageViewState {
|
||||
var image: UIImage?
|
||||
|
||||
init(view: UIImageView) {
|
||||
self.image = view.image
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ public enum GradientDirection {
|
||||
public func slidingAnimation(duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation {
|
||||
return SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: self, duration: duration)
|
||||
}
|
||||
|
||||
|
||||
// codebeat:disable[ABC]
|
||||
var startPoint: GradientAnimationPoint {
|
||||
switch self {
|
||||
case .leftRight:
|
||||
@@ -55,12 +56,11 @@ public enum GradientDirection {
|
||||
return ( from: CGPoint(x:2, y:2), to: CGPoint(x:0, y:0))
|
||||
}
|
||||
}
|
||||
// codebeat:enable[ABC]
|
||||
}
|
||||
|
||||
public class SkeletonAnimationBuilder {
|
||||
|
||||
public init() {
|
||||
}
|
||||
public init() { }
|
||||
|
||||
public func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation {
|
||||
return { layer in
|
||||
@@ -78,6 +78,7 @@ public class SkeletonAnimationBuilder {
|
||||
animGroup.duration = duration
|
||||
animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
|
||||
animGroup.repeatCount = .infinity
|
||||
animGroup.isRemovedOnCompletion = false
|
||||
|
||||
return animGroup
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Used to store all config needed to activate the skeleton layer.
|
||||
struct SkeletonConfig {
|
||||
/// Type of skeleton layer
|
||||
let type: SkeletonType
|
||||
|
||||
/// Colors used in skeleton layer
|
||||
let colors: [UIColor]
|
||||
|
||||
/// If type is gradient, which gradient direction
|
||||
let gradientDirection: GradientDirection?
|
||||
|
||||
/// Specify if skeleton is animated or not
|
||||
let animated: Bool
|
||||
|
||||
/// Used to execute a custom animation
|
||||
let animation: SkeletonLayerAnimation?
|
||||
|
||||
/// Transition style
|
||||
var transition: SkeletonTransitionStyle
|
||||
|
||||
init(
|
||||
type: SkeletonType,
|
||||
colors: [UIColor],
|
||||
gradientDirection: GradientDirection? = nil,
|
||||
animated: Bool = false,
|
||||
animation: SkeletonLayerAnimation? = nil,
|
||||
transition: SkeletonTransitionStyle = .none
|
||||
) {
|
||||
self.type = type
|
||||
self.colors = colors
|
||||
self.gradientDirection = gradientDirection
|
||||
self.animated = animated
|
||||
self.animation = animation
|
||||
self.transition = transition
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,42 @@
|
||||
import UIKit
|
||||
|
||||
protocol SkeletonFlowDelegate {
|
||||
func willBeginShowingSkeletons(withRootView rootView: UIView)
|
||||
func didShowSkeletons(withRootView rootView: UIView)
|
||||
func willBeginHidingSkeletons(withRootView rootView: UIView)
|
||||
func didHideSkeletons(withRootView rootView: UIView)
|
||||
|
||||
func willBeginShowingSkeletons(rootView: UIView)
|
||||
func didShowSkeletons(rootView: UIView)
|
||||
func willBeginUpdatingSkeletons(rootView: UIView)
|
||||
func didUpdateSkeletons(rootView: UIView)
|
||||
func willBeginLayingSkeletonsIfNeeded(rootView: UIView)
|
||||
func didLayoutSkeletonsIfNeeded(rootView: UIView)
|
||||
func willBeginHidingSkeletons(rootView: UIView)
|
||||
func didHideSkeletons(rootView: UIView)
|
||||
}
|
||||
|
||||
class SkeletonFlowHandler: SkeletonFlowDelegate {
|
||||
|
||||
func willBeginShowingSkeletons(withRootView rootView: UIView) {
|
||||
func willBeginShowingSkeletons(rootView: UIView) {
|
||||
rootView.addAppNotificationsObservers()
|
||||
}
|
||||
|
||||
func didShowSkeletons(withRootView rootView: UIView) {
|
||||
func didShowSkeletons(rootView: UIView) {
|
||||
printSkeletonHierarchy(in: rootView)
|
||||
}
|
||||
|
||||
func willBeginHidingSkeletons(withRootView rootView: UIView) {
|
||||
func willBeginUpdatingSkeletons(rootView: UIView) {
|
||||
}
|
||||
|
||||
func didUpdateSkeletons(rootView: UIView) {
|
||||
}
|
||||
|
||||
func willBeginLayingSkeletonsIfNeeded(rootView: UIView) {
|
||||
}
|
||||
|
||||
func didLayoutSkeletonsIfNeeded(rootView: UIView) {
|
||||
}
|
||||
|
||||
func willBeginHidingSkeletons(rootView: UIView) {
|
||||
rootView.removeAppNoticationsObserver()
|
||||
}
|
||||
|
||||
func didHideSkeletons(withRootView rootView: UIView) {
|
||||
func didHideSkeletons(rootView: UIView) {
|
||||
rootView.flowDelegate = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
import UIKit
|
||||
|
||||
public struct SkeletonGradient {
|
||||
|
||||
private var gradientColors: [UIColor]
|
||||
private let gradientColors: [UIColor]
|
||||
|
||||
public var colors: [UIColor] {
|
||||
return gradientColors
|
||||
|
||||
@@ -8,26 +8,6 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SkeletonLayerFactory {
|
||||
|
||||
func makeSkeletonLayer(withType type: SkeletonType, usingColors colors: [UIColor], andHolder holder: UIView) -> SkeletonLayer {
|
||||
return SkeletonLayer(withType: type, usingColors: colors, andSkeletonHolder: holder)
|
||||
}
|
||||
|
||||
func makeMultilineLayer(withType type: SkeletonType, for index: Int, width: CGFloat, multilineCornerRadius: Int) -> CALayer {
|
||||
let spaceRequiredForEachLine = SkeletonAppearance.default.multilineHeight + SkeletonAppearance.default.multilineSpacing
|
||||
let layer = type.layer
|
||||
layer.anchorPoint = .zero
|
||||
layer.name = CALayer.skeletonSubLayersName
|
||||
layer.frame = CGRect(x: 0.0, y: CGFloat(index) * spaceRequiredForEachLine, width: width, height: SkeletonAppearance.default.multilineHeight)
|
||||
|
||||
layer.cornerRadius = CGFloat(multilineCornerRadius)
|
||||
layer.masksToBounds = true
|
||||
|
||||
return layer
|
||||
}
|
||||
}
|
||||
|
||||
public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation
|
||||
|
||||
public enum SkeletonType {
|
||||
@@ -54,7 +34,6 @@ public enum SkeletonType {
|
||||
}
|
||||
|
||||
struct SkeletonLayer {
|
||||
|
||||
private var maskLayer: CALayer
|
||||
private weak var holder: UIView?
|
||||
|
||||
@@ -66,32 +45,83 @@ struct SkeletonLayer {
|
||||
return maskLayer
|
||||
}
|
||||
|
||||
init(withType type: SkeletonType, usingColors colors: [UIColor], andSkeletonHolder holder: UIView) {
|
||||
init(type: SkeletonType, colors: [UIColor], skeletonHolder holder: UIView) {
|
||||
self.holder = holder
|
||||
self.maskLayer = type.layer
|
||||
self.maskLayer.anchorPoint = .zero
|
||||
self.maskLayer.bounds = holder.maxBoundsEstimated
|
||||
addMultilinesIfNeeded()
|
||||
addTextLinesIfNeeded()
|
||||
self.maskLayer.tint(withColors: colors)
|
||||
}
|
||||
|
||||
func removeLayer() {
|
||||
maskLayer.removeFromSuperlayer()
|
||||
func update(usingColors colors: [UIColor]) {
|
||||
layoutIfNeeded()
|
||||
maskLayer.tint(withColors: colors)
|
||||
}
|
||||
|
||||
func layoutIfNeeded() {
|
||||
if let bounds = holder?.maxBoundsEstimated {
|
||||
maskLayer.bounds = bounds
|
||||
}
|
||||
updateLinesIfNeeded()
|
||||
}
|
||||
|
||||
func addMultilinesIfNeeded() {
|
||||
guard let multiLineView = holder as? ContainsMultilineText else { return }
|
||||
maskLayer.addMultilinesLayers(lines: multiLineView.numLines, type: type, lastLineFillPercent: multiLineView.lastLineFillingPercent, multilineCornerRadius: multiLineView.multilineCornerRadius)
|
||||
func removeLayer(transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
|
||||
switch transition {
|
||||
case .none:
|
||||
maskLayer.removeFromSuperlayer()
|
||||
completion?()
|
||||
case .crossDissolve(let duration):
|
||||
maskLayer.setOpacity(from: 1, to: 0, duration: duration) {
|
||||
self.maskLayer.removeFromSuperlayer()
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 config = SkeletonMultilinesLayerConfig(lines: textView.numLines,
|
||||
lineHeight: textView.multilineTextFont?.lineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
multilineCornerRadius: textView.multilineCornerRadius,
|
||||
multilineSpacing: textView.multilineSpacing,
|
||||
paddingInsets: textView.paddingInsets)
|
||||
|
||||
maskLayer.addMultilinesLayers(for: config)
|
||||
}
|
||||
|
||||
func updateLinesIfNeeded() {
|
||||
guard let textView = holderAsTextView else { return }
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numLines,
|
||||
lineHeight: textView.multilineTextFont?.lineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
multilineCornerRadius: textView.multilineCornerRadius,
|
||||
multilineSpacing: textView.multilineSpacing,
|
||||
paddingInsets: textView.paddingInsets)
|
||||
|
||||
maskLayer.updateMultilinesLayers(for: config)
|
||||
}
|
||||
|
||||
var holderAsTextView: ContainsMultilineText? {
|
||||
guard let textView = holder as? ContainsMultilineText,
|
||||
(textView.numLines == 0 || textView.numLines > 1 || textView.numLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
|
||||
return nil
|
||||
}
|
||||
return textView
|
||||
}
|
||||
}
|
||||
|
||||
extension SkeletonLayer {
|
||||
|
||||
func start(_ anim: SkeletonLayerAnimation? = nil) {
|
||||
func start(_ anim: SkeletonLayerAnimation? = nil, completion: (() -> Void)? = nil) {
|
||||
let animation = anim ?? type.layerAnimation
|
||||
contentLayer.playAnimation(animation, key: "skeletonAnimation")
|
||||
contentLayer.playAnimation(animation, key: "skeletonAnimation", completion: completion)
|
||||
}
|
||||
|
||||
|
||||
func stopAnimation() {
|
||||
contentLayer.stopAnimation(forKey: "skeletonAnimation")
|
||||
}
|
||||
|
||||
@@ -3,37 +3,89 @@
|
||||
import UIKit
|
||||
|
||||
public extension UIView {
|
||||
|
||||
func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor) {
|
||||
showSkeleton(withType: .solid, usingColors: [color])
|
||||
/// Shows the skeleton without animation using the view that calls this method as root view.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - color: The color of the skeleton. Defaults to `SkeletonAppearance.default.tintColor`.
|
||||
/// - transition: The style of the transition when the skeleton appears. Defaults to `.none`.
|
||||
func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, transition: SkeletonTransitionStyle = .none) {
|
||||
let config = SkeletonConfig(type: .solid, colors: [color], transition: transition)
|
||||
showSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
func showGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient) {
|
||||
showSkeleton(withType: .gradient, usingColors: gradient.colors)
|
||||
/// Shows the gradient skeleton without animation using the view that calls this method as root view.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - gradient: The gradient of the skeleton. Defaults to `SkeletonAppearance.default.gradient`.
|
||||
/// - transition: The style of the transition when the skeleton appears. Defaults to `.none`.
|
||||
func showGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, transition: SkeletonTransitionStyle = .none) {
|
||||
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, transition: transition)
|
||||
showSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
func showAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil) {
|
||||
showSkeleton(withType: .solid, usingColors: [color], animated: true, animation: animation)
|
||||
/// Shows the animated skeleton using the view that calls this method as root view.
|
||||
///
|
||||
/// If animation is nil, sliding animation will be used, with direction left to right.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - color: The color of skeleton. Defaults to `SkeletonAppearance.default.tintColor`.
|
||||
/// - animation: The animation of the skeleton. Defaults to `nil`.
|
||||
/// - transition: The style of the transition when the skeleton appears. Defaults to `.none`.
|
||||
func showAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .none) {
|
||||
let config = SkeletonConfig(type: .solid, colors: [color], animated: true, animation: animation, transition: transition)
|
||||
showSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
func showAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil) {
|
||||
showSkeleton(withType: .gradient, usingColors: gradient.colors, animated: true, animation: animation)
|
||||
/// Shows the gradient skeleton without animation using the view that calls this method as root view.
|
||||
///
|
||||
/// If animation is nil, sliding animation will be used, with direction left to right.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - gradient: The gradient of the skeleton. Defaults to `SkeletonAppearance.default.gradient`.
|
||||
/// - animation: The animation of the skeleton. Defaults to `nil`.
|
||||
/// - transition: The style of the transition when the skeleton appears. Defaults to `.none`.
|
||||
func showAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .none) {
|
||||
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: true, animation: animation, transition: transition)
|
||||
showSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
func updateSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor) {
|
||||
let config = SkeletonConfig(type: .solid, colors: [color])
|
||||
updateSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
func updateGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient) {
|
||||
let config = SkeletonConfig(type: .gradient, colors: gradient.colors)
|
||||
updateSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
func updateAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil) {
|
||||
let config = SkeletonConfig(type: .solid, colors: [color], animated: true, animation: animation)
|
||||
updateSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
func updateAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil) {
|
||||
let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: true, animation: animation)
|
||||
updateSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
func layoutSkeletonIfNeeded() {
|
||||
flowDelegate?.willBeginLayingSkeletonsIfNeeded(rootView: self)
|
||||
recursiveLayoutSkeletonIfNeeded(root: self)
|
||||
}
|
||||
|
||||
func hideSkeleton(reloadDataAfter reload: Bool = true) {
|
||||
flowDelegate?.willBeginHidingSkeletons(withRootView: self)
|
||||
recursiveHideSkeleton(reloadDataAfter: reload, root: self)
|
||||
func hideSkeleton(reloadDataAfter reload: Bool = true, transition: SkeletonTransitionStyle = .none) {
|
||||
flowDelegate?.willBeginHidingSkeletons(rootView: self)
|
||||
recursiveHideSkeleton(reloadDataAfter: reload, transition: transition, root: self)
|
||||
}
|
||||
|
||||
func startSkeletonAnimation(_ anim: SkeletonLayerAnimation? = nil) {
|
||||
skeletonIsAnimated = true
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: startSkeletonLayerAnimationBlock(anim)) { subview in
|
||||
subview.startSkeletonAnimation(anim)
|
||||
}
|
||||
}
|
||||
|
||||
func stopSkeletonAnimation() {
|
||||
skeletonIsAnimated = false
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: stopSkeletonLayerAnimationBlock) { subview in
|
||||
subview.stopSkeletonAnimation()
|
||||
}
|
||||
@@ -41,77 +93,194 @@ public extension UIView {
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
func showSkeleton(withType type: SkeletonType = .solid, usingColors colors: [UIColor], animated: Bool = false, animation: SkeletonLayerAnimation? = nil) {
|
||||
skeletonIsAnimated = animated
|
||||
flowDelegate = SkeletonFlowHandler()
|
||||
flowDelegate?.willBeginShowingSkeletons(withRootView: self)
|
||||
recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation, root: self)
|
||||
@objc func skeletonLayoutSubviews() {
|
||||
skeletonLayoutSubviews()
|
||||
guard isSkeletonActive else { return }
|
||||
layoutSkeletonIfNeeded()
|
||||
}
|
||||
|
||||
@objc func skeletonTraitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
guard isSkeletonActive, let config = currentSkeletonConfig else { return }
|
||||
updateSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
fileprivate func recursiveShowSkeleton(withType type: SkeletonType, usingColors colors: [UIColor], animated: Bool, animation: SkeletonLayerAnimation?, root: UIView? = nil) {
|
||||
addDummyDataSourceIfNeeded()
|
||||
func showSkeleton(skeletonConfig config: SkeletonConfig) {
|
||||
isSkeletonAnimated = config.animated
|
||||
flowDelegate = SkeletonFlowHandler()
|
||||
flowDelegate?.willBeginShowingSkeletons(rootView: self)
|
||||
recursiveShowSkeleton(skeletonConfig: config, root: self)
|
||||
}
|
||||
|
||||
private func recursiveShowSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
|
||||
guard !isSkeletonActive && isSkeletonable else { return }
|
||||
currentSkeletonConfig = config
|
||||
swizzleLayoutSubviews()
|
||||
swizzleTraitCollectionDidChange()
|
||||
addDummyDataSourceIfNeeded()
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
guard !isSkeletonActive else { return }
|
||||
isUserInteractionEnabled = false
|
||||
saveViewState()
|
||||
(self as? PrepareForSkeleton)?.prepareViewForSkeleton()
|
||||
addSkeletonLayer(withType: type, usingColors: colors, animated: animated, animation: animation)
|
||||
}) { subview in
|
||||
subview.recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation)
|
||||
showSkeletonIfNotActive(skeletonConfig: config)
|
||||
}){ subview in
|
||||
subview.recursiveShowSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
if let root = root {
|
||||
flowDelegate?.didShowSkeletons(withRootView: root)
|
||||
flowDelegate?.didShowSkeletons(rootView: root)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func recursiveHideSkeleton(reloadDataAfter reload: Bool, root: UIView? = nil) {
|
||||
removeDummyDataSourceIfNeeded()
|
||||
|
||||
private func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
|
||||
guard !isSkeletonActive else { return }
|
||||
saveViewState()
|
||||
isUserInteractionEnabled = false
|
||||
prepareViewForSkeleton()
|
||||
addSkeletonLayer(skeletonConfig: config)
|
||||
}
|
||||
|
||||
func updateSkeleton(skeletonConfig config: SkeletonConfig) {
|
||||
isSkeletonAnimated = config.animated
|
||||
flowDelegate?.willBeginUpdatingSkeletons(rootView: self)
|
||||
recursiveUpdateSkeleton(skeletonConfig: config, root: self)
|
||||
}
|
||||
|
||||
private func recursiveUpdateSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
|
||||
guard isSkeletonActive else { return }
|
||||
currentSkeletonConfig = config
|
||||
updateDummyDataSourceIfNeeded()
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
if skeletonLayer?.type != config.type {
|
||||
removeSkeletonLayer()
|
||||
addSkeletonLayer(skeletonConfig: config)
|
||||
} else {
|
||||
updateSkeletonLayer(skeletonConfig: config)
|
||||
}
|
||||
}) { subview in
|
||||
subview.recursiveUpdateSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
if let root = root {
|
||||
flowDelegate?.didUpdateSkeletons(rootView: root)
|
||||
}
|
||||
}
|
||||
|
||||
private func recursiveLayoutSkeletonIfNeeded(root: UIView? = nil) {
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
guard isSkeletonable, isSkeletonActive else { return }
|
||||
layoutSkeletonLayerIfNeeded()
|
||||
if let config = currentSkeletonConfig, config.animated, !isSkeletonAnimated {
|
||||
startSkeletonAnimation(config.animation)
|
||||
}
|
||||
}) { subview in
|
||||
subview.recursiveLayoutSkeletonIfNeeded()
|
||||
}
|
||||
|
||||
if let root = root {
|
||||
flowDelegate?.didLayoutSkeletonsIfNeeded(rootView: root)
|
||||
}
|
||||
}
|
||||
|
||||
private func recursiveHideSkeleton(reloadDataAfter reload: Bool, transition: SkeletonTransitionStyle, root: UIView? = nil) {
|
||||
guard isSkeletonActive else { return }
|
||||
currentSkeletonConfig?.transition = transition
|
||||
isUserInteractionEnabled = true
|
||||
removeDummyDataSourceIfNeeded(reloadAfter: reload)
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
recoverViewState(forced: false)
|
||||
removeSkeletonLayer()
|
||||
}) { subview in
|
||||
subview.recursiveHideSkeleton(reloadDataAfter: reload)
|
||||
subview.recursiveHideSkeleton(reloadDataAfter: reload, transition: transition)
|
||||
}
|
||||
|
||||
if let root = root {
|
||||
flowDelegate?.didHideSkeletons(withRootView: root)
|
||||
flowDelegate?.didHideSkeletons(rootView: root)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock {
|
||||
private func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock {
|
||||
return {
|
||||
self.isSkeletonAnimated = true
|
||||
guard let layer = self.skeletonLayer else { return }
|
||||
layer.start(anim)
|
||||
layer.start(anim) { [weak self] in
|
||||
self?.isSkeletonAnimated = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var stopSkeletonLayerAnimationBlock: VoidBlock {
|
||||
private var stopSkeletonLayerAnimationBlock: VoidBlock {
|
||||
return {
|
||||
self.isSkeletonAnimated = false
|
||||
guard let layer = self.skeletonLayer else { return }
|
||||
layer.stopAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
private func swizzleLayoutSubviews() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
DispatchQueue.once(token: "UIView.SkeletonView.swizzleLayoutSubviews") {
|
||||
swizzle(selector: #selector(UIView.layoutSubviews),
|
||||
with: #selector(UIView.skeletonLayoutSubviews),
|
||||
inClass: UIView.self,
|
||||
usingClass: UIView.self)
|
||||
self.layoutSkeletonIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func swizzleTraitCollectionDidChange() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
DispatchQueue.once(token: "UIView.SkeletonView.swizzleTraitCollectionDidChange") {
|
||||
swizzle(selector: #selector(UIView.traitCollectionDidChange(_:)),
|
||||
with: #selector(UIView.skeletonTraitCollectionDidChange(_:)),
|
||||
inClass: UIView.self,
|
||||
usingClass: UIView.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
func addSkeletonLayer(withType type: SkeletonType, usingColors colors: [UIColor], gradientDirection direction: GradientDirection? = nil, animated: Bool, animation: SkeletonLayerAnimation? = nil) {
|
||||
self.skeletonLayer = SkeletonLayerFactory().makeSkeletonLayer(withType: type, usingColors: colors, andHolder: self)
|
||||
layer.insertSublayer(skeletonLayer!.contentLayer, at: UInt32.max)
|
||||
if animated { skeletonLayer!.start(animation) }
|
||||
func addSkeletonLayer(skeletonConfig config: SkeletonConfig) {
|
||||
guard let skeletonLayer = SkeletonLayerBuilder()
|
||||
.setSkeletonType(config.type)
|
||||
.addColors(config.colors)
|
||||
.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)
|
||||
}
|
||||
}
|
||||
status = .on
|
||||
}
|
||||
|
||||
func updateSkeletonLayer(skeletonConfig config: SkeletonConfig) {
|
||||
guard let skeletonLayer = skeletonLayer else { return }
|
||||
skeletonLayer.update(usingColors: config.colors)
|
||||
if config.animated {
|
||||
startSkeletonAnimation(config.animation)
|
||||
} else {
|
||||
skeletonLayer.stopAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
func layoutSkeletonLayerIfNeeded() {
|
||||
guard let skeletonLayer = skeletonLayer else { return }
|
||||
skeletonLayer.layoutIfNeeded()
|
||||
}
|
||||
|
||||
func removeSkeletonLayer() {
|
||||
guard isSkeletonActive,
|
||||
let layer = skeletonLayer else { return }
|
||||
layer.stopAnimation()
|
||||
layer.removeLayer()
|
||||
skeletonLayer = nil
|
||||
status = .off
|
||||
let skeletonLayer = skeletonLayer,
|
||||
let transitionStyle = currentSkeletonConfig?.transition else { return }
|
||||
skeletonLayer.stopAnimation()
|
||||
skeletonLayer.removeLayer(transition: transitionStyle) {
|
||||
self.skeletonLayer = nil
|
||||
self.status = .off
|
||||
self.currentSkeletonConfig = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,35 +13,36 @@ extension UIView {
|
||||
}
|
||||
|
||||
extension UITableView {
|
||||
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return visibleCells
|
||||
}
|
||||
}
|
||||
|
||||
extension UITableViewCell {
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return contentView.subviews
|
||||
}
|
||||
}
|
||||
|
||||
extension UITableViewHeaderFooterView {
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return contentView.subviews
|
||||
}
|
||||
}
|
||||
|
||||
extension UICollectionView {
|
||||
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return subviews
|
||||
}
|
||||
}
|
||||
|
||||
extension UICollectionViewCell {
|
||||
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return contentView.subviews
|
||||
}
|
||||
}
|
||||
|
||||
extension UIStackView {
|
||||
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return arrangedSubviews
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright © 2019 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
public enum SkeletonTransitionStyle: Equatable {
|
||||
case none
|
||||
case crossDissolve(TimeInterval)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright © 2019 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
extension CALayer {
|
||||
func insertSublayer(_ layer: SkeletonLayer, at idx: UInt32, transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
|
||||
insertSublayer(layer.contentLayer, at: idx)
|
||||
switch transition {
|
||||
case .none:
|
||||
completion?()
|
||||
break
|
||||
case .crossDissolve(let duration):
|
||||
layer.contentLayer.setOpacity(from: 0, to: 1, duration: duration, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
func startTransition(transitionBlock: @escaping () -> Void) {
|
||||
guard let transitionStyle = currentSkeletonConfig?.transition,
|
||||
transitionStyle != .none
|
||||
else {
|
||||
transitionBlock()
|
||||
return
|
||||
}
|
||||
|
||||
if case let .crossDissolve(duration) = transitionStyle {
|
||||
UIView.transition(with: self,
|
||||
duration: duration,
|
||||
options: .transitionCrossDissolve,
|
||||
animations: transitionBlock,
|
||||
completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||