Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e8d5eb61d8 | |||
| c2a029ed51 | |||
| a1c8276980 | |||
| e9ac3a5ab3 | |||
| fb83a62f7b | |||
| 12521c1d87 | |||
| c266035888 | |||
| 74b5172ea5 | |||
| 318e629d04 | |||
| 62193db76f | |||
| 19e7866d3d | |||
| 36668f450b | |||
| 1bdb3b9c72 | |||
| 6c4e9091a7 | |||
| 61a6efbc7b | |||
| 4143a6065c | |||
| f3baeedc14 | |||
| 29f7d8fd6a | |||
| 83e1600a73 | |||
| 450cecbd9c | |||
| 1676f8720a |
@@ -16,4 +16,4 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: xcodebuild clean -target '${{ matrix.build-config['target'] }}' -sdk '${{ matrix.build-config['sdk'] }}' -destination '${{ matrix.build-config['destination'] }}'
|
||||
run: xcodebuild clean build -target '${{ matrix.build-config['target'] }}' -sdk '${{ matrix.build-config['sdk'] }}' -destination '${{ matrix.build-config['destination'] }}'
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
name: Pod lint
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
pod_lib_lint:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- env:
|
||||
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
|
||||
run: |
|
||||
set -eo pipefail
|
||||
pod lib lint --allow-warnings
|
||||
@@ -7,10 +7,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: setup fastlane
|
||||
- name: Setup fastlane
|
||||
run: brew install fastlane
|
||||
|
||||
- name: publish release
|
||||
- name: Publish release
|
||||
id: publish_release
|
||||
uses: release-drafter/release-drafter@v5
|
||||
with:
|
||||
@@ -18,10 +18,10 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: update podspec file
|
||||
- name: Update podspec
|
||||
run: fastlane bump_version next_version:${{ steps.publish_release.outputs.tag_name }}
|
||||
|
||||
- name: commit changes
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
branch: 'main'
|
||||
@@ -29,7 +29,10 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: push pod
|
||||
- name: Deploy to Cocoapods
|
||||
env:
|
||||
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
|
||||
run: pod trunk push SkeletonView.podspec --allow-warnings --verbose
|
||||
run: |
|
||||
set -eo pipefail
|
||||
pod lib lint --allow-warnings
|
||||
pod trunk push --allow-warnings
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
|
||||
<device id="retina5_9" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@@ -155,6 +155,7 @@
|
||||
<connections>
|
||||
<outlet property="avatar" destination="oiE-tt-nc2" id="Dkh-R5-Qhu"/>
|
||||
<outlet property="label1" destination="VhU-1t-AaI" id="kUW-HV-KrD"/>
|
||||
<outlet property="textField" destination="dha-bH-Ipf" id="OHI-6P-tuU"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
|
||||
@@ -9,11 +9,25 @@
|
||||
import UIKit
|
||||
|
||||
class Cell: UITableViewCell {
|
||||
|
||||
|
||||
@IBOutlet weak var avatar: UIImageView!
|
||||
@IBOutlet weak var label1: UILabel!
|
||||
|
||||
@IBOutlet weak var textField: UITextField!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
setUpInputAccessoryView()
|
||||
}
|
||||
|
||||
func setUpInputAccessoryView() {
|
||||
let bar = UIToolbar()
|
||||
let reset = UIBarButtonItem(title: "InputAccessoryView", style: .plain, target: self, action: #selector(resetTapped))
|
||||
bar.items = [reset]
|
||||
bar.sizeToFit()
|
||||
textField.inputAccessoryView = bar
|
||||
}
|
||||
|
||||
@objc func resetTapped() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,10 @@ func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection s
|
||||
// It calculates how many cells need to populate whole tableview
|
||||
```
|
||||
|
||||
> 📣 **IMPORTANT!**
|
||||
>
|
||||
> If you return `SkeletonCollectionDataSource.automaticNumberOfRows` in the above method, it acts like the default behavior (i.e. it calculates how many cells needed to populate the whole tableview).
|
||||
|
||||
There is only one method you need to implement to let Skeleton know the cell identifier. This method doesn't have default implementation:
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
@@ -491,6 +495,15 @@ Sometimes you wanna hide some view when the animation starts, so there is a quic
|
||||
view.isHiddenWhenSkeletonIsActive = true // This works only when isSkeletonable = true
|
||||
```
|
||||
|
||||
**Don't modify user interaction when the skeleton is active**
|
||||
|
||||
|
||||
By default, user interaction is disabled for skeletonized items, but if you don't want to modify the user interaction indicator when skeleton is active, you can use the `isDisableWhenSkeletonIsActive` property:
|
||||
|
||||
```swift
|
||||
view.isDisableWhenSkeletonIsActive = false // The view will be active when the skeleton will be active.
|
||||
```
|
||||
|
||||
**Debug**
|
||||
|
||||
To facilitate the debug tasks when something is not working fine. **`SkeletonView`** has some new tools.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.12.1"
|
||||
s.version = "1.16.0"
|
||||
s.summary = "An elegant way to show users that something is happening and also prepare them to which contents he is waiting"
|
||||
s.description = <<-DESC
|
||||
Today almost all apps have async processes, as API requests, long runing processes, etc. And while the processes are working, usually developers place a loading view to show users that something is going on.
|
||||
|
||||
@@ -619,6 +619,7 @@
|
||||
52D6D97B1BEFF229002C0205 = {
|
||||
CreatedOnToolsVersion = 7.1;
|
||||
LastSwiftMigration = 1000;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
F5F899F11FABA607002E8FDA = {
|
||||
CreatedOnToolsVersion = 9.1;
|
||||
@@ -1117,8 +1118,10 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1131,6 +1134,8 @@
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.SkeletonView.SkeletonView-iOS";
|
||||
PRODUCT_NAME = SkeletonView;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -1142,8 +1147,10 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1155,6 +1162,8 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.SkeletonView.SkeletonView-iOS";
|
||||
PRODUCT_NAME = SkeletonView;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
@@ -31,12 +31,16 @@ extension CollectionSkeleton where Self: UIScrollView {
|
||||
func removeDummyDataSource(reloadAfter: Bool) {}
|
||||
|
||||
func disableUserInteraction() {
|
||||
isUserInteractionEnabled = false
|
||||
isScrollEnabled = false
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
isScrollEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
func enableUserInteraction() {
|
||||
isUserInteractionEnabled = true
|
||||
isScrollEnabled = true
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = true
|
||||
isScrollEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
|
||||
public extension SkeletonCollectionViewDataSource {
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return skeletonView.estimatedNumberOfRows
|
||||
return SkeletonCollectionDataSource.automaticNumberOfRows
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView,
|
||||
|
||||
@@ -7,11 +7,18 @@
|
||||
//
|
||||
|
||||
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))
|
||||
switch flowlayout.scrollDirection {
|
||||
case .vertical:
|
||||
return Int(ceil(frame.height / flowlayout.itemSize.height))
|
||||
case .horizontal:
|
||||
return Int(ceil(frame.width / flowlayout.itemSize.width))
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
var skeletonDataSource: SkeletonCollectionDataSource? {
|
||||
|
||||
@@ -11,6 +11,8 @@ import UIKit
|
||||
public typealias ReusableCellIdentifier = String
|
||||
|
||||
class SkeletonCollectionDataSource: NSObject {
|
||||
static let automaticNumberOfRows = -1
|
||||
|
||||
weak var originalTableViewDataSource: SkeletonTableViewDataSource?
|
||||
weak var originalCollectionViewDataSource: SkeletonCollectionViewDataSource?
|
||||
var rowHeight: CGFloat = 0.0
|
||||
@@ -32,7 +34,17 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
originalTableViewDataSource?.collectionSkeletonView(tableView, numberOfRowsInSection: section) ?? 0
|
||||
guard let originalTableViewDataSource = originalTableViewDataSource else {
|
||||
return 0
|
||||
}
|
||||
|
||||
let numberOfRows = originalTableViewDataSource.collectionSkeletonView(tableView, numberOfRowsInSection: section)
|
||||
|
||||
if numberOfRows == Self.automaticNumberOfRows {
|
||||
return tableView.estimatedNumberOfRows
|
||||
} else {
|
||||
return numberOfRows
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
@@ -50,7 +62,17 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
originalCollectionViewDataSource?.collectionSkeletonView(collectionView, numberOfItemsInSection: section) ?? 0
|
||||
guard let originalCollectionViewDataSource = originalCollectionViewDataSource else {
|
||||
return 0
|
||||
}
|
||||
|
||||
let numberOfItems = originalCollectionViewDataSource.collectionSkeletonView(collectionView, numberOfItemsInSection: section)
|
||||
|
||||
if numberOfItems == Self.automaticNumberOfRows {
|
||||
return collectionView.estimatedNumberOfRows
|
||||
} else {
|
||||
return numberOfItems
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
|
||||
@@ -25,7 +25,7 @@ extension SkeletonCollectionDelegate: UITableViewDelegate {
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForHeaderInSection: section))
|
||||
headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForFooterInSection: section))
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) {
|
||||
|
||||
@@ -16,7 +16,7 @@ public protocol SkeletonTableViewDataSource: UITableViewDataSource {
|
||||
|
||||
public extension SkeletonTableViewDataSource {
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return skeletonView.estimatedNumberOfRows
|
||||
return SkeletonCollectionDataSource.automaticNumberOfRows
|
||||
}
|
||||
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int { return 1 }
|
||||
|
||||
@@ -125,8 +125,12 @@ extension CALayer {
|
||||
private func calculateNumLines(for config: SkeletonMultilinesLayerConfig) -> Int {
|
||||
let definedNumberOfLines = config.lines
|
||||
let requiredSpaceForEachLine = config.lineHeight + config.multilineSpacing
|
||||
let calculatedNumberOfLines = Int(round(CGFloat(bounds.height - config.paddingInsets.top - config.paddingInsets.bottom) / CGFloat(requiredSpaceForEachLine)))
|
||||
|
||||
let neededLines = round(CGFloat(bounds.height - config.paddingInsets.top - config.paddingInsets.bottom) / CGFloat(requiredSpaceForEachLine))
|
||||
guard neededLines.isNormal else {
|
||||
return 0
|
||||
}
|
||||
|
||||
let calculatedNumberOfLines = Int(neededLines)
|
||||
guard calculatedNumberOfLines > 0 else {
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ enum ViewAssociatedKeys {
|
||||
static var buttonViewState = "buttonViewState"
|
||||
static var currentSkeletonConfig = "currentSkeletonConfig"
|
||||
static var skeletonCornerRadius = "skeletonCornerRadius"
|
||||
static var disableWhenSkeletonIsActive = "disableWhenSkeletonIsActive"
|
||||
}
|
||||
// codebeat:enable[TOO_MANY_IVARS]
|
||||
|
||||
@@ -49,4 +50,8 @@ extension UIView {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.isSkeletonAnimated) as? Bool ?? false }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.isSkeletonAnimated) }
|
||||
}
|
||||
|
||||
var isSuperviewAStackView: Bool {
|
||||
superview is UIStackView
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,12 @@ public extension UIView {
|
||||
get { return hiddenWhenSkeletonIsActive }
|
||||
set { hiddenWhenSkeletonIsActive = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var isDisableWhenSkeletonIsActive: Bool {
|
||||
get { return disableWhenSkeletonIsActive }
|
||||
set { disableWhenSkeletonIsActive = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var skeletonCornerRadius: Float {
|
||||
@@ -34,6 +40,11 @@ public extension UIView {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.hiddenWhenSkeletonIsActive) as? Bool ?? false }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.hiddenWhenSkeletonIsActive) }
|
||||
}
|
||||
|
||||
private var disableWhenSkeletonIsActive: Bool {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.disableWhenSkeletonIsActive) as? Bool ?? true }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.disableWhenSkeletonIsActive) }
|
||||
}
|
||||
|
||||
private var skeletonableCornerRadius: Float {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.skeletonCornerRadius) as? Float ?? 0.0 }
|
||||
|
||||
@@ -10,7 +10,10 @@ import UIKit
|
||||
|
||||
extension UIView {
|
||||
@objc func prepareViewForSkeleton() {
|
||||
isUserInteractionEnabled = false
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
startTransition { [weak self] in
|
||||
self?.backgroundColor = .clear
|
||||
}
|
||||
@@ -28,7 +31,13 @@ extension UILabel {
|
||||
}
|
||||
|
||||
func updateHeightConstraintsIfNeeded() {
|
||||
guard numberOfLines > 1 else { return }
|
||||
guard numberOfLines > 1 || numberOfLines == 0 else { return }
|
||||
|
||||
// Workaround to simulate content when the label is contained in a `UIStackView`.
|
||||
if isSuperviewAStackView, bounds.height == 0 {
|
||||
// This is a placeholder text to simulate content because it's contained in a stack view in order to prevent that the content size will be zero.
|
||||
text = " "
|
||||
}
|
||||
|
||||
let desiredHeight = desiredHeightBasedOnNumberOfLines
|
||||
if desiredHeight > definedMaxHeight {
|
||||
@@ -49,7 +58,11 @@ extension UILabel {
|
||||
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
isUserInteractionEnabled = false
|
||||
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
resignFirstResponder()
|
||||
startTransition { [weak self] in
|
||||
self?.updateHeightConstraintsIfNeeded()
|
||||
@@ -61,7 +74,11 @@ extension UILabel {
|
||||
extension UITextView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
isUserInteractionEnabled = false
|
||||
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
resignFirstResponder()
|
||||
startTransition { [weak self] in
|
||||
self?.textColor = .clear
|
||||
@@ -84,7 +101,11 @@ extension UITextField {
|
||||
extension UIImageView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
isUserInteractionEnabled = false
|
||||
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
startTransition { [weak self] in
|
||||
self?.image = nil
|
||||
}
|
||||
@@ -94,7 +115,11 @@ extension UIImageView {
|
||||
extension UIButton {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
isUserInteractionEnabled = false
|
||||
|
||||
if isDisableWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
startTransition { [weak self] in
|
||||
self?.setTitle(nil, for: .normal)
|
||||
}
|
||||
|
||||
@@ -4,21 +4,28 @@ import Foundation
|
||||
|
||||
extension DispatchQueue {
|
||||
private static var _onceTracker = [String]()
|
||||
|
||||
|
||||
class func once(token: String, block: () -> Void) {
|
||||
objc_sync_enter(self); defer { objc_sync_exit(self) }
|
||||
guard !_onceTracker.contains(token) else { return }
|
||||
|
||||
|
||||
_onceTracker.append(token)
|
||||
block()
|
||||
}
|
||||
|
||||
class func removeOnce(token: String, block: () -> Void) {
|
||||
objc_sync_enter(self); defer { objc_sync_exit(self) }
|
||||
guard let index = _onceTracker.firstIndex(of: token) else { return }
|
||||
_onceTracker.remove(at: index)
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
func swizzle(selector originalSelector: Selector, with swizzledSelector: Selector, inClass: AnyClass, usingClass: AnyClass) {
|
||||
guard let originalMethod = class_getInstanceMethod(inClass, originalSelector),
|
||||
let swizzledMethod = class_getInstanceMethod(usingClass, swizzledSelector)
|
||||
else { return }
|
||||
|
||||
|
||||
if class_addMethod(inClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) {
|
||||
class_replaceMethod(inClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
|
||||
} else {
|
||||
|
||||
@@ -27,12 +27,17 @@ extension UIView: Recoverable {
|
||||
guard let storedViewState = viewState else { return }
|
||||
|
||||
startTransition { [weak self] in
|
||||
self?.layer.cornerRadius = storedViewState.cornerRadius
|
||||
self?.layer.masksToBounds = storedViewState.clipToBounds
|
||||
self?.isUserInteractionEnabled = storedViewState.isUserInteractionsEnabled
|
||||
guard let self = self else { return }
|
||||
|
||||
if self?.backgroundColor == .clear || forced {
|
||||
self?.backgroundColor = storedViewState.backgroundColor
|
||||
self.layer.cornerRadius = storedViewState.cornerRadius
|
||||
self.layer.masksToBounds = storedViewState.clipToBounds
|
||||
|
||||
if self.isDisableWhenSkeletonIsActive {
|
||||
self.isUserInteractionEnabled = storedViewState.isUserInteractionsEnabled
|
||||
}
|
||||
|
||||
if self.backgroundColor == .clear || forced {
|
||||
self.backgroundColor = storedViewState.backgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,8 @@ extension UIView {
|
||||
isHidden = false
|
||||
}
|
||||
currentSkeletonConfig?.transition = transition
|
||||
unSwizzleLayoutSubviews()
|
||||
unSwizzleTraitCollectionDidChange()
|
||||
removeDummyDataSourceIfNeeded(reloadAfter: reload)
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
recoverViewState(forced: false)
|
||||
@@ -233,6 +235,17 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
private func unSwizzleLayoutSubviews() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
DispatchQueue.removeOnce(token: "UIView.SkeletonView.swizzleLayoutSubviews") {
|
||||
swizzle(selector: #selector(UIView.skeletonLayoutSubviews),
|
||||
with: #selector(UIView.layoutSubviews),
|
||||
inClass: UIView.self,
|
||||
usingClass: UIView.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func swizzleTraitCollectionDidChange() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
DispatchQueue.once(token: "UIView.SkeletonView.swizzleTraitCollectionDidChange") {
|
||||
@@ -243,6 +256,17 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func unSwizzleTraitCollectionDidChange() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
DispatchQueue.removeOnce(token: "UIView.SkeletonView.swizzleTraitCollectionDidChange") {
|
||||
swizzle(selector: #selector(UIView.skeletonTraitCollectionDidChange(_:)),
|
||||
with: #selector(UIView.traitCollectionDidChange(_:)),
|
||||
inClass: UIView.self,
|
||||
usingClass: UIView.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
@@ -14,7 +14,11 @@ extension UIView {
|
||||
|
||||
extension UITableView {
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
visibleCells + visibleSectionHeaders + visibleSectionFooters
|
||||
// on `UIViewController'S onViewDidLoad`, the window is still nil.
|
||||
// Some developer trying to call `view.showAnimatedSkeleton()`
|
||||
// when the request or data is loading which sometimes happens before the ViewDidAppear
|
||||
guard window != nil else { return [] }
|
||||
return subviews
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@ Install _fastlane_ using
|
||||
or alternatively using `brew install fastlane`
|
||||
|
||||
# Available Actions
|
||||
### bump_version
|
||||
```
|
||||
fastlane bump_version
|
||||
```
|
||||
|
||||
### release_current
|
||||
```
|
||||
fastlane release_current
|
||||
|
||||
Reference in New Issue
Block a user