diff --git a/Examples/iOS Example/Sources/ViewController.swift b/Examples/iOS Example/Sources/ViewController.swift index 3796abc0..cb80e51c 100644 --- a/Examples/iOS Example/Sources/ViewController.swift +++ b/Examples/iOS Example/Sources/ViewController.swift @@ -76,8 +76,8 @@ class ViewController: UIViewController { } @IBAction func showOrHideSkeleton(_ sender: Any) { - showOrHideSkeletonButton.setTitle((view.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal) - view.isSkeletonActive ? hideSkeleton() : showSkeleton() + showOrHideSkeletonButton.setTitle((view.sk.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal) + view.sk.isSkeletonActive ? hideSkeleton() : showSkeleton() } @IBAction func transitionDurationStepperAction(_ sender: Any) { diff --git a/README.md b/README.md index 07287296..7abcf655 100755 --- a/README.md +++ b/README.md @@ -548,14 +548,11 @@ func showGradientSkeleton(usingGradient: SkeletonGradient, To facilitate the debug tasks when something is not working fine. **`SkeletonView`** has some new tools. -First, `UIView` has available a new property with his skeleton info: +First, `UIView` has available a property with his skeleton info: ```swift -var skeletonDescription: String +var sk.skeletonTreeDescription: String ``` -The skeleton representation looks like this: - -![](Assets/debug_description.png) Besides, you can activate the new **debug mode**. You just add the environment variable `SKELETON_DEBUG` and activate it. @@ -563,11 +560,21 @@ Besides, you can activate the new **debug mode**. You just add the environment v Then, when the skeleton appears, you can see the view hierarchy in the Xcode console. -
-Open to see an output example - -
- +``` +{ + "type" : "UIView", // UITableView, UILabel... + "isSkeletonable" : true, + "reference" : "0x000000014751ce30", + "children" : [ + { + "type" : "UIView", + "isSkeletonable" : true, + "children" : [ ... ], + "reference" : "0x000000014751cfa0" + } + ] +} +``` **Supported OS & SDK Versions** diff --git a/SkeletonView.xcodeproj/project.pbxproj b/SkeletonView.xcodeproj/project.pbxproj index 3ab80ac1..6047c617 100644 --- a/SkeletonView.xcodeproj/project.pbxproj +++ b/SkeletonView.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + F53D731826D399E100249D46 /* SkeletonTreeNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */; }; + F53D731926D399E100249D46 /* SkeletonTreeNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */; }; + F53D731B26D3A35100249D46 /* SkeletonExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731A26D3A35100249D46 /* SkeletonExtended.swift */; }; + F53D731C26D3A35100249D46 /* SkeletonExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D731A26D3A35100249D46 /* SkeletonExtended.swift */; }; + F53D731F26D3AC4000249D46 /* SkeletonTreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */; }; F556F56626CD1F3900A80B83 /* SkeletonAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* SkeletonAppearance.swift */; }; F556F56726CD1F3900A80B83 /* SkeletonLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* SkeletonLayerBuilder.swift */; }; F556F56826CD1F3900A80B83 /* SkeletonMultilineLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* SkeletonMultilineLayerBuilder.swift */; }; @@ -57,8 +62,8 @@ F556F69626CD509E00A80B83 /* Notification+SkeletonFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F69426CD509E00A80B83 /* Notification+SkeletonFlow.swift */; }; F556F69E26CD553B00A80B83 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F69D26CD553B00A80B83 /* UIView+Extensions.swift */; }; F556F69F26CD553B00A80B83 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F69D26CD553B00A80B83 /* UIView+Extensions.swift */; }; - F556F6A126CD566C00A80B83 /* UIView+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A026CD566C00A80B83 /* UIView+Debug.swift */; }; - F556F6A226CD566C00A80B83 /* UIView+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A026CD566C00A80B83 /* UIView+Debug.swift */; }; + F556F6A126CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A026CD566C00A80B83 /* UIView+SKExtensions.swift */; }; + F556F6A226CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A026CD566C00A80B83 /* UIView+SKExtensions.swift */; }; F556F6A426CD5A9000A80B83 /* CALayer+Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A326CD5A9000A80B83 /* CALayer+Animations.swift */; }; F556F6A526CD5A9000A80B83 /* CALayer+Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A326CD5A9000A80B83 /* CALayer+Animations.swift */; }; F556F6A726CD5B0400A80B83 /* CALayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A626CD5B0400A80B83 /* CALayer+Extensions.swift */; }; @@ -90,9 +95,8 @@ F556F6DD26CE33CE00A80B83 /* UIView+Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */; }; F556F6E026CE367600A80B83 /* UIView+SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */; }; F556F6E126CE367600A80B83 /* UIView+SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */; }; - F556F6F226CE818B00A80B83 /* UIView+Flags.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6F126CE818B00A80B83 /* UIView+Flags.swift */; }; - F556F6F326CE818B00A80B83 /* UIView+Flags.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6F126CE818B00A80B83 /* UIView+Flags.swift */; }; F556F6F626CE876300A80B83 /* UIView+Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */; }; + F556F70826D38F3100A80B83 /* SkeletonTreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */; }; OBJ_101 /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* Int+Extensions.swift */; }; OBJ_103 /* UIColor+Skeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* UIColor+Skeleton.swift */; }; OBJ_104 /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* UITableView+Extensions.swift */; }; @@ -143,6 +147,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SkeletonTreeNode+Extensions.swift"; sourceTree = ""; }; + F53D731A26D3A35100249D46 /* SkeletonExtended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonExtended.swift; sourceTree = ""; }; F556F51026CD1B7900A80B83 /* SkeletonView.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = SkeletonView.podspec; sourceTree = ""; }; F556F51126CD1B8000A80B83 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; F556F51426CD1BFF00A80B83 /* README_zh.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README_zh.md; path = Translations/README_zh.md; sourceTree = ""; }; @@ -181,7 +187,7 @@ F556F69126CD506C00A80B83 /* Deprecated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deprecated.swift; sourceTree = ""; }; F556F69426CD509E00A80B83 /* Notification+SkeletonFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+SkeletonFlow.swift"; sourceTree = ""; }; F556F69D26CD553B00A80B83 /* UIView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; - F556F6A026CD566C00A80B83 /* UIView+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Debug.swift"; sourceTree = ""; }; + F556F6A026CD566C00A80B83 /* UIView+SKExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+SKExtensions.swift"; sourceTree = ""; }; F556F6A326CD5A9000A80B83 /* CALayer+Animations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+Animations.swift"; sourceTree = ""; }; F556F6A626CD5B0400A80B83 /* CALayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+Extensions.swift"; sourceTree = ""; }; F556F6AA26CD5C4900A80B83 /* SkeletonMultilinesLayerConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonMultilinesLayerConfig.swift; sourceTree = ""; }; @@ -198,7 +204,7 @@ F556F6D826CE315A00A80B83 /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = ""; }; F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Swizzling.swift"; sourceTree = ""; }; F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+SkeletonView.swift"; sourceTree = ""; }; - F556F6F126CE818B00A80B83 /* UIView+Flags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Flags.swift"; sourceTree = ""; }; + F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTreeNode.swift; sourceTree = ""; }; OBJ_11 /* SkeletonLayerBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonLayerBuilder.swift; sourceTree = ""; }; OBJ_12 /* SkeletonMultilineLayerBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonMultilineLayerBuilder.swift; sourceTree = ""; }; OBJ_14 /* CollectionSkeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionSkeleton.swift; sourceTree = ""; }; @@ -356,6 +362,7 @@ F556F68C26CD4EB400A80B83 /* FoundationExtensions */, F556F6B726CE25B100A80B83 /* Models */, F556F64F26CD2DFD00A80B83 /* SkeletonView.swift */, + F53D731A26D3A35100249D46 /* SkeletonExtended.swift */, F556F68526CD49E900A80B83 /* UIKitExtensions */, ); path = API; @@ -364,6 +371,7 @@ F556F64B26CD2CD600A80B83 /* Internal */ = { isa = PBXGroup; children = ( + F556F70626D38E8300A80B83 /* SkeletonTree */, F556F6D326CE2F3700A80B83 /* Collections */, F556F65226CD2E0A00A80B83 /* Debug */, F556F67E26CD476300A80B83 /* FoundationExtensions */, @@ -411,6 +419,7 @@ F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */, F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */, OBJ_61 /* UIView+Transitions.swift */, + F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */, ); path = UIKitExtensions; sourceTree = ""; @@ -431,11 +440,10 @@ children = ( F556F6A326CD5A9000A80B83 /* CALayer+Animations.swift */, F556F68626CD49F900A80B83 /* UIView+IBInspectable.swift */, - F556F6A026CD566C00A80B83 /* UIView+Debug.swift */, + F556F6A026CD566C00A80B83 /* UIView+SKExtensions.swift */, F556F6C526CE2A2100A80B83 /* UILabel+IBInspectable.swift */, F556F6C826CE2A4A00A80B83 /* UITextView+IBInspectable.swift */, F556F6D826CE315A00A80B83 /* UICollectionView+Extensions.swift */, - F556F6F126CE818B00A80B83 /* UIView+Flags.swift */, ); path = UIKitExtensions; sourceTree = ""; @@ -544,6 +552,14 @@ path = Appearance; sourceTree = ""; }; + F556F70626D38E8300A80B83 /* SkeletonTree */ = { + isa = PBXGroup; + children = ( + F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */, + ); + path = SkeletonTree; + sourceTree = ""; + }; OBJ_15 /* CollectionViews */ = { isa = PBXGroup; children = ( @@ -771,6 +787,7 @@ F556F6D026CE2AB800A80B83 /* SkeletonTextNode.swift in Sources */, F556F65126CD2DFD00A80B83 /* SkeletonView.swift in Sources */, F556F56826CD1F3900A80B83 /* SkeletonMultilineLayerBuilder.swift in Sources */, + F53D731926D399E100249D46 /* SkeletonTreeNode+Extensions.swift in Sources */, F556F56926CD1F3900A80B83 /* CollectionSkeleton.swift in Sources */, F556F56A26CD1F3900A80B83 /* SkeletonCollectionViewProtocols.swift in Sources */, F556F6C026CE277F00A80B83 /* PrepareViewForSkeleton.swift in Sources */, @@ -794,6 +811,7 @@ F556F57E26CD1F3900A80B83 /* AssociationPolicy.swift in Sources */, F556F6B026CE244100A80B83 /* DispatchQueue+Extensions.swift in Sources */, F556F58026CD1F3900A80B83 /* Recursive.swift in Sources */, + F53D731C26D3A35100249D46 /* SkeletonExtended.swift in Sources */, F556F6F626CE876300A80B83 /* UIView+Swizzling.swift in Sources */, F556F58126CD1F3900A80B83 /* Swizzling.swift in Sources */, F556F6B626CE258300A80B83 /* GradientDirection+Animations.swift in Sources */, @@ -805,14 +823,14 @@ F556F58926CD1F3900A80B83 /* SkeletonFlowHandler.swift in Sources */, F556F58A26CD1F3900A80B83 /* SkeletonGradient.swift in Sources */, F556F6C326CE27FD00A80B83 /* SkeletonType.swift in Sources */, - F556F6F326CE818B00A80B83 /* UIView+Flags.swift in Sources */, F556F58B26CD1F3900A80B83 /* SkeletonLayer.swift in Sources */, - F556F6A226CD566C00A80B83 /* UIView+Debug.swift in Sources */, + F556F6A226CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */, F556F69326CD506C00A80B83 /* Deprecated.swift in Sources */, F556F6BA26CE262700A80B83 /* GradientDirection.swift in Sources */, F556F58D26CD1F3900A80B83 /* SubviewsSkeletonables.swift in Sources */, F556F58E26CD1F3900A80B83 /* SkeletonTransitionStyle.swift in Sources */, F556F6AC26CD5C4900A80B83 /* SkeletonMultilinesLayerConfig.swift in Sources */, + F53D731F26D3AC4000249D46 /* SkeletonTreeNode.swift in Sources */, F556F68126CD47CF00A80B83 /* ProcessInfo+Extensions.swift in Sources */, F556F68826CD49F900A80B83 /* UIView+IBInspectable.swift in Sources */, F556F6C726CE2A2100A80B83 /* UILabel+IBInspectable.swift in Sources */, @@ -849,7 +867,7 @@ F556F6CF26CE2AB800A80B83 /* SkeletonTextNode.swift in Sources */, F556F65026CD2DFD00A80B83 /* SkeletonView.swift in Sources */, OBJ_88 /* SkeletonMultilineLayerBuilder.swift in Sources */, - F556F6F226CE818B00A80B83 /* UIView+Flags.swift in Sources */, + F53D731B26D3A35100249D46 /* SkeletonExtended.swift in Sources */, OBJ_89 /* CollectionSkeleton.swift in Sources */, OBJ_90 /* SkeletonCollectionViewProtocols.swift in Sources */, F556F6BF26CE277F00A80B83 /* PrepareViewForSkeleton.swift in Sources */, @@ -872,7 +890,9 @@ OBJ_109 /* UIView+AppLifecycleNotifications.swift in Sources */, OBJ_110 /* AssociationPolicy.swift in Sources */, F556F6AF26CE244100A80B83 /* DispatchQueue+Extensions.swift in Sources */, + F53D731826D399E100249D46 /* SkeletonTreeNode+Extensions.swift in Sources */, OBJ_112 /* Recursive.swift in Sources */, + F556F70826D38F3100A80B83 /* SkeletonTreeNode.swift in Sources */, OBJ_113 /* Swizzling.swift in Sources */, F556F6B526CE258300A80B83 /* GradientDirection+Animations.swift in Sources */, F556F69E26CD553B00A80B83 /* UIView+Extensions.swift in Sources */, @@ -885,7 +905,7 @@ F556F6C226CE27FD00A80B83 /* SkeletonType.swift in Sources */, OBJ_123 /* SkeletonLayer.swift in Sources */, F556F6E026CE367600A80B83 /* UIView+SkeletonView.swift in Sources */, - F556F6A126CD566C00A80B83 /* UIView+Debug.swift in Sources */, + F556F6A126CD566C00A80B83 /* UIView+SKExtensions.swift in Sources */, F556F69226CD506C00A80B83 /* Deprecated.swift in Sources */, F556F6B926CE262700A80B83 /* GradientDirection.swift in Sources */, OBJ_125 /* SubviewsSkeletonables.swift in Sources */, diff --git a/SkeletonViewCore/Sources/API/Deprecated.swift b/SkeletonViewCore/Sources/API/Deprecated.swift index 5f6ad3d0..fbacf205 100644 --- a/SkeletonViewCore/Sources/API/Deprecated.swift +++ b/SkeletonViewCore/Sources/API/Deprecated.swift @@ -11,7 +11,7 @@ // // Created by Juanpe Catalán on 18/8/21. -import Foundation +import UIKit public extension Notification.Name { @@ -34,3 +34,17 @@ public extension Notification.Name { static let didHideSkeletons = Notification.Name.skeletonDidDisappearNotification } + +public extension UIView { + + @available(*, deprecated, renamed: "sk.treeNodesDescription") + var skeletonDescription: String { + sk.skeletonTreeDescription + } + + @available(*, deprecated, renamed: "sk.isSkeletonActive") + var isSkeletonActive: Bool { + sk.isSkeletonActive + } + +} diff --git a/SkeletonViewCore/Sources/API/SkeletonExtended.swift b/SkeletonViewCore/Sources/API/SkeletonExtended.swift new file mode 100644 index 00000000..483633bb --- /dev/null +++ b/SkeletonViewCore/Sources/API/SkeletonExtended.swift @@ -0,0 +1,45 @@ +// +// Copyright SkeletonView. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// SkeletonExtended.swift +// +// Created by Juanpe Catalán on 23/8/21. + +import Foundation + +/// Type that acts as a generic extension point for all `SkeletonViewExtended` types. +public struct SkeletonViewExtension { + /// Stores the type or meta-type of any extended type. + public private(set) var type: ExtendedType + + /// Create an instance from the provided value. + /// + /// - Parameter type: Instance being extended. + public init(_ type: ExtendedType) { + self.type = type + } +} + +/// Protocol describing the `sk` extension points for SkeletonView extended types. +public protocol SkeletonViewExtended { + /// Type being extended. + associatedtype ExtendedType + + /// Instance SkeletonView extension point. + var sk: SkeletonViewExtension { get set } +} + +extension SkeletonViewExtended { + /// Instance SkeletonView extension point. + public var sk: SkeletonViewExtension { + get { SkeletonViewExtension(self) } + // swiftlint:disable:next unused_setter_value + set {} + } +} diff --git a/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+Debug.swift b/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+Debug.swift deleted file mode 100644 index dfc33bc0..00000000 --- a/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+Debug.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright SkeletonView. All Rights Reserved. -// -// Licensed under the MIT License (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://opensource.org/licenses/MIT -// -// UIView+Debug.swift -// -// Created by Juanpe Catalán on 18/8/21. - -import UIKit - -public extension UIView { - - /// Returns a string that describes the hierarchy of the skeleton, indicating - /// whether the receiver is skeletonable and all skeletonable children. - var skeletonDescription: String { - var description = "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())" - let subSkeletons = subviewsSkeletonables - - if !subSkeletons.isEmpty { - description += " | (\(subSkeletons.count)) subSkeletons" - } - - if isSkeletonable { - description += " | ☠️ " - } - - return description + ">" - } - -} diff --git a/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+Flags.swift b/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+Flags.swift deleted file mode 100644 index 2471dd4f..00000000 --- a/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+Flags.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Copyright SkeletonView. All Rights Reserved. -// -// Licensed under the MIT License (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://opensource.org/licenses/MIT -// -// UIView+Flags.swift -// -// Created by Juanpe Catalán on 19/8/21. - -import UIKit - -public extension UIView { - - var isSkeletonActive: Bool { - return _status == .on || subviewsSkeletonables.contains(where: { $0.isSkeletonActive }) - } - -} diff --git a/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+SKExtensions.swift b/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+SKExtensions.swift new file mode 100644 index 00000000..7262ce89 --- /dev/null +++ b/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+SKExtensions.swift @@ -0,0 +1,33 @@ +// +// Copyright SkeletonView. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// UIView+SKExtensions.swift +// +// Created by Juanpe Catalán on 18/8/21. + +import UIKit + +public extension SkeletonViewExtension where ExtendedType: UIView { + + /// Returns a string that describes the hierarchy of the skeleton, indicating + /// whether the receiver is skeletonable and all skeletonable children. + var skeletonTreeDescription: String { + guard let theJSONData = try? JSONSerialization.data(withJSONObject: treeNode.dictionaryRepresentation, options: [.prettyPrinted]) else { + skeletonLog("Skeleton tree generation has failed!") + return "" + } + + return String(data: theJSONData, encoding: .utf8)! + } + + var isSkeletonActive: Bool { + type._status == .on || type.subviewsSkeletonables.contains(where: { $0.sk.isSkeletonActive }) + } + +} diff --git a/SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDataSource.swift b/SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDataSource.swift index b18e9149..1b56a044 100644 --- a/SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDataSource.swift +++ b/SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDataSource.swift @@ -114,7 +114,7 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource { extension SkeletonCollectionDataSource { private func skeletonViewIfContainerSkeletonIsActive(container: UIView, view: UIView) { - guard container.isSkeletonActive, + guard container.sk.isSkeletonActive, let skeletonConfig = container._currentSkeletonConfig else { return } diff --git a/SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDelegate.swift b/SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDelegate.swift index 3a8fd40d..aa9ca703 100644 --- a/SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDelegate.swift +++ b/SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDelegate.swift @@ -55,7 +55,7 @@ extension SkeletonCollectionDelegate: UICollectionViewDelegate { } extension SkeletonCollectionDelegate { private func skeletonViewIfContainerSkeletonIsActive(container: UIView, view: UIView) { - guard container.isSkeletonActive, + guard container.sk.isSkeletonActive, let skeletonConfig = container._currentSkeletonConfig else { return } diff --git a/SkeletonViewCore/Sources/Internal/Debug/SkeletonDebug.swift b/SkeletonViewCore/Sources/Internal/Debug/SkeletonDebug.swift index 18b2dc8b..27fcb53c 100644 --- a/SkeletonViewCore/Sources/Internal/Debug/SkeletonDebug.swift +++ b/SkeletonViewCore/Sources/Internal/Debug/SkeletonDebug.swift @@ -25,10 +25,6 @@ extension Dictionary { } } -func printSkeletonHierarchy(in view: UIView) { - skeletonLog(view.skeletonHierarchy()) -} - func skeletonLog(_ message: String) { #if DEBUG if ProcessInfo.processInfo.environment[.debugMode] != nil { @@ -36,17 +32,3 @@ func skeletonLog(_ message: String) { } #endif } - -extension UIView { - - func skeletonHierarchy(indentationLevel level: Int = 0) -> String { - var description = level == 0 ? "\n ⬇⬇ ☠️ Root view hierarchy with Skeletons ⬇⬇ \n" : "" - description += "\(level == 0 ? "\n" : 3.whitespaces) \(skeletonDescription) \n" - subviewsToSkeleton.forEach { - description += (level + 2).whitespaces - description += $0.skeletonHierarchy(indentationLevel: level + 1) - } - return description - } - -} diff --git a/SkeletonViewCore/Sources/Internal/SkeletonFlowHandler.swift b/SkeletonViewCore/Sources/Internal/SkeletonFlowHandler.swift index ef1ef325..90dc0e36 100644 --- a/SkeletonViewCore/Sources/Internal/SkeletonFlowHandler.swift +++ b/SkeletonViewCore/Sources/Internal/SkeletonFlowHandler.swift @@ -20,7 +20,7 @@ class SkeletonFlowHandler: SkeletonFlowDelegate { } func didShowSkeletons(rootView: UIView) { - printSkeletonHierarchy(in: rootView) + skeletonLog(rootView.sk.skeletonTreeDescription) NotificationCenter.default.post(name: .skeletonWillAppearNotification, object: rootView, userInfo: nil) } diff --git a/SkeletonViewCore/Sources/Internal/SkeletonTree/SkeletonTreeNode.swift b/SkeletonViewCore/Sources/Internal/SkeletonTree/SkeletonTreeNode.swift new file mode 100644 index 00000000..2cd95871 --- /dev/null +++ b/SkeletonViewCore/Sources/Internal/SkeletonTree/SkeletonTreeNode.swift @@ -0,0 +1,27 @@ +// +// Copyright SkeletonView. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// SkeletonTreeNode.swift +// +// Created by Juanpe Catalán on 23/8/21. + +import UIKit + +public struct SkeletonTreeNode { + /// Base object to extend. + let base: Base + + /// Creates extensions with base object. + /// + /// - parameter base: Base object. + init(_ base: Base) { + self.base = base + } + +} diff --git a/SkeletonViewCore/Sources/Internal/UIKitExtensions/SkeletonTreeNode+Extensions.swift b/SkeletonViewCore/Sources/Internal/UIKitExtensions/SkeletonTreeNode+Extensions.swift new file mode 100644 index 00000000..ec3ee862 --- /dev/null +++ b/SkeletonViewCore/Sources/Internal/UIKitExtensions/SkeletonTreeNode+Extensions.swift @@ -0,0 +1,50 @@ +// +// Copyright SkeletonView. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// SkeletonTreeNode+Extensions.swift +// +// Created by Juanpe Catalán on 23/8/21. + +import UIKit + +extension UIView: SkeletonViewExtended { } + +extension SkeletonTreeNode where Base: UIView { + + var children: [SkeletonTreeNode] { + base.subviewsSkeletonables.map { $0.sk.treeNode } + } + + var parent: SkeletonTreeNode? { + base.superview?.sk.treeNode + } + +} + +// MARK: Debug + +extension SkeletonTreeNode where Base: UIView { + + var dictionaryRepresentation: [String: Any] { + let children = children + + var nodeInfo: [String: Any] = [ + "type": "\(type(of: base))", + "reference": "\(Unmanaged.passUnretained(base).toOpaque())", + "isSkeletonable": base.isSkeletonable + ] + + if !children.isEmpty { + nodeInfo["children"] = children.map { $0.dictionaryRepresentation } + } + + return nodeInfo + } + +} diff --git a/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+AppLifecycleNotifications.swift b/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+AppLifecycleNotifications.swift index e03365df..8f1a4771 100644 --- a/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+AppLifecycleNotifications.swift +++ b/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+AppLifecycleNotifications.swift @@ -38,7 +38,7 @@ extension UIView { } @objc func appDidEnterBackground() { - UserDefaults.standard.set((isSkeletonActive && _isSkeletonAnimated), forKey: Constants.needAnimatedSkeletonKey) + UserDefaults.standard.set((sk.isSkeletonActive && _isSkeletonAnimated), forKey: Constants.needAnimatedSkeletonKey) } @objc func willTerminateNotification() { diff --git a/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Extensions.swift b/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Extensions.swift index 72b441cb..c62b1c2e 100644 --- a/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Extensions.swift +++ b/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Extensions.swift @@ -13,6 +13,14 @@ import UIKit +extension SkeletonViewExtension where ExtendedType: UIView { + + var treeNode: SkeletonTreeNode { + SkeletonTreeNode(self.type) + } + +} + extension UIView { /// Flags @@ -159,7 +167,7 @@ extension UIView { } func removeSkeletonLayer() { - guard isSkeletonActive, + guard sk.isSkeletonActive, let skeletonLayer = _skeletonLayer, let transitionStyle = _currentSkeletonConfig?.transition else { return } skeletonLayer.stopAnimation() diff --git a/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+SkeletonView.swift b/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+SkeletonView.swift index 159a43fc..df6bc33a 100644 --- a/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+SkeletonView.swift +++ b/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+SkeletonView.swift @@ -30,7 +30,7 @@ extension UIView { func recursiveLayoutSkeletonIfNeeded(root: UIView? = nil) { subviewsSkeletonables.recursiveSearch(leafBlock: { - guard isSkeletonable, isSkeletonActive else { return } + guard isSkeletonable, sk.isSkeletonActive else { return } layoutSkeletonLayerIfNeeded() if let config = _currentSkeletonConfig, config.animated, !_isSkeletonAnimated { startSkeletonAnimation(config.animation) @@ -45,7 +45,7 @@ extension UIView { } func recursiveHideSkeleton(reloadDataAfter reload: Bool, transition: SkeletonTransitionStyle, root: UIView? = nil) { - guard isSkeletonActive else { return } + guard sk.isSkeletonActive else { return } if isHiddenWhenSkeletonIsActive { isHidden = false } @@ -70,7 +70,7 @@ extension UIView { private extension UIView { func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) { - guard !isSkeletonActive else { return } + guard !sk.isSkeletonActive else { return } saveViewState() prepareViewForSkeleton() @@ -81,7 +81,7 @@ private extension UIView { if isHiddenWhenSkeletonIsActive { isHidden = true } - guard isSkeletonable && !isSkeletonActive else { return } + guard isSkeletonable && !sk.isSkeletonActive else { return } _currentSkeletonConfig = config swizzleLayoutSubviews() swizzleTraitCollectionDidChange() @@ -98,7 +98,7 @@ private extension UIView { } func recursiveUpdateSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) { - guard isSkeletonActive else { return } + guard sk.isSkeletonActive else { return } _currentSkeletonConfig = config updateDummyDataSourceIfNeeded() subviewsSkeletonables.recursiveSearch(leafBlock: { diff --git a/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Swizzling.swift b/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Swizzling.swift index 866c68d8..3c257161 100644 --- a/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Swizzling.swift +++ b/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Swizzling.swift @@ -18,13 +18,13 @@ extension UIView { @objc func skeletonLayoutSubviews() { guard Thread.isMainThread else { return } skeletonLayoutSubviews() - guard isSkeletonActive else { return } + guard sk.isSkeletonActive else { return } layoutSkeletonIfNeeded() } @objc func skeletonTraitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { skeletonTraitCollectionDidChange(previousTraitCollection) - guard isSkeletonable, isSkeletonActive, let config = _currentSkeletonConfig else { return } + guard isSkeletonable, sk.isSkeletonActive, let config = _currentSkeletonConfig else { return } updateSkeleton(skeletonConfig: config) }