Compare commits

...

49 Commits

Author SHA1 Message Date
Juanpe Catalán 9a106d1af2 Merge pull request #254 from Juanpe/develop
Merge changes for version 1.8.4
2020-02-12 01:16:35 +01:00
Juanpe Catalán 5544acc63e bump version 1.8.4 2020-02-12 01:14:17 +01:00
Juanpe Catalán 65299378c4 fix: problem with text views that only have one line 2020-02-12 01:13:21 +01:00
Juanpe Catalán d8f1b4c53b Merge pull request #195 from esme-putt/Customise-Single-Line
Customise Single Line
2020-02-12 00:13:39 +01:00
Juanpe Catalán d2d8c1f5db Merge branch 'develop' into Customise-Single-Line 2020-02-12 00:11:28 +01:00
Juanpe Catalán ab385e5afb Merge pull request #234 from AnatoliBenke-Helsana/master
Update SkeletonView.swift
2020-02-12 00:05:32 +01:00
Juanpe Catalán 5d8cd9432e Merge branch 'master' into develop 2020-02-11 14:53:59 +01:00
Juanpe Catalán fbaf2e7b4b Update greetings.yml 2020-02-11 11:38:45 +01:00
Juanpe Catalán 97d83c7038 Create greetings.yml 2020-02-11 11:36:24 +01:00
Juanpe Catalán ce83713240 update staling issues
just stale issues with state equal to awaiting user input
2020-02-11 11:13:32 +01:00
Juanpe Catalán 56d3156f8e Merge branch 'master' into develop 2020-02-11 10:52:04 +01:00
Juanpe Catalán aeb9dcf2c7 Create auto-comment.yml 2020-02-11 10:43:30 +01:00
Juanpe Catalán 9914be0f5b Merge pull request #240 from damien-danglard/master
[fix] Use UIFont.lineHeight for calculate the number of CALayer used in …
2020-02-11 10:33:28 +01:00
Juanpe Catalán 7d0609098c update stale.yml to not stale assigned issues 2020-02-10 00:29:24 +01:00
Juanpe Catalán b431aabddb increase pod version 2020-01-31 15:22:54 +01:00
Juanpe Catalán 0091ffc08b Merge branch 'develop' 2020-01-31 14:45:07 +01:00
Juanpe Catalán e948e313a1 Merge pull request #216 from MikeGlotov/fix/preserve-user-interactions-state
Added "isUserInteractionsEnabled" state preservation to UITextView an…
2020-01-30 11:17:43 +01:00
Damien Danglard a7ae5f0f9f [fix] resolve issues from codebeat 2020-01-27 17:11:23 +01:00
Damien Danglard 85cce0cd7c [fix] Use font lineHeight for calculate the number of CALayer used in multiline Skeleton UILabel
close #239
2020-01-24 15:35:29 +01:00
Juanpe Catalán 3afe7286a2 Merge pull request #152 from eduardbosch/feature/fix_skeleton_hierarchy
Fix skeleton hierarchy
2020-01-23 20:38:19 +01:00
AnatoliBenke-Helsana 6f1db7e303 Update SkeletonView.swift
- Fixes for Issue #202 UIView.layoutSubviews swizzle is messing with standard controls
- Due to Swizzling, LayoutSubviews is not called. This fixes the issue.
2020-01-07 13:39:32 +01:00
Juanpe Catalán c1815642db Merge pull request #209 from nikitskynikita/master
Add support skeletonable headers and footers of UITableView and UICollectionView
2019-12-30 20:12:00 +01:00
Juanpe Catalán a6a168a919 Merge pull request #225 from Juanpe/dependabot/bundler/excon-0.71.0
build(deps): bump excon from 0.65.0 to 0.71.0
2019-12-30 20:10:58 +01:00
dependabot[bot] d0dbd1e004 build(deps): bump excon from 0.65.0 to 0.71.0
Bumps [excon](https://github.com/excon/excon) from 0.65.0 to 0.71.0.
- [Release notes](https://github.com/excon/excon/releases)
- [Changelog](https://github.com/excon/excon/blob/master/changelog.txt)
- [Commits](https://github.com/excon/excon/compare/v0.65.0...v0.71.0)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-16 22:14:49 +00:00
Juanpe Catalán 4ade8f8797 Update FUNDING.yml 2019-12-10 17:37:49 +01:00
Nikita Nikitsky 264195bf16 Add support skeletonable headers and footers of UITableView and UICollectionView 2019-12-01 14:37:37 +04:00
Mikhail Glotov 11cc9a3ff7 Added "isUserInteractionsEnabled" state preservation to UITextView and UILabel 2019-11-19 14:26:22 +03:00
Nikita Nikitsky 37ac7d6e5d Fix attributes for tvOS 2019-11-05 14:37:55 +04:00
Juanpe Catalán a1e183a5e1 Merge pull request #199 from Wilsonator5000/feature/dark-mode
Support dark mode
2019-11-04 11:57:38 +01:00
Juanpe Catalán 420785502b Merge pull request #208 from Juanpe/dependabot/bundler/rubyzip-1.3.0
build(deps): bump rubyzip from 1.2.3 to 1.3.0
2019-11-04 11:57:07 +01:00
dependabot[bot] eed18e5372 build(deps): bump rubyzip from 1.2.3 to 1.3.0
Bumps [rubyzip](https://github.com/rubyzip/rubyzip) from 1.2.3 to 1.3.0.
- [Release notes](https://github.com/rubyzip/rubyzip/releases)
- [Changelog](https://github.com/rubyzip/rubyzip/blob/master/Changelog.md)
- [Commits](https://github.com/rubyzip/rubyzip/compare/v1.2.3...v1.3.0)

Signed-off-by: dependabot[bot] <support@github.com>
2019-11-03 14:00:59 +00:00
Wilson Gramer 0a86ac4a9c Merge branch 'master' into feature/dark-mode 2019-10-22 12:53:16 -04:00
Wilson Gramer 264b0a70c2 Update CHANGELOG 2019-10-22 12:51:26 -04:00
Juanpe Catalán 01d68190ab Merge pull request #196 from ashleyng/FixCellDataRefresh
Update tableview cells after hiding skeleton
2019-10-17 09:12:32 +02:00
Juanpe Catalán 2d57efd9cb Update CHANGELOG.md 2019-10-17 09:07:47 +02:00
Wilson Gramer da51a6f673 Update English README 2019-10-13 21:04:45 -04:00
Wilson Gramer f6d4343cc6 Update example to support dark mode 2019-10-13 20:55:53 -04:00
Wilson Gramer c6e20aa1a0 Swizzle traitCollectionDidChange(_:) method in skeleton views
Make skeleton views respond to changes in system appearance by
updating the skeleton view whenever the trait collection changes,
so the colors change automatically with dark mode.
2019-10-13 20:47:03 -04:00
Wilson Gramer 809544066f Make skeleton colors dynamic
If on iOS 13 or higher, returns a UIColor using the
`init(dynamicProvider:)` initializer so it automatically adjusts to
dark mode. Also switches out the `clouds` default color with the
dynamic `skeletonDefault` color.
2019-10-13 20:45:53 -04:00
Ashley Ng 9406e3ef62 refresh tableview after removing skeleton views 2019-10-05 20:49:20 -05:00
Esme Putt 6d4c7d76c3 Added flipper for custom options 2019-10-04 13:28:21 +13:00
Esme Putt f580fdaac2 Changed update function 2019-10-04 13:11:45 +13:00
Esme Putt 4be93db383 Added spacing 2019-10-04 11:52:38 +13:00
Esme Putt ae4ecfa760 Added comments 2019-10-04 11:28:17 +13:00
Esme Putt f60e5cd7ae Add-custom-options-for-single-line 2019-10-04 11:06:30 +13:00
Juanpe Catalán e097385de9 Merge pull request #174 from superhuman/master
Add ability to customize line spacing per label
2019-09-15 10:40:10 +02:00
gshahbazian 84d8971aa2 Add ability to customize line spacing per label
Also customizability of edge insets for skeleton rows
2019-09-13 09:59:17 -07:00
Eduard Bosch Bertran 731509a46f fix: Update examples to show skeletons 2019-08-27 21:17:47 +02:00
Eduard Bosch Bertran 7833c94f2e fix: Stop showing skeleton views when a view is not skeletonable 2019-08-27 21:17:33 +02:00
29 changed files with 418 additions and 110 deletions
+1 -9
View File
@@ -1,9 +1 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: skeletonview
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
custom: # Replace with a single custom sponsorship URL
github: [juanpe]
+9
View File
@@ -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.
+2 -7
View File
@@ -1,15 +1,10 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- work in progress
onlyLabels:
- awaiting user input
staleLabel: given up
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
+14
View File
@@ -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'
+22
View File
@@ -3,9 +3,31 @@ 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
+3
View File
@@ -188,6 +188,9 @@
<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"/>
@@ -45,7 +45,6 @@ class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.isSkeletonable = true
collectionView.prepareSkeleton(completion: { done in
self.view.showAnimatedSkeleton()
})
+21 -20
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<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="14824"/>
<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>
@@ -13,19 +13,19 @@
<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="0.0" width="375" height="243"/>
<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"/>
<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>
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<userDefinedRuntimeAttributes>
@@ -50,7 +50,7 @@
</userDefinedRuntimeAttributes>
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<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"/>
@@ -65,7 +65,7 @@
</userDefinedRuntimeAttributes>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="UCB-SP-lQk">
<rect key="frame" x="0.0" y="243" width="375" height="215"/>
<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>
@@ -87,12 +87,12 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhU-1t-AaI" userLabel="Label">
<rect key="frame" x="118" y="29" width="237" height="20.5"/>
<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>
@@ -131,10 +131,11 @@
</userDefinedRuntimeAttributes>
<connections>
<outlet property="dataSource" destination="BYZ-38-t0r" id="Hxi-nC-gbY"/>
<outlet property="delegate" destination="BYZ-38-t0r" id="Z10-Nx-iGb"/>
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XgY-1a-UGc">
<rect key="frame" x="0.0" y="458" width="375" height="160"/>
<rect key="frame" x="0.0" y="569" width="375" height="160"/>
<subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="xOL-Sq-r4i">
<rect key="frame" x="20" y="23" width="145" height="32"/>
@@ -159,7 +160,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Color" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7CF-rV-pK2">
<rect key="frame" x="20" y="73.5" width="90" height="21"/>
<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"/>
@@ -190,7 +191,7 @@
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fade Duration: 0 sec" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mrw-PM-jJJ">
<rect key="frame" x="113.5" y="130" width="141.5" height="18"/>
<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"/>
@@ -202,7 +203,7 @@
</connections>
</stepper>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="leading" secondItem="mrw-PM-jJJ" secondAttribute="trailing" constant="8" id="5iU-dO-qVc"/>
<constraint firstItem="mrw-PM-jJJ" firstAttribute="centerY" secondItem="l4N-LL-ZrJ" secondAttribute="centerY" id="9OM-mx-4Jo"/>
@@ -227,7 +228,7 @@
</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"/>
@@ -242,7 +243,7 @@
</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"/>
@@ -260,16 +261,16 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-2682" y="340"/>
<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="667"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="Ao1-hk-zrH"/>
</view>
<tabBarItem key="tabBarItem" title="Item" id="iKp-9S-aib"/>
+1 -1
View File
@@ -2,4 +2,4 @@
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")]
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")]
+23
View File
@@ -14,6 +14,8 @@ class ViewController: UIViewController {
didSet {
tableview.rowHeight = UITableView.automaticDimension
tableview.estimatedRowHeight = 120.0
tableview.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "HeaderIdentifier")
tableview.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "FooterIdentifier")
}
}
@@ -178,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
}
}
+2 -2
View File
@@ -58,7 +58,7 @@ GEM
dotenv (2.7.4)
emoji_regex (1.0.1)
escape (0.0.4)
excon (0.65.0)
excon (0.71.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
@@ -163,7 +163,7 @@ GEM
retriable (3.1.2)
rouge (2.0.7)
ruby-macho (1.4.0)
rubyzip (1.2.3)
rubyzip (1.3.0)
security (0.1.3)
signet (0.11.0)
addressable (~> 2.3)
+20 -2
View File
@@ -213,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.
@@ -230,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
@@ -326,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
@@ -344,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
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "SkeletonView"
s.version = "1.8.2"
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.
+5 -2
View File
@@ -9,6 +9,7 @@ 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 {
@@ -19,9 +20,9 @@ public enum SkeletonAppearance {
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,5 +31,7 @@ class SkeletonViewAppearance: Appearance {
var multilineLastLineFillPercent: Int = 70
var multilineCornerRadius: Int = 0
var renderSingleLineAsView: Bool = false
}
// codebeat:enable[TOO_MANY_IVARS]
@@ -7,8 +7,11 @@ import UIKit
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
@@ -20,6 +23,11 @@ class SkeletonMultilineLayerBuilder {
return self
}
func setHeight(_ height: CGFloat) -> SkeletonMultilineLayerBuilder {
self.height = height
return self
}
func setWidth(_ width: CGFloat) -> SkeletonMultilineLayerBuilder {
self.width = width
return self
@@ -30,17 +38,28 @@ class SkeletonMultilineLayerBuilder {
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, width: width)
layer.updateLayerFrame(for: index, size: CGSize(width: width, height: height), multilineSpacing: self.multilineSpacing, paddingInsets: paddingInsets)
layer.cornerRadius = CGFloat(radius)
layer.masksToBounds = true
@@ -36,7 +36,7 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = originalTableViewDataSource?.collectionSkeletonView(tableView, cellIdentifierForRowAt: indexPath) ?? ""
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
skeletonCellIfContainerSkeletonIsActive(container: tableView, cell: cell)
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: cell)
return cell
}
}
@@ -54,7 +54,7 @@ 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)
skeletonCellIfContainerSkeletonIsActive(container: collectionView, cell: cell)
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: cell)
return cell
}
@@ -63,8 +63,9 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
at indexPath: IndexPath) -> UICollectionReusableView {
if let viewIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, supplementaryViewIdentifierOfKind: kind, at: indexPath) {
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: viewIdentifier, for: indexPath)
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: viewIdentifier, for: indexPath)
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: view)
return view
}
return UICollectionReusableView()
@@ -73,12 +74,12 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
}
extension SkeletonCollectionDataSource {
private func skeletonCellIfContainerSkeletonIsActive(container: UIView, cell: UIView) {
private func skeletonViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
guard container.isSkeletonActive,
let skeletonConfig = container.currentSkeletonConfig else {
return
}
cell.showSkeleton(skeletonConfig: skeletonConfig)
view.showSkeleton(skeletonConfig: skeletonConfig)
}
}
@@ -18,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)
}
}
@@ -29,4 +29,17 @@ public extension SkeletonTableViewDataSource {
}
}
public protocol SkeletonTableViewDelegate: UITableViewDelegate { }
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,6 +8,8 @@
import UIKit
public typealias ReusableHeaderFooterIdentifier = String
extension UITableView: CollectionSkeleton {
var estimatedNumberOfRows: Int {
return Int(ceil(frame.height/rowHeight))
@@ -37,6 +39,14 @@ extension UITableView: CollectionSkeleton {
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()
}
@@ -53,6 +63,12 @@ extension UITableView: CollectionSkeleton {
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() }
}
+52 -27
View File
@@ -28,6 +28,16 @@ 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 {
@@ -37,15 +47,22 @@ extension CALayer {
return sublayers?.filter { $0.name == CALayer.skeletonSubLayersName } ?? [CALayer]()
}
func addMultilinesLayers(lines: Int, type: SkeletonType, lastLineFillPercent: Int, multilineCornerRadius: Int) {
let numberOfSublayers = calculateNumLines(maxLines: lines)
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(type)
.setCornerRadius(multilineCornerRadius)
.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: lastLineFillPercent)
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: config.lastLineFillPercent, paddingInsets: config.paddingInsets)
if let layer = layerBuilder
.setIndex(index)
.setWidth(width)
@@ -54,33 +71,41 @@ extension CALayer {
}
}
}
func updateMultilinesLayers(lastLineFillPercent: Int) {
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)
layer.updateLayerFrame(for: index, width: width)
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) -> CGFloat {
var width = bounds.width
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, width: CGFloat) {
let spaceRequiredForEachLine = SkeletonAppearance.default.multilineHeight + SkeletonAppearance.default.multilineSpacing
frame = CGRect(x: 0.0, y: CGFloat(index) * spaceRequiredForEachLine, width: width, height: SkeletonAppearance.default.multilineHeight)
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(maxLines: Int) -> Int {
let requiredSpaceForEachLine = SkeletonAppearance.default.multilineHeight + SkeletonAppearance.default.multilineSpacing
var numberOfSublayers = Int(round(CGFloat(bounds.height)/CGFloat(requiredSpaceForEachLine)))
if maxLines != 0, maxLines <= numberOfSublayers { numberOfSublayers = maxLines }
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
}
}
@@ -139,15 +164,15 @@ public extension CALayer {
}
extension CALayer {
func setOpacity(from: Int, to: Int, duration: TimeInterval, completion: (() -> Void)?) {
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)
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")
add(animation, forKey: "setOpacityAnimation")
DispatchQueue.main.async { CATransaction.commit() }
}
}
}
+21 -1
View File
@@ -21,7 +21,13 @@ extension UIColor {
}
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 {
@@ -58,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]
@@ -5,12 +5,17 @@ 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 {
@@ -13,9 +13,23 @@ public extension UILabel {
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
}
@@ -29,4 +43,14 @@ extension UILabel: ContainsMultilineText {
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) }
}
}
@@ -14,9 +14,25 @@ public extension UITextView {
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
@@ -32,4 +48,14 @@ extension UITextView: ContainsMultilineText {
}
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) }
}
}
+2
View File
@@ -53,6 +53,7 @@ extension UILabel{
startTransition { [weak self] in
self?.textColor = self?.labelState?.textColor
self?.text = self?.labelState?.text
self?.isUserInteractionEnabled = self?.labelState?.isUserInteractionsEnabled ?? false
}
}
}
@@ -73,6 +74,7 @@ extension UITextView{
startTransition { [weak self] in
self?.textColor = self?.textState?.textColor
self?.text = self?.textState?.text
self?.isUserInteractionEnabled = self?.textState?.isUserInteractionsEnabled ?? false
}
}
}
@@ -23,15 +23,18 @@ struct RecoverableViewState {
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
}
}
+39 -21
View File
@@ -50,7 +50,7 @@ struct SkeletonLayer {
self.maskLayer = type.layer
self.maskLayer.anchorPoint = .zero
self.maskLayer.bounds = holder.maxBoundsEstimated
addMultilinesIfNeeded()
addTextLinesIfNeeded()
self.maskLayer.tint(withColors: colors)
}
@@ -63,7 +63,7 @@ struct SkeletonLayer {
if let bounds = holder?.maxBoundsEstimated {
maskLayer.bounds = bounds
}
updateMultilinesIfNeeded()
updateLinesIfNeeded()
}
func removeLayer(transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
@@ -72,29 +72,47 @@ struct SkeletonLayer {
maskLayer.removeFromSuperlayer()
completion?()
case .crossDissolve(let duration):
maskLayer.setOpacity(from: 1, to: 0, duration: duration) {
self.maskLayer.removeFromSuperlayer()
completion?()
}
maskLayer.setOpacity(from: 1, to: 0, duration: duration) {
self.maskLayer.removeFromSuperlayer()
completion?()
}
}
}
/// Returns a multiLineViewHolder only if the current holder implements the `ContainsMultilineText` protocol,
/// and actually displays multiple lines of text.
private var multiLineViewHolder: ContainsMultilineText? {
guard let multiLineView = holder as? ContainsMultilineText,
multiLineView.numLines != 1 else { return nil }
return multiLineView
}
/// 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)
func addMultilinesIfNeeded() {
guard let multiLineView = multiLineViewHolder else { return }
maskLayer.addMultilinesLayers(lines: multiLineView.numLines, type: type, lastLineFillPercent: multiLineView.lastLineFillingPercent, multilineCornerRadius: multiLineView.multilineCornerRadius)
maskLayer.addMultilinesLayers(for: config)
}
func updateMultilinesIfNeeded() {
guard let multiLineView = multiLineViewHolder else { return }
maskLayer.updateMultilinesLayers(lastLineFillPercent: multiLineView.lastLineFillingPercent)
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
}
}
@@ -103,7 +121,7 @@ extension SkeletonLayer {
let animation = anim ?? type.layerAnimation
contentLayer.playAnimation(animation, key: "skeletonAnimation", completion: completion)
}
func stopAnimation() {
contentLayer.stopAnimation(forKey: "skeletonAnimation")
}
+23 -5
View File
@@ -94,9 +94,15 @@ public extension UIView {
extension UIView {
@objc func skeletonLayoutSubviews() {
skeletonLayoutSubviews()
guard isSkeletonActive else { return }
layoutSkeletonIfNeeded()
}
@objc func skeletonTraitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
guard isSkeletonActive, let config = currentSkeletonConfig else { return }
updateSkeleton(skeletonConfig: config)
}
func showSkeleton(skeletonConfig config: SkeletonConfig) {
isSkeletonAnimated = config.animated
@@ -106,9 +112,10 @@ extension UIView {
}
private func recursiveShowSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
guard !isSkeletonActive else { return }
guard !isSkeletonActive && isSkeletonable else { return }
currentSkeletonConfig = config
swizzleLayoutSubviews()
swizzleTraitCollectionDidChange()
addDummyDataSourceIfNeeded()
subviewsSkeletonables.recursiveSearch(leafBlock: {
showSkeletonIfNotActive(skeletonConfig: config)
@@ -123,8 +130,8 @@ extension UIView {
private func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
guard !isSkeletonActive else { return }
isUserInteractionEnabled = false
saveViewState()
isUserInteractionEnabled = false
prepareViewForSkeleton()
addSkeletonLayer(skeletonConfig: config)
}
@@ -173,16 +180,16 @@ extension UIView {
private func recursiveHideSkeleton(reloadDataAfter reload: Bool, transition: SkeletonTransitionStyle, root: UIView? = nil) {
guard isSkeletonActive else { return }
removeDummyDataSourceIfNeeded(reloadAfter: reload)
currentSkeletonConfig?.transition = transition
isUserInteractionEnabled = true
removeDummyDataSourceIfNeeded(reloadAfter: reload)
subviewsSkeletonables.recursiveSearch(leafBlock: {
recoverViewState(forced: false)
removeSkeletonLayer()
}) { subview in
subview.recursiveHideSkeleton(reloadDataAfter: reload, transition: transition)
}
if let root = root {
flowDelegate?.didHideSkeletons(rootView: root)
}
@@ -208,7 +215,7 @@ extension UIView {
private func swizzleLayoutSubviews() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
DispatchQueue.once(token: "UIView.SkeletonView.swizzle") {
DispatchQueue.once(token: "UIView.SkeletonView.swizzleLayoutSubviews") {
swizzle(selector: #selector(UIView.layoutSubviews),
with: #selector(UIView.skeletonLayoutSubviews),
inClass: UIView.self,
@@ -217,6 +224,17 @@ extension UIView {
}
}
}
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 {
+6
View File
@@ -24,6 +24,12 @@ extension UITableViewCell {
}
}
extension UITableViewHeaderFooterView {
override var subviewsToSkeleton: [UIView] {
return contentView.subviews
}
}
extension UICollectionView {
override var subviewsToSkeleton: [UIView] {
return subviews