Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f7505bed9 | |||
| 5436e44f15 | |||
| 2f2e542d51 | |||
| 15764debf0 | |||
| bcd0fa7983 | |||
| 4cdc5935fc | |||
| 93769902a3 | |||
| a1d54a448d | |||
| cc8d21e7af | |||
| 69a0c8319d | |||
| 3c173c0a23 | |||
| dce910d6d0 | |||
| 2f751b9036 | |||
| 978fd553e1 |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 228 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 400 KiB After Width: | Height: | Size: 108 KiB |
@@ -3,8 +3,22 @@ All notable changes to this project will be documented in this file
|
||||
|
||||
## Next version
|
||||
|
||||
### Improvements
|
||||
- Fix completion call in .none transition style while hide skeletons. (thanks @aadudyrev)
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<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">
|
||||
<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">
|
||||
<rect key="frame" x="118" y="29" width="237" height="20.5"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="71" id="HRL-cI-ieC"/>
|
||||
|
||||
@@ -46,10 +46,6 @@ class ViewController: UIViewController {
|
||||
super.viewDidLoad()
|
||||
tableview.isSkeletonable = true
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
view.layoutSkeletonIfNeeded()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
@@ -516,6 +516,7 @@ See [all contributors](https://github.com/Juanpe/SkeletonView/graphs/contributor
|
||||
- [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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@
|
||||
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 */; };
|
||||
@@ -185,6 +187,7 @@
|
||||
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>"; };
|
||||
@@ -437,6 +440,7 @@
|
||||
F5F899D11FAB9630002E8FDA /* AssociationPolicy.swift */,
|
||||
F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */,
|
||||
F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */,
|
||||
F570ABF32314629700390248 /* Swizzling.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
@@ -684,6 +688,7 @@
|
||||
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 */,
|
||||
17DD0E14207FB28F00C56334 /* SkeletonAnimationBuilder.swift in Sources */,
|
||||
17DD0E11207FB28C00C56334 /* UIView+Frame.swift in Sources */,
|
||||
@@ -740,6 +745,7 @@
|
||||
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 */,
|
||||
@@ -1038,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*]" = "";
|
||||
@@ -1063,7 +1069,7 @@
|
||||
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*]" = "";
|
||||
|
||||
@@ -29,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 }
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import UIKit
|
||||
extension UIView {
|
||||
func addDummyDataSourceIfNeeded() {
|
||||
guard let collection = self as? CollectionSkeleton else { return }
|
||||
status = .on
|
||||
collection.addDummyDataSource()
|
||||
collection.disableUserInteraction()
|
||||
}
|
||||
@@ -22,6 +23,7 @@ extension UIView {
|
||||
|
||||
func removeDummyDataSourceIfNeeded(reloadAfter reload: Bool = true) {
|
||||
guard let collection = self as? CollectionSkeleton else { return }
|
||||
status = .off
|
||||
collection.removeDummyDataSource(reloadAfter: reload)
|
||||
collection.enableUserInteraction()
|
||||
}
|
||||
|
||||
@@ -117,15 +117,18 @@ public extension CALayer {
|
||||
animGroup.duration = 1.5
|
||||
animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
|
||||
animGroup.repeatCount = .infinity
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,15 +143,14 @@ public extension CALayer {
|
||||
|
||||
extension CALayer {
|
||||
func setOpacity(from: Int, to: Int, duration: TimeInterval, completion: (() -> Void)?) {
|
||||
CATransaction.begin()
|
||||
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)
|
||||
// layer.contentLayer.opacity = 1
|
||||
CATransaction.setCompletionBlock(completion)
|
||||
DispatchQueue.main.async { CATransaction.setCompletionBlock(completion) }
|
||||
add(animation, forKey: "setOpacityAnimation")
|
||||
CATransaction.commit()
|
||||
DispatchQueue.main.async { CATransaction.commit() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ extension UIView {
|
||||
set { ao_set(newValue ?? .off, pkey: &ViewAssociatedKeys.status) }
|
||||
}
|
||||
|
||||
var skeletonIsAnimated: Bool! {
|
||||
var isSkeletonAnimated: Bool! {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.isSkeletonAnimated) as? Bool ?? false }
|
||||
set { ao_set(newValue ?? false, pkey: &ViewAssociatedKeys.isSkeletonAnimated) }
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public extension UIView {
|
||||
return status == .on || (subviewsSkeletonables.first(where: { $0.isSkeletonActive }) != nil)
|
||||
}
|
||||
|
||||
fileprivate var skeletonable: Bool! {
|
||||
private var skeletonable: Bool! {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.skeletonable) as? Bool ?? false }
|
||||
set { ao_set(newValue ?? false, pkey: &ViewAssociatedKeys.skeletonable) }
|
||||
}
|
||||
|
||||
@@ -32,6 +32,6 @@ extension UIView {
|
||||
}
|
||||
|
||||
@objc func appDidEnterBackground() {
|
||||
UserDefaults.standard.set((isSkeletonActive && skeletonIsAnimated), forKey: Constants.needAnimatedSkeletonKey)
|
||||
UserDefaults.standard.set((isSkeletonActive && isSkeletonAnimated), forKey: Constants.needAnimatedSkeletonKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+16
-17
@@ -3,43 +3,42 @@
|
||||
import UIKit
|
||||
|
||||
protocol SkeletonFlowDelegate {
|
||||
func willBeginShowingSkeletons(withRootView rootView: UIView)
|
||||
func didShowSkeletons(withRootView rootView: UIView)
|
||||
func willBeginUpdatingSkeletons(withRootView rootView: UIView)
|
||||
func didUpdateSkeletons(withRootView rootView: UIView)
|
||||
func willBeginLayingSkeletonsIfNeeded(withRootView: UIView)
|
||||
func didLayoutSkeletonsIfNeeded(withRootView: 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 willBeginUpdatingSkeletons(withRootView rootView: UIView) {
|
||||
func willBeginUpdatingSkeletons(rootView: UIView) {
|
||||
}
|
||||
|
||||
func didUpdateSkeletons(withRootView rootView: UIView) {
|
||||
func didUpdateSkeletons(rootView: UIView) {
|
||||
}
|
||||
|
||||
func willBeginLayingSkeletonsIfNeeded(withRootView: UIView) {
|
||||
func willBeginLayingSkeletonsIfNeeded(rootView: UIView) {
|
||||
}
|
||||
|
||||
func didLayoutSkeletonsIfNeeded(withRootView: UIView) {
|
||||
func didLayoutSkeletonsIfNeeded(rootView: UIView) {
|
||||
}
|
||||
|
||||
func willBeginHidingSkeletons(withRootView rootView: UIView) {
|
||||
func willBeginHidingSkeletons(rootView: UIView) {
|
||||
rootView.removeAppNoticationsObserver()
|
||||
}
|
||||
|
||||
func didHideSkeletons(withRootView rootView: UIView) {
|
||||
func didHideSkeletons(rootView: UIView) {
|
||||
rootView.flowDelegate = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ struct SkeletonLayer {
|
||||
switch transition {
|
||||
case .none:
|
||||
maskLayer.removeFromSuperlayer()
|
||||
completion?()
|
||||
case .crossDissolve(let duration):
|
||||
maskLayer.setOpacity(from: 1, to: 0, duration: duration) {
|
||||
self.maskLayer.removeFromSuperlayer()
|
||||
@@ -90,9 +91,9 @@ struct SkeletonLayer {
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
+81
-48
@@ -3,21 +3,47 @@
|
||||
import UIKit
|
||||
|
||||
public extension UIView {
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
@@ -44,29 +70,22 @@ public extension UIView {
|
||||
}
|
||||
|
||||
func layoutSkeletonIfNeeded() {
|
||||
guard let flowDelegate = flowDelegate else { return }
|
||||
flowDelegate.willBeginLayingSkeletonsIfNeeded(withRootView: self)
|
||||
flowDelegate?.willBeginLayingSkeletonsIfNeeded(rootView: self)
|
||||
recursiveLayoutSkeletonIfNeeded(root: self)
|
||||
}
|
||||
|
||||
func hideSkeleton(reloadDataAfter reload: Bool = true, transition: SkeletonTransitionStyle = .none) {
|
||||
if var config = currentSkeletonConfig {
|
||||
config.transition = transition
|
||||
updateSkeleton(skeletonConfig: config)
|
||||
}
|
||||
flowDelegate?.willBeginHidingSkeletons(withRootView: self)
|
||||
recursiveHideSkeleton(reloadDataAfter: reload, root: self)
|
||||
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()
|
||||
}
|
||||
@@ -74,19 +93,21 @@ public extension UIView {
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
@objc func skeletonLayoutSubviews() {
|
||||
layoutSkeletonIfNeeded()
|
||||
}
|
||||
|
||||
func showSkeleton(skeletonConfig config: SkeletonConfig) {
|
||||
skeletonIsAnimated = config.animated
|
||||
isSkeletonAnimated = config.animated
|
||||
flowDelegate = SkeletonFlowHandler()
|
||||
flowDelegate?.willBeginShowingSkeletons(withRootView: self)
|
||||
flowDelegate?.willBeginShowingSkeletons(rootView: self)
|
||||
recursiveShowSkeleton(skeletonConfig: config, root: self)
|
||||
}
|
||||
|
||||
fileprivate func recursiveShowSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
|
||||
layoutIfNeeded()
|
||||
|
||||
private func recursiveShowSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
|
||||
guard !isSkeletonActive else { return }
|
||||
currentSkeletonConfig = config
|
||||
|
||||
swizzleLayoutSubviews()
|
||||
addDummyDataSourceIfNeeded()
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
showSkeletonIfNotActive(skeletonConfig: config)
|
||||
@@ -95,11 +116,11 @@ extension UIView {
|
||||
}
|
||||
|
||||
if let root = root {
|
||||
flowDelegate?.didShowSkeletons(withRootView: root)
|
||||
flowDelegate?.didShowSkeletons(rootView: root)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
|
||||
private func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
|
||||
guard !isSkeletonActive else { return }
|
||||
isUserInteractionEnabled = false
|
||||
saveViewState()
|
||||
@@ -108,81 +129,90 @@ extension UIView {
|
||||
}
|
||||
|
||||
func updateSkeleton(skeletonConfig config: SkeletonConfig) {
|
||||
guard let flowDelegate = flowDelegate else { return }
|
||||
skeletonIsAnimated = config.animated
|
||||
flowDelegate.willBeginUpdatingSkeletons(withRootView: self)
|
||||
isSkeletonAnimated = config.animated
|
||||
flowDelegate?.willBeginUpdatingSkeletons(rootView: self)
|
||||
recursiveUpdateSkeleton(skeletonConfig: config, root: self)
|
||||
}
|
||||
|
||||
fileprivate func recursiveUpdateSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
|
||||
layoutIfNeeded()
|
||||
|
||||
private func recursiveUpdateSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
|
||||
guard isSkeletonActive else { return }
|
||||
currentSkeletonConfig = config
|
||||
|
||||
updateDummyDataSourceIfNeeded()
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
guard isSkeletonActive else { return }
|
||||
|
||||
if skeletonLayer?.type != config.type {
|
||||
hideSkeleton()
|
||||
}
|
||||
|
||||
if isSkeletonActive {
|
||||
updateSkeletonLayer(skeletonConfig: config)
|
||||
removeSkeletonLayer()
|
||||
addSkeletonLayer(skeletonConfig: config)
|
||||
} else {
|
||||
showSkeletonIfNotActive(skeletonConfig: config)
|
||||
updateSkeletonLayer(skeletonConfig: config)
|
||||
}
|
||||
}) { subview in
|
||||
subview.recursiveUpdateSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
if let root = root {
|
||||
flowDelegate?.didUpdateSkeletons(withRootView: root)
|
||||
flowDelegate?.didUpdateSkeletons(rootView: root)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func recursiveLayoutSkeletonIfNeeded(root: UIView? = nil) {
|
||||
layoutIfNeeded()
|
||||
|
||||
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(withRootView: root)
|
||||
flowDelegate?.didLayoutSkeletonsIfNeeded(rootView: root)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func recursiveHideSkeleton(reloadDataAfter reload: Bool, root: UIView? = nil) {
|
||||
private func recursiveHideSkeleton(reloadDataAfter reload: Bool, transition: SkeletonTransitionStyle, root: UIView? = nil) {
|
||||
guard isSkeletonActive else { return }
|
||||
removeDummyDataSourceIfNeeded(reloadAfter: reload)
|
||||
currentSkeletonConfig?.transition = transition
|
||||
isUserInteractionEnabled = true
|
||||
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.once(token: "UIView.SkeletonView.swizzle") {
|
||||
swizzle(selector: #selector(UIView.layoutSubviews),
|
||||
with: #selector(UIView.skeletonLayoutSubviews),
|
||||
inClass: UIView.self,
|
||||
usingClass: UIView.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
@@ -197,9 +227,9 @@ extension UIView {
|
||||
self.skeletonLayer = skeletonLayer
|
||||
layer.insertSublayer(skeletonLayer,
|
||||
at: UInt32.max,
|
||||
transition: config.transition) {
|
||||
transition: config.transition) { [weak self] in
|
||||
if config.animated {
|
||||
skeletonLayer.start(config.animation)
|
||||
self?.startSkeletonAnimation(config.animation)
|
||||
}
|
||||
}
|
||||
status = .on
|
||||
@@ -208,8 +238,11 @@ extension UIView {
|
||||
func updateSkeletonLayer(skeletonConfig config: SkeletonConfig) {
|
||||
guard let skeletonLayer = skeletonLayer else { return }
|
||||
skeletonLayer.update(usingColors: config.colors)
|
||||
if config.animated { skeletonLayer.start(config.animation) }
|
||||
else { skeletonLayer.stopAnimation() }
|
||||
if config.animated {
|
||||
startSkeletonAnimation(config.animation)
|
||||
} else {
|
||||
skeletonLayer.stopAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
func layoutSkeletonLayerIfNeeded() {
|
||||
|
||||
@@ -7,6 +7,7 @@ extension CALayer {
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user