diff --git a/Assets/debug_description.png b/Assets/debug_description.png new file mode 100644 index 00000000..766451ef Binary files /dev/null and b/Assets/debug_description.png differ diff --git a/Assets/debug_mode.png b/Assets/debug_mode.png new file mode 100644 index 00000000..240c5864 Binary files /dev/null and b/Assets/debug_mode.png differ diff --git a/Assets/hierarchy_output.png b/Assets/hierarchy_output.png new file mode 100644 index 00000000..f1d5e91b Binary files /dev/null and b/Assets/hierarchy_output.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b785ccb..53ffdc77 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ # Change Log All notable changes to this project will be documented in this file +## [Debug (1.4)](https://github.com/Juanpe/SkeletonView/releases/tag/1.4) + +### New + +- Create `skeletonDescription` print a skeleton representation of the view. +- Create `SKELETON_DEBUG` environment variable, in order to print the view hierarchy when the skeleton appears. + +### Improvements +- Add two new methods to `SkeletonFlowDelegate` protocol. Now you can know when the skeleton did show and when it did hide. +- `Recursive` protocol + +### Bug fixes +- Solved issue [#86](https://github.com/Juanpe/SkeletonView/issues/86) (thanks @reececomo) + ## [Custom defaults (1.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.3) ### New diff --git a/Example UICollectionView/AppDelegate.swift b/Example UICollectionView/AppDelegate.swift index 5383e4ae..d5e870ea 100644 --- a/Example UICollectionView/AppDelegate.swift +++ b/Example UICollectionView/AppDelegate.swift @@ -1,10 +1,4 @@ -// -// AppDelegate.swift -// SkeletonViewExampleUICollectionView -// -// Created by Andrei Hogea on 14/02/2018. // Copyright © 2018 SkeletonView. All rights reserved. -// import UIKit diff --git a/Example UICollectionView/CollectionViewCell.swift b/Example UICollectionView/CollectionViewCell.swift index 2b212b40..91f3be9c 100644 --- a/Example UICollectionView/CollectionViewCell.swift +++ b/Example UICollectionView/CollectionViewCell.swift @@ -1,10 +1,4 @@ -// -// CollectionViewCell.swift -// SkeletonView-iOS -// -// Created by Andrei Hogea on 14/02/2018. // Copyright © 2018 SkeletonView. All rights reserved. -// import UIKit import SkeletonView diff --git a/Example UICollectionView/Constants.swift b/Example UICollectionView/Constants.swift deleted file mode 100644 index 4254eaf7..00000000 --- a/Example UICollectionView/Constants.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Constants.swift -// SkeletonViewExampleUICollectionView -// -// Created by Andrei Hogea on 15/02/2018. -// Copyright © 2018 SkeletonView. All rights reserved. -// - -import UIKit - -let colors = [(UIColor.turquoise,"turquoise"), (UIColor.emerald,"emerald"), (UIColor.peterRiver,"peterRiver"), (UIColor.amethyst,"amethyst"),(UIColor.wetAsphalt,"wetAsphalt"), (UIColor.nephritis,"nephritis"), (UIColor.belizeHole,"belizeHole"), (UIColor.wisteria,"wisteria"), (UIColor.midnightBlue,"midnightBlue"), (UIColor.sunFlower,"sunFlower"), (UIColor.carrot,"carrot"), (UIColor.alizarin,"alizarin"),(UIColor.clouds,"clouds"), (UIColor.concrete,"concrete"), (UIColor.flatOrange,"flatOrange"), (UIColor.pumpkin,"pumpkin"), (UIColor.pomegranate,"pomegranate"), (UIColor.silver,"silver"), (UIColor.asbestos,"asbestos")] - diff --git a/Example UICollectionView/ViewController.swift b/Example UICollectionView/ViewController.swift index c035640e..47981438 100644 --- a/Example UICollectionView/ViewController.swift +++ b/Example UICollectionView/ViewController.swift @@ -1,10 +1,4 @@ - // -// ViewController.swift -// SkeletonViewExampleUICollectionView -// -// Created by Andrei Hogea on 14/02/2018. // Copyright © 2018 SkeletonView. All rights reserved. -// import UIKit import SkeletonView diff --git a/Example/Base.lproj/Main.storyboard b/Example/Base.lproj/Main.storyboard index 2000c24c..daa411b6 100644 --- a/Example/Base.lproj/Main.storyboard +++ b/Example/Base.lproj/Main.storyboard @@ -201,6 +201,9 @@ + + + diff --git a/Example/Constants.swift b/Example/Constants.swift index cf461ae0..cb6e0f29 100644 --- a/Example/Constants.swift +++ b/Example/Constants.swift @@ -1,10 +1,4 @@ -// -// Constants.swift -// SkeletonView-iOS -// -// Created by Renato Mendes on 28/11/2017. -// Copyright © 2017 SkeletonView. All rights reserved. -// +// Copyright © 2018 SkeletonView. All rights reserved. import UIKit diff --git a/README.md b/README.md index b4db9894..6ad776f4 100755 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Enjoy it! 🙂 * [Appearance](#-appearance) * [Custom animations](#-custom-animations) * [Hierarchy](#-hierarchy) + * [Debug](#-debug) * [Documentation](#-documentation) * [Next steps](#-next-steps) * [Contributing](#-contributing) @@ -366,6 +367,31 @@ Because an image is worth a thousand words: |![](Assets/all_skeletonables.png) | ![](Assets/all_skeletonables_result.png) +### 🔬 Debug + +**NEW** In order 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: +```swift +var skeletonDescription: 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. + +![](Assets/debug_mode.png) + +Then, when the skeleton appears, you can see the view hierarchy in the Xcode console. + +
+Open to see an output example + +
+ + ### 📚 Documentation Coming soon...😅 @@ -379,6 +405,7 @@ Coming soon...😅 * [x] tvOS compatible * [x] Add recovery state * [x] Custom default appearance +* [x] Debug mode * [ ] Custom collections compatible * [ ] Add animations when it shows/hides the skeletons * [ ] MacOS and WatchOS compatible diff --git a/SkeletonView.podspec b/SkeletonView.podspec index 9d549a3e..f93ba09d 100644 --- a/SkeletonView.podspec +++ b/SkeletonView.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SkeletonView" - s.version = "1.3" + s.version = "1.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. diff --git a/SkeletonView.xcodeproj/project.pbxproj b/SkeletonView.xcodeproj/project.pbxproj index eb9f3e65..e361b1ae 100644 --- a/SkeletonView.xcodeproj/project.pbxproj +++ b/SkeletonView.xcodeproj/project.pbxproj @@ -38,11 +38,13 @@ 42ABD07A210B54E200BEEFF4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 42ABD072210B54E100BEEFF4 /* Assets.xcassets */; }; 42ABD07B210B54E200BEEFF4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD073210B54E100BEEFF4 /* ViewController.swift */; }; 42ABD07C210B54E200BEEFF4 /* Base.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 42ABD074210B54E100BEEFF4 /* Base.lproj */; }; - 42ABD07D210B54E200BEEFF4 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD075210B54E100BEEFF4 /* Constants.swift */; }; 42ABD07E210B54E200BEEFF4 /* SkeletonViewExampleCollectionview-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 42ABD076210B54E200BEEFF4 /* SkeletonViewExampleCollectionview-Info.plist */; }; 42ABD07F210B54E200BEEFF4 /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */; }; 8748240D20C6A88A00E92179 /* UIView+IBInspectable.swift in Headers */ = {isa = PBXBuildFile; fileRef = F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 8748240E20C6A88E00E92179 /* ContainsMultilineText.swift in Headers */ = {isa = PBXBuildFile; fileRef = F5307E3A1FB123C100EE67C5 /* ContainsMultilineText.swift */; settings = {ATTRIBUTES = (Public, ); }; }; + 8785E3A1211C9C9800CC9DFD /* SkeletonDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */; }; + 8785E3A2211C9CA500CC9DFD /* SkeletonDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */; }; + 8785E3A3211C9CE800CC9DFD /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DEA97D1FCDBD1F006C80EF /* Constants.swift */; }; 88DEA97F1FCDBD78006C80EF /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DEA97D1FCDBD1F006C80EF /* Constants.swift */; }; 8933C7851EB5B820000D00A4 /* SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* SkeletonView.swift */; }; F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */; }; @@ -71,6 +73,8 @@ F58A6E6C20A8C54100612494 /* RecoverableViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F58A6E6A20A8C54100612494 /* RecoverableViewState.swift */; }; F58A6E6E20A8C66300612494 /* Recoverable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F58A6E6D20A8C66300612494 /* Recoverable.swift */; }; F58A6E6F20A8C66300612494 /* Recoverable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F58A6E6D20A8C66300612494 /* Recoverable.swift */; }; + F5A5E8B4211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5A5E8B3211DCE7000FD20D0 /* Int+Whitespaces.swift */; }; + F5A5E8B5211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5A5E8B3211DCE7000FD20D0 /* Int+Whitespaces.swift */; }; F5D3FB0B209DCAA300003FCF /* SubviewsSkeletonables.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D3FB0A209DCAA300003FCF /* SubviewsSkeletonables.swift */; }; F5D3FB0C209DCAA300003FCF /* SubviewsSkeletonables.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D3FB0A209DCAA300003FCF /* SubviewsSkeletonables.swift */; }; F5F622411FAC6E31007C062A /* UIColor+Skeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */; }; @@ -142,10 +146,10 @@ 42ABD072210B54E100BEEFF4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 42ABD073210B54E100BEEFF4 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 42ABD074210B54E100BEEFF4 /* Base.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base.lproj; sourceTree = ""; }; - 42ABD075210B54E100BEEFF4 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 42ABD076210B54E200BEEFF4 /* SkeletonViewExampleCollectionview-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SkeletonViewExampleCollectionview-Info.plist"; sourceTree = ""; }; 42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = ""; }; 52D6D97C1BEFF229002C0205 /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonDebug.swift; sourceTree = ""; }; 88DEA97D1FCDBD1F006C80EF /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 8933C7841EB5B820000D00A4 /* SkeletonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkeletonView.swift; sourceTree = ""; }; F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonAnimationBuilder.swift; sourceTree = ""; }; @@ -167,6 +171,7 @@ F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+UIApplicationDelegate.swift"; sourceTree = ""; }; F58A6E6A20A8C54100612494 /* RecoverableViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoverableViewState.swift; sourceTree = ""; }; F58A6E6D20A8C66300612494 /* Recoverable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recoverable.swift; sourceTree = ""; }; + F5A5E8B3211DCE7000FD20D0 /* Int+Whitespaces.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Whitespaces.swift"; sourceTree = ""; }; F5D3FB0A209DCAA300003FCF /* SubviewsSkeletonables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubviewsSkeletonables.swift; sourceTree = ""; }; F5F622401FAC6E31007C062A /* UIColor+Skeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Skeleton.swift"; sourceTree = ""; }; F5F622421FAC81FD007C062A /* CALayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+Extensions.swift"; sourceTree = ""; }; @@ -226,7 +231,6 @@ 42ABD072210B54E100BEEFF4 /* Assets.xcassets */, 42ABD074210B54E100BEEFF4 /* Base.lproj */, 42ABD077210B54E200BEEFF4 /* CollectionViewCell.swift */, - 42ABD075210B54E100BEEFF4 /* Constants.swift */, 42ABD071210B54E100BEEFF4 /* Main.storyboard */, 42ABD073210B54E100BEEFF4 /* ViewController.swift */, ); @@ -267,15 +271,32 @@ path = Configs; sourceTree = ""; }; + 8785E39E211C9C6D00CC9DFD /* Appearance */ = { + isa = PBXGroup; + children = ( + F5307E2F1FB0EC9D00EE67C5 /* SkeletonAppearance.swift */, + ); + path = Appearance; + sourceTree = ""; + }; + 8785E39F211C9C7C00CC9DFD /* Debug */ = { + isa = PBXGroup; + children = ( + 8785E3A0211C9C9800CC9DFD /* SkeletonDebug.swift */, + ); + path = Debug; + sourceTree = ""; + }; 8933C7811EB5B7E0000D00A4 /* Sources */ = { isa = PBXGroup; children = ( + 8785E39F211C9C7C00CC9DFD /* Debug */, + 8785E39E211C9C6D00CC9DFD /* Appearance */, F58A6E7020A8C87100612494 /* Recoverable */, F5307E331FB1068500EE67C5 /* Collections */, F5307E341FB106A500EE67C5 /* Extensions */, F5307E351FB106BF00EE67C5 /* Helpers */, F51DE1081FBF70A70037919A /* SkeletonAnimationBuilder.swift */, - F5307E2F1FB0EC9D00EE67C5 /* SkeletonAppearance.swift */, F587FB83202CBFC8002DB5FE /* SkeletonFlow.swift */, F5307E2B1FAF6BC900EE67C5 /* SkeletonGradient.swift */, F5F899E81FAB9D2B002E8FDA /* SkeletonLayer.swift */, @@ -343,6 +364,7 @@ F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */, F5F899CF1FAA6A4D002E8FDA /* UIView+IBInspectable.swift */, F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */, + F5A5E8B3211DCE7000FD20D0 /* Int+Whitespaces.swift */, ); path = Extensions; sourceTree = ""; @@ -593,6 +615,7 @@ 17DD0E1E207FB32100C56334 /* PrepareForSkeletonProtocol.swift in Sources */, F51ED28520973CC9008B2434 /* SkeletonReusableCell.swift in Sources */, 17DD0E17207FB28F00C56334 /* SkeletonLayer.swift in Sources */, + 8785E3A2211C9CA500CC9DFD /* SkeletonDebug.swift in Sources */, 17DD0E08207FB28900C56334 /* CollectionSkeletonProtocol.swift in Sources */, F58A6E6C20A8C54100612494 /* RecoverableViewState.swift in Sources */, 17DD0E0B207FB28900C56334 /* SkeletonTableViewProtocols.swift in Sources */, @@ -614,6 +637,7 @@ F51ED28420973CC6008B2434 /* GenericCollectionView.swift in Sources */, 17DD0E0A207FB28900C56334 /* SkeletonCollectionViewProtocols.swift in Sources */, 17DD0E1C207FB32100C56334 /* AssociationPolicy.swift in Sources */, + F5A5E8B5211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */, 17DD0E1D207FB32100C56334 /* ContainsMultilineText.swift in Sources */, 17DD0E0F207FB28C00C56334 /* CALayer+Extensions.swift in Sources */, 17DD0E16207FB28F00C56334 /* SkeletonGradient.swift in Sources */, @@ -627,7 +651,7 @@ 42ABD07B210B54E200BEEFF4 /* ViewController.swift in Sources */, 42ABD078210B54E200BEEFF4 /* AppDelegate.swift in Sources */, 42ABD07F210B54E200BEEFF4 /* CollectionViewCell.swift in Sources */, - 42ABD07D210B54E200BEEFF4 /* Constants.swift in Sources */, + 8785E3A3211C9CE800CC9DFD /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -639,6 +663,7 @@ F54CF5E42024CEB000330B0D /* UIView+CollectionSkeleton.swift in Sources */, F5307E371FB1076E00EE67C5 /* SkeletonTableViewProtocols.swift in Sources */, 8933C7851EB5B820000D00A4 /* SkeletonView.swift in Sources */, + 8785E3A1211C9C9800CC9DFD /* SkeletonDebug.swift in Sources */, F5307E301FB0EC9D00EE67C5 /* SkeletonAppearance.swift in Sources */, F58A6E6B20A8C54100612494 /* RecoverableViewState.swift in Sources */, F54CF5E52024CEB000330B0D /* UITableView+CollectionSkeleton.swift in Sources */, @@ -660,6 +685,7 @@ F5307E3B1FB123C100EE67C5 /* ContainsMultilineText.swift in Sources */, F5F899E91FAB9D2B002E8FDA /* SkeletonLayer.swift in Sources */, F5307E2E1FB0E5E400EE67C5 /* UIView+Frame.swift in Sources */, + F5A5E8B4211DCE7000FD20D0 /* Int+Whitespaces.swift in Sources */, F51DF871206E91B300D23301 /* SkeletonReusableCell.swift in Sources */, F51DF879206E9F5500D23301 /* SkeletonCollectionDelegate.swift in Sources */, F51DE1091FBF70A70037919A /* SkeletonAnimationBuilder.swift in Sources */, diff --git a/SkeletonViewExample copy-Info.plist b/SkeletonViewExample copy-Info.plist deleted file mode 100644 index 9751b3b3..00000000 --- a/SkeletonViewExample copy-Info.plist +++ /dev/null @@ -1,45 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.3 - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/Sources/SkeletonAppearance.swift b/Sources/Appearance/SkeletonAppearance.swift similarity index 100% rename from Sources/SkeletonAppearance.swift rename to Sources/Appearance/SkeletonAppearance.swift diff --git a/Sources/Debug/SkeletonDebug.swift b/Sources/Debug/SkeletonDebug.swift new file mode 100644 index 00000000..2ab78adc --- /dev/null +++ b/Sources/Debug/SkeletonDebug.swift @@ -0,0 +1,50 @@ + +// Copyright © 2018 SkeletonView. All rights reserved. + +import Foundation +import UIKit + +enum SkeletonEnvironmentKey: String { + case debugMode = "SKELETON_DEBUG" +} + +extension Dictionary { + subscript (_ key: SkeletonEnvironmentKey) -> Value? { + return self[key.rawValue as! Key] + } +} + +func printSkeletonHierarchy(in view: UIView) { + skeletonLog(view.skeletonHierarchy()) +} + +func skeletonLog(_ message: String) { + if let _ = ProcessInfo.processInfo.environment[.debugMode] { + print(message) + } +} + +extension UIView { + + public var skeletonDescription: String { + var description = "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())" + let subSkeletons = subviewsSkeletonables + if subSkeletons.count != 0 { + description += " | (\(subSkeletons.count)) subSkeletons" + } + if isSkeletonable { + description += " | ☠️ " + } + return description + ">" + } + + public func skeletonHierarchy(index: Int = 0) -> String { + var description = index == 0 ? "\n ⬇⬇ ☠️ Root view hierarchy with Skeletons ⬇⬇ \n" : "" + description += "\(index == 0 ? "\n" : 3.whitespaces) \(skeletonDescription) \n" + subviewsToSkeleton.forEach { + description += (index + 2).whitespaces + description += $0.skeletonHierarchy(index: index + 1) + } + return description + } +} diff --git a/Sources/Extensions/CALayer+Extensions.swift b/Sources/Extensions/CALayer+Extensions.swift index 4f73bccb..25a6ce7f 100644 --- a/Sources/Extensions/CALayer+Extensions.swift +++ b/Sources/Extensions/CALayer+Extensions.swift @@ -10,18 +10,20 @@ import UIKit extension CALayer { @objc func tint(withColors colors: [UIColor]) { - recursiveSearch(inArray: skeletonSublayers, - leafBlock: { backgroundColor = colors.first?.cgColor }) { - $0.tint(withColors: colors) + skeletonSublayers.recursiveSearch(leafBlock: { + backgroundColor = colors.first?.cgColor + }) { + $0.tint(withColors: colors) } } } extension CAGradientLayer { override func tint(withColors colors: [UIColor]) { - recursiveSearch(inArray: skeletonSublayers, - leafBlock: { self.colors = colors.map { $0.cgColor } }) { - $0.tint(withColors: colors) + skeletonSublayers.recursiveSearch(leafBlock: { + self.colors = colors.map { $0.cgColor } + }) { + $0.tint(withColors: colors) } } } @@ -91,16 +93,18 @@ public extension CALayer { } func playAnimation(_ anim: SkeletonLayerAnimation, key: String) { - recursiveSearch(inArray: skeletonSublayers, - leafBlock: { add(anim(self), forKey: key) }) { - $0.playAnimation(anim, key: key) + skeletonSublayers.recursiveSearch(leafBlock: { + add(anim(self), forKey: key) + }) { + $0.playAnimation(anim, key: key) } } func stopAnimation(forKey key: String) { - recursiveSearch(inArray: skeletonSublayers, - leafBlock: { removeAnimation(forKey: key) }) { - $0.stopAnimation(forKey: key) + skeletonSublayers.recursiveSearch(leafBlock: { + removeAnimation(forKey: key) + }) { + $0.stopAnimation(forKey: key) } } } diff --git a/Sources/Extensions/Int+Whitespaces.swift b/Sources/Extensions/Int+Whitespaces.swift new file mode 100644 index 00000000..7d5bef58 --- /dev/null +++ b/Sources/Extensions/Int+Whitespaces.swift @@ -0,0 +1,14 @@ +// Copyright © 2018 SkeletonView. All rights reserved. + +import Foundation + +extension Int { + + var whitespace: String { + return whitespaces + } + + var whitespaces: String { + return String(repeating: " ", count: self) + } +} diff --git a/Sources/Helpers/RecursiveProtocol.swift b/Sources/Helpers/RecursiveProtocol.swift index 9b77e63b..29c73e9d 100644 --- a/Sources/Helpers/RecursiveProtocol.swift +++ b/Sources/Helpers/RecursiveProtocol.swift @@ -1,37 +1,28 @@ -// -// HelperProtocols.swift -// SkeletonView-iOS -// -// Created by Juanpe Catalán on 06/11/2017. // Copyright © 2017 SkeletonView. All rights reserved. -// import UIKit typealias VoidBlock = () -> Void typealias RecursiveBlock = (T) -> Void +protocol IterableElement {} +extension UIView: IterableElement {} +extension CALayer: IterableElement {} + //MARK: Recursive protocol Recursive { - associatedtype Element - func recursiveSearch(inArray array:[Element], leafBlock: VoidBlock, recursiveBlock: RecursiveBlock) + associatedtype Element: IterableElement + func recursiveSearch(leafBlock: VoidBlock, recursiveBlock: RecursiveBlock) } -extension Recursive { - func recursiveSearch(inArray array:[Element], leafBlock: VoidBlock, recursiveBlock: RecursiveBlock) { - guard array.count > 0 else { +extension Array: Recursive where Element: IterableElement { + func recursiveSearch(leafBlock: VoidBlock, recursiveBlock: RecursiveBlock) { + guard count > 0 else { leafBlock() return } - array.forEach { recursiveBlock($0) } + forEach { recursiveBlock($0) } } } -extension UIView: Recursive { - typealias Element = UIView -} -extension CALayer: Recursive { - typealias Element = CALayer -} - diff --git a/Sources/SkeletonFlow.swift b/Sources/SkeletonFlow.swift index c9526e83..8d57a995 100644 --- a/Sources/SkeletonFlow.swift +++ b/Sources/SkeletonFlow.swift @@ -1,26 +1,30 @@ -// -// SkeletonFlow.swift -// SkeletonView-iOS -// -// Created by Juanpe Catalán on 08/02/2018. // Copyright © 2018 SkeletonView. All rights reserved. -// import UIKit protocol SkeletonFlowDelegate { func willBeginShowingSkeletons(withRootView rootView: UIView) + func didShowSkeletons(withRootView rootView: UIView) func willBeginHidingSkeletons(withRootView rootView: UIView) + func didHideSkeletons(withRootView rootView: UIView) + } class SkeletonFlowHandler: SkeletonFlowDelegate { - + func willBeginShowingSkeletons(withRootView rootView: UIView) { rootView.addAppNotificationsObservers() } + + func didShowSkeletons(withRootView rootView: UIView) { + printSkeletonHierarchy(in: rootView) + } func willBeginHidingSkeletons(withRootView rootView: UIView) { rootView.removeAppNoticationsObserver() + } + + func didHideSkeletons(withRootView rootView: UIView) { rootView.flowDelegate = nil } } diff --git a/Sources/SkeletonView.swift b/Sources/SkeletonView.swift index 8f51576e..37eedb5e 100644 --- a/Sources/SkeletonView.swift +++ b/Sources/SkeletonView.swift @@ -22,22 +22,20 @@ public extension UIView { func hideSkeleton(reloadDataAfter reload: Bool = true) { flowDelegate?.willBeginHidingSkeletons(withRootView: self) - recursiveHideSkeleton(reloadDataAfter: reload) + recursiveHideSkeleton(reloadDataAfter: reload, root: self) } func startSkeletonAnimation(_ anim: SkeletonLayerAnimation? = nil) { skeletonIsAnimated = true - recursiveSearch(inArray: subviewsSkeletonables, - leafBlock: startSkeletonLayerAnimationBlock(anim)) { - $0.startSkeletonAnimation(anim) - } + subviewsSkeletonables.recursiveSearch(leafBlock: startSkeletonLayerAnimationBlock(anim)) { subview in + subview.startSkeletonAnimation(anim) + } } func stopSkeletonAnimation() { skeletonIsAnimated = false - recursiveSearch(inArray: subviewsSkeletonables, - leafBlock: stopSkeletonLayerAnimationBlock) { - $0.stopSkeletonAnimation() + subviewsSkeletonables.recursiveSearch(leafBlock: stopSkeletonLayerAnimationBlock) { subview in + subview.stopSkeletonAnimation() } } } @@ -48,33 +46,39 @@ extension UIView { skeletonIsAnimated = animated flowDelegate = SkeletonFlowHandler() flowDelegate?.willBeginShowingSkeletons(withRootView: self) - recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation) + recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation, root: self) } - fileprivate func recursiveShowSkeleton(withType type: SkeletonType, usingColors colors: [UIColor], animated: Bool, animation: SkeletonLayerAnimation?) { + fileprivate func recursiveShowSkeleton(withType type: SkeletonType, usingColors colors: [UIColor], animated: Bool, animation: SkeletonLayerAnimation?, root: UIView? = nil) { addDummyDataSourceIfNeeded() - recursiveSearch(inArray: subviewsSkeletonables, - leafBlock: { - guard !isSkeletonActive else { return } - isUserInteractionEnabled = false - saveViewState() - (self as? PrepareForSkeleton)?.prepareViewForSkeleton() - addSkeletonLayer(withType: type, usingColors: colors, animated: animated, animation: animation) - }) { - $0.recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation) + + subviewsSkeletonables.recursiveSearch(leafBlock: { + guard !isSkeletonActive else { return } + isUserInteractionEnabled = false + saveViewState() + (self as? PrepareForSkeleton)?.prepareViewForSkeleton() + addSkeletonLayer(withType: type, usingColors: colors, animated: animated, animation: animation) + }) { subview in + subview.recursiveShowSkeleton(withType: type, usingColors: colors, animated: animated, animation: animation) + } + + if let root = root { + flowDelegate?.didShowSkeletons(withRootView: root) } } - fileprivate func recursiveHideSkeleton(reloadDataAfter reload: Bool) { + fileprivate func recursiveHideSkeleton(reloadDataAfter reload: Bool, root: UIView? = nil) { removeDummyDataSourceIfNeeded() isUserInteractionEnabled = true - recursiveSearch(inArray: subviewsSkeletonables, - leafBlock: { - recoverViewState(forced: false) - removeSkeletonLayer() - }, recursiveBlock: { - $0.recursiveHideSkeleton(reloadDataAfter: reload) - }) + subviewsSkeletonables.recursiveSearch(leafBlock: { + recoverViewState(forced: false) + removeSkeletonLayer() + }) { subview in + subview.recursiveHideSkeleton(reloadDataAfter: reload) + } + if let root = root { + flowDelegate?.didHideSkeletons(withRootView: root) + } } fileprivate func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock { diff --git a/Sources/SubviewsSkeletonables.swift b/Sources/SubviewsSkeletonables.swift index 289f87f8..aa950a91 100644 --- a/Sources/SubviewsSkeletonables.swift +++ b/Sources/SubviewsSkeletonables.swift @@ -1,45 +1,48 @@ -// -// SubviewsSkeletonables.swift -// SkeletonView -// -// Created by Juanpe Catalán on 05/05/2018. // Copyright © 2018 SkeletonView. All rights reserved. -// import UIKit extension UIView { @objc var subviewsSkeletonables: [UIView] { - return subviews.filter { $0.isSkeletonable } + return subviewsToSkeleton.filter { $0.isSkeletonable } + } + + @objc var subviewsToSkeleton: [UIView] { + return subviews } } extension UITableView { - override var subviewsSkeletonables: [UIView] { - return visibleCells.filter { $0.isSkeletonable } + + override var subviewsToSkeleton: [UIView] { + return visibleCells } } extension UITableViewCell { - override var subviewsSkeletonables: [UIView] { - return contentView.subviews.filter { $0.isSkeletonable } + + override var subviewsToSkeleton: [UIView] { + return contentView.subviews } } extension UICollectionView { - override var subviewsSkeletonables: [UIView] { - return subviews.filter { $0.isSkeletonable } + + override var subviewsToSkeleton: [UIView] { + return subviews } } extension UICollectionViewCell { - override var subviewsSkeletonables: [UIView] { - return contentView.subviews.filter { $0.isSkeletonable } + + override var subviewsToSkeleton: [UIView] { + return contentView.subviews } } extension UIStackView { - override var subviewsSkeletonables: [UIView] { - return arrangedSubviews.filter { $0.isSkeletonable } + + override var subviewsToSkeleton: [UIView] { + return arrangedSubviews } }