Repository: Juanpe/SkeletonView Branch: main Commit: 30c92f099288 Files: 165 Total size: 530.4 KB Directory structure: gitextract_9ngk7ec7/ ├── .codebeatsettings ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feedback.md │ │ └── submit-a-request.md │ ├── pull_request_template.md │ ├── release-drafter.yml │ └── workflows/ │ ├── CD.yml │ ├── main.yml │ ├── needs-attention.yml │ ├── pod_lib_lint.yml │ ├── pod_trunk.yml │ ├── release.yml │ ├── release_notes.yml │ ├── stale.yml │ └── validations.yml ├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── .swiftpm/ │ └── xcode/ │ └── package.xcworkspace/ │ └── contents.xcworkspacedata ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dangerfile.swift ├── Examples/ │ ├── CollectionView/ │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── avatar.imageset/ │ │ │ │ └── Contents.json │ │ │ └── picture.imageset/ │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ └── LaunchScreen.storyboard │ │ ├── CollectionViewCell.swift │ │ ├── Main.storyboard │ │ ├── SkeletonViewExampleCollectionview-Info.plist │ │ └── ViewController.swift │ ├── iOS Example/ │ │ ├── Sources/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── avatar.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Cell.swift │ │ │ ├── Constants.swift │ │ │ ├── HeaderFooterSection.swift │ │ │ ├── Info.plist │ │ │ ├── UITextViewByCodeViewController.swift │ │ │ └── ViewController.swift │ │ └── iOS Example.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── tvOS Example/ │ ├── Sources/ │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── App Icon & Top Shelf Image.brandassets/ │ │ │ │ ├── App Icon - App Store.imagestack/ │ │ │ │ │ ├── Back.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Front.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Middle.imagestacklayer/ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── App Icon.imagestack/ │ │ │ │ │ ├── Back.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Front.imagestacklayer/ │ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Middle.imagestacklayer/ │ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Top Shelf Image Wide.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Top Shelf Image.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── ViewController.swift │ └── tvOS Example.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist ├── Gemfile ├── LICENSE ├── Package.swift ├── README.md ├── SkeletonVIew.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist ├── SkeletonView.podspec ├── SkeletonView.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata/ │ ├── IDETemplateMacros.plist │ └── xcschemes/ │ ├── SkeletonView iOS.xcscheme │ └── SkeletonView tvOS.xcscheme ├── SkeletonViewCore/ │ ├── Sources/ │ │ ├── API/ │ │ │ ├── AnimationBuilder/ │ │ │ │ └── SkeletonAnimationBuilder.swift │ │ │ ├── Appearance/ │ │ │ │ └── SkeletonAppearance.swift │ │ │ ├── Collections/ │ │ │ │ ├── CollectionViews/ │ │ │ │ │ └── SkeletonCollectionViewProtocols.swift │ │ │ │ └── TableViews/ │ │ │ │ └── SkeletonTableViewProtocols.swift │ │ │ ├── Deprecated.swift │ │ │ ├── FoundationExtensions/ │ │ │ │ └── Notification+SkeletonFlow.swift │ │ │ ├── Models/ │ │ │ │ ├── GradientDirection.swift │ │ │ │ ├── SkeletonGradient.swift │ │ │ │ ├── SkeletonTextLineHeight.swift │ │ │ │ ├── SkeletonTextNumberOfLines.swift │ │ │ │ ├── SkeletonTransitionStyle.swift │ │ │ │ └── SkeletonType.swift │ │ │ ├── SkeletonExtended.swift │ │ │ ├── SkeletonView.swift │ │ │ └── UIKitExtensions/ │ │ │ ├── CALayer+Animations.swift │ │ │ ├── UICollectionView+Extensions.swift │ │ │ ├── UILabel+IBInspectable.swift │ │ │ ├── UILabel+SKExtensions.swift │ │ │ ├── UITextView+IBInspectable.swift │ │ │ ├── UITextView+SKExtensions.swift │ │ │ ├── UIView+IBInspectable.swift │ │ │ └── UIView+SKExtensions.swift │ │ ├── Internal/ │ │ │ ├── Collections/ │ │ │ │ ├── CollectionSkeleton.swift │ │ │ │ ├── SkeletonCollectionDataSource.swift │ │ │ │ ├── SkeletonCollectionDelegate.swift │ │ │ │ └── SkeletonReusableCell.swift │ │ │ ├── Debug/ │ │ │ │ └── SkeletonDebug.swift │ │ │ ├── FoundationExtensions/ │ │ │ │ ├── DispatchQueue+Extensions.swift │ │ │ │ ├── Int+Extensions.swift │ │ │ │ ├── Notification+Extensions.swift │ │ │ │ └── ProcessInfo+Extensions.swift │ │ │ ├── Helpers/ │ │ │ │ ├── AssociationPolicy.swift │ │ │ │ ├── Recursive.swift │ │ │ │ └── Swizzling.swift │ │ │ ├── Models/ │ │ │ │ ├── RecoverableViewState.swift │ │ │ │ └── SkeletonLayer.swift │ │ │ ├── SkeletonConfigs/ │ │ │ │ ├── SkeletonConfig.swift │ │ │ │ └── SkeletonMultilinesLayerConfig.swift │ │ │ ├── SkeletonExtensions/ │ │ │ │ ├── GradientDirection+Animations.swift │ │ │ │ ├── PrepareViewForSkeleton.swift │ │ │ │ ├── Recoverable.swift │ │ │ │ ├── SkeletonTextNode.swift │ │ │ │ └── SubviewsSkeletonables.swift │ │ │ ├── SkeletonFlowHandler.swift │ │ │ ├── SkeletonLayerBuilders/ │ │ │ │ ├── SkeletonLayerBuilder.swift │ │ │ │ └── SkeletonMultilineLayerBuilder.swift │ │ │ ├── SkeletonTree/ │ │ │ │ └── SkeletonTreeNode.swift │ │ │ └── UIKitExtensions/ │ │ │ ├── CALayer+Extensions.swift │ │ │ ├── SkeletonTreeNode+Extensions.swift │ │ │ ├── UICollectionView+CollectionSkeleton.swift │ │ │ ├── UIColor+Skeleton.swift │ │ │ ├── UILabel+Extensions.swift │ │ │ ├── UITableView+CollectionSkeleton.swift │ │ │ ├── UITableView+Extensions.swift │ │ │ ├── UIView+AppLifecycleNotifications.swift │ │ │ ├── UIView+AssociatedObjects.swift │ │ │ ├── UIView+CollectionSkeleton.swift │ │ │ ├── UIView+Extensions.swift │ │ │ ├── UIView+SkeletonView.swift │ │ │ ├── UIView+Swizzling.swift │ │ │ └── UIView+Transitions.swift │ │ └── Supporting Files/ │ │ ├── Info.plist │ │ └── PrivacyInfo.xcprivacy │ └── Tests/ │ ├── Debug/ │ │ └── SkeletonDebugTests.swift │ └── Supporting Files/ │ └── Info.plist ├── Translations/ │ ├── README_de.md │ ├── README_es.md │ ├── README_fr.md │ ├── README_ko.md │ ├── README_pt-br.md │ └── README_zh.md └── fastlane/ ├── Fastfile └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codebeatsettings ================================================ { "SWIFT": { "TOO_MANY_FUNCTIONS": [50, 100, 150, 200], "TOTAL_LOC": [200, 400, 500, 600] } } ================================================ FILE: .gitattributes ================================================ *.sh linguist-language=Swift *.podspec linguist-language=Swift ================================================ FILE: .github/FUNDING.yml ================================================ github: [juanpe] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: "\U0001F41B Bug report" about: Report a bug or unexpected behavior while using SkeletonView title: '' labels: bug assignees: '' --- ### Description Describe your issue here. ### What type of issue is this? (place an `x` in one of the `[ ]`) - [ ] bug - [ ] enhancement (feature request) - [ ] question - [ ] documentation related - [ ] discussion ### Requirements (place an `x` in each of the `[ ]`) * [ ] I've read and understood the [Contributing guidelines](https://github.com/Juanpe/SkeletonView/blob/main/CONTRIBUTING.md) and have done my best effort to follow them. * [ ] I've read and agree to the [Code of Conduct](https://github.com/Juanpe/SkeletonView/blob/main/CODE_OF_CONDUCT.md). * [ ] I've searched for any related issues and avoided creating a duplicate issue. --- ### Bug Report Filling out the following details about bugs will help us solve your issue sooner. ### SkeletonView Environment: **SkeletonView version:** **Xcode version:** **Swift version:** #### Steps to reproduce: *Please replace this with the steps to reproduce the behavior.* 1. 2. 3. #### Expected result: *Please replace this with what you expected to happen.* #### Actual result: *Please replace this with of what happened instead.* #### Attachments: Logs, screenshots, sample project, funny gif, etc. ================================================ FILE: .github/ISSUE_TEMPLATE/feedback.md ================================================ --- name: "\U0001F4E3 Feedback" about: Give us general feedback about the SkeletonView title: '' labels: feedback assignees: '' --- # SkeletonView Feedback You can use this template to give us structured feedback or just wipe it and leave us a note. Thank you! ## What have you loved? _eg "the nice colors"_ ## What was confusing or gave you pause? _eg "it did something unexpected"_ ## Are there features you'd like to see added? _eg "SkeletonView should be compatible with SwiftUI"_ ## Anything else? _eg "have a nice day"_ ================================================ FILE: .github/ISSUE_TEMPLATE/submit-a-request.md ================================================ --- name: "⭐ Submit a request" about: Surface a feature or problem that you think should be solved title: '' labels: enhancement assignees: '' --- ### Describe the feature or problem you’d like to solve A clear and concise description of what the feature or problem is. ### Proposed solution How will it benefit SkeletonView and its users? ### Additional context Add any other context like screenshots or mockups are helpful, if applicable. ================================================ FILE: .github/pull_request_template.md ================================================ ### Summary Describe the goal of this PR. Mention any related Issue numbers. ### Requirements (place an `x` in each of the `[ ]`) * [ ] I've read and understood the [Contributing guidelines](https://github.com/Juanpe/SkeletonView/blob/main/CONTRIBUTING.md) and have done my best effort to follow them. * [ ] I've read and agree to the [Code of Conduct](https://github.com/Juanpe/SkeletonView/blob/main/CODE_OF_CONDUCT.md). ================================================ FILE: .github/release-drafter.yml ================================================ name-template: '📦 $RESOLVED_VERSION' tag-template: '$RESOLVED_VERSION' category-template: '#### $TITLE' change-template: '- **#$NUMBER**: $TITLE - @$AUTHOR' template: | $CHANGES categories: - title: '🚨 Breaking' label: 'breaking' - title: '🔬Improvements' label: '💡 enhancement' - title: '🙌 New' label: 'feature' - title: '🩹 Bug fixes' label: '🐞 bug' - title: '⚙️ Maintenance' label: '⚙️ maintenance' - title: '📚 Documentation' label: '📚 docs' - title: '💾 Dependency Updates' label: 'dependencies' version-resolver: major: labels: - 'breaking' minor: labels: - '💡 enhancement' - 'feature' patch: labels: - '🐞 bug' - '⚙️ maintenance' - '📚 docs' - 'dependencies' exclude-labels: - 'skip-changelog' ================================================ FILE: .github/workflows/CD.yml ================================================ name: CD on: pull_request_target: branches: [main] types: [closed] jobs: release_version: if: github.event.pull_request.milestone == null && github.event.pull_request.merged == true runs-on: macOS-latest steps: - uses: actions/checkout@v2 - name: Publish release id: publish_release uses: release-drafter/release-drafter@v5 with: publish: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Update podspec run: fastlane bump_version next_version:${{ steps.publish_release.outputs.tag_name }} - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v4 with: branch: 'main' commit_message: 'Bump version ${{ steps.publish_release.outputs.tag_name }}' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Deploy to Cocoapods continue-on-error: true env: COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} run: | set -eo pipefail pod lib lint --allow-warnings pod trunk push --allow-warnings - name: Tweet the release uses: ethomson/send-tweet-action@v1 with: consumer-key: ${{ secrets.TWITTER_CONSUMER_API_KEY }} consumer-secret: ${{ secrets.TWITTER_CONSUMER_API_SECRET }} access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }} access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} status: | 🎉 New release ${{ steps.publish_release.outputs.tag_name }} is out 🚀 Check out all the changes here: ${{ steps.publish_release.outputs.html_url }} ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: pull_request: branches: [main] workflow_dispatch: jobs: build: runs-on: macos-latest strategy: matrix: build-config: - { scheme: 'SkeletonView iOS', destination: 'platform=iOS Simulator,name=iPhone 8', sdk: 'iphonesimulator' } - { scheme: 'SkeletonView tvOS', destination: 'platform=tvOS Simulator,name=Apple TV', sdk: 'appletvsimulator' } - { scheme: 'iOS Example', destination: 'platform=iOS Simulator,name=iPhone 8', sdk: 'iphonesimulator' } - { scheme: 'tvOS Example', destination: 'platform=tvOS Simulator,name=Apple TV', sdk: 'appletvsimulator' } steps: - uses: actions/checkout@v2 - name: Build run: xcodebuild clean build -workspace 'SkeletonView.xcworkspace' -scheme '${{ matrix.build-config['scheme'] }}' -sdk '${{ matrix.build-config['sdk'] }}' -destination '${{ matrix.build-config['destination'] }}' ================================================ FILE: .github/workflows/needs-attention.yml ================================================ name: Issue Needs Attention # This workflow is triggered on issue comments. on: issue_comment: types: created jobs: applyNeedsAttentionLabel: name: Apply Needs Attention Label runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Apply Needs Attention Label uses: hramos/needs-attention@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} response-required-label: 'awaiting user info' needs-attention-label: 'needs triage' ================================================ FILE: .github/workflows/pod_lib_lint.yml ================================================ 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 ================================================ FILE: .github/workflows/pod_trunk.yml ================================================ name: Pod trunk on: [workflow_dispatch] jobs: release_version: runs-on: macOS-latest steps: - uses: actions/checkout@v2 - name: Deploy to Cocoapods env: COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} run: | set -eo pipefail pod lib lint --allow-warnings pod trunk push --allow-warnings ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: [workflow_dispatch] jobs: release_version: runs-on: macOS-latest steps: - uses: actions/checkout@v2 - name: Publish release id: publish_release uses: release-drafter/release-drafter@v5 with: publish: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Update podspec run: fastlane bump_version next_version:${{ steps.publish_release.outputs.tag_name }} - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v4 with: branch: 'main' commit_message: 'Bump version ${{ steps.publish_release.outputs.tag_name }}' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Deploy to Cocoapods env: COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} run: | set -eo pipefail pod lib lint --allow-warnings pod trunk push --allow-warnings - name: Tweet the release uses: ethomson/send-tweet-action@v1 with: consumer-key: ${{ secrets.TWITTER_CONSUMER_API_KEY }} consumer-secret: ${{ secrets.TWITTER_CONSUMER_API_SECRET }} access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }} access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} status: | 🎉 New release ${{ steps.publish_release.outputs.tag_name }} is out 🚀 Check out all the changes here: ${{ steps.publish_release.outputs.html_url }} ================================================ FILE: .github/workflows/release_notes.yml ================================================ name: Release Notes on: push: branches: - main jobs: update_release_notes: runs-on: ubuntu-latest steps: - uses: release-drafter/release-drafter@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/stale.yml ================================================ name: 'Close stale issues and PRs' on: schedule: - cron: '30 5 * * *' jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v4 with: close-issue-message: 'Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.' stale-issue-message: '🤖 This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions 🙂' days-before-stale: 5 days-before-close: 3 enable-statistics: true operations-per-run: 60 only-labels: 'awaiting user input' ================================================ FILE: .github/workflows/validations.yml ================================================ name: Validations on: pull_request_target: branches: [main] types: [opened, reoneped, edited, synchronized] # workflow_dispatch: # inputs: # commit hash: # description: "Commit hash" # required: true # default: "" jobs: lint: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Run SwiftLint run: swiftlint lint --reporter github-actions-logging danger: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Danger uses: docker://frmeloni/danger-swift-with-swiftlint:1.3.1 with: args: --failOnErrors --verbose env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint .DS_Store ## Obj-C/Swift specific *.hmap *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins .build/ # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output # JetBrains .idea ================================================ FILE: .swift-version ================================================ 5.0 ================================================ FILE: .swiftlint.yml ================================================ included: - SkeletonViewCore/Sources disabled_rules: - trailing_whitespace - line_length - type_body_length - identifier_name - multiple_closures_with_trailing_closure - class_delegate_protocol - force_unwrapping - force_try - force_cast - function_parameter_count - discouraged_optional_collection - shorthand_operator - reduce_boolean - weak_delegate - nesting - closure_end_indentation - function_default_parameter_at_end - unowned_variable_capture - legacy_constructor - redundant_type_annotation - vertical_whitespace_opening_braces opt_in_rules: - multiline_arguments - multiline_parameters - closure_spacing - closure_body_length - collection_alignment - contains_over_filter_is_empty - contains_over_filter_count - contains_over_first_not_nil - contains_over_range_nil_comparison - convenience_type - discouraged_object_literal - discouraged_optional_boolean - empty_count - empty_string - fallthrough - file_name_no_space - first_where - flatmap_over_map_reduce - implicitly_unwrapped_optional - joined_default_parameter - last_where - literal_expression_end_indentation - multiline_function_chains - operator_usage_whitespace - private_action - private_outlet - redundant_optional_initialization - redundant_set_access_control - sorted_first_last - switch_case_on_newline - unneeded_parentheses_in_closure_argument - unused_declaration - unused_import - discouraged_optional_collection - enum_case_associated_values_count - legacy_multiple - legacy_random indentation: 2 type_name: min_length: 2 max_length: warning: 50 error: 60 file_length: - 2500 - 3000 large_tuple: - 5 - 6 ================================================ FILE: .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file ### Next version #### 🙌 New #### 🔬 Improvements * [**369**](https://github.com/Juanpe/SkeletonView/pull/369): remove useless corner radius constraint - [@Juanpe](https://github.com/Juanpe) * [**357**](https://github.com/Juanpe/SkeletonView/pull/357): Removed duplicate code in SkeletonCollectionDelegate. - [@keshavamurthy1](https://github.com/keshavamurthy1) #### 🩹 Bug fixes * [**359**](https://github.com/Juanpe/SkeletonView/pull/359): SkeletonView respecting Font's height, rather than the `UIView` actual height. - [@keshavamurthy1](https://github.com/keshavamurthy1) ## 📦 [1.11.0](https://github.com/Juanpe/SkeletonView/releases/tag/1.11.0) #### 🙌 New * [**339**](https://github.com/Juanpe/SkeletonView/pull/339): Add `hiddenWhenSkeletonIsActive` property - [@mohn93](https://github.com/mohn93) * [**341**](https://github.com/Juanpe/SkeletonView/pull/341): Support autoreverses in gradient animations - [@Juanpe](https://github.com/Juanpe) #### 🔬Improvements * [**344**](https://github.com/Juanpe/SkeletonView/pull/344): Resize labels based on number of lines - [@Juanpe](https://github.com/Juanpe) #### 🩹 Bug fixes * [**340**](https://github.com/Juanpe/SkeletonView/pull/340): Fixed incorrect padding, and incorrect multiline layer frame calculation - [@yzhao198](https://github.com/yzhao198) ## 📦 [1.10.0](https://github.com/Juanpe/SkeletonView/releases/tag/1.10.0) #### 🙌 New * [**327**](https://github.com/Juanpe/SkeletonView/pull/327): Add SwiftLint - [@Juanpe](https://github.com/Juanpe) * [**329**](https://github.com/Juanpe/SkeletonView/pull/329): Spanish README 🇪🇸 - [@Juanpe](https://github.com/Juanpe) #### 🩹 Bug fixes * [**336**](https://github.com/Juanpe/SkeletonView/pull/336): Not replace text when the skeleton disappears. Solved issues: [#296](https://github.com/Juanpe/SkeletonView/issues/296), [#330](https://github.com/Juanpe/SkeletonView/issues/330) - [@Juanpe](https://github.com/Juanpe) * [**337**](https://github.com/Juanpe/SkeletonView/pull/337): RTL support. Solved issues: [#143](https://github.com/Juanpe/SkeletonView/issues/143) - [@Juanpe](https://github.com/Juanpe) ## 📦 [1.9](https://github.com/Juanpe/SkeletonView/releases/tag/1.9) #### 🩹 Bug fixes * [**319**](https://github.com/Juanpe/SkeletonView/pull/319): Fix to consider the top and bottom edge insets when updating the skeleton layer height - [@xpereta](https://github.com/xpereta) * [**320**](https://github.com/Juanpe/SkeletonView/pull/320): Fix Single line customisation - [@Juanpe](https://github.com/juanpe) * [**323**](https://github.com/Juanpe/SkeletonView/pull/323): Save and restore view state for UIButton - [@Juanpe](https://github.com/juanpe) ## 📦 [1.8.8](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.8) #### 🙌 New * [**304**](https://github.com/Juanpe/SkeletonView/pull/304): French README 🇫🇷 - [@OmarJalil](https://github.com/OmarJalil) #### 🔬Improvements * [**311**](https://github.com/Juanpe/SkeletonView/pull/311): Bump json from 2.2.0 to 2.3.1 - [@dependabot](https://github.com/dependabot) #### 🩹 Bug fixes * [**286**](https://github.com/Juanpe/SkeletonView/pull/286): Fix issue when WKWebView calls skeletonLayoutSubviews not on the main thread - [@paulanatoleclaudot-betclic](https://github.com/paulanatoleclaudot-betclic) * [**292**](https://github.com/Juanpe/SkeletonView/pull/292): Fix IBInspectable support when using Carthage - [@marisalaneous](https://github.com/marisalaneous) * [**308**](https://github.com/Juanpe/SkeletonView/pull/308): Fix example backgroundColor in DarkMode - [@toshi0383](https://github.com/toshi0383) * [**307**](https://github.com/Juanpe/SkeletonView/pull/307): Prevent incorrect skeletonLayer to be added when updating skeleton - [@wsalim1610](https://github.com/wsalim1610) ## 📦 [1.8.7](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.7) #### 🔬Improvements * [**271**](https://github.com/Juanpe/SkeletonView/pull/271): Add corner radius for skeletonView as IBInspectable (CGFloat) default is 0.0 - [@paulanatoleclaudot-betclic](https://github.com/paulanatoleclaudot-betclic) #### 🩹 Bug fixes * [**259**](https://github.com/Juanpe/SkeletonView/issues/259): Prevent isSkeletonActive to be called when isSkeletonable is false - [@wsalim1610](https://github.com/wsalim1610) * [**274**](https://github.com/Juanpe/SkeletonView/pull/274): Fix: hiding skeleton when header and footer views of section would not hide it - [@darkside999](https://github.com/darkside999) * [**273**](https://github.com/Juanpe/SkeletonView/pull/273): Fix: in vertical stack view with center alignment show incorrect position - [@koooootake](https://github.com/koooootake) ## 📦 [1.8.6](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.6) #### 🔬Improvements * [**242**](https://github.com/Juanpe/SkeletonView/pull/242): Offscreen table view layout issue fixed - [@Cacodemon](https://github.com/Cacodemon) * [**261**](https://github.com/Juanpe/SkeletonView/pull/261): Fixes removing skeleton layers from table header footer sections - [@darkside999](https://github.com/darkside999) * [**263**](https://github.com/Juanpe/SkeletonView/pull/263): Feature/set cross dissolve transitions as default - [@Juanpe](https://github.com/Juanpe) * [**264**](https://github.com/Juanpe/SkeletonView/pull/264): not replace original datasource is running XCTests - [@Juanpe](https://github.com/Juanpe) * [**265**](https://github.com/Juanpe/SkeletonView/pull/265): call original traitCollectionDidChange method - [@Juanpe](https://github.com/Juanpe) #### 🩹 Bug fixes * [**260**](https://github.com/Juanpe/SkeletonView/issues/260): Don't hide skeleton layers on TableViewHeaderFooterView * [**257**](https://github.com/Juanpe/SkeletonView/issues/257): Unit test problem when using SkeletonView ## 📦 [1.8.3](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.3) - Support for iOS 13 dark mode. (thanks @Wilsonator5000) ## 📦 [1.8.2](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.2) #### 🙌 New - Add ability to customize line spacing per label. (thanks @gshahbazian) ## 📦 [LayoutSkeleton (1.8.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.1) #### 🔬Improvements - Fix completion call in .none transition style while hide skeletons. (thanks @aadudyrev) #### 🙌 New - Swizzle `layoutSubviews` method. #### 🔬Improvements - Fix completion call in .none transition style while hiding skeletons. (thanks @aadudyrev) - Swift format. #### 🩹 Bug fixes - Update layout subviews when the original method is called. - Issues: [#88, #149] ## 📦 [Transitions (1.8)](https://github.com/Juanpe/SkeletonView/releases/tag/1.8) #### 🙌 New - 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) #### 🙌 New - Allow updating skeleton layout to recalculate skeleton bounds: `layoutSkeletonIfNeeded`. See the examples to know how to use it. (thanks @eduardbosch) #### 🔬Improvements - Allow updating skeleton layers without recreating them: `updateSkeleton`, `updateGradientSkeleton`, `updateAnimatedSkeleton`, `updateAnimatedGradientSkeleton`. (thanks @eduardbosch) ## 📦 [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 - Default values customizables. Now you can set the default values of Skeleton appearance.(thanks @reececomo) - issues: [[#50](https://github.com/Juanpe/SkeletonView/issues/50), [#83](https://github.com/Juanpe/SkeletonView/issues/83)] #### 🩹 Bug fixes - Solved issue [#41](https://github.com/Juanpe/SkeletonView/issues/41). Now, Skeleton works if UICollectionView cell's Nib is registered in code. (thanks @kjoneandrei) ## 📦 [Typo (1.2.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2.3) #### Fixes - Fix typo in `SkeletonTableViewDataSource` protocol #### 🔬Improvements - Now it takes in account the `UIStackView` to calculate the `SkeletonLayer` bounds (thanks @giantramen) ## 📦 [New face (1.2.2)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2.2) #### 🙌 New - Rebranding #### 🩹 Bug fixes - Solved issue [#23](https://github.com/Juanpe/SkeletonView/issues/23). Problem with UIStackView. (thanks @giantramen) ## 📦 [State (1.2.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2.1) #### 🙌 New - You can set the corner radius multiline elements (thanks @B4V4-G) - Save view state when skeleton appears and recovery when it is hidden (@juanpe) #### 🩹 Bug fixes - Solved issue [#51](https://github.com/Juanpe/SkeletonView/issues/51). Support inspectable properties when using Carthage. (thanks @eduardbosch) ## 📦 [On TV (1.2)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2) #### 🙌 New - Now ```SkeletonView``` is **tvOS** compatible! 🎉. (thanks @mihai8804858) #### 🩹 Bug fixes - Solved issue [#46](https://github.com/Juanpe/SkeletonView/issues/46). It crashes the application when tap on it, didSelect called and crash. ## 📦 [Hotfix (1.1.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.1.1) #### 🩹 Bug fixes - Now yes, solved issue [#39](https://github.com/Juanpe/SkeletonView/issues/39) ## 📦 [Needed (1.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.1) #### 🙌 New - Now ```SkeletonView```supports **UICollectionViews**! 🎉. (thanks @Renatdz) #### 🩹 Bug fixes - Solved issue [#39](https://github.com/Juanpe/SkeletonView/issues/39). Gradient animation did not work when app becomes active. ## 📦 [Resizable (1.0.5)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.5) #### 🙌 New - Now you can use table views with resizable cells. #### 🩹 Bug fixes - Solved issues. [#17](https://github.com/Juanpe/SkeletonView/issues/17), [#30](https://github.com/Juanpe/SkeletonView/issues/30), [#34](https://github.com/Juanpe/SkeletonView/issues/34). ## 📦 [Filled or not (1.0.4)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.4) #### 🙌 New - You can set the filling percent of the last line in multiline elements (thanks @jontelang!) #### 🩹 Bug fixes - Solved issue [#14](https://github.com/Juanpe/SkeletonView/issues/14). You could edit text views with skeleton active. ## 📦 [In all directions (1.0.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.3) #### 🙌 New - Create ```SkeletonAnimationBuilder```, to facilitate the creation of layer animations. ```GradientDirection``` enum. ## 📦 [Retro (1.0.2)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.2) #### 🙌 New - Change some private keywords, to be Swift 3 compatible ## 📦 [Early bird bug (1.0.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.2) #### 🩹 Bug fixes - It was not removing the skeleton layer ## 📦 [Starter (1.0)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0) - First release ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct The Code of Conduct governs how we behave in public or in private whenever the project will be judged by our actions. We expect it to be honored by everyone who represents the project officially or informally, claims affiliation with the project, or participates directly. We strive to: * **Be open**: We invite anybody to participate in any aspect of our projects. Our community is open, and any responsibility can be carried by any contributor who demonstrates the required capacity and competence. * **Be empathetic**: We work together to resolve conflict, assume good intentions, and do our best to act in an empathic fashion. By understanding that humanity drops a few packets in online interactions, and adjusting accordingly, we can create a comfortable environment for everyone to share their ideas. * **Be collaborative**: We prefer to work transparently and to involve interested parties early on in the process. Wherever possible, we work closely with others in the open source community to coordinate our efforts. * **Be decisive**: We expect participants in the project to resolve disagreements constructively. When they cannot, we escalate the matter to structures with designated leaders to arbitrate and provide clarity and direction. * **Be responsible**: We hold ourselves accountable for our actions. When we make mistakes, we take responsibility for them. When we need help, we reach out to others. When it comes time to move on from a project, we take the proper steps to ensure that others can pick up where we left off. This code is not exhaustive or complete. It serves to distill our common understanding of a collaborative, shared environment and goals. We expect it to be followed in spirit as much as in the letter. --- The **SkeletonView** Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributors Guide Interested in contributing? Awesome! Before you do though, please read our [Code of Conduct](https://github.com/Juanpe/SkeletonView/blob/main/CODE_OF_CONDUCT.md). We take it very seriously, and expect that you will as well. There are many ways you can contribute! :heart: ### Bug Reports and Fixes :bug: - If you find a bug, please search for it in the [Issues](https://github.com/Juanpe/SkeletonView/issues), and if it isn't already tracked, [create a new issue](https://github.com/slackhq/PanModal/issues/new). Fill out the "Bug Report" section of the issue template. Even if an Issue is closed, feel free to comment and add details, it will still be reviewed. - Issues that have already been identified as a bug (note: able to reproduce) will be labelled `🐞 Bug`. - If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number. ### New Features :bulb: - If you'd like to add new functionality to this project, describe the problem you want to solve in a [new Issue](https://github.com/Juanpe/SkeletonView/issues/new). - Issues that have been identified as a feature request will be labelled `💡 Enhancement`. - If you'd like to implement the new feature, please wait for feedback from the project maintainers before spending too much time writing the code. In some cases, `💡 Enhancement`s may not align well with the project objectives at the time. ### Miscellaneous :sparkles: - If you have an alternative implementation of something that may have advantages over the way its currently done, or you have any other change, we would be happy to hear about it! - If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind. - If not, [open an Issue](https://github.com/Juanpe/SkeletonView/issues/new) to discuss the idea first. If you're new to our project and looking for some way to make your first contribution, look for Issues labelled `good first issue`. ## Requirements For your contribution to be accepted: - [x] The changes must be approved by code review. - [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number. If the contribution doesn't meet the above criteria, you may fail our automated checks or a maintainer will discuss it with you. You can continue to improve a Pull Request by adding commits to the branch from which the PR was created. ## Creating a Pull Request 1. :fork_and_knife: Fork the repository on GitHub. 2. :runner: Clone/fetch your fork to your local development machine. 3. :herb: Create a new branch and check it out. 4. :crystal_ball: Make your changes and commit them locally. 5. :arrow_heading_up: Push your new branch to your fork. (e.g. `git push username fix-issue-300`). 6. :inbox_tray: Open a Pull Request on github.com from your new branch on your fork to `main` in this repository. ## Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: - (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or - (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or - (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. - (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. *Wording of statement copied from [elinux.org](http://elinux.org/Developer_Certificate_Of_Origin)* ================================================ FILE: Dangerfile.swift ================================================ import Danger let danger = Danger() let github = danger.github // Make it more obvious that a PR is a work in progress and shouldn't be merged yet if danger.github.pullRequest.title.contains("WIP") { warn("PR is classed as Work in Progress") } // Warn, asking to update all README files if only English README are updated let enReameModified = danger.git.modifiedFiles.contains { $0.contains("README.md") } let zhReameModified = danger.git.modifiedFiles.contains { $0.contains("README_zh.md") } let koReameModified = danger.git.modifiedFiles.contains { $0.contains("README_ko.md") } let ptBrReameModified = danger.git.modifiedFiles.contains { $0.contains("README_pt-br.md") } let otherLanguagesReadmeHaveBeenModified = zhReameModified && koReameModified && ptBrReameModified if (enReameModified && !otherLanguagesReadmeHaveBeenModified) { warn("Consider **also** updating the README for other languages.") } // Warn when there is a big PR if (danger.github.pullRequest.additions ?? 0) > 500 { warn("Big PR, try to keep changes smaller if you can") } // Added (or removed) library files need to be added (or removed) from the // Xcode project to avoid breaking things. let addedSwiftLibraryFiles = danger.git.createdFiles.contains { $0.fileType == .swift && $0.hasPrefix("Sources") } let deletedSwiftLibraryFiles = danger.git.deletedFiles.contains { $0.fileType == .swift && $0.hasPrefix("Sources") } let modifiedXcodeProject = danger.git.modifiedFiles.contains { $0.contains(".xcodeproj") } if (addedSwiftLibraryFiles || deletedSwiftLibraryFiles) && !modifiedXcodeProject { fail("Added or removed files require the Xcode project to be updated.") } ================================================ FILE: Examples/CollectionView/AppDelegate.swift ================================================ // Copyright © 2018 SkeletonView. All rights reserved. import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: Examples/CollectionView/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Examples/CollectionView/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Examples/CollectionView/Assets.xcassets/avatar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "avatar.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Examples/CollectionView/Assets.xcassets/picture.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "picture.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Examples/CollectionView/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Examples/CollectionView/CollectionViewCell.swift ================================================ // Copyright © 2018 SkeletonView. All rights reserved. import UIKit import SkeletonView class CollectionViewCell: UICollectionViewCell { var label: UILabel! var imageView: UIImageView! override init(frame: CGRect) { super.init(frame: frame) isSkeletonable = true createLabel() createImageView() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func createImageView() { imageView = UIImageView(image: UIImage(named: "picture")) imageView.isSkeletonable = true imageView.translatesAutoresizingMaskIntoConstraints = false imageView.contentMode = .scaleAspectFit addSubview(imageView) NSLayoutConstraint.activate([ imageView.centerXAnchor.constraint(equalTo: centerXAnchor), imageView.topAnchor.constraint(equalTo: topAnchor), imageView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75), imageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.75) ]) } private func createLabel() { label = UILabel() label.isSkeletonable = true label.text = "Lorem ipsum" label.textAlignment = .center label.translatesAutoresizingMaskIntoConstraints = false addSubview(label) NSLayoutConstraint.activate([ label.centerXAnchor.constraint(equalTo: centerXAnchor), label.bottomAnchor.constraint(equalTo: bottomAnchor), label.heightAnchor.constraint(equalToConstant: 40), label.widthAnchor.constraint(equalToConstant: frame.width) ]) } } ================================================ FILE: Examples/CollectionView/Main.storyboard ================================================ Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ================================================ FILE: Examples/CollectionView/SkeletonViewExampleCollectionview-Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Examples/CollectionView/ViewController.swift ================================================ // Copyright © 2018 SkeletonView. All rights reserved. import UIKit import SkeletonView class ViewController: UIViewController { @IBOutlet weak var collectionView: UICollectionView! { didSet { collectionView.isSkeletonable = true collectionView.backgroundColor = .clear collectionView.showsHorizontalScrollIndicator = false collectionView.showsVerticalScrollIndicator = false collectionView.dataSource = self collectionView.delegate = self collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell") } } @IBOutlet weak var avatarImage: UIImageView! { didSet { avatarImage.layer.cornerRadius = avatarImage.frame.width/2 avatarImage.layer.masksToBounds = true } } @IBOutlet weak var colorSelectedView: UIView! { didSet { colorSelectedView.layer.cornerRadius = 5 colorSelectedView.layer.masksToBounds = true colorSelectedView.backgroundColor = SkeletonAppearance.default.tintColor } } @IBOutlet weak var switchAnimated: UISwitch! @IBOutlet weak var skeletonTypeSelector: UISegmentedControl! @IBOutlet weak var showOrHideSkeletonButton: UIButton! @IBOutlet weak var transitionDurationLabel: UILabel! @IBOutlet weak var transitionDurationStepper: UIStepper! var type: SkeletonType { return skeletonTypeSelector.selectedSegmentIndex == 0 ? .solid : .gradient } override func viewDidLoad() { super.viewDidLoad() transitionDurationStepper.value = 0.25 collectionView.prepareSkeleton(completion: { done in self.view.showAnimatedSkeleton() }) } @IBAction func changeAnimated(_ sender: Any) { if switchAnimated.isOn { view.startSkeletonAnimation() } else { view.stopSkeletonAnimation() } } @IBAction func changeSkeletonType(_ sender: Any) { refreshSkeleton() } @IBAction func btnChangeColorTouchUpInside(_ sender: Any) { showAlertPicker() } @IBAction func showOrHideSkeleton(_ sender: Any) { showOrHideSkeletonButton.setTitle((view.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal) view.isSkeletonActive ? hideSkeleton() : showSkeleton() } @IBAction func transitionDurationStepperAction(_ sender: Any) { transitionDurationLabel.text = "transition duration: \(transitionDurationStepper.value) sec" } func showSkeleton() { refreshSkeleton() } func hideSkeleton() { view.hideSkeleton(transition: .crossDissolve(transitionDurationStepper.value)) } func refreshSkeleton() { self.view.hideSkeleton() if type == .gradient { showGradientSkeleton() } else { showSolidSkeleton() } } func showSolidSkeleton() { if switchAnimated.isOn { view.showAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!, transition: .crossDissolve(transitionDurationStepper.value)) } else { view.showSkeleton(usingColor: colorSelectedView.backgroundColor!, transition: .crossDissolve(transitionDurationStepper.value)) } } func showGradientSkeleton() { let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!) if switchAnimated.isOn { view.showAnimatedGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value)) } else { view.showGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value)) } } func showAlertPicker() { let alertView = UIAlertController(title: "Select color", message: "\n\n\n\n\n\n", preferredStyle: .alert) let pickerView = UIPickerView(frame: CGRect(x: 0, y: 50, width: 260, height: 115)) pickerView.dataSource = self pickerView.delegate = self alertView.view.addSubview(pickerView) let action = UIAlertAction(title: "OK", style: .default) { [unowned pickerView, unowned self] _ in let row = pickerView.selectedRow(inComponent: 0) self.colorSelectedView.backgroundColor = colors[row].0 self.refreshSkeleton() } alertView.addAction(action) let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) alertView.addAction(cancelAction) present(alertView, animated: false, completion: { pickerView.frame.size.width = alertView.view.frame.size.width }) } } // MARK: - UIPickerViewDelegate, UIPickerViewDataSource extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource { func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return colors.count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return colors[row].1 } } // MARK: - UICollectionViewDelegateFlowLayout extension ViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: view.frame.width/3 - 10, height: view.frame.width/3 - 10) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return 5 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 5 } } // MARK: - SkeletonCollectionViewDataSource extension ViewController: SkeletonCollectionViewDataSource { func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier { return "CollectionViewCell" } func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 10 } func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as? CollectionViewCell cell?.isSkeletonable = indexPath.row != 0 return cell } // MARK: - UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 10 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell return cell } func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) { let cell = cell as? CollectionViewCell cell?.isSkeletonable = indexPath.row != 0 } } ================================================ FILE: Examples/iOS Example/Sources/AppDelegate.swift ================================================ // // AppDelegate.swift // SkeletonViewExample // // Created by Juanpe Catalán on 02/11/2017. // Copyright © 2017 SkeletonView. All rights reserved. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: Examples/iOS Example/Sources/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "color" : { "platform" : "universal", "reference" : "systemBlueColor" }, "idiom" : "universal" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "color" : { "color-space" : "srgb", "components" : { "alpha" : "1.000", "blue" : "1.000", "green" : "1.000", "red" : "1.000" } }, "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/iOS Example/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Examples/iOS Example/Sources/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/iOS Example/Sources/Assets.xcassets/avatar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "avatar.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Examples/iOS Example/Sources/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Examples/iOS Example/Sources/Base.lproj/Main.storyboard ================================================ Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ================================================ FILE: Examples/iOS Example/Sources/Cell.swift ================================================ // // Cell.swift // SkeletonViewExample // // Created by Juanpe Catalán on 03/11/2017. // Copyright © 2017 SkeletonView. All rights reserved. // 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() { } } ================================================ FILE: Examples/iOS Example/Sources/Constants.swift ================================================ // Copyright © 2018 SkeletonView. All rights reserved. import UIKit let colors = [(UIColor.skeletonDefault,"skeletonDefault"),(UIColor.turquoise,"turquoise"), (UIColor.emerald,"emerald"), (UIColor.peterRiver,"peterRiver"), (UIColor.amethyst,"amethyst"),(UIColor.wetAsphalt,"wetAsphalt"), (UIColor.nephritis,"nephritis"), (UIColor.belizeHole,"belizeHole"), (UIColor.wisteria,"wisteria"), (UIColor.midnightBlue,"midnightBlue"), (UIColor.sunFlower,"sunFlower"), (UIColor.carrot,"carrot"), (UIColor.alizarin,"alizarin"),(UIColor.clouds,"clouds"), (UIColor.concrete,"concrete"), (UIColor.flatOrange,"flatOrange"), (UIColor.pumpkin,"pumpkin"), (UIColor.pomegranate,"pomegranate"), (UIColor.silver,"silver"), (UIColor.asbestos,"asbestos")] ================================================ FILE: Examples/iOS Example/Sources/HeaderFooterSection.swift ================================================ // Copyright © 2020 SkeletonView. All rights reserved. import UIKit class HeaderFooterSection: UITableViewHeaderFooterView { lazy var titleLabel: UILabel = { let label = UILabel() label.text = " " label.isSkeletonable = true label.linesCornerRadius = 10 return label }() override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) isSkeletonable = true contentView.addSubview(titleLabel) titleLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10), titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10), titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10) ]) backgroundView = UIView() if #available(iOS 13.0, *) { backgroundView?.backgroundColor = .systemBackground } else { backgroundView?.backgroundColor = .white } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: Examples/iOS Example/Sources/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UIApplicationSupportsIndirectInputEvents UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Examples/iOS Example/Sources/UITextViewByCodeViewController.swift ================================================ // Copyright © 2022 SkeletonView. All rights reserved. import UIKit import SkeletonView class UITextViewByCodeViewController: UIViewController { lazy var textView: UITextView = { let tv = UITextView() tv.text = " " tv.linesCornerRadius = 10 tv.isSkeletonable = true tv.translatesAutoresizingMaskIntoConstraints = false return tv }() override func viewDidLoad() { super.viewDidLoad() setupUI() setupElementsConstraints() showSkeletonForElements() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } func setupUI() { view.addSubview(textView) } func setupElementsConstraints() { textView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true textView.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor, constant: 10).isActive = true textView.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true textView.heightAnchor.constraint(equalToConstant: 100).isActive = true } func showSkeletonForElements() { textView.showSkeleton() } } ================================================ FILE: Examples/iOS Example/Sources/ViewController.swift ================================================ // // ViewController.swift // SkeletonViewExample // // Created by Juanpe Catalán on 02/11/2017. // Copyright © 2017 SkeletonView. All rights reserved. // import UIKit import SkeletonView class ViewController: UIViewController { @IBOutlet weak var tableview: UITableView! { didSet { tableview.rowHeight = UITableView.automaticDimension tableview.sectionHeaderHeight = UITableView.automaticDimension tableview.sectionFooterHeight = UITableView.automaticDimension tableview.estimatedRowHeight = 120.0 tableview.estimatedSectionFooterHeight = 20.0 tableview.estimatedSectionHeaderHeight = 20.0 tableview.register(HeaderFooterSection.self, forHeaderFooterViewReuseIdentifier: "HeaderIdentifier") tableview.register(HeaderFooterSection.self, forHeaderFooterViewReuseIdentifier: "FooterIdentifier") } } @IBOutlet weak var avatarImage: UIImageView! { didSet { avatarImage.layer.cornerRadius = avatarImage.frame.width/2 avatarImage.layer.masksToBounds = true } } @IBOutlet weak var colorSelectedView: UIView! { didSet { colorSelectedView.layer.cornerRadius = 5 colorSelectedView.layer.masksToBounds = true colorSelectedView.backgroundColor = SkeletonAppearance.default.tintColor } } @IBOutlet weak var switchAnimated: UISwitch! @IBOutlet weak var skeletonTypeSelector: UISegmentedControl! @IBOutlet weak var showOrHideSkeletonButton: UIButton! @IBOutlet weak var transitionDurationLabel: UILabel! @IBOutlet weak var transitionDurationStepper: UIStepper! var type: SkeletonType { return skeletonTypeSelector.selectedSegmentIndex == 0 ? .solid : .gradient } override func viewDidLoad() { super.viewDidLoad() tableview.isSkeletonable = true transitionDurationStepper.value = 0.25 } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) view.showAnimatedSkeleton() } @IBAction func changeAnimated(_ sender: Any) { if switchAnimated.isOn { view.startSkeletonAnimation() } else { view.stopSkeletonAnimation() } } @IBAction func changeSkeletonType(_ sender: Any) { refreshSkeleton() } @IBAction func btnChangeColorTouchUpInside(_ sender: Any) { showAlertPicker() } @IBAction func showOrHideSkeleton(_ sender: Any) { showOrHideSkeletonButton.setTitle((view.sk.isSkeletonActive ? "Show skeleton" : "Hide skeleton"), for: .normal) view.sk.isSkeletonActive ? hideSkeleton() : showSkeleton() } @IBAction func transitionDurationStepperAction(_ sender: Any) { transitionDurationLabel.text = "Transition duration: \(transitionDurationStepper.value) sec" } func showSkeleton() { if type == .gradient { let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!) if switchAnimated.isOn { view.showAnimatedGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value)) } else { view.showGradientSkeleton(usingGradient: gradient, transition: .crossDissolve(transitionDurationStepper.value)) } } else { if switchAnimated.isOn { view.showAnimatedSkeleton(transition: .crossDissolve(transitionDurationStepper.value)) } else { view.showSkeleton(transition: .crossDissolve(transitionDurationStepper.value)) } } } func hideSkeleton() { view.hideSkeleton(transition: .crossDissolve(transitionDurationStepper.value)) } func refreshSkeleton() { if type == .gradient { showOrUpdateGradientSkeleton() } else { showOrUpdatepdateSolidSkeleton() } } func showOrUpdatepdateSolidSkeleton() { if switchAnimated.isOn { view.updateAnimatedSkeleton(usingColor: colorSelectedView.backgroundColor!) } else { view.updateSkeleton(usingColor: colorSelectedView.backgroundColor!) } } func showOrUpdateGradientSkeleton() { let gradient = SkeletonGradient(baseColor: colorSelectedView.backgroundColor!) if switchAnimated.isOn { view.updateAnimatedGradientSkeleton(usingGradient: gradient) } else { view.updateGradientSkeleton(usingGradient: gradient) } } func showAlertPicker() { let alertView = UIAlertController(title: "Select color", message: "\n\n\n\n\n\n", preferredStyle: .alert) let pickerView = UIPickerView(frame: CGRect(x: 0, y: 50, width: 260, height: 115)) pickerView.dataSource = self pickerView.delegate = self alertView.view.addSubview(pickerView) let action = UIAlertAction(title: "OK", style: .default) { [unowned pickerView, unowned self] _ in let row = pickerView.selectedRow(inComponent: 0) self.colorSelectedView.backgroundColor = colors[row].0 self.refreshSkeleton() } alertView.addAction(action) let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) alertView.addAction(cancelAction) present(alertView, animated: false, completion: { pickerView.frame.size.width = alertView.view.frame.size.width }) } } extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource { func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return colors.count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return colors[row].1 } } extension ViewController: SkeletonTableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 10 } func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier { return "CellIdentifier" } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as! Cell cell.label1.text = "cell -> \(indexPath.row)" return cell } func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? { let cell = skeletonView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as? Cell cell?.textField.isHidden = indexPath.row == 0 return cell } func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) { let cell = cell as? Cell cell?.textField.isHidden = indexPath.row == 0 } } extension ViewController: SkeletonTableViewDelegate { func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? { return "HeaderIdentifier" } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let header = tableView .dequeueReusableHeaderFooterView(withIdentifier: "HeaderIdentifier") as! HeaderFooterSection header.titleLabel.text = "header -> \(section)" return header } func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? { return "FooterIdentifier" } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { let footer = tableView .dequeueReusableHeaderFooterView(withIdentifier: "FooterIdentifier") as! HeaderFooterSection footer.titleLabel.text = "footer -> \(section)" return footer } } ================================================ FILE: Examples/iOS Example/iOS Example.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ E21D8BB727888D050041DBCE /* UITextViewByCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21D8BB627888D050041DBCE /* UITextViewByCodeViewController.swift */; }; F556F5C026CD20A300A80B83 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F5B726CD20A300A80B83 /* ViewController.swift */; }; F556F5C126CD20A300A80B83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F556F5B826CD20A300A80B83 /* Assets.xcassets */; }; F556F5C226CD20A300A80B83 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F556F5B926CD20A300A80B83 /* LaunchScreen.storyboard */; }; F556F5C326CD20A300A80B83 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F556F5BB26CD20A300A80B83 /* Main.storyboard */; }; F556F5C426CD20A300A80B83 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F5BD26CD20A300A80B83 /* AppDelegate.swift */; }; F556F5E626CD21D300A80B83 /* SkeletonView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F556F5E026CD21CB00A80B83 /* SkeletonView.framework */; }; F556F6EE26CE813F00A80B83 /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6E626CE813F00A80B83 /* Cell.swift */; }; F556F6EF26CE813F00A80B83 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6EC26CE813F00A80B83 /* Constants.swift */; }; F556F6F026CE813F00A80B83 /* HeaderFooterSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6ED26CE813F00A80B83 /* HeaderFooterSection.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F556F5DF26CD21CB00A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F556F5D926CD21CB00A80B83 /* SkeletonView.xcodeproj */; proxyType = 2; remoteGlobalIDString = "SkeletonView::SkeletonView::Product"; remoteInfo = "SkeletonView iOS"; }; F556F5E126CD21CB00A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F556F5D926CD21CB00A80B83 /* SkeletonView.xcodeproj */; proxyType = 2; remoteGlobalIDString = "SkeletonView::SkeletonViewTests::Product"; remoteInfo = SkeletonViewTests; }; F556F5E326CD21CB00A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F556F5D926CD21CB00A80B83 /* SkeletonView.xcodeproj */; proxyType = 2; remoteGlobalIDString = F556F59426CD1F3900A80B83; remoteInfo = "SkeletonView tvOS"; }; F556F5E926CD21DA00A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F556F5D926CD21CB00A80B83 /* SkeletonView.xcodeproj */; proxyType = 1; remoteGlobalIDString = "SkeletonView::SkeletonView"; remoteInfo = "SkeletonView iOS"; }; F556F6EA26CE813F00A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F556F5D926CD21CB00A80B83 /* SkeletonView.xcodeproj */; proxyType = 2; remoteGlobalIDString = F556F67126CD458500A80B83; remoteInfo = "SkeletonView tvOS Tests"; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ E21D8BB627888D050041DBCE /* UITextViewByCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextViewByCodeViewController.swift; sourceTree = ""; }; F556F59F26CD201B00A80B83 /* iOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; F556F5B726CD20A300A80B83 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; F556F5B826CD20A300A80B83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; F556F5BA26CD20A300A80B83 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; F556F5BC26CD20A300A80B83 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; F556F5BD26CD20A300A80B83 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; F556F5BE26CD20A300A80B83 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F556F5D926CD21CB00A80B83 /* SkeletonView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SkeletonView.xcodeproj; path = ../../SkeletonView.xcodeproj; sourceTree = ""; }; F556F6E626CE813F00A80B83 /* Cell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cell.swift; sourceTree = ""; }; F556F6EC26CE813F00A80B83 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; F556F6ED26CE813F00A80B83 /* HeaderFooterSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderFooterSection.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F556F59C26CD201B00A80B83 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F556F5E626CD21D300A80B83 /* SkeletonView.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F556F59626CD201B00A80B83 = { isa = PBXGroup; children = ( F556F5B626CD20A300A80B83 /* Sources */, F556F5A026CD201B00A80B83 /* Products */, F556F5D926CD21CB00A80B83 /* SkeletonView.xcodeproj */, ); sourceTree = ""; }; F556F5A026CD201B00A80B83 /* Products */ = { isa = PBXGroup; children = ( F556F59F26CD201B00A80B83 /* iOS Example.app */, ); name = Products; sourceTree = ""; }; F556F5B626CD20A300A80B83 /* Sources */ = { isa = PBXGroup; children = ( F556F6E626CE813F00A80B83 /* Cell.swift */, F556F6EC26CE813F00A80B83 /* Constants.swift */, F556F6ED26CE813F00A80B83 /* HeaderFooterSection.swift */, F556F5B726CD20A300A80B83 /* ViewController.swift */, E21D8BB627888D050041DBCE /* UITextViewByCodeViewController.swift */, F556F5B826CD20A300A80B83 /* Assets.xcassets */, F556F5B926CD20A300A80B83 /* LaunchScreen.storyboard */, F556F5BB26CD20A300A80B83 /* Main.storyboard */, F556F5BD26CD20A300A80B83 /* AppDelegate.swift */, F556F5BE26CD20A300A80B83 /* Info.plist */, ); path = Sources; sourceTree = ""; }; F556F5DA26CD21CB00A80B83 /* Products */ = { isa = PBXGroup; children = ( F556F5E026CD21CB00A80B83 /* SkeletonView.framework */, F556F5E226CD21CB00A80B83 /* SkeletonViewTests.xctest */, F556F5E426CD21CB00A80B83 /* SkeletonView.framework */, F556F6EB26CE813F00A80B83 /* SkeletonView tvOS Tests.xctest */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F556F59E26CD201B00A80B83 /* iOS Example */ = { isa = PBXNativeTarget; buildConfigurationList = F556F5B326CD201C00A80B83 /* Build configuration list for PBXNativeTarget "iOS Example" */; buildPhases = ( F556F59B26CD201B00A80B83 /* Sources */, F556F59C26CD201B00A80B83 /* Frameworks */, F556F59D26CD201B00A80B83 /* Resources */, ); buildRules = ( ); dependencies = ( F556F5EA26CD21DA00A80B83 /* PBXTargetDependency */, ); name = "iOS Example"; productName = "iOS Example"; productReference = F556F59F26CD201B00A80B83 /* iOS Example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F556F59726CD201B00A80B83 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1250; LastUpgradeCheck = 1300; TargetAttributes = { F556F59E26CD201B00A80B83 = { CreatedOnToolsVersion = 12.5.1; }; }; }; buildConfigurationList = F556F59A26CD201B00A80B83 /* Build configuration list for PBXProject "iOS Example" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = F556F59626CD201B00A80B83; productRefGroup = F556F5A026CD201B00A80B83 /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = F556F5DA26CD21CB00A80B83 /* Products */; ProjectRef = F556F5D926CD21CB00A80B83 /* SkeletonView.xcodeproj */; }, ); projectRoot = ""; targets = ( F556F59E26CD201B00A80B83 /* iOS Example */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ F556F5E026CD21CB00A80B83 /* SkeletonView.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = SkeletonView.framework; remoteRef = F556F5DF26CD21CB00A80B83 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F556F5E226CD21CB00A80B83 /* SkeletonViewTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = SkeletonViewTests.xctest; remoteRef = F556F5E126CD21CB00A80B83 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F556F5E426CD21CB00A80B83 /* SkeletonView.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = SkeletonView.framework; remoteRef = F556F5E326CD21CB00A80B83 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F556F6EB26CE813F00A80B83 /* SkeletonView tvOS Tests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = "SkeletonView tvOS Tests.xctest"; remoteRef = F556F6EA26CE813F00A80B83 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ F556F59D26CD201B00A80B83 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( F556F5C326CD20A300A80B83 /* Main.storyboard in Resources */, F556F5C126CD20A300A80B83 /* Assets.xcassets in Resources */, F556F5C226CD20A300A80B83 /* LaunchScreen.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ F556F59B26CD201B00A80B83 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F556F6EF26CE813F00A80B83 /* Constants.swift in Sources */, F556F5C426CD20A300A80B83 /* AppDelegate.swift in Sources */, F556F6EE26CE813F00A80B83 /* Cell.swift in Sources */, F556F5C026CD20A300A80B83 /* ViewController.swift in Sources */, F556F6F026CE813F00A80B83 /* HeaderFooterSection.swift in Sources */, E21D8BB727888D050041DBCE /* UITextViewByCodeViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F556F5EA26CD21DA00A80B83 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "SkeletonView iOS"; targetProxy = F556F5E926CD21DA00A80B83 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ F556F5B926CD20A300A80B83 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( F556F5BA26CD20A300A80B83 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; F556F5BB26CD20A300A80B83 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( F556F5BC26CD20A300A80B83 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ F556F5B126CD201C00A80B83 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; F556F5B226CD201C00A80B83 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; F556F5B426CD201C00A80B83 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.skeletonview.iOS-Example"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; F556F5B526CD201C00A80B83 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.skeletonview.iOS-Example"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F556F59A26CD201B00A80B83 /* Build configuration list for PBXProject "iOS Example" */ = { isa = XCConfigurationList; buildConfigurations = ( F556F5B126CD201C00A80B83 /* Debug */, F556F5B226CD201C00A80B83 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F556F5B326CD201C00A80B83 /* Build configuration list for PBXNativeTarget "iOS Example" */ = { isa = XCConfigurationList; buildConfigurations = ( F556F5B426CD201C00A80B83 /* Debug */, F556F5B526CD201C00A80B83 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F556F59726CD201B00A80B83 /* Project object */; } ================================================ FILE: Examples/iOS Example/iOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Examples/iOS Example/iOS Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Examples/tvOS Example/Sources/AppDelegate.swift ================================================ // // AppDelegate.swift // tvOS Example // // Created by Juanpe Catalán on 18/8/21. // import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 }, "layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Middle.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ] } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" }, { "idiom" : "tv", "scale" : "2x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 }, "layers" : [ { "filename" : "Front.imagestacklayer" }, { "filename" : "Middle.imagestacklayer" }, { "filename" : "Back.imagestacklayer" } ] } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" }, { "idiom" : "tv", "scale" : "2x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" }, { "idiom" : "tv", "scale" : "2x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json ================================================ { "assets" : [ { "filename" : "App Icon - App Store.imagestack", "idiom" : "tv", "role" : "primary-app-icon", "size" : "1280x768" }, { "filename" : "App Icon.imagestack", "idiom" : "tv", "role" : "primary-app-icon", "size" : "400x240" }, { "filename" : "Top Shelf Image Wide.imageset", "idiom" : "tv", "role" : "top-shelf-image-wide", "size" : "2320x720" }, { "filename" : "Top Shelf Image.imageset", "idiom" : "tv", "role" : "top-shelf-image", "size" : "1920x720" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" }, { "idiom" : "tv", "scale" : "2x" }, { "idiom" : "tv-marketing", "scale" : "1x" }, { "idiom" : "tv-marketing", "scale" : "2x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "tv", "scale" : "1x" }, { "idiom" : "tv", "scale" : "2x" }, { "idiom" : "tv-marketing", "scale" : "1x" }, { "idiom" : "tv-marketing", "scale" : "2x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Examples/tvOS Example/Sources/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Examples/tvOS Example/Sources/Base.lproj/Main.storyboard ================================================ ================================================ FILE: Examples/tvOS Example/Sources/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UIUserInterfaceStyle Automatic ================================================ FILE: Examples/tvOS Example/Sources/ViewController.swift ================================================ // // ViewController.swift // tvOS Example // // Created by Juanpe Catalán on 18/8/21. // import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } } ================================================ FILE: Examples/tvOS Example/tvOS Example.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F556F61226CD224900A80B83 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F60A26CD224900A80B83 /* ViewController.swift */; }; F556F61326CD224900A80B83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F556F60B26CD224900A80B83 /* Assets.xcassets */; }; F556F61426CD224900A80B83 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F556F60C26CD224900A80B83 /* LaunchScreen.storyboard */; }; F556F61526CD224900A80B83 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F556F60E26CD224900A80B83 /* Main.storyboard */; }; F556F61626CD224900A80B83 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F61026CD224900A80B83 /* AppDelegate.swift */; }; F556F62526CD225C00A80B83 /* SkeletonView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F556F62326CD224F00A80B83 /* SkeletonView.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F556F61E26CD224F00A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F556F61826CD224F00A80B83 /* SkeletonView.xcodeproj */; proxyType = 2; remoteGlobalIDString = "SkeletonView::SkeletonView::Product"; remoteInfo = "SkeletonView iOS"; }; F556F62026CD224F00A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F556F61826CD224F00A80B83 /* SkeletonView.xcodeproj */; proxyType = 2; remoteGlobalIDString = "SkeletonView::SkeletonViewTests::Product"; remoteInfo = SkeletonViewTests; }; F556F62226CD224F00A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F556F61826CD224F00A80B83 /* SkeletonView.xcodeproj */; proxyType = 2; remoteGlobalIDString = F556F59426CD1F3900A80B83; remoteInfo = "SkeletonView tvOS"; }; F556F62826CD226600A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F556F61826CD224F00A80B83 /* SkeletonView.xcodeproj */; proxyType = 1; remoteGlobalIDString = F556F56426CD1F3900A80B83; remoteInfo = "SkeletonView tvOS"; }; F556F6FE26CE88DC00A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F556F61826CD224F00A80B83 /* SkeletonView.xcodeproj */; proxyType = 2; remoteGlobalIDString = F556F67126CD458500A80B83; remoteInfo = "SkeletonView tvOS Tests"; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ F556F5F426CD221300A80B83 /* tvOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "tvOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; F556F60A26CD224900A80B83 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; F556F60B26CD224900A80B83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; F556F60D26CD224900A80B83 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; F556F60F26CD224900A80B83 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; F556F61026CD224900A80B83 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; F556F61126CD224900A80B83 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F556F61826CD224F00A80B83 /* SkeletonView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SkeletonView.xcodeproj; path = ../../SkeletonView.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F556F5F126CD221300A80B83 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F556F62526CD225C00A80B83 /* SkeletonView.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F556F5EB26CD221300A80B83 = { isa = PBXGroup; children = ( F556F60926CD224900A80B83 /* Sources */, F556F5F526CD221300A80B83 /* Products */, F556F61826CD224F00A80B83 /* SkeletonView.xcodeproj */, ); sourceTree = ""; }; F556F5F526CD221300A80B83 /* Products */ = { isa = PBXGroup; children = ( F556F5F426CD221300A80B83 /* tvOS Example.app */, ); name = Products; sourceTree = ""; }; F556F60926CD224900A80B83 /* Sources */ = { isa = PBXGroup; children = ( F556F60A26CD224900A80B83 /* ViewController.swift */, F556F60B26CD224900A80B83 /* Assets.xcassets */, F556F60C26CD224900A80B83 /* LaunchScreen.storyboard */, F556F60E26CD224900A80B83 /* Main.storyboard */, F556F61026CD224900A80B83 /* AppDelegate.swift */, F556F61126CD224900A80B83 /* Info.plist */, ); path = Sources; sourceTree = ""; }; F556F61926CD224F00A80B83 /* Products */ = { isa = PBXGroup; children = ( F556F61F26CD224F00A80B83 /* SkeletonView.framework */, F556F62126CD224F00A80B83 /* SkeletonViewTests.xctest */, F556F62326CD224F00A80B83 /* SkeletonView.framework */, F556F6FF26CE88DC00A80B83 /* SkeletonView tvOS Tests.xctest */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F556F5F326CD221300A80B83 /* tvOS Example */ = { isa = PBXNativeTarget; buildConfigurationList = F556F60626CD221400A80B83 /* Build configuration list for PBXNativeTarget "tvOS Example" */; buildPhases = ( F556F5F026CD221300A80B83 /* Sources */, F556F5F126CD221300A80B83 /* Frameworks */, F556F5F226CD221300A80B83 /* Resources */, ); buildRules = ( ); dependencies = ( F556F62926CD226600A80B83 /* PBXTargetDependency */, ); name = "tvOS Example"; productName = "tvOS Example"; productReference = F556F5F426CD221300A80B83 /* tvOS Example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F556F5EC26CD221300A80B83 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1250; LastUpgradeCheck = 1300; TargetAttributes = { F556F5F326CD221300A80B83 = { CreatedOnToolsVersion = 12.5.1; }; }; }; buildConfigurationList = F556F5EF26CD221300A80B83 /* Build configuration list for PBXProject "tvOS Example" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = F556F5EB26CD221300A80B83; productRefGroup = F556F5F526CD221300A80B83 /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = F556F61926CD224F00A80B83 /* Products */; ProjectRef = F556F61826CD224F00A80B83 /* SkeletonView.xcodeproj */; }, ); projectRoot = ""; targets = ( F556F5F326CD221300A80B83 /* tvOS Example */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ F556F61F26CD224F00A80B83 /* SkeletonView.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = SkeletonView.framework; remoteRef = F556F61E26CD224F00A80B83 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F556F62126CD224F00A80B83 /* SkeletonViewTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = SkeletonViewTests.xctest; remoteRef = F556F62026CD224F00A80B83 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F556F62326CD224F00A80B83 /* SkeletonView.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = SkeletonView.framework; remoteRef = F556F62226CD224F00A80B83 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F556F6FF26CE88DC00A80B83 /* SkeletonView tvOS Tests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = "SkeletonView tvOS Tests.xctest"; remoteRef = F556F6FE26CE88DC00A80B83 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ F556F5F226CD221300A80B83 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( F556F61526CD224900A80B83 /* Main.storyboard in Resources */, F556F61326CD224900A80B83 /* Assets.xcassets in Resources */, F556F61426CD224900A80B83 /* LaunchScreen.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ F556F5F026CD221300A80B83 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F556F61626CD224900A80B83 /* AppDelegate.swift in Sources */, F556F61226CD224900A80B83 /* ViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F556F62926CD226600A80B83 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "SkeletonView tvOS"; targetProxy = F556F62826CD226600A80B83 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ F556F60C26CD224900A80B83 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( F556F60D26CD224900A80B83 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; F556F60E26CD224900A80B83 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( F556F60F26CD224900A80B83 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ F556F60426CD221400A80B83 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = appletvos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; F556F60526CD221400A80B83 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = appletvos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; }; name = Release; }; F556F60726CD221400A80B83 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = Sources/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.skeletonview.tvOS-Example"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; F556F60826CD221400A80B83 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = Sources/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.skeletonview.tvOS-Example"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F556F5EF26CD221300A80B83 /* Build configuration list for PBXProject "tvOS Example" */ = { isa = XCConfigurationList; buildConfigurations = ( F556F60426CD221400A80B83 /* Debug */, F556F60526CD221400A80B83 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F556F60626CD221400A80B83 /* Build configuration list for PBXNativeTarget "tvOS Example" */ = { isa = XCConfigurationList; buildConfigurations = ( F556F60726CD221400A80B83 /* Debug */, F556F60826CD221400A80B83 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F556F5EC26CD221300A80B83 /* Project object */; } ================================================ FILE: Examples/tvOS Example/tvOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Examples/tvOS Example/tvOS Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Gemfile ================================================ source "https://rubygems.org" gem "fastlane" gem 'cocoapods', '~> 1.7.0.beta.2' ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2017 Juanpe Catalán Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Package.swift ================================================ // swift-tools-version:5.3 import PackageDescription let package = Package( name: "SkeletonView", platforms: [ .iOS(.v9), .tvOS(.v9) ], products: [ .library( name: "SkeletonView", targets: ["SkeletonView"] ) ], targets: [ .target( name: "SkeletonView", path: "SkeletonViewCore/Sources", resources: [.copy("Supporting Files/PrivacyInfo.xcprivacy")] ), .testTarget( name: "SkeletonViewTests", dependencies: ["SkeletonView"], path: "SkeletonViewCore/Tests" ) ], swiftLanguageVersions: [.v5] ) ================================================ FILE: README.md ================================================ ![](Assets/header2.jpg)

codebeat badge SkeletonView Playground

FeaturesGuidesInstallationUsageMiscellaneousContributing

**🌎 README is available in other languages: [🇪🇸](Translations/README_es.md) . [🇨🇳](Translations/README_zh.md) . [🇧🇷](Translations/README_pt-br.md) . [🇰🇷](Translations/README_ko.md) . [🇫🇷](Translations/README_fr.md) . [🇩🇪](Translations/README_de.md)** Today almost all apps have async processes, such as API requests, long running processes, etc. While the processes are working, usually developers place a loading view to show users that something is going on. **SkeletonView** has been conceived to address this need, an elegant way to show users that something is happening and also prepare them for which contents are waiting. Enjoy it! 🙂 ## - [🌟 Features](#-features) - [🎬 Guides](#-guides) - [📲 Installation](#-installation) - [🐒 Usage](#-usage) - [🌿 Collections](#-collections) - [🔠 Texts](#-texts) - [🦋 Appearance](#-appearance) - [🎨 Custom colors](#-custom-colors) - [🏃‍♀️ Animations](#️-animations) - [🏄 Transitions](#-transitions) - [✨ Miscellaneous](#-miscellaneous) - [❤️ Contributing](#️-contributing) - [📢 Mentions](#-mentions) - [🏆 Sponsors](#-sponsors) - [👨🏻‍💻 Author](#-author) - [👮🏻 License](#-license) ## 🌟 Features * Easy to use * All UIViews are skeletonables * Fully customizable * Universal (iPhone & iPad) * Interface Builder friendly * Simple Swift syntax * Lightweight readable codebase ## 🎬 Guides | [![](https://img.youtube.com/vi/75kgOhWsPNA/maxresdefault.jpg)](https://youtu.be/75kgOhWsPNA)|[![](https://img.youtube.com/vi/MVCiM_VdxVA/maxresdefault.jpg)](https://youtu.be/MVCiM_VdxVA)|[![](https://img.youtube.com/vi/Qq3Evspeea8/maxresdefault.jpg)](https://youtu.be/Qq3Evspeea8)|[![](https://img.youtube.com/vi/Zx1Pg1gPfxA/maxresdefault.jpg)](https://www.youtube.com/watch?v=Zx1Pg1gPfxA) |:---: | :---: | :---: | :---: |[**SkeletonView Guides - Getting started**](https://youtu.be/75kgOhWsPNA)|[**How to Create Loading View with Skeleton View in Swift 5.2**](https://youtu.be/MVCiM_VdxVA) by iKh4ever Studio|[**Create Skeleton Loading View in App (Swift 5) - Xcode 11, 2020**](https://youtu.be/Qq3Evspeea8) by iOS Academy| [**Cómo crear una ANIMACIÓN de CARGA de DATOS en iOS**](https://www.youtube.com/watch?v=Zx1Pg1gPfxA) by MoureDev ## 📲 Installation * [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html): ```ruby pod 'SkeletonView' ``` * [Carthage](https://github.com/Carthage/Carthage): ```ruby github "Juanpe/SkeletonView" ``` * [Swift Package Manager](https://swift.org/package-manager/): ```swift dependencies: [ .package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0") ] ``` > 📣 **IMPORTANT!** > > Since version 1.30.0, `SkeletonView` supports **XCFrameworks**, so if you want to install it as a **XCFramework**, please use [this repo](https://github.com/Juanpe/SkeletonView-XCFramework.git) instead. ## 🐒 Usage Only **3** steps needed to use `SkeletonView`: 1️⃣ Import SkeletonView in proper place. ```swift import SkeletonView ``` 2️⃣ Now, set which views will be `skeletonables`. You achieve this in two ways: **Using code:** ```swift avatarImageView.isSkeletonable = true ``` **Using IB/Storyboards:** ![](Assets/storyboard.png) 3️⃣ Once you've set the views, you can show the **skeleton**. To do so, you have **4** choices: ```swift (1) view.showSkeleton() // Solid (2) view.showGradientSkeleton() // Gradient (3) view.showAnimatedSkeleton() // Solid animated (4) view.showAnimatedGradientSkeleton() // Gradient animated ``` **Preview**
Solid
Gradient
Solid Animated
Gradient Animated
> 📣 **IMPORTANT!** > > `SkeletonView` is recursive, so if you want show the skeleton in all skeletonable views, you only need to call the show method in the main container view. For example, with `UIViewControllers`. ### 🌿 Collections ```SkeletonView``` is compatible with ```UITableView``` and ```UICollectionView```. **UITableView** If you want to show the skeleton in a ```UITableView```, you need to conform to ```SkeletonTableViewDataSource``` protocol. ``` swift public protocol SkeletonTableViewDataSource: UITableViewDataSource { func numSections(in collectionSkeletonView: UITableView) -> Int // Default: 1 func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? // Default: nil func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) } ``` As you can see, this protocol inherits from ```UITableViewDataSource```, so you can replace this protocol with the skeleton protocol. This protocol has a default implementation for some methods. For example, the number of rows for each section is calculated in runtime: ``` swift func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int // Default: // It calculates how many cells need to populate whole tableview ``` > 📣 **IMPORTANT!** > > If you return `UITableView.automaticNumberOfSkeletonRows` 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 { return "CellIdentifier" } ``` By default, the library dequeues the cells from each indexPath, but you can also do this if you want to make some changes before the skeleton appears: ``` swift func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? { let cell = skeletonView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as? Cell cell?.textField.isHidden = indexPath.row == 0 return cell } ``` If you prefer to leave the deque part to the library you can configure the cell using this method: ``` swift func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) { let cell = cell as? Cell cell?.textField.isHidden = indexPath.row == 0 } ``` Besides, you can skeletonize both the headers and footers. You need to conform to `SkeletonTableViewDelegate` protocol. ```swift public protocol SkeletonTableViewDelegate: UITableViewDelegate { func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? // default: nil func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? // default: nil } ``` > 📣 **IMPORTANT!** > > 1️⃣ If you are using resizable cells (**`tableView.rowHeight = UITableViewAutomaticDimension`**), it's mandatory define the **`estimatedRowHeight`**. > > 2️⃣ When you add elements in a **`UITableViewCell`** you should add it to **`contentView`** and not to the cell directly. > ```swift > self.contentView.addSubview(titleLabel) ✅ > self.addSubview(titleLabel) ❌ > ``` **UICollectionView** For `UICollectionView`, you need to conform to `SkeletonCollectionViewDataSource` protocol. ``` swift public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource { func numSections(in collectionSkeletonView: UICollectionView) -> Int // default: 1 func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? // default: nil func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? // default: nil func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) func collectionSkeletonView(_ skeletonView: UICollectionView, prepareViewForSkeleton view: UICollectionReusableView, at indexPath: IndexPath) } ``` The rest of the process is the same as ```UITableView``` ### 🔠 Texts ![](Assets/multilines2.png) When using elements with text, ```SkeletonView``` draws lines to simulate text. You can set some properties for multilines elements. | Property | Type | Default | Preview | ------- | ------- |------- | ------- | **lastLineFillPercent** | `CGFloat` | `70`| ![](Assets/multiline_lastline.png) | **linesCornerRadius** | `Int` | `0` | ![](Assets/multiline_corner.png) | **skeletonLineSpacing** | `CGFloat` | `10` | ![](Assets/multiline_lineSpacing.png) | **skeletonPaddingInsets** | `UIEdgeInsets` | `.zero` | ![](Assets/multiline_insets.png) | **skeletonTextLineHeight** | `SkeletonTextLineHeight` | `.fixed(15)` | ![](Assets/multiline_lineHeight.png) | **skeletonTextNumberOfLines** | `SkeletonTextNumberOfLines` | `.inherited` | ![](Assets/multiline_corner.png)
To modify the percent or radius **using code**, set the properties: ```swift descriptionTextView.lastLineFillPercent = 50 descriptionTextView.linesCornerRadius = 5 ``` Or, if you prefer use **IB/Storyboard**: ![](Assets/multiline_customize.png)
**How to define the number of lines?** By default, the number of lines is the same as the value of the `numberOfLines` property. And, if it's set to **zero**, it'll calculate how many lines are needed to populate the whole skeleton and draw it. However, if you want to set a specific number of skeleton lines you can do it by setting the `skeletonTextNumberOfLines` property. This property has two possible values, `inherited` which returns `numberOfLines` value and `custom(Int)` which returns the specific number of lines specified as the associated value. For example: ```swift label.skeletonTextNumberOfLines = 3 // .custom(3) ```
> **⚠️ DEPRECATED!** > > **useFontLineHeight** has been deprecated. You can use **skeletonTextLineHeight** instead: > ```swift > descriptionTextView.skeletonTextLineHeight = .relativeToFont > ``` > **📣 IMPORTANT!** > > Please note that for views without multiple lines, the single line will be considered > as the last line. ### 🦋 Appearance The skeletons have a default appearance. So, when you don't specify the color, gradient or multilines properties, `SkeletonView` uses the default values. Default values: - **tintColor**: `UIColor` - *default: `.skeletonDefault` (same as `.clouds` but adaptive to dark mode)* - **gradient**: SkeletonGradient - *default: `SkeletonGradient(baseColor: .skeletonDefault)`* - **multilineHeight**: `CGFloat` - *default: 15* - **multilineSpacing**: `CGFloat` - *default: 10* - **multilineLastLineFillPercent**: `Int` - *default: 70* - **multilineCornerRadius**: `Int` - *default: 0* - **skeletonCornerRadius**: `CGFloat` (IBInspectable) (Make your skeleton view with corner) - *default: 0* To get these default values you can use `SkeletonAppearance.default`. Using this property you can set the values as well: ```swift SkeletonAppearance.default.multilineHeight = 20 SkeletonAppearance.default.tintColor = .green ``` > **⚠️ DEPRECATED!** > > **useFontLineHeight** has been deprecated. You can use **textLineHeight** instead: > ```swift > SkeletonAppearance.default.textLineHeight = .relativeToFont > ``` ### 🎨 Custom colors You can decide which color the skeleton is tinted with. You only need to pass as a parameter the color or gradient you want. **Using solid colors** ```swift view.showSkeleton(usingColor: UIColor.gray) // Solid // or view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0)) ``` **Using gradients** ``` swift let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue) view.showGradientSkeleton(usingGradient: gradient) // Gradient ``` Besides, **SkeletonView** features 20 flat colors 🤙🏼 ```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...``` ![](Assets/flatcolors.png) ###### Image captured from website [https://flatuicolors.com](https://flatuicolors.com) ### 🏃‍♀️ Animations **SkeletonView** has two built-in animations, *pulse* for solid skeletons and *sliding* for gradients. Besides, if you want to do your own skeleton animation, it's really easy. Skeleton provides the `showAnimatedSkeleton` function which has a ```SkeletonLayerAnimation``` closure where you can define your custom animation. ```swift public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation ``` You can call the function like this: ```swift view.showAnimatedSkeleton { (layer) -> CAAnimation in let animation = CAAnimation() // Customize here your animation return animation } ``` It's available ```SkeletonAnimationBuilder```. It's a builder to make ```SkeletonLayerAnimation```. Today, you can create **sliding animations** for gradients, deciding the **direction** and setting the **duration** of the animation (default = 1.5s). ```swift // func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight) view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation) ``` ```GradientDirection``` is an enum, with theses cases: | Direction | Preview |------- | ------- | .leftRight | ![](Assets/sliding_left_to_right.gif) | .rightLeft | ![](Assets/sliding_right_to_left.gif) | .topBottom | ![](Assets/sliding_top_to_bottom.gif) | .bottomTop | ![](Assets/sliding_bottom_to_top.gif) | .topLeftBottomRight | ![](Assets/sliding_topLeft_to_bottomRight.gif) | .bottomRightTopLeft | ![](Assets/sliding_bottomRight_to_topLeft.gif) > **😉 TRICK!** > > Exist another way to create sliding animations, just using this shortcut: > ```swift > let animation = GradientDirection.leftToRight.slidingAnimation() > ``` ### 🏄 Transitions **SkeletonView** has built-in transitions to **show** or **hide** the skeletons in a *smoother* way 🤙 To use the transition, simply add the ```transition``` parameter to your ```showSkeleton()``` or ```hideSkeleton()``` function with the transition time, like this: ```swift view.showSkeleton(transition: .crossDissolve(0.25)) //Show skeleton cross dissolve transition with 0.25 seconds fade time view.hideSkeleton(transition: .crossDissolve(0.25)) //Hide skeleton cross dissolve transition with 0.25 seconds fade time ``` The default value is `crossDissolve(0.25)` **Preview**
None
Cross dissolve
## ✨ Miscellaneous **Hierarchy** Since ```SkeletonView``` is recursive, and we want skeleton to be very efficient, we want to stop recursion as soon as possible. For this reason, you must set the container view as `Skeletonable`, because Skeleton will stop looking for `skeletonable` subviews as soon as a view is not Skeletonable, breaking then the recursion. Because an image is worth a thousand words: In this example we have a `UIViewController` with a `ContainerView` and a `UITableView`. When the view is ready, we show the skeleton using this method: ``` view.showSkeleton() ``` > ```isSkeletonable```= ☠️ | Configuration | Result| |:-------:|:-------:| | | | | | | | | | || | | | | | | | **Skeleton views layout** Sometimes skeleton layout may not fit your layout because the parent view bounds have changed. ~For example, rotating the device.~ You can relayout the skeleton views like so: ```swift override func viewDidLayoutSubviews() { view.layoutSkeletonIfNeeded() } ``` > 📣 **IMPORTANT!** > > You shouldn't call this method. From **version 1.8.1** you don't need to call this method, the library does automatically. So, you can use this method **ONLY** in the cases when you need to update the layout of the skeleton manually. **Update skeleton** You can change the skeleton configuration at any time like its colour, animation, etc. with the following methods: ```swift (1) view.updateSkeleton() // Solid (2) view.updateGradientSkeleton() // Gradient (3) view.updateAnimatedSkeleton() // Solid animated (4) view.updateAnimatedGradientSkeleton() // Gradient animated ``` **Hiding views when the animation starts** Sometimes you wanna hide some view when the animation starts, so there is a quick property that you can use to make this happen: ```swift view.isHiddenWhenSkeletonIsActive = true // This works only when isSkeletonable = true ``` **Don't modify user interaction when the skeleton is active** By default, the 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 `isUserInteractionDisabledWhenSkeletonIsActive` property: ```swift view.isUserInteractionDisabledWhenSkeletonIsActive = false // The view will be active when the skeleton will be active. ``` **Don't use the font line height for the skeleton lines in labels** False to disable skeleton to auto-adjust to font height for a `UILabel` or `UITextView`. By default, the skeleton lines height is auto-adjusted to font height to more accurately reflect the text in the label rect rather than using the bounding box. ```swift label.useFontLineHeight = false ``` **Delayed show skeleton** You can delay the presentation of the skeleton if the views update quickly. ```swift func showSkeleton(usingColor: UIColor, animated: Bool, delay: TimeInterval, transition: SkeletonTransitionStyle) ``` ```swift func showGradientSkeleton(usingGradient: SkeletonGradient, animated: Bool, delay: TimeInterval, transition: SkeletonTransitionStyle) ``` **Debug** To facilitate the debug tasks when something is not working fine. **`SkeletonView`** has some new tools. First, `UIView` has available a property with his skeleton info: ```swift var sk.skeletonTreeDescription: String ``` 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. ``` { "type" : "UIView", // UITableView, UILabel... "isSkeletonable" : true, "reference" : "0x000000014751ce30", "children" : [ { "type" : "UIView", "isSkeletonable" : true, "children" : [ ... ], "reference" : "0x000000014751cfa0" } ] } ``` **Supported OS & SDK Versions** * iOS 9.0+ * tvOS 9.0+ * Swift 5.3 ## ❤️ Contributing This is an open source project, so feel free to contribute. How? - Open an [issue](https://github.com/Juanpe/SkeletonView/issues/new). - Send feedback via [email](mailto://juanpecatalan.com). - Propose your own fixes, suggestions and open a pull request with the changes. See [all contributors](https://github.com/Juanpe/SkeletonView/graphs/contributors) For more information, please read the [contributing guidelines](https://github.com/Juanpe/SkeletonView/blob/main/CONTRIBUTING.md). ## 📢 Mentions - [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start) - [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful) - [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65) - [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9) - [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899) - [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d) - [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/) - [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204) - [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759) - [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) ## 🏆 Sponsors Open-source projects cannot live long without your help. If you find **SkeletonView** is useful, please consider supporting this project by becoming a sponsor. Become a sponsor through [GitHub Sponsors](https://github.com/sponsors/Juanpe) :heart: ## 👨🏻‍💻 Author [Juanpe Catalán](http://www.twitter.com/JuanpeCatalan) Buy me a coffee ## 👮🏻 License ``` MIT License Copyright (c) 2017 Juanpe Catalán Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ================================================ FILE: SkeletonVIew.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: SkeletonVIew.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: SkeletonView.podspec ================================================ Pod::Spec.new do |s| s.name = "SkeletonView" s.version = "1.31.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. SkeletonView has been conceived to address this need, an elegant way to show users that something is happening and also prepare them to which contents he is waiting. DESC s.homepage = "https://github.com/Juanpe/SkeletonView" s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Juanpe Catalán" => "juanpecm@gmail.com" } s.social_media_url = "https://x.com/JuanpeCatalan" s.ios.deployment_target = "9.0" s.tvos.deployment_target = "9.0" s.swift_version = "5.0" s.source = { :git => "https://github.com/Juanpe/SkeletonView.git", :tag => s.version.to_s } s.source_files = "SkeletonViewCore/Sources/**/*.{swift,h}" end ================================================ FILE: SkeletonView.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ F5225F2A278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */; }; F5225F2B278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */; }; 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 */; }; F53D732326D3C3A800249D46 /* UILabel+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D732226D3C3A800249D46 /* UILabel+SKExtensions.swift */; }; F53D732426D3C3A800249D46 /* UILabel+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D732226D3C3A800249D46 /* UILabel+SKExtensions.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 */; }; F556F56926CD1F3900A80B83 /* CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* CollectionSkeleton.swift */; }; F556F56A26CD1F3900A80B83 /* SkeletonCollectionViewProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* SkeletonCollectionViewProtocols.swift */; }; F556F56B26CD1F3900A80B83 /* UICollectionView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* UICollectionView+CollectionSkeleton.swift */; }; F556F56D26CD1F3900A80B83 /* SkeletonReusableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* SkeletonReusableCell.swift */; }; F556F56E26CD1F3900A80B83 /* SkeletonCollectionDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* SkeletonCollectionDataSource.swift */; }; F556F56F26CD1F3900A80B83 /* SkeletonCollectionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* SkeletonCollectionDelegate.swift */; }; F556F57026CD1F3900A80B83 /* SkeletonTableViewProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* SkeletonTableViewProtocols.swift */; }; F556F57126CD1F3900A80B83 /* UITableView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* UITableView+CollectionSkeleton.swift */; }; F556F57226CD1F3900A80B83 /* UIView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* UIView+CollectionSkeleton.swift */; }; F556F57526CD1F3900A80B83 /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* Int+Extensions.swift */; }; F556F57726CD1F3900A80B83 /* UIColor+Skeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* UIColor+Skeleton.swift */; }; F556F57826CD1F3900A80B83 /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* UITableView+Extensions.swift */; }; F556F57D26CD1F3900A80B83 /* UIView+AppLifecycleNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_39 /* UIView+AppLifecycleNotifications.swift */; }; F556F57E26CD1F3900A80B83 /* AssociationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_41 /* AssociationPolicy.swift */; }; F556F58026CD1F3900A80B83 /* Recursive.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_43 /* Recursive.swift */; }; F556F58126CD1F3900A80B83 /* Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_44 /* Swizzling.swift */; }; F556F58526CD1F3900A80B83 /* Recoverable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_50 /* Recoverable.swift */; }; F556F58626CD1F3900A80B83 /* RecoverableViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_51 /* RecoverableViewState.swift */; }; F556F58726CD1F3900A80B83 /* SkeletonAnimationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_52 /* SkeletonAnimationBuilder.swift */; }; F556F58826CD1F3900A80B83 /* SkeletonConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_53 /* SkeletonConfig.swift */; }; F556F58926CD1F3900A80B83 /* SkeletonFlowHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_54 /* SkeletonFlowHandler.swift */; }; F556F58A26CD1F3900A80B83 /* SkeletonGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_55 /* SkeletonGradient.swift */; }; F556F58B26CD1F3900A80B83 /* SkeletonLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_56 /* SkeletonLayer.swift */; }; F556F58D26CD1F3900A80B83 /* SubviewsSkeletonables.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_58 /* SubviewsSkeletonables.swift */; }; F556F58E26CD1F3900A80B83 /* SkeletonTransitionStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_60 /* SkeletonTransitionStyle.swift */; }; F556F58F26CD1F3900A80B83 /* UIView+Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_61 /* UIView+Transitions.swift */; }; F556F64D26CD2CF800A80B83 /* SkeletonDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F64C26CD2CF700A80B83 /* SkeletonDebug.swift */; }; F556F64E26CD2D3D00A80B83 /* SkeletonDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F64C26CD2CF700A80B83 /* SkeletonDebug.swift */; }; F556F65026CD2DFD00A80B83 /* SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F64F26CD2DFD00A80B83 /* SkeletonView.swift */; }; F556F65126CD2DFD00A80B83 /* SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F64F26CD2DFD00A80B83 /* SkeletonView.swift */; }; F556F65D26CD3E3600A80B83 /* SkeletonDebugTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F65C26CD3E3600A80B83 /* SkeletonDebugTests.swift */; }; F556F67626CD458500A80B83 /* SkeletonView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F556F59426CD1F3900A80B83 /* SkeletonView.framework */; platformFilter = maccatalyst; }; F556F67C26CD45A300A80B83 /* SkeletonDebugTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F65C26CD3E3600A80B83 /* SkeletonDebugTests.swift */; }; F556F68026CD47CF00A80B83 /* ProcessInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F67F26CD47CF00A80B83 /* ProcessInfo+Extensions.swift */; }; F556F68126CD47CF00A80B83 /* ProcessInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F67F26CD47CF00A80B83 /* ProcessInfo+Extensions.swift */; }; F556F68326CD48F700A80B83 /* UIView+AssociatedObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F68226CD48F700A80B83 /* UIView+AssociatedObjects.swift */; }; F556F68426CD48F700A80B83 /* UIView+AssociatedObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F68226CD48F700A80B83 /* UIView+AssociatedObjects.swift */; }; F556F68726CD49F900A80B83 /* UIView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F68626CD49F900A80B83 /* UIView+IBInspectable.swift */; }; F556F68826CD49F900A80B83 /* UIView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F68626CD49F900A80B83 /* UIView+IBInspectable.swift */; }; F556F68A26CD4D6100A80B83 /* Notification+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F68926CD4D6100A80B83 /* Notification+Extensions.swift */; }; F556F68B26CD4D6100A80B83 /* Notification+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F68926CD4D6100A80B83 /* Notification+Extensions.swift */; }; F556F69226CD506C00A80B83 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F69126CD506C00A80B83 /* Deprecated.swift */; }; F556F69326CD506C00A80B83 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F69126CD506C00A80B83 /* Deprecated.swift */; }; F556F69526CD509E00A80B83 /* Notification+SkeletonFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F69426CD509E00A80B83 /* Notification+SkeletonFlow.swift */; }; 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+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 */; }; F556F6A826CD5B0400A80B83 /* CALayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6A626CD5B0400A80B83 /* CALayer+Extensions.swift */; }; F556F6AB26CD5C4900A80B83 /* SkeletonMultilinesLayerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6AA26CD5C4900A80B83 /* SkeletonMultilinesLayerConfig.swift */; }; F556F6AC26CD5C4900A80B83 /* SkeletonMultilinesLayerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6AA26CD5C4900A80B83 /* SkeletonMultilinesLayerConfig.swift */; }; F556F6AF26CE244100A80B83 /* DispatchQueue+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6AE26CE244100A80B83 /* DispatchQueue+Extensions.swift */; }; F556F6B026CE244100A80B83 /* DispatchQueue+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6AE26CE244100A80B83 /* DispatchQueue+Extensions.swift */; }; F556F6B526CE258300A80B83 /* GradientDirection+Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6B426CE258300A80B83 /* GradientDirection+Animations.swift */; }; F556F6B626CE258300A80B83 /* GradientDirection+Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6B426CE258300A80B83 /* GradientDirection+Animations.swift */; }; F556F6B926CE262700A80B83 /* GradientDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6B826CE262700A80B83 /* GradientDirection.swift */; }; F556F6BA26CE262700A80B83 /* GradientDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6B826CE262700A80B83 /* GradientDirection.swift */; }; F556F6BC26CE272600A80B83 /* UILabel+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6BB26CE272600A80B83 /* UILabel+Extensions.swift */; }; F556F6BD26CE272600A80B83 /* UILabel+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6BB26CE272600A80B83 /* UILabel+Extensions.swift */; }; F556F6BF26CE277F00A80B83 /* PrepareViewForSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6BE26CE277F00A80B83 /* PrepareViewForSkeleton.swift */; }; F556F6C026CE277F00A80B83 /* PrepareViewForSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6BE26CE277F00A80B83 /* PrepareViewForSkeleton.swift */; }; F556F6C226CE27FD00A80B83 /* SkeletonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6C126CE27FD00A80B83 /* SkeletonType.swift */; }; F556F6C326CE27FD00A80B83 /* SkeletonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6C126CE27FD00A80B83 /* SkeletonType.swift */; }; F556F6C626CE2A2100A80B83 /* UILabel+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6C526CE2A2100A80B83 /* UILabel+IBInspectable.swift */; }; F556F6C726CE2A2100A80B83 /* UILabel+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6C526CE2A2100A80B83 /* UILabel+IBInspectable.swift */; }; F556F6C926CE2A4A00A80B83 /* UITextView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6C826CE2A4A00A80B83 /* UITextView+IBInspectable.swift */; }; F556F6CA26CE2A4A00A80B83 /* UITextView+IBInspectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6C826CE2A4A00A80B83 /* UITextView+IBInspectable.swift */; }; F556F6CC26CE2A7400A80B83 /* UITextView+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6CB26CE2A7400A80B83 /* UITextView+SKExtensions.swift */; }; F556F6CD26CE2A7400A80B83 /* UITextView+SKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6CB26CE2A7400A80B83 /* UITextView+SKExtensions.swift */; }; F556F6CF26CE2AB800A80B83 /* SkeletonTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6CE26CE2AB800A80B83 /* SkeletonTextNode.swift */; }; F556F6D026CE2AB800A80B83 /* SkeletonTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6CE26CE2AB800A80B83 /* SkeletonTextNode.swift */; }; F556F6D926CE315A00A80B83 /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6D826CE315A00A80B83 /* UICollectionView+Extensions.swift */; }; F556F6DA26CE315A00A80B83 /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6D826CE315A00A80B83 /* UICollectionView+Extensions.swift */; }; 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 */; }; F556F6F626CE876300A80B83 /* UIView+Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */; }; F556F70826D38F3100A80B83 /* SkeletonTreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */; }; F5C84884274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */; }; F5C84885274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.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 */; }; OBJ_109 /* UIView+AppLifecycleNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_39 /* UIView+AppLifecycleNotifications.swift */; }; OBJ_110 /* AssociationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_41 /* AssociationPolicy.swift */; }; OBJ_112 /* Recursive.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_43 /* Recursive.swift */; }; OBJ_113 /* Swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_44 /* Swizzling.swift */; }; OBJ_117 /* Recoverable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_50 /* Recoverable.swift */; }; OBJ_118 /* RecoverableViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_51 /* RecoverableViewState.swift */; }; OBJ_119 /* SkeletonAnimationBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_52 /* SkeletonAnimationBuilder.swift */; }; OBJ_120 /* SkeletonConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_53 /* SkeletonConfig.swift */; }; OBJ_121 /* SkeletonFlowHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_54 /* SkeletonFlowHandler.swift */; }; OBJ_122 /* SkeletonGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_55 /* SkeletonGradient.swift */; }; OBJ_123 /* SkeletonLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_56 /* SkeletonLayer.swift */; }; OBJ_125 /* SubviewsSkeletonables.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_58 /* SubviewsSkeletonables.swift */; }; OBJ_126 /* SkeletonTransitionStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_60 /* SkeletonTransitionStyle.swift */; }; OBJ_127 /* UIView+Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_61 /* UIView+Transitions.swift */; }; OBJ_147 /* SkeletonView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "SkeletonView::SkeletonView::Product" /* SkeletonView.framework */; }; OBJ_86 /* SkeletonAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* SkeletonAppearance.swift */; }; OBJ_87 /* SkeletonLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* SkeletonLayerBuilder.swift */; }; OBJ_88 /* SkeletonMultilineLayerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* SkeletonMultilineLayerBuilder.swift */; }; OBJ_89 /* CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* CollectionSkeleton.swift */; }; OBJ_90 /* SkeletonCollectionViewProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* SkeletonCollectionViewProtocols.swift */; }; OBJ_91 /* UICollectionView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* UICollectionView+CollectionSkeleton.swift */; }; OBJ_93 /* SkeletonReusableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* SkeletonReusableCell.swift */; }; OBJ_94 /* SkeletonCollectionDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* SkeletonCollectionDataSource.swift */; }; OBJ_95 /* SkeletonCollectionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* SkeletonCollectionDelegate.swift */; }; OBJ_96 /* SkeletonTableViewProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* SkeletonTableViewProtocols.swift */; }; OBJ_97 /* UITableView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* UITableView+CollectionSkeleton.swift */; }; OBJ_98 /* UIView+CollectionSkeleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* UIView+CollectionSkeleton.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F556F50C26CD1B1500A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = OBJ_1 /* Project object */; proxyType = 1; remoteGlobalIDString = "SkeletonView::SkeletonView"; remoteInfo = SkeletonView; }; F556F67726CD458500A80B83 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = OBJ_1 /* Project object */; proxyType = 1; remoteGlobalIDString = F556F56426CD1F3900A80B83; remoteInfo = "SkeletonView tvOS"; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 0AEC95C32AF537B600CD241A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTextNumberOfLines.swift; sourceTree = ""; }; 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 = ""; }; F53D732226D3C3A800249D46 /* UILabel+SKExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+SKExtensions.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 = ""; }; F556F51526CD1BFF00A80B83 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; F556F51626CD1BFF00A80B83 /* README_ko.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README_ko.md; path = Translations/README_ko.md; sourceTree = ""; }; F556F51726CD1BFF00A80B83 /* README_fr.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README_fr.md; path = Translations/README_fr.md; sourceTree = ""; }; F556F51826CD1BFF00A80B83 /* README_es.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README_es.md; path = Translations/README_es.md; sourceTree = ""; }; F556F51926CD1BFF00A80B83 /* README_pt-br.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = "README_pt-br.md"; path = "Translations/README_pt-br.md"; sourceTree = ""; }; F556F51A26CD1C1200A80B83 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; F556F51B26CD1C1200A80B83 /* CODE_OF_CONDUCT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CODE_OF_CONDUCT.md; sourceTree = ""; }; F556F51C26CD1C1200A80B83 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; F556F51E26CD1C1C00A80B83 /* FUNDING.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = FUNDING.yml; sourceTree = ""; }; F556F52026CD1C1C00A80B83 /* release.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = release.yml; sourceTree = ""; }; F556F52126CD1C1C00A80B83 /* CD.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = CD.yml; sourceTree = ""; }; F556F52226CD1C1C00A80B83 /* validations.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = validations.yml; sourceTree = ""; }; F556F52326CD1C1C00A80B83 /* pod_lib_lint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = pod_lib_lint.yml; sourceTree = ""; }; F556F52426CD1C1C00A80B83 /* main.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = main.yml; sourceTree = ""; }; F556F52526CD1C1C00A80B83 /* release_notes.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = release_notes.yml; sourceTree = ""; }; F556F52626CD1C1C00A80B83 /* stale.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = stale.yml; sourceTree = ""; }; F556F52726CD1C1C00A80B83 /* pull_request_template.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = pull_request_template.md; sourceTree = ""; }; F556F52826CD1C1C00A80B83 /* release-drafter.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = "release-drafter.yml"; sourceTree = ""; }; F556F52A26CD1C1C00A80B83 /* submit-a-request.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "submit-a-request.md"; sourceTree = ""; }; F556F52B26CD1C1C00A80B83 /* bug_report.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = bug_report.md; sourceTree = ""; }; F556F52C26CD1C1C00A80B83 /* feedback.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = feedback.md; sourceTree = ""; }; F556F56226CD1D8500A80B83 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F556F56326CD1D8B00A80B83 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F556F59426CD1F3900A80B83 /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F556F64C26CD2CF700A80B83 /* SkeletonDebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonDebug.swift; sourceTree = ""; }; F556F64F26CD2DFD00A80B83 /* SkeletonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonView.swift; sourceTree = ""; }; F556F65C26CD3E3600A80B83 /* SkeletonDebugTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonDebugTests.swift; sourceTree = ""; }; F556F67126CD458500A80B83 /* SkeletonView tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SkeletonView tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; F556F67F26CD47CF00A80B83 /* ProcessInfo+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+Extensions.swift"; sourceTree = ""; }; F556F68226CD48F700A80B83 /* UIView+AssociatedObjects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+AssociatedObjects.swift"; sourceTree = ""; }; F556F68626CD49F900A80B83 /* UIView+IBInspectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+IBInspectable.swift"; sourceTree = ""; }; F556F68926CD4D6100A80B83 /* Notification+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Extensions.swift"; sourceTree = ""; }; 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+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 = ""; }; F556F6AE26CE244100A80B83 /* DispatchQueue+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Extensions.swift"; sourceTree = ""; }; F556F6B426CE258300A80B83 /* GradientDirection+Animations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GradientDirection+Animations.swift"; sourceTree = ""; }; F556F6B826CE262700A80B83 /* GradientDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientDirection.swift; sourceTree = ""; }; F556F6BB26CE272600A80B83 /* UILabel+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Extensions.swift"; sourceTree = ""; }; F556F6BE26CE277F00A80B83 /* PrepareViewForSkeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrepareViewForSkeleton.swift; sourceTree = ""; }; F556F6C126CE27FD00A80B83 /* SkeletonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonType.swift; sourceTree = ""; }; F556F6C526CE2A2100A80B83 /* UILabel+IBInspectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+IBInspectable.swift"; sourceTree = ""; }; F556F6C826CE2A4A00A80B83 /* UITextView+IBInspectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+IBInspectable.swift"; sourceTree = ""; }; F556F6CB26CE2A7400A80B83 /* UITextView+SKExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+SKExtensions.swift"; sourceTree = ""; }; F556F6CE26CE2AB800A80B83 /* SkeletonTextNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTextNode.swift; sourceTree = ""; }; 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 = ""; }; F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTreeNode.swift; sourceTree = ""; }; F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTextLineHeight.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 = ""; }; OBJ_16 /* SkeletonCollectionViewProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCollectionViewProtocols.swift; sourceTree = ""; }; OBJ_17 /* UICollectionView+CollectionSkeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+CollectionSkeleton.swift"; sourceTree = ""; }; OBJ_20 /* SkeletonReusableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonReusableCell.swift; sourceTree = ""; }; OBJ_21 /* SkeletonCollectionDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCollectionDataSource.swift; sourceTree = ""; }; OBJ_22 /* SkeletonCollectionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCollectionDelegate.swift; sourceTree = ""; }; OBJ_24 /* SkeletonTableViewProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTableViewProtocols.swift; sourceTree = ""; }; OBJ_25 /* UITableView+CollectionSkeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+CollectionSkeleton.swift"; sourceTree = ""; }; OBJ_26 /* UIView+CollectionSkeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+CollectionSkeleton.swift"; sourceTree = ""; }; OBJ_31 /* Int+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Extensions.swift"; sourceTree = ""; }; OBJ_33 /* UIColor+Skeleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Skeleton.swift"; sourceTree = ""; }; OBJ_34 /* UITableView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Extensions.swift"; sourceTree = ""; }; OBJ_39 /* UIView+AppLifecycleNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+AppLifecycleNotifications.swift"; sourceTree = ""; }; OBJ_41 /* AssociationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssociationPolicy.swift; sourceTree = ""; }; OBJ_43 /* Recursive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recursive.swift; sourceTree = ""; }; OBJ_44 /* Swizzling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swizzling.swift; sourceTree = ""; }; OBJ_50 /* Recoverable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recoverable.swift; sourceTree = ""; }; OBJ_51 /* RecoverableViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoverableViewState.swift; sourceTree = ""; }; OBJ_52 /* SkeletonAnimationBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonAnimationBuilder.swift; sourceTree = ""; }; OBJ_53 /* SkeletonConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonConfig.swift; sourceTree = ""; }; OBJ_54 /* SkeletonFlowHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonFlowHandler.swift; sourceTree = ""; }; OBJ_55 /* SkeletonGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonGradient.swift; sourceTree = ""; }; OBJ_56 /* SkeletonLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonLayer.swift; sourceTree = ""; }; OBJ_58 /* SubviewsSkeletonables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubviewsSkeletonables.swift; sourceTree = ""; }; OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; OBJ_60 /* SkeletonTransitionStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonTransitionStyle.swift; sourceTree = ""; }; OBJ_61 /* UIView+Transitions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Transitions.swift"; sourceTree = ""; }; OBJ_9 /* SkeletonAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonAppearance.swift; sourceTree = ""; }; "SkeletonView::SkeletonView::Product" /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; "SkeletonView::SkeletonViewTests::Product" /* SkeletonViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = SkeletonViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F556F59026CD1F3900A80B83 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 0; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; F556F66E26CD458500A80B83 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F556F67626CD458500A80B83 /* SkeletonView.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; OBJ_128 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 0; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; OBJ_146 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 0; files = ( OBJ_147 /* SkeletonView.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F556F50F26CD1B6200A80B83 /* Deployment */ = { isa = PBXGroup; children = ( F556F51126CD1B8000A80B83 /* LICENSE */, F556F51026CD1B7900A80B83 /* SkeletonView.podspec */, OBJ_6 /* Package.swift */, ); name = Deployment; sourceTree = ""; }; F556F51326CD1BED00A80B83 /* Documentation */ = { isa = PBXGroup; children = ( F556F51D26CD1C1C00A80B83 /* .github */, F556F51A26CD1C1200A80B83 /* CHANGELOG.md */, F556F51B26CD1C1200A80B83 /* CODE_OF_CONDUCT.md */, F556F51C26CD1C1200A80B83 /* CONTRIBUTING.md */, F556F51826CD1BFF00A80B83 /* README_es.md */, F556F51726CD1BFF00A80B83 /* README_fr.md */, F556F51626CD1BFF00A80B83 /* README_ko.md */, F556F51926CD1BFF00A80B83 /* README_pt-br.md */, F556F51426CD1BFF00A80B83 /* README_zh.md */, F556F51526CD1BFF00A80B83 /* README.md */, ); name = Documentation; sourceTree = ""; }; F556F51D26CD1C1C00A80B83 /* .github */ = { isa = PBXGroup; children = ( F556F51E26CD1C1C00A80B83 /* FUNDING.yml */, F556F51F26CD1C1C00A80B83 /* workflows */, F556F52626CD1C1C00A80B83 /* stale.yml */, F556F52726CD1C1C00A80B83 /* pull_request_template.md */, F556F52826CD1C1C00A80B83 /* release-drafter.yml */, F556F52926CD1C1C00A80B83 /* ISSUE_TEMPLATE */, ); path = .github; sourceTree = ""; }; F556F51F26CD1C1C00A80B83 /* workflows */ = { isa = PBXGroup; children = ( F556F52026CD1C1C00A80B83 /* release.yml */, F556F52126CD1C1C00A80B83 /* CD.yml */, F556F52226CD1C1C00A80B83 /* validations.yml */, F556F52326CD1C1C00A80B83 /* pod_lib_lint.yml */, F556F52426CD1C1C00A80B83 /* main.yml */, F556F52526CD1C1C00A80B83 /* release_notes.yml */, ); path = workflows; sourceTree = ""; }; F556F52926CD1C1C00A80B83 /* ISSUE_TEMPLATE */ = { isa = PBXGroup; children = ( F556F52A26CD1C1C00A80B83 /* submit-a-request.md */, F556F52B26CD1C1C00A80B83 /* bug_report.md */, F556F52C26CD1C1C00A80B83 /* feedback.md */, ); path = ISSUE_TEMPLATE; sourceTree = ""; }; F556F55F26CD1D4400A80B83 /* Supporting Files */ = { isa = PBXGroup; children = ( F556F56226CD1D8500A80B83 /* Info.plist */, 0AEC95C32AF537B600CD241A /* PrivacyInfo.xcprivacy */, ); path = "Supporting Files"; sourceTree = ""; }; F556F56026CD1D5300A80B83 /* Supporting Files */ = { isa = PBXGroup; children = ( F556F56326CD1D8B00A80B83 /* Info.plist */, ); path = "Supporting Files"; sourceTree = ""; }; F556F64A26CD2CD100A80B83 /* API */ = { isa = PBXGroup; children = ( F556F6B226CE24DA00A80B83 /* AnimationBuilder */, F556F6DB26CE32DA00A80B83 /* Appearance */, F556F6D726CE30EE00A80B83 /* Collections */, F556F69126CD506C00A80B83 /* Deprecated.swift */, F556F68C26CD4EB400A80B83 /* FoundationExtensions */, F556F6B726CE25B100A80B83 /* Models */, F556F64F26CD2DFD00A80B83 /* SkeletonView.swift */, F53D731A26D3A35100249D46 /* SkeletonExtended.swift */, F556F68526CD49E900A80B83 /* UIKitExtensions */, ); path = API; sourceTree = ""; }; F556F64B26CD2CD600A80B83 /* Internal */ = { isa = PBXGroup; children = ( F556F70626D38E8300A80B83 /* SkeletonTree */, F556F6D326CE2F3700A80B83 /* Collections */, F556F65226CD2E0A00A80B83 /* Debug */, F556F67E26CD476300A80B83 /* FoundationExtensions */, F556F6AD26CE241D00A80B83 /* Helpers */, F556F6C426CE284300A80B83 /* Models */, F556F6A926CD5C3E00A80B83 /* SkeletonConfigs */, F556F6B326CE256D00A80B83 /* SkeletonExtensions */, OBJ_54 /* SkeletonFlowHandler.swift */, F556F6B126CE246E00A80B83 /* SkeletonLayerBuilders */, F556F67D26CD475800A80B83 /* UIKitExtensions */, ); path = Internal; sourceTree = ""; }; F556F65226CD2E0A00A80B83 /* Debug */ = { isa = PBXGroup; children = ( F556F64C26CD2CF700A80B83 /* SkeletonDebug.swift */, ); path = Debug; sourceTree = ""; }; F556F65B26CD3E1400A80B83 /* Debug */ = { isa = PBXGroup; children = ( F556F65C26CD3E3600A80B83 /* SkeletonDebugTests.swift */, ); path = Debug; sourceTree = ""; }; F556F67D26CD475800A80B83 /* UIKitExtensions */ = { isa = PBXGroup; children = ( F556F6A626CD5B0400A80B83 /* CALayer+Extensions.swift */, OBJ_17 /* UICollectionView+CollectionSkeleton.swift */, OBJ_33 /* UIColor+Skeleton.swift */, F556F6BB26CE272600A80B83 /* UILabel+Extensions.swift */, OBJ_25 /* UITableView+CollectionSkeleton.swift */, OBJ_34 /* UITableView+Extensions.swift */, OBJ_39 /* UIView+AppLifecycleNotifications.swift */, F556F68226CD48F700A80B83 /* UIView+AssociatedObjects.swift */, OBJ_26 /* UIView+CollectionSkeleton.swift */, F556F69D26CD553B00A80B83 /* UIView+Extensions.swift */, F556F6DF26CE367600A80B83 /* UIView+SkeletonView.swift */, F556F6DC26CE33CE00A80B83 /* UIView+Swizzling.swift */, OBJ_61 /* UIView+Transitions.swift */, F53D731726D399E100249D46 /* SkeletonTreeNode+Extensions.swift */, ); path = UIKitExtensions; sourceTree = ""; }; F556F67E26CD476300A80B83 /* FoundationExtensions */ = { isa = PBXGroup; children = ( OBJ_31 /* Int+Extensions.swift */, F556F67F26CD47CF00A80B83 /* ProcessInfo+Extensions.swift */, F556F68926CD4D6100A80B83 /* Notification+Extensions.swift */, F556F6AE26CE244100A80B83 /* DispatchQueue+Extensions.swift */, ); path = FoundationExtensions; sourceTree = ""; }; F556F68526CD49E900A80B83 /* UIKitExtensions */ = { isa = PBXGroup; children = ( F556F6A326CD5A9000A80B83 /* CALayer+Animations.swift */, F556F68626CD49F900A80B83 /* UIView+IBInspectable.swift */, F556F6A026CD566C00A80B83 /* UIView+SKExtensions.swift */, F556F6C526CE2A2100A80B83 /* UILabel+IBInspectable.swift */, F53D732226D3C3A800249D46 /* UILabel+SKExtensions.swift */, F556F6C826CE2A4A00A80B83 /* UITextView+IBInspectable.swift */, F556F6D826CE315A00A80B83 /* UICollectionView+Extensions.swift */, F556F6CB26CE2A7400A80B83 /* UITextView+SKExtensions.swift */, ); path = UIKitExtensions; sourceTree = ""; }; F556F68C26CD4EB400A80B83 /* FoundationExtensions */ = { isa = PBXGroup; children = ( F556F69426CD509E00A80B83 /* Notification+SkeletonFlow.swift */, ); path = FoundationExtensions; sourceTree = ""; }; F556F6A926CD5C3E00A80B83 /* SkeletonConfigs */ = { isa = PBXGroup; children = ( OBJ_53 /* SkeletonConfig.swift */, F556F6AA26CD5C4900A80B83 /* SkeletonMultilinesLayerConfig.swift */, ); path = SkeletonConfigs; sourceTree = ""; }; F556F6AD26CE241D00A80B83 /* Helpers */ = { isa = PBXGroup; children = ( OBJ_43 /* Recursive.swift */, OBJ_41 /* AssociationPolicy.swift */, OBJ_44 /* Swizzling.swift */, ); path = Helpers; sourceTree = ""; }; F556F6B126CE246E00A80B83 /* SkeletonLayerBuilders */ = { isa = PBXGroup; children = ( OBJ_12 /* SkeletonMultilineLayerBuilder.swift */, OBJ_11 /* SkeletonLayerBuilder.swift */, ); path = SkeletonLayerBuilders; sourceTree = ""; }; F556F6B226CE24DA00A80B83 /* AnimationBuilder */ = { isa = PBXGroup; children = ( OBJ_52 /* SkeletonAnimationBuilder.swift */, ); path = AnimationBuilder; sourceTree = ""; }; F556F6B326CE256D00A80B83 /* SkeletonExtensions */ = { isa = PBXGroup; children = ( OBJ_58 /* SubviewsSkeletonables.swift */, F556F6BE26CE277F00A80B83 /* PrepareViewForSkeleton.swift */, F556F6CE26CE2AB800A80B83 /* SkeletonTextNode.swift */, OBJ_50 /* Recoverable.swift */, F556F6B426CE258300A80B83 /* GradientDirection+Animations.swift */, ); path = SkeletonExtensions; sourceTree = ""; }; F556F6B726CE25B100A80B83 /* Models */ = { isa = PBXGroup; children = ( OBJ_60 /* SkeletonTransitionStyle.swift */, OBJ_55 /* SkeletonGradient.swift */, F556F6B826CE262700A80B83 /* GradientDirection.swift */, F556F6C126CE27FD00A80B83 /* SkeletonType.swift */, F5C84883274BB6F000004D1A /* SkeletonTextLineHeight.swift */, F5225F29278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.swift */, ); path = Models; sourceTree = ""; }; F556F6C426CE284300A80B83 /* Models */ = { isa = PBXGroup; children = ( OBJ_56 /* SkeletonLayer.swift */, OBJ_51 /* RecoverableViewState.swift */, ); path = Models; sourceTree = ""; }; F556F6D326CE2F3700A80B83 /* Collections */ = { isa = PBXGroup; children = ( OBJ_21 /* SkeletonCollectionDataSource.swift */, OBJ_22 /* SkeletonCollectionDelegate.swift */, OBJ_20 /* SkeletonReusableCell.swift */, OBJ_14 /* CollectionSkeleton.swift */, ); path = Collections; sourceTree = ""; }; F556F6D726CE30EE00A80B83 /* Collections */ = { isa = PBXGroup; children = ( OBJ_23 /* TableViews */, OBJ_15 /* CollectionViews */, ); path = Collections; sourceTree = ""; }; F556F6DB26CE32DA00A80B83 /* Appearance */ = { isa = PBXGroup; children = ( OBJ_9 /* SkeletonAppearance.swift */, ); path = Appearance; sourceTree = ""; }; F556F70626D38E8300A80B83 /* SkeletonTree */ = { isa = PBXGroup; children = ( F556F70726D38F3100A80B83 /* SkeletonTreeNode.swift */, ); path = SkeletonTree; sourceTree = ""; }; OBJ_15 /* CollectionViews */ = { isa = PBXGroup; children = ( OBJ_16 /* SkeletonCollectionViewProtocols.swift */, ); path = CollectionViews; sourceTree = ""; }; OBJ_23 /* TableViews */ = { isa = PBXGroup; children = ( OBJ_24 /* SkeletonTableViewProtocols.swift */, ); path = TableViews; sourceTree = ""; }; OBJ_5 = { isa = PBXGroup; children = ( F556F51326CD1BED00A80B83 /* Documentation */, F556F50F26CD1B6200A80B83 /* Deployment */, OBJ_7 /* Sources */, OBJ_62 /* Tests */, OBJ_64 /* Products */, ); sourceTree = ""; }; OBJ_62 /* Tests */ = { isa = PBXGroup; children = ( F556F65B26CD3E1400A80B83 /* Debug */, F556F56026CD1D5300A80B83 /* Supporting Files */, ); name = Tests; path = SkeletonViewCore/Tests; sourceTree = SOURCE_ROOT; }; OBJ_64 /* Products */ = { isa = PBXGroup; children = ( "SkeletonView::SkeletonView::Product" /* SkeletonView.framework */, "SkeletonView::SkeletonViewTests::Product" /* SkeletonViewTests.xctest */, F556F59426CD1F3900A80B83 /* SkeletonView.framework */, F556F67126CD458500A80B83 /* SkeletonView tvOS Tests.xctest */, ); name = Products; sourceTree = BUILT_PRODUCTS_DIR; }; OBJ_7 /* Sources */ = { isa = PBXGroup; children = ( F556F64A26CD2CD100A80B83 /* API */, F556F64B26CD2CD600A80B83 /* Internal */, F556F55F26CD1D4400A80B83 /* Supporting Files */, ); name = Sources; path = SkeletonViewCore/Sources; sourceTree = SOURCE_ROOT; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F556F56426CD1F3900A80B83 /* SkeletonView tvOS */ = { isa = PBXNativeTarget; buildConfigurationList = F556F59126CD1F3900A80B83 /* Build configuration list for PBXNativeTarget "SkeletonView tvOS" */; buildPhases = ( F556F65626CD312100A80B83 /* Swiftlint */, F556F56526CD1F3900A80B83 /* Sources */, F556F59026CD1F3900A80B83 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = "SkeletonView tvOS"; productName = SkeletonView; productReference = F556F59426CD1F3900A80B83 /* SkeletonView.framework */; productType = "com.apple.product-type.framework"; }; F556F67026CD458500A80B83 /* SkeletonView tvOS Tests */ = { isa = PBXNativeTarget; buildConfigurationList = F556F67926CD458500A80B83 /* Build configuration list for PBXNativeTarget "SkeletonView tvOS Tests" */; buildPhases = ( F556F66D26CD458500A80B83 /* Sources */, F556F66E26CD458500A80B83 /* Frameworks */, F556F66F26CD458500A80B83 /* Resources */, ); buildRules = ( ); dependencies = ( F556F67826CD458500A80B83 /* PBXTargetDependency */, ); name = "SkeletonView tvOS Tests"; productName = "SkeletonView tvOS Tests"; productReference = F556F67126CD458500A80B83 /* SkeletonView tvOS Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; "SkeletonView::SkeletonView" /* SkeletonView iOS */ = { isa = PBXNativeTarget; buildConfigurationList = OBJ_82 /* Build configuration list for PBXNativeTarget "SkeletonView iOS" */; buildPhases = ( F556F65526CD311200A80B83 /* Swiftlint */, OBJ_85 /* Sources */, OBJ_128 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = "SkeletonView iOS"; productName = SkeletonView; productReference = "SkeletonView::SkeletonView::Product" /* SkeletonView.framework */; productType = "com.apple.product-type.framework"; }; "SkeletonView::SkeletonViewTests" /* SkeletonView iOS Tests */ = { isa = PBXNativeTarget; buildConfigurationList = OBJ_141 /* Build configuration list for PBXNativeTarget "SkeletonView iOS Tests" */; buildPhases = ( OBJ_144 /* Sources */, OBJ_146 /* Frameworks */, ); buildRules = ( ); dependencies = ( OBJ_148 /* PBXTargetDependency */, ); name = "SkeletonView iOS Tests"; productName = SkeletonViewTests; productReference = "SkeletonView::SkeletonViewTests::Product" /* SkeletonViewTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ OBJ_1 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftMigration = 9999; LastSwiftUpdateCheck = 1250; LastUpgradeCheck = 9999; TargetAttributes = { F556F67026CD458500A80B83 = { CreatedOnToolsVersion = 12.5.1; }; }; }; buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "SkeletonView" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = OBJ_5; productRefGroup = OBJ_64 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( "SkeletonView::SkeletonView" /* SkeletonView iOS */, "SkeletonView::SkeletonViewTests" /* SkeletonView iOS Tests */, F556F56426CD1F3900A80B83 /* SkeletonView tvOS */, F556F67026CD458500A80B83 /* SkeletonView tvOS Tests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ F556F66F26CD458500A80B83 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ F556F65526CD311200A80B83 /* Swiftlint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = Swiftlint; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "if test -d \"/opt/homebrew/bin/\"; then\n PATH=\"/opt/homebrew/bin/:${PATH}\"\nfi\n\nexport PATH\n\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; F556F65626CD312100A80B83 /* Swiftlint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = Swiftlint; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "if test -d \"/opt/homebrew/bin/\"; then\n PATH=\"/opt/homebrew/bin/:${PATH}\"\nfi\n\nexport PATH\n\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ F556F56526CD1F3900A80B83 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( F556F56626CD1F3900A80B83 /* SkeletonAppearance.swift in Sources */, F556F6CA26CE2A4A00A80B83 /* UITextView+IBInspectable.swift in Sources */, F556F6E126CE367600A80B83 /* UIView+SkeletonView.swift in Sources */, F556F56726CD1F3900A80B83 /* SkeletonLayerBuilder.swift in Sources */, 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 */, F556F6A826CD5B0400A80B83 /* CALayer+Extensions.swift in Sources */, F556F56B26CD1F3900A80B83 /* UICollectionView+CollectionSkeleton.swift in Sources */, F556F56D26CD1F3900A80B83 /* SkeletonReusableCell.swift in Sources */, F556F56E26CD1F3900A80B83 /* SkeletonCollectionDataSource.swift in Sources */, F556F56F26CD1F3900A80B83 /* SkeletonCollectionDelegate.swift in Sources */, F556F68426CD48F700A80B83 /* UIView+AssociatedObjects.swift in Sources */, F556F6CD26CE2A7400A80B83 /* UITextView+SKExtensions.swift in Sources */, F556F57026CD1F3900A80B83 /* SkeletonTableViewProtocols.swift in Sources */, F556F57126CD1F3900A80B83 /* UITableView+CollectionSkeleton.swift in Sources */, F556F57226CD1F3900A80B83 /* UIView+CollectionSkeleton.swift in Sources */, F556F6BD26CE272600A80B83 /* UILabel+Extensions.swift in Sources */, F556F68B26CD4D6100A80B83 /* Notification+Extensions.swift in Sources */, F556F57526CD1F3900A80B83 /* Int+Extensions.swift in Sources */, F556F57726CD1F3900A80B83 /* UIColor+Skeleton.swift in Sources */, F556F69626CD509E00A80B83 /* Notification+SkeletonFlow.swift in Sources */, F556F57826CD1F3900A80B83 /* UITableView+Extensions.swift in Sources */, F556F57D26CD1F3900A80B83 /* UIView+AppLifecycleNotifications.swift in Sources */, 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 */, F53D732426D3C3A800249D46 /* UILabel+SKExtensions.swift in Sources */, F556F6B626CE258300A80B83 /* GradientDirection+Animations.swift in Sources */, F556F69F26CD553B00A80B83 /* UIView+Extensions.swift in Sources */, F556F58526CD1F3900A80B83 /* Recoverable.swift in Sources */, F5C84885274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */, F556F58626CD1F3900A80B83 /* RecoverableViewState.swift in Sources */, F556F58726CD1F3900A80B83 /* SkeletonAnimationBuilder.swift in Sources */, F556F58826CD1F3900A80B83 /* SkeletonConfig.swift in Sources */, F556F58926CD1F3900A80B83 /* SkeletonFlowHandler.swift in Sources */, F556F58A26CD1F3900A80B83 /* SkeletonGradient.swift in Sources */, F556F6C326CE27FD00A80B83 /* SkeletonType.swift in Sources */, F556F58B26CD1F3900A80B83 /* SkeletonLayer.swift in Sources */, F5225F2B278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.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 */, F556F6DA26CE315A00A80B83 /* UICollectionView+Extensions.swift in Sources */, F556F64E26CD2D3D00A80B83 /* SkeletonDebug.swift in Sources */, F556F6A526CD5A9000A80B83 /* CALayer+Animations.swift in Sources */, F556F58F26CD1F3900A80B83 /* UIView+Transitions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F556F66D26CD458500A80B83 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F556F67C26CD45A300A80B83 /* SkeletonDebugTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; OBJ_144 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( F556F65D26CD3E3600A80B83 /* SkeletonDebugTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; OBJ_85 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( OBJ_86 /* SkeletonAppearance.swift in Sources */, F556F6C926CE2A4A00A80B83 /* UITextView+IBInspectable.swift in Sources */, OBJ_87 /* SkeletonLayerBuilder.swift in Sources */, F556F6CF26CE2AB800A80B83 /* SkeletonTextNode.swift in Sources */, F556F65026CD2DFD00A80B83 /* SkeletonView.swift in Sources */, OBJ_88 /* SkeletonMultilineLayerBuilder.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 */, F556F6A726CD5B0400A80B83 /* CALayer+Extensions.swift in Sources */, OBJ_91 /* UICollectionView+CollectionSkeleton.swift in Sources */, OBJ_93 /* SkeletonReusableCell.swift in Sources */, OBJ_94 /* SkeletonCollectionDataSource.swift in Sources */, OBJ_95 /* SkeletonCollectionDelegate.swift in Sources */, F556F68326CD48F700A80B83 /* UIView+AssociatedObjects.swift in Sources */, F556F6CC26CE2A7400A80B83 /* UITextView+SKExtensions.swift in Sources */, OBJ_96 /* SkeletonTableViewProtocols.swift in Sources */, OBJ_97 /* UITableView+CollectionSkeleton.swift in Sources */, OBJ_98 /* UIView+CollectionSkeleton.swift in Sources */, F556F6BC26CE272600A80B83 /* UILabel+Extensions.swift in Sources */, F556F68A26CD4D6100A80B83 /* Notification+Extensions.swift in Sources */, OBJ_101 /* Int+Extensions.swift in Sources */, OBJ_103 /* UIColor+Skeleton.swift in Sources */, F556F69526CD509E00A80B83 /* Notification+SkeletonFlow.swift in Sources */, OBJ_104 /* UITableView+Extensions.swift in Sources */, 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 */, F53D732326D3C3A800249D46 /* UILabel+SKExtensions.swift in Sources */, F556F69E26CD553B00A80B83 /* UIView+Extensions.swift in Sources */, OBJ_117 /* Recoverable.swift in Sources */, OBJ_118 /* RecoverableViewState.swift in Sources */, F5C84884274BB6F000004D1A /* SkeletonTextLineHeight.swift in Sources */, OBJ_119 /* SkeletonAnimationBuilder.swift in Sources */, OBJ_120 /* SkeletonConfig.swift in Sources */, OBJ_121 /* SkeletonFlowHandler.swift in Sources */, OBJ_122 /* SkeletonGradient.swift in Sources */, F556F6C226CE27FD00A80B83 /* SkeletonType.swift in Sources */, OBJ_123 /* SkeletonLayer.swift in Sources */, F556F6E026CE367600A80B83 /* UIView+SkeletonView.swift in Sources */, F5225F2A278C2BCE0061A9B0 /* SkeletonTextNumberOfLines.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 */, OBJ_126 /* SkeletonTransitionStyle.swift in Sources */, F556F6AB26CD5C4900A80B83 /* SkeletonMultilinesLayerConfig.swift in Sources */, F556F68026CD47CF00A80B83 /* ProcessInfo+Extensions.swift in Sources */, F556F68726CD49F900A80B83 /* UIView+IBInspectable.swift in Sources */, F556F6C626CE2A2100A80B83 /* UILabel+IBInspectable.swift in Sources */, F556F6D926CE315A00A80B83 /* UICollectionView+Extensions.swift in Sources */, F556F6DD26CE33CE00A80B83 /* UIView+Swizzling.swift in Sources */, F556F64D26CD2CF800A80B83 /* SkeletonDebug.swift in Sources */, F556F6A426CD5A9000A80B83 /* CALayer+Animations.swift in Sources */, OBJ_127 /* UIView+Transitions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F556F67826CD458500A80B83 /* PBXTargetDependency */ = { isa = PBXTargetDependency; platformFilter = maccatalyst; target = F556F56426CD1F3900A80B83 /* SkeletonView tvOS */; targetProxy = F556F67726CD458500A80B83 /* PBXContainerItemProxy */; }; OBJ_148 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = "SkeletonView::SkeletonView" /* SkeletonView iOS */; targetProxy = F556F50C26CD1B1500A80B83 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ F556F59226CD1F3900A80B83 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CURRENT_PROJECT_VERSION = 1; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PLATFORM_DIR)/Developer/Library/Frameworks", ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "SkeletonViewCore/Sources/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", ); MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = SkeletonView; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; TARGET_NAME = SkeletonView; TVOS_DEPLOYMENT_TARGET = 9.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Debug; }; F556F59326CD1F3900A80B83 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CURRENT_PROJECT_VERSION = 1; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PLATFORM_DIR)/Developer/Library/Frameworks", ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "SkeletonViewCore/Sources/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", ); MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = SkeletonView; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; TARGET_NAME = SkeletonView; TVOS_DEPLOYMENT_TARGET = 9.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Release; }; F556F67A26CD458500A80B83 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_STYLE = Automatic; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = "SkeletonViewCore/Tests/Supporting Files/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.skeletonview.SkeletonView-tvOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; F556F67B26CD458500A80B83 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = "SkeletonViewCore/Tests/Supporting Files/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.skeletonview.SkeletonView-tvOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; VALIDATE_PRODUCT = YES; }; name = Release; }; OBJ_142 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PLATFORM_DIR)/Developer/Library/Frameworks", ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "SkeletonViewCore/Tests/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@loader_path/../Frameworks", "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TARGET_NAME = SkeletonViewTests; TVOS_DEPLOYMENT_TARGET = 9.0; WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Debug; }; OBJ_143 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PLATFORM_DIR)/Developer/Library/Frameworks", ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "SkeletonViewCore/Tests/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@loader_path/../Frameworks", "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TARGET_NAME = SkeletonViewTests; TVOS_DEPLOYMENT_TARGET = 9.0; WATCHOS_DEPLOYMENT_TARGET = 7.0; }; name = Release; }; OBJ_3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "SWIFT_PACKAGE=1", "DEBUG=1", ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SUPPORTED_PLATFORMS = macosx; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; USE_HEADERMAP = NO; }; name = Debug; }; OBJ_4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_OPTIMIZATION_LEVEL = s; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "SWIFT_PACKAGE=1", ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SUPPORTED_PLATFORMS = macosx; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; USE_HEADERMAP = NO; }; name = Release; }; OBJ_83 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CURRENT_PROJECT_VERSION = 1; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PLATFORM_DIR)/Developer/Library/Frameworks", ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "SkeletonViewCore/Sources/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", ); MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = SkeletonView; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TARGET_NAME = SkeletonView; TVOS_DEPLOYMENT_TARGET = 9.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Debug; }; OBJ_84 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CURRENT_PROJECT_VERSION = 1; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PLATFORM_DIR)/Developer/Library/Frameworks", ); HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "SkeletonViewCore/Sources/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", ); MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = SkeletonView; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TARGET_NAME = SkeletonView; TVOS_DEPLOYMENT_TARGET = 9.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F556F59126CD1F3900A80B83 /* Build configuration list for PBXNativeTarget "SkeletonView tvOS" */ = { isa = XCConfigurationList; buildConfigurations = ( F556F59226CD1F3900A80B83 /* Debug */, F556F59326CD1F3900A80B83 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F556F67926CD458500A80B83 /* Build configuration list for PBXNativeTarget "SkeletonView tvOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( F556F67A26CD458500A80B83 /* Debug */, F556F67B26CD458500A80B83 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; OBJ_141 /* Build configuration list for PBXNativeTarget "SkeletonView iOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( OBJ_142 /* Debug */, OBJ_143 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; OBJ_2 /* Build configuration list for PBXProject "SkeletonView" */ = { isa = XCConfigurationList; buildConfigurations = ( OBJ_3 /* Debug */, OBJ_4 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; OBJ_82 /* Build configuration list for PBXNativeTarget "SkeletonView iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( OBJ_83 /* Debug */, OBJ_84 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = OBJ_1 /* Project object */; } ================================================ FILE: SkeletonView.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: SkeletonView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: SkeletonView.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded ================================================ FILE: SkeletonView.xcodeproj/xcshareddata/IDETemplateMacros.plist ================================================ FILEHEADER // 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 // // ___FILENAME___ // // Created by ___FULLUSERNAME___ on ___DATE___. ================================================ FILE: SkeletonView.xcodeproj/xcshareddata/xcschemes/SkeletonView iOS.xcscheme ================================================ ================================================ FILE: SkeletonView.xcodeproj/xcshareddata/xcschemes/SkeletonView tvOS.xcscheme ================================================ ================================================ FILE: SkeletonViewCore/Sources/API/AnimationBuilder/SkeletonAnimationBuilder.swift ================================================ // // SkeletonAnimationBuilder.swift // SkeletonView-iOS // // Created by Juanpe Catalán on 17/11/2017. // Copyright © 2017 SkeletonView. All rights reserved. // import UIKit public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation public class SkeletonAnimationBuilder { public init() { } public func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5, autoreverses: Bool = false) -> SkeletonLayerAnimation { { _ in let startPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.startPoint)) startPointAnim.fromValue = direction.startPoint.from startPointAnim.toValue = direction.startPoint.to let endPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.endPoint)) endPointAnim.fromValue = direction.endPoint.from endPointAnim.toValue = direction.endPoint.to let animGroup = CAAnimationGroup() animGroup.animations = [startPointAnim, endPointAnim] animGroup.duration = duration animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) animGroup.repeatCount = .infinity animGroup.autoreverses = autoreverses animGroup.isRemovedOnCompletion = false return animGroup } } } ================================================ FILE: SkeletonViewCore/Sources/API/Appearance/SkeletonAppearance.swift ================================================ // // 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 // // SkeletonAppearance.swift // import UIKit public enum SkeletonAppearance { public static var `default` = SkeletonViewAppearance.shared } // codebeat:disable[TOO_MANY_IVARS] public class SkeletonViewAppearance { static var shared = SkeletonViewAppearance() public var tintColor: UIColor = .skeletonDefault public var gradient = SkeletonGradient(baseColor: .skeletonDefault) public var multilineHeight: CGFloat = 15 public lazy var textLineHeight: SkeletonTextLineHeight = .fixed(SkeletonAppearance.default.multilineHeight) public var multilineSpacing: CGFloat = 10 public var multilineLastLineFillPercent: Int = 70 public var multilineCornerRadius: Int = 0 public var renderSingleLineAsView: Bool = false public var skeletonCornerRadius: Float = 0 } // codebeat:enable[TOO_MANY_IVARS] ================================================ FILE: SkeletonViewCore/Sources/API/Collections/CollectionViews/SkeletonCollectionViewProtocols.swift ================================================ // // SkeletonCollectionViewProtocols.swift // SkeletonView-iOS // // Created by Juanpe Catalán on 06/11/2017. // Copyright © 2017 SkeletonView. All rights reserved. // import UIKit public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource { func numSections(in collectionSkeletonView: UICollectionView) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) func collectionSkeletonView(_ skeletonView: UICollectionView, prepareViewForSkeleton view: UICollectionReusableView, at indexPath: IndexPath) } public extension SkeletonCollectionViewDataSource { func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int { UICollectionView.automaticNumberOfSkeletonItems } func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? { nil } func numSections(in collectionSkeletonView: UICollectionView) -> Int { 1 } func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? { nil } func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) { } func collectionSkeletonView(_ skeletonView: UICollectionView, prepareViewForSkeleton view: UICollectionReusableView, at indexPath: IndexPath) { } } public protocol SkeletonCollectionViewDelegate: UICollectionViewDelegate { } ================================================ FILE: SkeletonViewCore/Sources/API/Collections/TableViews/SkeletonTableViewProtocols.swift ================================================ // // SkeletonTableViewProtocols.swift // SkeletonView-iOS // // Created by Juanpe Catalán on 06/11/2017. // Copyright © 2017 SkeletonView. All rights reserved. // import UIKit extension UITableView { public static let automaticNumberOfSkeletonRows = -1 } public typealias ReusableHeaderFooterIdentifier = String public protocol SkeletonTableViewDataSource: UITableViewDataSource { func numSections(in collectionSkeletonView: UITableView) -> Int func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) } public extension SkeletonTableViewDataSource { func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int { return UITableView.automaticNumberOfSkeletonRows } func numSections(in collectionSkeletonView: UITableView) -> Int { return 1 } /// Keeping the misspelled version around until it can be deprecated /// Right now, it just calls the new correctly spelled method and returns its result @available(*, deprecated, renamed: "collectionSkeletonView(_:cellIdentifierForRowAt:)") func collectionSkeletonView(_ skeletonView: UITableView, cellIdenfierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier { return collectionSkeletonView(skeletonView, cellIdentifierForRowAt: indexPath) } func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? { nil } func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) { } } public protocol SkeletonTableViewDelegate: UITableViewDelegate { func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? } public extension SkeletonTableViewDelegate { func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? { return nil } func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? { return nil } } ================================================ FILE: SkeletonViewCore/Sources/API/Deprecated.swift ================================================ // // 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 // // Deprecated.swift // // Created by Juanpe Catalán on 18/8/21. import UIKit public extension Notification.Name { @available(*, deprecated, renamed: "skeletonWillAppear") static let willBeginShowingSkeletons = Notification.Name.skeletonWillAppearNotification @available(*, deprecated, renamed: "skeletonDidAppear") static let didShowSkeletons = Notification.Name.skeletonDidAppearNotification @available(*, deprecated, renamed: "skeletonWillUpdate") static let willBeginUpdatingSkeletons = Notification.Name.skeletonWillUpdateNotification @available(*, deprecated, renamed: "skeletonDidUpdate") static let didUpdateSkeletons = Notification.Name.skeletonDidUpdateNotification @available(*, deprecated, renamed: "skeletonWillDisappear") static let willBeginHidingSkeletons = Notification.Name.skeletonWillDisappearNotification @available(*, deprecated, renamed: "skeletonDidDisappear") 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 } } public extension UILabel { @IBInspectable @available(*, deprecated, renamed: "skeletonTextLineHeight") var useFontLineHeight: Bool { get { textLineHeight == .relativeToFont } set { textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight) } } } public extension UITextView { @IBInspectable @available(*, deprecated, renamed: "skeletonTextLineHeight") var useFontLineHeight: Bool { get { textLineHeight == .relativeToFont } set { textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight) } } } public extension SkeletonViewAppearance { @available(*, deprecated, renamed: "textLineHeight") var useFontLineHeight: Bool { get { textLineHeight == .relativeToFont } set { textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight) } } } ================================================ FILE: SkeletonViewCore/Sources/API/FoundationExtensions/Notification+SkeletonFlow.swift ================================================ // // 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 // // Notification+SkeletonFlow.swift // // Created by Juanpe Catalán on 18/8/21. import Foundation public extension Notification.Name { static let skeletonWillAppearNotification = Notification.Name("skeletonWillAppear") static let skeletonDidAppearNotification = Notification.Name("skeletonDidAppear") static let skeletonWillUpdateNotification = Notification.Name("skeletonWillUpdate") static let skeletonDidUpdateNotification = Notification.Name("skeletonDidUpdate") static let skeletonWillDisappearNotification = Notification.Name("skeletonWillDisappear") static let skeletonDidDisappearNotification = Notification.Name("skeletonDidDisappear") } ================================================ FILE: SkeletonViewCore/Sources/API/Models/GradientDirection.swift ================================================ // // 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 // // GradientDirection.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit public enum GradientDirection { case leftRight case rightLeft case topBottom case bottomTop case topLeftBottomRight case bottomRightTopLeft public func slidingAnimation(duration: CFTimeInterval = 1.5, autoreverses: Bool = false) -> SkeletonLayerAnimation { return SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: self, duration: duration, autoreverses: autoreverses) } } ================================================ FILE: SkeletonViewCore/Sources/API/Models/SkeletonGradient.swift ================================================ // // 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 // // SkeletonGradient.swift // // Created by Juanpe Catalán on 05/11/2017. import UIKit public struct SkeletonGradient { private let gradientColors: [UIColor] public var colors: [UIColor] { return gradientColors } public init(baseColor: UIColor, secondaryColor: UIColor? = nil) { if let secondary = secondaryColor { self.gradientColors = [baseColor, secondary, baseColor] } else { self.gradientColors = baseColor.makeGradient() } } public init(colors: [UIColor]) { self.gradientColors = colors } } ================================================ FILE: SkeletonViewCore/Sources/API/Models/SkeletonTextLineHeight.swift ================================================ // // 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 // // SkeletonTextLineHeight.swift // // Created by Juanpe Catalán on 22/11/21. import UIKit public enum SkeletonTextLineHeight: Equatable { /// Calculates the line height based on the font line height. case relativeToFont /// Calculates the line height based on the height constraints. /// /// If no constraints exist, the height will be set to the `multilineHeight` /// value defined in the `SkeletonAppearance`. case relativeToConstraints /// Returns the specific height specified as the associated value. case fixed(CGFloat) } ================================================ FILE: SkeletonViewCore/Sources/API/Models/SkeletonTextNumberOfLines.swift ================================================ // // 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 // // SkeletonTextNumberOfLines.swift // // Created by Juanpe Catalán on 10/1/22. import UIKit public enum SkeletonTextNumberOfLines: Equatable, ExpressibleByIntegerLiteral { /// Returns `numberOfLines` value. case inherited /// Returns the specific number of lines specified as the associated value. case custom(Int) } public extension SkeletonTextNumberOfLines { init(integerLiteral value: Int) { self = .custom(value) } } ================================================ FILE: SkeletonViewCore/Sources/API/Models/SkeletonTransitionStyle.swift ================================================ // Copyright © 2019 SkeletonView. All rights reserved. import UIKit public enum SkeletonTransitionStyle: Equatable { case none case crossDissolve(TimeInterval) } ================================================ FILE: SkeletonViewCore/Sources/API/Models/SkeletonType.swift ================================================ // // 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 // // SkeletonType.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit public enum SkeletonType { case solid case gradient var layer: CALayer { switch self { case .solid: return CALayer() case .gradient: return CAGradientLayer() } } func defaultLayerAnimation(isRTL: Bool) -> SkeletonLayerAnimation { switch self { case .solid: return { $0.pulse } case .gradient: return { SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: isRTL ? .rightLeft : .leftRight) }() } } } ================================================ FILE: SkeletonViewCore/Sources/API/SkeletonExtended.swift ================================================ // // 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 {} } } ================================================ FILE: SkeletonViewCore/Sources/API/SkeletonView.swift ================================================ // // 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 // // PublicSkeletonView.swift // // Created by Juanpe Catalán on 18/8/21. 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 `.crossDissolve(0.25)`. func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) { _delayedShowSkeletonWorkItem?.cancel() let config = SkeletonConfig(type: .solid, colors: [color], transition: transition) showSkeleton(skeletonConfig: config) } /// Shows the skeleton using the view that calls this method as root view. /// /// - Parameters: /// - color: The color of the skeleton. Defaults to `SkeletonAppearance.default.tintColor`. /// - animated: If the skeleton is animated or not. Defaults to `true`. /// - delay: The amount of time (measured in seconds) to wait before show the skeleton. /// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`. func showSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animated: Bool = true, delay: TimeInterval, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) { _delayedShowSkeletonWorkItem?.cancel() _delayedShowSkeletonWorkItem = DispatchWorkItem { [weak self] in let config = SkeletonConfig(type: .solid, colors: [color], animated: animated, transition: transition) self?.showSkeleton(skeletonConfig: config) } DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: _delayedShowSkeletonWorkItem!) } /// 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 `.crossDissolve(0.25)`. func showGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) { _delayedShowSkeletonWorkItem?.cancel() let config = SkeletonConfig(type: .gradient, colors: gradient.colors, transition: transition) showSkeleton(skeletonConfig: config) } /// Shows the gradient skeleton using the view that calls this method as root view. /// /// - Parameters: /// - gradient: The gradient of the skeleton. Defaults to `SkeletonAppearance.default.gradient`. /// - animated: If the skeleton is animated or not. Defaults to `true`. /// - delay: The amount of time (measured in seconds) to wait before show the skeleton. /// - transition: The style of the transition when the skeleton appears. Defaults to `.crossDissolve(0.25)`. func showGradientSkeleton( usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animated: Bool = true, delay: TimeInterval, transition: SkeletonTransitionStyle = .crossDissolve(0.25) ) { _delayedShowSkeletonWorkItem?.cancel() _delayedShowSkeletonWorkItem = DispatchWorkItem { [weak self] in let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: animated, transition: transition) self?.showSkeleton(skeletonConfig: config) } DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: _delayedShowSkeletonWorkItem!) } /// 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 `.crossDissolve(0.25)`. func showAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) { _delayedShowSkeletonWorkItem?.cancel() 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 `.crossDissolve(0.25)`. func showAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) { _delayedShowSkeletonWorkItem?.cancel() let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: true, animation: animation, transition: transition) showSkeleton(skeletonConfig: config) } func updateSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor) { let config = SkeletonConfig(type: .solid, colors: [color]) updateSkeleton(skeletonConfig: config) } func updateGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient) { let config = SkeletonConfig(type: .gradient, colors: gradient.colors) updateSkeleton(skeletonConfig: config) } func updateAnimatedSkeleton(usingColor color: UIColor = SkeletonAppearance.default.tintColor, animation: SkeletonLayerAnimation? = nil) { let config = SkeletonConfig(type: .solid, colors: [color], animated: true, animation: animation) updateSkeleton(skeletonConfig: config) } func updateAnimatedGradientSkeleton(usingGradient gradient: SkeletonGradient = SkeletonAppearance.default.gradient, animation: SkeletonLayerAnimation? = nil) { let config = SkeletonConfig(type: .gradient, colors: gradient.colors, animated: true, animation: animation) updateSkeleton(skeletonConfig: config) } func layoutSkeletonIfNeeded() { _flowDelegate?.willBeginLayingSkeletonsIfNeeded(rootView: self) recursiveLayoutSkeletonIfNeeded(root: self) } func hideSkeleton(reloadDataAfter reload: Bool = true, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) { _delayedShowSkeletonWorkItem?.cancel() _flowDelegate?.willBeginHidingSkeletons(rootView: self) recursiveHideSkeleton(reloadDataAfter: reload, transition: transition, root: self) } func startSkeletonAnimation(_ anim: SkeletonLayerAnimation? = nil) { subviewsSkeletonables.recursiveSearch(leafBlock: startSkeletonLayerAnimationBlock(anim)) { subview in subview.startSkeletonAnimation(anim) } } func stopSkeletonAnimation() { subviewsSkeletonables.recursiveSearch(leafBlock: stopSkeletonLayerAnimationBlock) { subview in subview.stopSkeletonAnimation() } } } ================================================ FILE: SkeletonViewCore/Sources/API/UIKitExtensions/CALayer+Animations.swift ================================================ // // 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 // // CALayer+Animations.swift // // Created by Juanpe Catalán on 18/8/21. import UIKit public extension CALayer { var pulse: CAAnimation { let pulseAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.backgroundColor)) pulseAnimation.fromValue = backgroundColor // swiftlint:disable:next force_unwrapping pulseAnimation.toValue = UIColor(cgColor: backgroundColor!).complementaryColor.cgColor pulseAnimation.duration = 1 pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) pulseAnimation.autoreverses = true pulseAnimation.repeatCount = .infinity pulseAnimation.isRemovedOnCompletion = false return pulseAnimation } } ================================================ FILE: SkeletonViewCore/Sources/API/UIKitExtensions/UICollectionView+Extensions.swift ================================================ // // 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 // // UICollectionView+Extensions.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit public extension UICollectionView { static let automaticNumberOfSkeletonItems = -1 func prepareSkeleton(completion: @escaping (Bool) -> Void) { guard let originalDataSource = self.dataSource as? SkeletonCollectionViewDataSource, !(originalDataSource is SkeletonCollectionDataSource) else { return } let dataSource = SkeletonCollectionDataSource(collectionViewDataSource: originalDataSource, rowHeight: 0.0) self.skeletonDataSource = dataSource performBatchUpdates({ self.reloadData() }) { done in completion(done) } } } ================================================ FILE: SkeletonViewCore/Sources/API/UIKitExtensions/UILabel+IBInspectable.swift ================================================ // // 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 // // UILabel+IBInspectable.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit public extension UILabel { @IBInspectable var lastLineFillPercent: Int { get { return lastLineFillingPercent } set { lastLineFillingPercent = min(newValue, 100) } } @IBInspectable var linesCornerRadius: Int { get { return multilineCornerRadius } set { multilineCornerRadius = newValue } } @IBInspectable var skeletonLineSpacing: CGFloat { get { return multilineSpacing } set { multilineSpacing = newValue } } } ================================================ FILE: SkeletonViewCore/Sources/API/UIKitExtensions/UILabel+SKExtensions.swift ================================================ // // 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 // // UILabel+SKExtensions.swift // // Created by Juanpe Catalán on 23/8/21. import UIKit public extension UILabel { /// Defines the skeleton paddings. var skeletonPaddingInsets: UIEdgeInsets { get { paddingInsets } set { paddingInsets = newValue } } /// Defines the logic for calculating the height of the skeleton lines. /// Default: `SkeletonAppearance.default.textLineHeight` var skeletonTextLineHeight: SkeletonTextLineHeight { get { textLineHeight } set { textLineHeight = newValue } } /// Defines the logic for calculating the number of lines of the skeleton. /// Default: `inherited` var skeletonTextNumberOfLines: SkeletonTextNumberOfLines { get { skeletonNumberOfLines } set { skeletonNumberOfLines = newValue } } } ================================================ FILE: SkeletonViewCore/Sources/API/UIKitExtensions/UITextView+IBInspectable.swift ================================================ // // 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 // // UITextView+IBInspectable.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit public extension UITextView { @IBInspectable var lastLineFillPercent: Int { get { return lastLineFillingPercent } set { lastLineFillingPercent = min(newValue, 100) } } @IBInspectable var linesCornerRadius: Int { get { return multilineCornerRadius } set { multilineCornerRadius = newValue } } @IBInspectable var skeletonLineSpacing: CGFloat { get { return multilineSpacing } set { multilineSpacing = newValue } } } ================================================ FILE: SkeletonViewCore/Sources/API/UIKitExtensions/UITextView+SKExtensions.swift ================================================ // // 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 // // UITextView+SKExtensions.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit public extension UITextView { /// Defines the skeleton paddings. var skeletonPaddingInsets: UIEdgeInsets { get { paddingInsets } set { paddingInsets = newValue } } /// Defines the logic for calculating the height of the skeleton lines. /// Default: `SkeletonAppearance.default.textLineHeight` var skeletonTextLineHeight: SkeletonTextLineHeight { get { textLineHeight } set { textLineHeight = newValue } } /// Defines the logic for calculating the number of lines of the skeleton. /// Default: `inherited` var skeletonTextNumberOfLines: SkeletonTextNumberOfLines { get { skeletonNumberOfLines } set { skeletonNumberOfLines = newValue } } } ================================================ FILE: SkeletonViewCore/Sources/API/UIKitExtensions/UIView+IBInspectable.swift ================================================ // // 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+IBInspectable.swift // // Created by Juanpe Catalán on 18/8/21. import UIKit public extension UIView { @IBInspectable var isSkeletonable: Bool { get { _skeletonable } set { _skeletonable = newValue } } @IBInspectable var isHiddenWhenSkeletonIsActive: Bool { get { _hiddenWhenSkeletonIsActive } set { _hiddenWhenSkeletonIsActive = newValue } } @IBInspectable var isUserInteractionDisabledWhenSkeletonIsActive: Bool { get { _disabledWhenSkeletonIsActive } set { _disabledWhenSkeletonIsActive = newValue } } @IBInspectable var skeletonCornerRadius: Float { get { _skeletonableCornerRadius } set { _skeletonableCornerRadius = newValue } } } ================================================ FILE: SkeletonViewCore/Sources/API/UIKitExtensions/UIView+SKExtensions.swift ================================================ // // 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 }) } } ================================================ FILE: SkeletonViewCore/Sources/Internal/Collections/CollectionSkeleton.swift ================================================ // // CollectionSkeleton.swift // SkeletonView-iOS // // Created by Juanpe Catalán on 02/11/2017. // Copyright © 2017 SkeletonView. All rights reserved. // import UIKit enum CollectionAssociatedKeys { static var dummyDataSource = "dummyDataSource" static var dummyDelegate = "dummyDelegate" } protocol CollectionSkeleton { var skeletonDataSource: SkeletonCollectionDataSource? { get set } var skeletonDelegate: SkeletonCollectionDelegate? { get set } var estimatedNumberOfRows: Int { get } func addDummyDataSource() func updateDummyDataSource() func removeDummyDataSource(reloadAfter: Bool) func disableUserInteraction() func enableUserInteraction() } extension CollectionSkeleton where Self: UIScrollView { var estimatedNumberOfRows: Int { return 0 } func addDummyDataSource() {} func removeDummyDataSource(reloadAfter: Bool) {} func disableUserInteraction() { if isUserInteractionDisabledWhenSkeletonIsActive { isUserInteractionEnabled = false isScrollEnabled = false } } func enableUserInteraction() { if isUserInteractionDisabledWhenSkeletonIsActive { isUserInteractionEnabled = true isScrollEnabled = true } } } ================================================ FILE: SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDataSource.swift ================================================ // // SkeletonCollectionDataSource.swift // SkeletonView-iOS // // Created by Juanpe Catalán on 02/11/2017. // Copyright © 2017 SkeletonView. All rights reserved. // import UIKit public typealias ReusableCellIdentifier = String class SkeletonCollectionDataSource: NSObject { weak var originalTableViewDataSource: SkeletonTableViewDataSource? weak var originalCollectionViewDataSource: SkeletonCollectionViewDataSource? var rowHeight: CGFloat = 0.0 var originalRowHeight: CGFloat = 0.0 convenience init(tableViewDataSource: SkeletonTableViewDataSource? = nil, collectionViewDataSource: SkeletonCollectionViewDataSource? = nil, rowHeight: CGFloat = 0.0, originalRowHeight: CGFloat = 0.0) { self.init() self.originalTableViewDataSource = tableViewDataSource self.originalCollectionViewDataSource = collectionViewDataSource self.rowHeight = rowHeight self.originalRowHeight = originalRowHeight } } // MARK: - UITableViewDataSource extension SkeletonCollectionDataSource: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { originalTableViewDataSource?.numSections(in: tableView) ?? 0 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let originalTableViewDataSource = originalTableViewDataSource else { return 0 } let numberOfRows = originalTableViewDataSource.collectionSkeletonView(tableView, numberOfRowsInSection: section) if numberOfRows == UITableView.automaticNumberOfSkeletonRows { return tableView.estimatedNumberOfRows } else { return numberOfRows } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = originalTableViewDataSource?.collectionSkeletonView(tableView, skeletonCellForRowAt: indexPath) else { let cellIdentifier = originalTableViewDataSource?.collectionSkeletonView(tableView, cellIdentifierForRowAt: indexPath) ?? "" let fakeCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: fakeCell, at: indexPath) skeletonizeViewIfContainerSkeletonIsActive(container: tableView, view: fakeCell) return fakeCell } originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: cell, at: indexPath) skeletonizeViewIfContainerSkeletonIsActive(container: tableView, view: cell) return cell } } // MARK: - UICollectionViewDataSource extension SkeletonCollectionDataSource: UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { originalCollectionViewDataSource?.numSections(in: collectionView) ?? 0 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { guard let originalCollectionViewDataSource = originalCollectionViewDataSource else { return 0 } let numberOfItems = originalCollectionViewDataSource.collectionSkeletonView(collectionView, numberOfItemsInSection: section) if numberOfItems == UICollectionView.automaticNumberOfSkeletonItems { return collectionView.estimatedNumberOfRows } else { return numberOfItems } } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, skeletonCellForItemAt: indexPath) else { let cellIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, cellIdentifierForItemAt: indexPath) ?? "" let fakeCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: fakeCell, at: indexPath) skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: fakeCell) return fakeCell } originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: cell, at: indexPath) skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: cell) return cell } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { if let viewIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, supplementaryViewIdentifierOfKind: kind, at: indexPath) { let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: viewIdentifier, for: indexPath) originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareViewForSkeleton: view, at: indexPath) skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: view) return view } return originalCollectionViewDataSource?.collectionView?(collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath) ?? UICollectionReusableView() } } extension SkeletonCollectionDataSource { private func skeletonizeViewIfContainerSkeletonIsActive(container: UIView, view: UIView) { guard container.sk.isSkeletonActive, let skeletonConfig = container._currentSkeletonConfig else { return } view.showSkeleton( skeletonConfig: skeletonConfig, notifyDelegate: false ) } } ================================================ FILE: SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDelegate.swift ================================================ // // SkeletonCollectionDelegate.swift // SkeletonView-iOS // // Created by Juanpe Catalán on 30/03/2018. // Copyright © 2018 SkeletonView. All rights reserved. // import UIKit class SkeletonCollectionDelegate: NSObject { weak var originalTableViewDelegate: SkeletonTableViewDelegate? weak var originalCollectionViewDelegate: SkeletonCollectionViewDelegate? init( tableViewDelegate: SkeletonTableViewDelegate? = nil, collectionViewDelegate: SkeletonCollectionViewDelegate? = nil ) { self.originalTableViewDelegate = tableViewDelegate self.originalCollectionViewDelegate = collectionViewDelegate } } // MARK: - UITableViewDelegate extension SkeletonCollectionDelegate: UITableViewDelegate { func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForHeaderInSection: section)) } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForFooterInSection: section)) } func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) { view.hideSkeleton() originalTableViewDelegate?.tableView?(tableView, didEndDisplayingHeaderView: view, forSection: section) } func tableView(_ tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int) { view.hideSkeleton() originalTableViewDelegate?.tableView?(tableView, didEndDisplayingFooterView: view, forSection: section) } func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { cell.hideSkeleton() originalTableViewDelegate?.tableView?(tableView, didEndDisplaying: cell, forRowAt: indexPath) } } // MARK: - UICollectionViewDelegate extension SkeletonCollectionDelegate: UICollectionViewDelegate { } private extension SkeletonCollectionDelegate { func skeletonizeViewIfContainerSkeletonIsActive(container: UIView, view: UIView) { guard container.sk.isSkeletonActive, let skeletonConfig = container._currentSkeletonConfig else { return } view.showSkeleton( skeletonConfig: skeletonConfig, notifyDelegate: false ) } func headerOrFooterView(_ tableView: UITableView, for viewIdentifier: String? ) -> UIView? { guard let viewIdentifier = viewIdentifier, let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier) else { return nil } skeletonizeViewIfContainerSkeletonIsActive( container: tableView, view: header ) return header } } ================================================ FILE: SkeletonViewCore/Sources/Internal/Collections/SkeletonReusableCell.swift ================================================ // // SkeletonReusableCell.swift // SkeletonView-iOS // // Created by Juanpe Catalán on 30/03/2018. // Copyright © 2018 SkeletonView. All rights reserved. // import UIKit public protocol SkeletonReusableCell { } extension UITableViewCell: SkeletonReusableCell { } extension UICollectionViewCell: SkeletonReusableCell { } ================================================ FILE: SkeletonViewCore/Sources/Internal/Debug/SkeletonDebug.swift ================================================ // // 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 // // SkeletonDebug.swift // // Created by Juanpe Catalán on 18/8/21. import Foundation import UIKit enum SkeletonEnvironmentKey: String { case debugMode = "SKELETON_DEBUG" } extension Dictionary { subscript (_ key: SkeletonEnvironmentKey) -> Value? { // swiftlint:disable:next force_cast return self[key.rawValue as! Key] } } func skeletonLog(_ message: String) { #if DEBUG if ProcessInfo.processInfo.environment[.debugMode] != nil { print(message) } #endif } ================================================ FILE: SkeletonViewCore/Sources/Internal/FoundationExtensions/DispatchQueue+Extensions.swift ================================================ // // 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 // // DispatchQueue+Extensions.swift // // Created by Juanpe Catalán on 19/8/21. 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() } } ================================================ FILE: SkeletonViewCore/Sources/Internal/FoundationExtensions/Int+Extensions.swift ================================================ // // 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 // // Int+Extensions.swift // import Foundation extension Int { var whitespace: String { whitespaces } var whitespaces: String { String(repeating: " ", count: self) } } ================================================ FILE: SkeletonViewCore/Sources/Internal/FoundationExtensions/Notification+Extensions.swift ================================================ // // 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 // // Notification+Extensions.swift // // Created by Juanpe Catalán on 18/8/21. import UIKit extension Notification.Name { static let applicationDidBecomeActiveNotification = UIApplication.didBecomeActiveNotification static let applicationWillTerminateNotification = UIApplication.willTerminateNotification static let applicationDidEnterForegroundNotification = UIApplication.didEnterBackgroundNotification } ================================================ FILE: SkeletonViewCore/Sources/Internal/FoundationExtensions/ProcessInfo+Extensions.swift ================================================ // // 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 // // ProcessInfo+Extensions.swift // // Created by Juanpe Catalán on 18/8/21. import Foundation extension ProcessInfo { enum Constants { static let testConfigurationFilePathKey = "XCTestConfigurationFilePath" } static var isRunningXCTest: Bool { return processInfo.environment[Constants.testConfigurationFilePathKey] != nil } } ================================================ FILE: SkeletonViewCore/Sources/Internal/Helpers/AssociationPolicy.swift ================================================ // Copyright © 2017 SkeletonView. All rights reserved. import Foundation // Partially copy/pasted from https://github.com/jameslintaylor/AssociatedObjects/blob/master/AssociatedObjects/AssociatedObjects.swift enum AssociationPolicy: UInt { // raw values map to objc_AssociationPolicy's raw values case assign = 0 case copy = 771 case copyNonatomic = 3 case retain = 769 case retainNonatomic = 1 var objc: objc_AssociationPolicy { // swiftlint:disable:next force_unwrapping return objc_AssociationPolicy(rawValue: rawValue)! } } protocol AssociatedObjects: AnyObject { } extension AssociatedObjects { /// wrapper around `objc_getAssociatedObject` func ao_get(pkey: UnsafeRawPointer) -> Any? { return objc_getAssociatedObject(self, pkey) } /// wrapper around `objc_setAssociatedObject` func ao_setOptional(_ value: Any?, pkey: UnsafeRawPointer, policy: AssociationPolicy = .retainNonatomic) { guard let value = value else { return } objc_setAssociatedObject(self, pkey, value, policy.objc) } /// wrapper around `objc_setAssociatedObject` func ao_set(_ value: Any, pkey: UnsafeRawPointer, policy: AssociationPolicy = .retainNonatomic) { objc_setAssociatedObject(self, pkey, value, policy.objc) } /// wrapper around 'objc_removeAssociatedObjects' func ao_removeAll() { objc_removeAssociatedObjects(self) } } extension NSObject: AssociatedObjects { } ================================================ FILE: SkeletonViewCore/Sources/Internal/Helpers/Recursive.swift ================================================ // 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: IterableElement func recursiveSearch(leafBlock: VoidBlock, recursiveBlock: RecursiveBlock) } extension Array: Recursive where Element: IterableElement { func recursiveSearch(leafBlock: VoidBlock, recursiveBlock: RecursiveBlock) { guard !isEmpty else { leafBlock() return } forEach { recursiveBlock($0) } } } ================================================ FILE: SkeletonViewCore/Sources/Internal/Helpers/Swizzling.swift ================================================ // Copyright © 2019 SkeletonView. All rights reserved. import Foundation 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) } } ================================================ FILE: SkeletonViewCore/Sources/Internal/Models/RecoverableViewState.swift ================================================ // // RecoverableViewState.swift // SkeletonView // // Created by Juanpe Catalán on 13/05/2018. // Copyright © 2018 SkeletonView. All rights reserved. // import UIKit struct RecoverableViewState { var backgroundColor: UIColor? var cornerRadius: CGFloat var clipToBounds: Bool var isUserInteractionsEnabled: Bool init(view: UIView) { self.backgroundColor = view.backgroundColor self.clipToBounds = view.layer.masksToBounds self.cornerRadius = view.layer.cornerRadius self.isUserInteractionsEnabled = view.isUserInteractionEnabled } } struct RecoverableLabelState { var attributedText: NSAttributedString? // we mess with `textColor`, which impacts attributed string if defined var text: String? // we mess with `text` if the label is within a `UIStackView` var textColor: UIColor? init(view: UILabel) { if let attributedText = view.attributedText { self.attributedText = attributedText } else { self.text = view.text } self.textColor = view.textColor } } struct RecoverableTextViewState { var attributedText: NSAttributedString? // we mess with `textColor`, which impacts attributed string if defined var textColor: UIColor? init(view: UITextView) { self.attributedText = view.attributedText self.textColor = view.textColor } } struct RecoverableTextFieldState { var attributedText: NSAttributedString? // we mess with `textColor`, which impacts attributed string if defined var textColor: UIColor? var placeholder: String? init(view: UITextField) { self.attributedText = view.attributedText self.textColor = view.textColor self.placeholder = view.placeholder } } struct RecoverableImageViewState { var image: UIImage? init(view: UIImageView) { self.image = view.image } } struct RecoverableButtonViewState { var title: String? init(view: UIButton) { self.title = view.titleLabel?.text } } struct RecoverableTableViewHeaderFooterViewState { var backgroundViewColor: UIColor? init(view: UITableViewHeaderFooterView) { self.backgroundViewColor = view.backgroundView?.backgroundColor } } ================================================ FILE: SkeletonViewCore/Sources/Internal/Models/SkeletonLayer.swift ================================================ // // SkeletonLayer.swift // SkeletonView-iOS // // Created by Juanpe Catalán on 02/11/2017. // Copyright © 2017 SkeletonView. All rights reserved. // import UIKit struct SkeletonLayer { private var maskLayer: CALayer private weak var holder: UIView? var type: SkeletonType { return maskLayer is CAGradientLayer ? .gradient : .solid } var contentLayer: CALayer { return maskLayer } init(type: SkeletonType, colors: [UIColor], skeletonHolder holder: UIView) { self.holder = holder self.maskLayer = type.layer self.maskLayer.anchorPoint = .zero self.maskLayer.bounds = holder.definedMaxBounds self.maskLayer.cornerRadius = CGFloat(holder.skeletonCornerRadius) addTextLinesIfNeeded() self.maskLayer.tint(withColors: colors, traitCollection: holder.traitCollection) } func update(usingColors colors: [UIColor]) { layoutIfNeeded() maskLayer.tint(withColors: colors, traitCollection: holder?.traitCollection) } func layoutIfNeeded() { if let bounds = holder?.definedMaxBounds { maskLayer.bounds = bounds } updateLinesIfNeeded() } func removeLayer(transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) { switch transition { case .none: maskLayer.removeFromSuperlayer() completion?() case .crossDissolve(let duration): maskLayer.setOpacity(from: 1, to: 0, duration: duration) { self.maskLayer.removeFromSuperlayer() completion?() } } } /// If there is more than one line, or custom preferences have been set for a single line, draw custom layers func addTextLinesIfNeeded() { guard let textView = holderAsTextView else { return } let config = SkeletonMultilinesLayerConfig(lines: textView.estimatedNumberOfLines, lineHeight: textView.estimatedLineHeight, type: type, lastLineFillPercent: textView.lastLineFillingPercent, multilineCornerRadius: textView.multilineCornerRadius, multilineSpacing: textView.multilineSpacing, paddingInsets: textView.paddingInsets, alignment: textView.textAlignment, isRTL: holder?.isRTL ?? false, shouldCenterVertically: textView.shouldCenterTextVertically) maskLayer.addMultilinesLayers(for: config) } func updateLinesIfNeeded() { guard let textView = holderAsTextView else { return } let config = SkeletonMultilinesLayerConfig(lines: textView.estimatedNumberOfLines, lineHeight: textView.estimatedLineHeight, type: type, lastLineFillPercent: textView.lastLineFillingPercent, multilineCornerRadius: textView.multilineCornerRadius, multilineSpacing: textView.multilineSpacing, paddingInsets: textView.paddingInsets, alignment: textView.textAlignment, isRTL: holder?.isRTL ?? false, shouldCenterVertically: textView.shouldCenterTextVertically) maskLayer.updateMultilinesLayers(for: config) } var holderAsTextView: SkeletonTextNode? { guard let textView = holder as? SkeletonTextNode, (textView.estimatedNumberOfLines == -1 || textView.estimatedNumberOfLines == 0 || textView.estimatedNumberOfLines > 1 || textView.estimatedNumberOfLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else { return nil } return textView } } extension SkeletonLayer { func start(_ anim: SkeletonLayerAnimation? = nil, completion: (() -> Void)? = nil) { let animation = anim ?? type.defaultLayerAnimation(isRTL: holder?.isRTL ?? false) contentLayer.playAnimation(animation, key: "skeletonAnimation", completion: completion) } func stopAnimation() { contentLayer.stopAnimation(forKey: "skeletonAnimation") } } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonConfigs/SkeletonConfig.swift ================================================ // Copyright © 2018 SkeletonView. All rights reserved. import UIKit /// Used to store all config needed to activate the skeleton layer. struct SkeletonConfig { /// Type of skeleton layer let type: SkeletonType /// Colors used in skeleton layer let colors: [UIColor] /// If type is gradient, which gradient direction let gradientDirection: GradientDirection? /// Specify if skeleton is animated or not let animated: Bool /// Used to execute a custom animation let animation: SkeletonLayerAnimation? /// Transition style var transition: SkeletonTransitionStyle init(type: SkeletonType, colors: [UIColor], gradientDirection: GradientDirection? = nil, animated: Bool = false, animation: SkeletonLayerAnimation? = nil, transition: SkeletonTransitionStyle = .crossDissolve(0.25)) { self.type = type self.colors = colors self.gradientDirection = gradientDirection self.animated = animated self.animation = animation self.transition = transition } } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonConfigs/SkeletonMultilinesLayerConfig.swift ================================================ // // 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 // // SkeletonMultilinesLayerConfig.swift // // Created by Juanpe Catalán on 18/8/21. import UIKit struct SkeletonMultilinesLayerConfig { var lines: Int var lineHeight: CGFloat var type: SkeletonType var lastLineFillPercent: Int var multilineCornerRadius: Int var multilineSpacing: CGFloat var paddingInsets: UIEdgeInsets var alignment: NSTextAlignment var isRTL: Bool var shouldCenterVertically: Bool /// Returns padding insets taking into account if the RTL is activated var calculatedPaddingInsets: UIEdgeInsets { UIEdgeInsets(top: paddingInsets.top, left: isRTL ? paddingInsets.right : paddingInsets.left, bottom: paddingInsets.bottom, right: isRTL ? paddingInsets.left : paddingInsets.right) } } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonExtensions/GradientDirection+Animations.swift ================================================ // // 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 // // GradientDirection+Animations.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit typealias GradientAnimationPoint = (from: CGPoint, to: CGPoint) extension GradientDirection { // codebeat:disable[ABC] var startPoint: GradientAnimationPoint { switch self { case .leftRight: return (from: CGPoint(x: -1, y: 0.5), to: CGPoint(x: 1, y: 0.5)) case .rightLeft: return (from: CGPoint(x: 1, y: 0.5), to: CGPoint(x: -1, y: 0.5)) case .topBottom: return (from: CGPoint(x: 0.5, y: -1), to: CGPoint(x: 0.5, y: 1)) case .bottomTop: return (from: CGPoint(x: 0.5, y: 1), to: CGPoint(x: 0.5, y: -1)) case .topLeftBottomRight: return (from: CGPoint(x: -1, y: -1), to: CGPoint(x: 1, y: 1)) case .bottomRightTopLeft: return (from: CGPoint(x: 1, y: 1), to: CGPoint(x: -1, y: -1)) } } var endPoint: GradientAnimationPoint { switch self { case .leftRight: return (from: CGPoint(x: 0, y: 0.5), to: CGPoint(x: 2, y: 0.5)) case .rightLeft: return ( from: CGPoint(x: 2, y: 0.5), to: CGPoint(x: 0, y: 0.5)) case .topBottom: return ( from: CGPoint(x: 0.5, y: 0), to: CGPoint(x: 0.5, y: 2)) case .bottomTop: return ( from: CGPoint(x: 0.5, y: 2), to: CGPoint(x: 0.5, y: 0)) case .topLeftBottomRight: return ( from: CGPoint(x: 0, y: 0), to: CGPoint(x: 2, y: 2)) case .bottomRightTopLeft: return ( from: CGPoint(x: 2, y: 2), to: CGPoint(x: 0, y: 0)) } } // codebeat:enable[ABC] } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonExtensions/PrepareViewForSkeleton.swift ================================================ // // 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 // // PrepareViewForSkeleton.swift // // Created by Juanpe Catalán on 04/11/2017. import UIKit extension UIView { @objc func prepareViewForSkeleton() { if isUserInteractionDisabledWhenSkeletonIsActive { isUserInteractionEnabled = false } startTransition { [weak self] in self?.backgroundColor = .clear } } } extension UILabel { override func prepareViewForSkeleton() { backgroundColor = .clear if isUserInteractionDisabledWhenSkeletonIsActive { isUserInteractionEnabled = false } resignFirstResponder() startTransition { [weak self] in self?.updateHeightConstraintsIfNeeded() self?.textColor = .clear } } } extension UITextView { override func prepareViewForSkeleton() { backgroundColor = .clear if isUserInteractionDisabledWhenSkeletonIsActive { isUserInteractionEnabled = false } resignFirstResponder() startTransition { [weak self] in self?.textColor = .clear } } } extension UITextField { override func prepareViewForSkeleton() { backgroundColor = .clear resignFirstResponder() startTransition { [weak self] in self?.textColor = .clear self?.placeholder = nil } } } extension UIImageView { override func prepareViewForSkeleton() { backgroundColor = .clear if isUserInteractionDisabledWhenSkeletonIsActive { isUserInteractionEnabled = false } startTransition { [weak self] in self?.image = nil } } } extension UIButton { override func prepareViewForSkeleton() { backgroundColor = .clear if isUserInteractionDisabledWhenSkeletonIsActive { isUserInteractionEnabled = false } startTransition { [weak self] in self?.setTitle(nil, for: .normal) } } } extension UITableViewHeaderFooterView { override func prepareViewForSkeleton() { backgroundView?.backgroundColor = .clear if isUserInteractionDisabledWhenSkeletonIsActive { isUserInteractionEnabled = false } } } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonExtensions/Recoverable.swift ================================================ // // Recoverable.swift // SkeletonView // // Created by Juanpe Catalán on 13/05/2018. // Copyright © 2018 SkeletonView. All rights reserved. // import UIKit protocol Recoverable { func saveViewState() func recoverViewState(forced: Bool) } extension UIView: Recoverable { var viewState: RecoverableViewState? { get { return ao_get(pkey: &ViewAssociatedKeys.viewState) as? RecoverableViewState } set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.viewState) } } @objc func saveViewState() { viewState = RecoverableViewState(view: self) } @objc func recoverViewState(forced: Bool) { guard let storedViewState = viewState else { return } startTransition { [weak self] in guard let self = self else { return } self.layer.cornerRadius = storedViewState.cornerRadius self.layer.masksToBounds = storedViewState.clipToBounds if self.isUserInteractionDisabledWhenSkeletonIsActive { self.isUserInteractionEnabled = storedViewState.isUserInteractionsEnabled } if self.backgroundColor == .clear || forced { self.backgroundColor = storedViewState.backgroundColor } } } } extension UILabel { var labelState: RecoverableLabelState? { get { return ao_get(pkey: &ViewAssociatedKeys.labelViewState) as? RecoverableLabelState } set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.labelViewState) } } override func saveViewState() { super.saveViewState() labelState = RecoverableLabelState(view: self) } override func recoverViewState(forced: Bool) { super.recoverViewState(forced: forced) startTransition { [weak self] in guard let self = self, let storedLabelState = self.labelState else { return } NSLayoutConstraint.deactivate(self.skeletonHeightConstraints) self.restoreBackupHeightConstraintsIfNeeded() if self.textColor == .clear || forced { self.textColor = storedLabelState.textColor if let attributedText = storedLabelState.attributedText { self.attributedText = attributedText } else { self.text = storedLabelState.text } } } } } extension UITextView { var textState: RecoverableTextViewState? { get { return ao_get(pkey: &ViewAssociatedKeys.labelViewState) as? RecoverableTextViewState } set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.labelViewState) } } override func saveViewState() { super.saveViewState() textState = RecoverableTextViewState(view: self) } override func recoverViewState(forced: Bool) { super.recoverViewState(forced: forced) startTransition { [weak self] in guard let storedLabelState = self?.textState else { return } if self?.textColor == .clear || forced { self?.textColor = storedLabelState.textColor if let attributedText = storedLabelState.attributedText { self?.attributedText = attributedText } } } } } extension UITextField { var textState: RecoverableTextFieldState? { get { return ao_get(pkey: &ViewAssociatedKeys.labelViewState) as? RecoverableTextFieldState } set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.labelViewState) } } override func saveViewState() { super.saveViewState() textState = RecoverableTextFieldState(view: self) } override func recoverViewState(forced: Bool) { super.recoverViewState(forced: forced) startTransition { [weak self] in guard let storedLabelState = self?.textState else { return } if self?.textColor == .clear || forced { self?.textColor = storedLabelState.textColor if let attributedText = storedLabelState.attributedText { self?.attributedText = attributedText } } if self?.placeholder == nil || forced { self?.placeholder = storedLabelState.placeholder } } } } extension UIImageView { var imageState: RecoverableImageViewState? { get { return ao_get(pkey: &ViewAssociatedKeys.imageViewState) as? RecoverableImageViewState } set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.imageViewState) } } override func saveViewState() { super.saveViewState() imageState = RecoverableImageViewState(view: self) } override func recoverViewState(forced: Bool) { super.recoverViewState(forced: forced) startTransition { [weak self] in self?.image = self?.image == nil || forced ? self?.imageState?.image : self?.image } } } extension UIButton { var buttonState: RecoverableButtonViewState? { get { return ao_get(pkey: &ViewAssociatedKeys.buttonViewState) as? RecoverableButtonViewState } set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.buttonViewState) } } override func saveViewState() { super.saveViewState() buttonState = RecoverableButtonViewState(view: self) } override func recoverViewState(forced: Bool) { super.recoverViewState(forced: forced) startTransition { [weak self] in if self?.title(for: .normal) == nil { self?.setTitle(self?.buttonState?.title, for: .normal) } } } } extension UITableViewHeaderFooterView { var headerFooterState: RecoverableTableViewHeaderFooterViewState? { get { return ao_get(pkey: &ViewAssociatedKeys.headerFooterViewState) as? RecoverableTableViewHeaderFooterViewState } set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.headerFooterViewState) } } override func saveViewState() { super.saveViewState() headerFooterState = RecoverableTableViewHeaderFooterViewState(view: self) } override func recoverViewState(forced: Bool) { super.recoverViewState(forced: forced) startTransition { [weak self] in self?.backgroundView?.backgroundColor = self?.headerFooterState?.backgroundViewColor } } } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonExtensions/SkeletonTextNode.swift ================================================ // // 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 // // SkeletonTextNode.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit protocol SkeletonTextNode { var textLineHeight: SkeletonTextLineHeight { get } var estimatedLineHeight: CGFloat { get } var estimatedNumberOfLines: Int { get } var textAlignment: NSTextAlignment { get } var lastLineFillingPercent: Int { get } var multilineCornerRadius: Int { get } var multilineSpacing: CGFloat { get } var paddingInsets: UIEdgeInsets { get } var shouldCenterTextVertically: Bool { get } } enum SkeletonTextNodeAssociatedKeys { static var lastLineFillingPercent = "lastLineFillingPercent" static var multilineCornerRadius = "multilineCornerRadius" static var multilineSpacing = "multilineSpacing" static var paddingInsets = "paddingInsets" static var backupHeightConstraints = "backupHeightConstraints" static var textLineHeight = "textLineHeight" static var skeletonNumberOfLines = "skeletonNumberOfLines" } extension UILabel: SkeletonTextNode { var estimatedLineHeight: CGFloat { switch textLineHeight { case .fixed(let height): return height case .relativeToFont: return fontLineHeight ?? SkeletonAppearance.default.multilineHeight case .relativeToConstraints: guard let constraintsLineHeight = heightConstraints.first?.constant, estimatedNumberOfLines != 0 else { return SkeletonAppearance.default.multilineHeight } return constraintsLineHeight / CGFloat(estimatedNumberOfLines) } } var textLineHeight: SkeletonTextLineHeight { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) as? SkeletonTextLineHeight ?? SkeletonAppearance.default.textLineHeight } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) } } var skeletonNumberOfLines: SkeletonTextNumberOfLines { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) as? SkeletonTextNumberOfLines ?? SkeletonTextNumberOfLines.inherited } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) } } var estimatedNumberOfLines: Int { switch skeletonNumberOfLines { case .inherited: return numberOfLines case .custom(let lines): return lines >= 0 ? lines : 1 } } var lastLineFillingPercent: Int { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.lastLineFillingPercent) as? Int ?? SkeletonAppearance.default.multilineLastLineFillPercent } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.lastLineFillingPercent) } } var multilineCornerRadius: Int { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.multilineCornerRadius) as? Int ?? SkeletonAppearance.default.multilineCornerRadius } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.multilineCornerRadius) } } var multilineSpacing: CGFloat { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.multilineSpacing) as? CGFloat ?? SkeletonAppearance.default.multilineSpacing } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.multilineSpacing) } } var paddingInsets: UIEdgeInsets { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.paddingInsets) as? UIEdgeInsets ?? .zero } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.paddingInsets) } } var backupHeightConstraints: [NSLayoutConstraint] { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.backupHeightConstraints) as? [NSLayoutConstraint] ?? [] } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.backupHeightConstraints) } } var shouldCenterTextVertically: Bool { true } var fontLineHeight: CGFloat? { if let attributedText = attributedText, attributedText.length > 0 { let attributes = attributedText.attributes(at: 0, effectiveRange: nil) let fontAttribute = attributes.first(where: { $0.key == .font }) return fontAttribute?.value as? CGFloat ?? font.lineHeight } else { return font.lineHeight } } } extension UITextView: SkeletonTextNode { var estimatedLineHeight: CGFloat { switch textLineHeight { case .fixed(let height): return height case .relativeToFont: return fontLineHeight ?? SkeletonAppearance.default.multilineHeight case .relativeToConstraints: return SkeletonAppearance.default.multilineHeight } } var textLineHeight: SkeletonTextLineHeight { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) as? SkeletonTextLineHeight ?? SkeletonAppearance.default.textLineHeight } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.textLineHeight) } } var skeletonNumberOfLines: SkeletonTextNumberOfLines { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) as? SkeletonTextNumberOfLines ?? SkeletonTextNumberOfLines.inherited } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.skeletonNumberOfLines) } } var estimatedNumberOfLines: Int { switch skeletonNumberOfLines { case .inherited: return -1 case .custom(let lines): return lines >= -1 ? lines : 1 } } var lastLineFillingPercent: Int { get { let defaultValue = SkeletonAppearance.default.multilineLastLineFillPercent return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.lastLineFillingPercent) as? Int ?? defaultValue } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.lastLineFillingPercent) } } var multilineCornerRadius: Int { get { let defaultValue = SkeletonAppearance.default.multilineCornerRadius return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.multilineCornerRadius) as? Int ?? defaultValue } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.multilineCornerRadius) } } var multilineSpacing: CGFloat { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.multilineSpacing) as? CGFloat ?? SkeletonAppearance.default.multilineSpacing } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.multilineSpacing) } } var paddingInsets: UIEdgeInsets { get { return ao_get(pkey: &SkeletonTextNodeAssociatedKeys.paddingInsets) as? UIEdgeInsets ?? .zero } set { ao_set(newValue, pkey: &SkeletonTextNodeAssociatedKeys.paddingInsets) } } var shouldCenterTextVertically: Bool { false } var fontLineHeight: CGFloat? { if let attributedText = attributedText, attributedText.length > 0 { let attributes = attributedText.attributes(at: 0, effectiveRange: nil) let fontAttribute = attributes.first(where: { $0.key == .font }) return fontAttribute?.value as? CGFloat ?? font?.lineHeight } else { return font?.lineHeight } } } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonExtensions/SubviewsSkeletonables.swift ================================================ // Copyright © 2018 SkeletonView. All rights reserved. import UIKit extension UIView { @objc var subviewsSkeletonables: [UIView] { subviewsToSkeleton.filter { $0.isSkeletonable } } @objc var subviewsToSkeleton: [UIView] { subviews } } extension UITableView { override var subviewsToSkeleton: [UIView] { // 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 [] } var result = [UIView]() for subview in subviews { if String(describing: type(of: subview)) == "UITableViewWrapperView" { result.append(contentsOf: subview.subviews) } else { result.append(subview) } } return result } } extension UITableViewCell { override var subviewsToSkeleton: [UIView] { contentView.subviews } } extension UITableViewHeaderFooterView { override var subviewsToSkeleton: [UIView] { contentView.subviews } } extension UICollectionView { override var subviewsToSkeleton: [UIView] { subviews } } extension UICollectionViewCell { override var subviewsToSkeleton: [UIView] { contentView.subviews } } extension UIStackView { override var subviewsToSkeleton: [UIView] { arrangedSubviews } } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonFlowHandler.swift ================================================ // Copyright © 2018 SkeletonView. All rights reserved. import UIKit protocol SkeletonFlowDelegate: AnyObject { 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(rootView: UIView) { NotificationCenter.default.post(name: .skeletonWillAppearNotification, object: rootView, userInfo: nil) rootView.startObservingAppLifecycleNotifications() } func didShowSkeletons(rootView: UIView) { skeletonLog(rootView.sk.skeletonTreeDescription) NotificationCenter.default.post(name: .skeletonDidAppearNotification, object: rootView, userInfo: nil) } func willBeginUpdatingSkeletons(rootView: UIView) { NotificationCenter.default.post(name: .skeletonWillUpdateNotification, object: rootView, userInfo: nil) } func didUpdateSkeletons(rootView: UIView) { NotificationCenter.default.post(name: .skeletonDidUpdateNotification, object: rootView, userInfo: nil) } func willBeginLayingSkeletonsIfNeeded(rootView: UIView) { } func didLayoutSkeletonsIfNeeded(rootView: UIView) { } func willBeginHidingSkeletons(rootView: UIView) { NotificationCenter.default.post(name: .skeletonWillDisappearNotification, object: rootView, userInfo: nil) rootView.stopObservingAppLifecycleNotications() } func didHideSkeletons(rootView: UIView) { rootView._flowDelegate = nil NotificationCenter.default.post(name: .skeletonDidDisappearNotification, object: rootView, userInfo: nil) } } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonLayerBuilders/SkeletonLayerBuilder.swift ================================================ // Copyright © 2018 SkeletonView. All rights reserved. import UIKit /// Object that facilitates the creation of skeleton layers, /// based on the builder pattern class SkeletonLayerBuilder { var skeletonType: SkeletonType? var colors: [UIColor] = [] var holder: UIView? @discardableResult func setSkeletonType(_ type: SkeletonType) -> SkeletonLayerBuilder { self.skeletonType = type return self } @discardableResult func addColor(_ color: UIColor) -> SkeletonLayerBuilder { addColors([color]) } @discardableResult func addColors(_ colors: [UIColor]) -> SkeletonLayerBuilder { self.colors.append(contentsOf: colors) return self } @discardableResult func setHolder(_ holder: UIView) -> SkeletonLayerBuilder { self.holder = holder return self } @discardableResult func build() -> SkeletonLayer? { guard let type = skeletonType, let holder = holder else { return nil } return SkeletonLayer(type: type, colors: colors, skeletonHolder: holder) } } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonLayerBuilders/SkeletonMultilineLayerBuilder.swift ================================================ // Copyright © 2018 SkeletonView. All rights reserved. import UIKit /// Object that facilitates the creation of skeleton layers for multiline /// elements, based on the builder pattern class SkeletonMultilineLayerBuilder { var skeletonType: SkeletonType? var index: Int? var height: CGFloat? var width: CGFloat? var cornerRadius: Int? var multilineSpacing: CGFloat = SkeletonAppearance.default.multilineSpacing var paddingInsets: UIEdgeInsets = .zero var alignment: NSTextAlignment = .natural var isRTL: Bool = false @discardableResult func setSkeletonType(_ type: SkeletonType) -> SkeletonMultilineLayerBuilder { self.skeletonType = type return self } @discardableResult func setIndex(_ index: Int) -> SkeletonMultilineLayerBuilder { self.index = index return self } @discardableResult func setHeight(_ height: CGFloat) -> SkeletonMultilineLayerBuilder { self.height = height return self } @discardableResult func setWidth(_ width: CGFloat) -> SkeletonMultilineLayerBuilder { self.width = width return self } @discardableResult func setCornerRadius(_ radius: Int) -> SkeletonMultilineLayerBuilder { self.cornerRadius = radius return self } @discardableResult func setMultilineSpacing(_ spacing: CGFloat) -> SkeletonMultilineLayerBuilder { self.multilineSpacing = spacing return self } @discardableResult func setPadding(_ insets: UIEdgeInsets) -> SkeletonMultilineLayerBuilder { self.paddingInsets = insets return self } @discardableResult func setAlignment(_ alignment: NSTextAlignment) -> SkeletonMultilineLayerBuilder { self.alignment = alignment return self } @discardableResult func setIsRTL(_ isRTL: Bool) -> SkeletonMultilineLayerBuilder { self.isRTL = isRTL return self } func build() -> CALayer? { guard let type = skeletonType, let index = index, let width = width, let height = height, let radius = cornerRadius else { return nil } let layer = type.layer layer.anchorPoint = .zero layer.name = CALayer.Constants.skeletonSubLayersName layer.updateLayerFrame(for: index, totalLines: layer.skeletonSublayers.count, size: CGSize(width: width, height: height), multilineSpacing: multilineSpacing, paddingInsets: paddingInsets, alignment: alignment, isRTL: isRTL) layer.cornerRadius = CGFloat(radius) layer.masksToBounds = true return layer } } ================================================ FILE: SkeletonViewCore/Sources/Internal/SkeletonTree/SkeletonTreeNode.swift ================================================ // // 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 } } ================================================ FILE: SkeletonViewCore/Sources/Internal/UIKitExtensions/CALayer+Extensions.swift ================================================ // // 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 // // CALayer+Tint.swift // // Created by Juanpe Catalán on 18/8/21. import UIKit extension CAGradientLayer { override func tint(withColors colors: [UIColor], traitCollection: UITraitCollection?) { skeletonSublayers.recursiveSearch(leafBlock: { if #available(iOS 13.0, tvOS 13, *), let traitCollection = traitCollection { self.colors = colors.map { $0.resolvedColor(with: traitCollection).cgColor } } else { self.colors = colors.map { $0.cgColor } } }) { $0.tint(withColors: colors, traitCollection: traitCollection) } } } extension CALayer { enum Constants { static let skeletonSubLayersName = "SkeletonSubLayersName" } var skeletonSublayers: [CALayer] { return sublayers?.filter { $0.name == Constants.skeletonSubLayersName } ?? [CALayer]() } @objc func tint(withColors colors: [UIColor], traitCollection: UITraitCollection?) { skeletonSublayers.recursiveSearch(leafBlock: { if #available(iOS 13.0, tvOS 13, *), let traitCollection = traitCollection { backgroundColor = colors.first?.resolvedColor(with: traitCollection).cgColor } else { backgroundColor = colors.first?.cgColor } }) { $0.tint(withColors: colors, traitCollection: traitCollection) } } 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, completion: completion) } } func stopAnimation(forKey key: String) { skeletonSublayers.recursiveSearch(leafBlock: { removeAnimation(forKey: key) }) { $0.stopAnimation(forKey: key) } } func setOpacity(from: Int, to: Int, duration: TimeInterval, completion: (() -> Void)?) { DispatchQueue.main.async { CATransaction.begin() } let animation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity)) animation.fromValue = from animation.toValue = to animation.duration = duration animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) DispatchQueue.main.async { CATransaction.setCompletionBlock(completion) } add(animation, forKey: "setOpacityAnimation") DispatchQueue.main.async { CATransaction.commit() } } func insertSkeletonLayer(_ sublayer: SkeletonLayer, atIndex index: UInt32, transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) { insertSublayer(sublayer.contentLayer, at: index) switch transition { case .none: DispatchQueue.main.async { completion?() } case .crossDissolve(let duration): sublayer.contentLayer.setOpacity(from: 0, to: 1, duration: duration, completion: completion) } } } private extension CALayer { func alignLayerFrame(_ rect: CGRect, paddingInsets: UIEdgeInsets, alignment: NSTextAlignment, isRTL: Bool) -> CGRect { var newRect = rect let superlayerWidth = (superlayer?.bounds.width ?? 0) switch alignment { case .natural where isRTL, .right: newRect.origin.x = superlayerWidth - rect.width - paddingInsets.right case .center: newRect.origin.x = (superlayerWidth + paddingInsets.left - paddingInsets.right - rect.width) / 2 case .natural, .left, .justified: break @unknown default: break } return newRect } func calculatedWidthForLine(at index: Int, totalLines: Int, lastLineFillPercent: Int, paddingInsets: UIEdgeInsets) -> CGFloat { var width = bounds.width - paddingInsets.left - paddingInsets.right if index == totalLines - 1 { width = width * CGFloat(lastLineFillPercent) / 100 } return width } func calculateNumLines(for config: SkeletonMultilinesLayerConfig) -> Int { let definedNumberOfLines = config.lines let requiredSpaceForEachLine = config.lineHeight + config.multilineSpacing 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 } if definedNumberOfLines > 0, definedNumberOfLines <= calculatedNumberOfLines { return definedNumberOfLines } return calculatedNumberOfLines } } extension CALayer { func addMultilinesLayers(for config: SkeletonMultilinesLayerConfig) { let numberOfSublayers = config.lines > 0 ? config.lines : calculateNumLines(for: config) var height = config.lineHeight if numberOfSublayers == 1 && SkeletonAppearance.default.renderSingleLineAsView { height = bounds.height } let layerBuilder = SkeletonMultilineLayerBuilder() .setSkeletonType(config.type) .setCornerRadius(config.multilineCornerRadius) .setMultilineSpacing(config.multilineSpacing) .setPadding(config.paddingInsets) .setHeight(height) .setAlignment(config.alignment) .setIsRTL(config.isRTL) (0.. CGRect in let moveDownBy = (bounds.height - (maxY + paddingInsets.top + paddingInsets.bottom)) / 2 return layer.frame.offsetBy(dx: 0, dy: moveDownBy) } for (index, layer) in currentSkeletonSublayers.enumerated() { layer.frame = verticallyCenterAlignedFrames[index] } } func updateLayerFrame(for index: Int, totalLines: Int, size: CGSize, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets, alignment: NSTextAlignment, isRTL: Bool) { let spaceRequiredForEachLine = size.height + multilineSpacing let newFrame = CGRect(x: paddingInsets.left, y: CGFloat(index) * spaceRequiredForEachLine + paddingInsets.top, width: size.width, height: size.height) if index == totalLines - 1 { frame = alignLayerFrame(newFrame, paddingInsets: paddingInsets, alignment: alignment, isRTL: isRTL) } else { frame = newFrame } } } ================================================ FILE: SkeletonViewCore/Sources/Internal/UIKitExtensions/SkeletonTreeNode+Extensions.swift ================================================ // // 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 skeletonableChildren = children var nodeInfo: [String: Any] = [ "type": "\(type(of: base))", "reference": "\(Unmanaged.passUnretained(base).toOpaque())", "isSkeletonable": base.isSkeletonable ] if !skeletonableChildren.isEmpty { nodeInfo["children"] = skeletonableChildren.map { $0.dictionaryRepresentation } } return nodeInfo } } ================================================ FILE: SkeletonViewCore/Sources/Internal/UIKitExtensions/UICollectionView+CollectionSkeleton.swift ================================================ // // UICollectionView+CollectionSkeleton.swift // SkeletonView-iOS // // Created by Juanpe Catalán on 02/02/2018. // Copyright © 2018 SkeletonView. All rights reserved. // import UIKit extension UICollectionView: CollectionSkeleton { var estimatedNumberOfRows: Int { guard let flowlayout = collectionViewLayout as? UICollectionViewFlowLayout else { return 0 } 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? { get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource } set { ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDataSource) self.dataSource = newValue } } var skeletonDelegate: SkeletonCollectionDelegate? { get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDelegate) as? SkeletonCollectionDelegate } set { ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDelegate) self.delegate = newValue } } func addDummyDataSource() { guard let originalDataSource = self.dataSource as? SkeletonCollectionViewDataSource, !(originalDataSource is SkeletonCollectionDataSource) else { return } let dataSource = SkeletonCollectionDataSource(collectionViewDataSource: originalDataSource) self.skeletonDataSource = dataSource reloadData() } func updateDummyDataSource() { if (dataSource as? SkeletonCollectionDataSource) != nil { reloadData() } else { addDummyDataSource() } } func removeDummyDataSource(reloadAfter: Bool) { guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return } self.skeletonDataSource = nil self.dataSource = dataSource.originalCollectionViewDataSource if reloadAfter { self.reloadData() } } } ================================================ FILE: SkeletonViewCore/Sources/Internal/UIKitExtensions/UIColor+Skeleton.swift ================================================ // Copyright © 2017 SkeletonView. All rights reserved. import UIKit // codebeat:disable[TOO_MANY_IVARS] public extension UIColor { static var greenSea = UIColor(0x16a085) static var turquoise = UIColor(0x1abc9c) static var emerald = UIColor(0x2ecc71) static var peterRiver = UIColor(0x3498db) static var amethyst = UIColor(0x9b59b6) static var wetAsphalt = UIColor(0x34495e) static var nephritis = UIColor(0x27ae60) static var belizeHole = UIColor(0x2980b9) static var wisteria = UIColor(0x8e44ad) static var midnightBlue = UIColor(0x2c3e50) static var sunFlower = UIColor(0xf1c40f) static var carrot = UIColor(0xe67e22) static var alizarin = UIColor(0xe74c3c) static var clouds = UIColor(0xecf0f1) static var darkClouds = UIColor(0x1c2325) static var concrete = UIColor(0x95a5a6) static var flatOrange = UIColor(0xf39c12) static var pumpkin = UIColor(0xd35400) static var pomegranate = UIColor(0xc0392b) static var silver = UIColor(0xbdc3c7) static var asbestos = UIColor(0x7f8c8d) static var skeletonDefault: UIColor { if #available(iOS 13, tvOS 13, *) { return UIColor { traitCollection in switch traitCollection.userInterfaceStyle { case .dark: return .darkClouds default: return .clouds } } } else { return .clouds } } var complementaryColor: UIColor { if #available(iOS 13, tvOS 13, *) { return UIColor { _ in self.isLight ? self.darker : self.lighter } } else { return isLight ? darker : lighter } } var lighter: UIColor { adjust(by: 1.35) } var darker: UIColor { adjust(by: 0.94) } } extension UIColor { convenience init(_ hex: UInt) { self.init( red: CGFloat((hex & 0xFF0000) >> 16) / 255.0, green: CGFloat((hex & 0x00FF00) >> 8) / 255.0, blue: CGFloat(hex & 0x0000FF) / 255.0, alpha: CGFloat(1.0) ) } var isLight: Bool { guard let components = cgColor.components, components.count >= 3 else { return false } let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000 return !(brightness < 0.5) } func adjust(by percent: CGFloat) -> UIColor { var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 getHue(&h, saturation: &s, brightness: &b, alpha: &a) return UIColor(hue: h, saturation: s, brightness: b * percent, alpha: a) } func makeGradient() -> [UIColor] { [self, self.complementaryColor, self] } } // codebeat:enable[TOO_MANY_IVARS] ================================================ FILE: SkeletonViewCore/Sources/Internal/UIKitExtensions/UILabel+Extensions.swift ================================================ // // 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 // // UILabel+Extensions.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit extension UILabel { var desiredHeightBasedOnNumberOfLines: CGFloat { let spaceNeededForEachLine = estimatedLineHeight * CGFloat(estimatedNumberOfLines) let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(estimatedNumberOfLines - 1) let padding = paddingInsets.top + paddingInsets.bottom return spaceNeededForEachLine + spaceNeededForSpaces + padding } func updateHeightConstraintsIfNeeded() { guard estimatedNumberOfLines > 1 || estimatedNumberOfLines == 0 else { return } // Workaround to simulate content when the label is contained in a `UIStackView`. if isSuperviewAStackView, bounds.height == 0, (text?.isEmpty ?? true) { // 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 { backupHeightConstraints = heightConstraints NSLayoutConstraint.deactivate(heightConstraints) setHeight(equalToConstant: desiredHeight) } } func restoreBackupHeightConstraintsIfNeeded() { guard !backupHeightConstraints.isEmpty else { return } NSLayoutConstraint.activate(backupHeightConstraints) backupHeightConstraints.removeAll() } } ================================================ FILE: SkeletonViewCore/Sources/Internal/UIKitExtensions/UITableView+CollectionSkeleton.swift ================================================ // // UITableView+CollectionSkeleton.swift // SkeletonView-iOS // // Created by Juanpe Catalán on 02/02/2018. // Copyright © 2018 SkeletonView. All rights reserved. // import UIKit extension UITableView: CollectionSkeleton { var estimatedNumberOfRows: Int { return Int(ceil(frame.height / rowHeight)) } var skeletonDataSource: SkeletonCollectionDataSource? { get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource } set { ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDataSource) self.dataSource = newValue } } var skeletonDelegate: SkeletonCollectionDelegate? { get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDelegate) as? SkeletonCollectionDelegate } set { ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDelegate) self.delegate = newValue } } func addDummyDataSource() { guard let originalDataSource = self.dataSource as? SkeletonTableViewDataSource, !(originalDataSource is SkeletonCollectionDataSource) else { return } let calculatedRowHeight = calculateRowHeight() let dataSource = SkeletonCollectionDataSource(tableViewDataSource: originalDataSource, rowHeight: rowHeight, originalRowHeight: self.rowHeight) rowHeight = calculatedRowHeight self.skeletonDataSource = dataSource if let originalDelegate = self.delegate as? SkeletonTableViewDelegate, !(originalDelegate is SkeletonCollectionDelegate) { let delegate = SkeletonCollectionDelegate(tableViewDelegate: originalDelegate) self.skeletonDelegate = delegate } reloadData() } func updateDummyDataSource() { if (dataSource as? SkeletonCollectionDataSource) != nil { reloadData() } else { addDummyDataSource() } } func removeDummyDataSource(reloadAfter: Bool) { guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return } restoreRowHeight() self.skeletonDataSource = nil self.dataSource = dataSource.originalTableViewDataSource if let delegate = self.delegate as? SkeletonCollectionDelegate { self.skeletonDelegate = nil self.delegate = delegate.originalTableViewDelegate } if reloadAfter { self.reloadData() } } } private extension UITableView { func restoreRowHeight() { guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return } rowHeight = dataSource.originalRowHeight } func calculateRowHeight() -> CGFloat { guard rowHeight == UITableView.automaticDimension else { return rowHeight } return estimatedRowHeight } } ================================================ FILE: SkeletonViewCore/Sources/Internal/UIKitExtensions/UITableView+Extensions.swift ================================================ // // 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 // // UITableView+Extensions.swift // // Created by Juanpe Catalán on 18/8/21. import UIKit extension UITableView { var indexesOfVisibleSections: [Int] { (0.. { SkeletonTreeNode(self.type) } } extension UIView { /// Flags var isSuperviewAStackView: Bool { superview is UIStackView } var isRTL: Bool { if #available(iOS 10.0, *), #available(tvOS 10.0, *) { return effectiveUserInterfaceLayoutDirection == .rightToLeft } else { return false } } /// Math var definedMaxBounds: CGRect { if let parentStackView = (superview as? UIStackView) { var origin: CGPoint = .zero switch parentStackView.alignment { case .trailing: origin.x = definedMaxWidth default: break } return CGRect(origin: origin, size: definedMaxSize) } return CGRect(origin: .zero, size: definedMaxSize) } var definedMaxSize: CGSize { CGSize(width: definedMaxWidth, height: definedMaxHeight) } var definedMaxWidth: CGFloat { let constraintsMaxWidth = widthConstraints .map { $0.constant } .max() ?? 0 return max(frame.size.width, constraintsMaxWidth) } var definedMaxHeight: CGFloat { let constraintsMaxHeight = heightConstraints .map { $0.constant } .max() ?? 0 return max(frame.size.height, constraintsMaxHeight) } /// Autolayout var widthConstraints: [NSLayoutConstraint] { nonContentSizeLayoutConstraints.filter { $0.firstAttribute == NSLayoutConstraint.Attribute.width } } var heightConstraints: [NSLayoutConstraint] { nonContentSizeLayoutConstraints.filter { $0.firstAttribute == NSLayoutConstraint.Attribute.height } } var skeletonHeightConstraints: [NSLayoutConstraint] { nonContentSizeLayoutConstraints.filter { $0.firstAttribute == NSLayoutConstraint.Attribute.height && $0.identifier?.contains("SkeletonView.Constraint.Height") ?? false } } @discardableResult func setHeight(equalToConstant constant: CGFloat) -> NSLayoutConstraint { let heightConstraint = heightAnchor.constraint(equalToConstant: constant) heightConstraint.identifier = "SkeletonView.Constraint.Height.\(constant)" NSLayoutConstraint.activate([heightConstraint]) return heightConstraint } var nonContentSizeLayoutConstraints: [NSLayoutConstraint] { constraints.filter({ "\(type(of: $0))" != "NSContentSizeLayoutConstraint" }) } /// Animations func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock { { self._isSkeletonAnimated = true guard let layer = self._skeletonLayer else { return } layer.start(anim) { [weak self] in self?._isSkeletonAnimated = false } } } var stopSkeletonLayerAnimationBlock: VoidBlock { { self._isSkeletonAnimated = false guard let layer = self._skeletonLayer else { return } layer.stopAnimation() } } /// Skeleton Layer func addSkeletonLayer(skeletonConfig config: SkeletonConfig) { guard let skeletonLayer = SkeletonLayerBuilder() .setSkeletonType(config.type) .addColors(config.colors) .setHolder(self) .build() else { return } self._skeletonLayer = skeletonLayer layer.insertSkeletonLayer( skeletonLayer, atIndex: UInt32.max, transition: config.transition ) { [weak self] in guard let self = self else { return } // Workaround to fix the problem when inserting a sublayer and // the content offset is modified by the system. (self as? UITextView)?.setContentOffset(.zero, animated: false) if config.animated { self.startSkeletonAnimation(config.animation) } } _status = .on } func updateSkeletonLayer(skeletonConfig config: SkeletonConfig) { guard let skeletonLayer = _skeletonLayer else { return } skeletonLayer.update(usingColors: config.colors) if config.animated { startSkeletonAnimation(config.animation) } else { skeletonLayer.stopAnimation() } } func layoutSkeletonLayerIfNeeded() { guard let skeletonLayer = _skeletonLayer else { return } skeletonLayer.layoutIfNeeded() } func removeSkeletonLayer() { guard sk.isSkeletonActive, let skeletonLayer = _skeletonLayer, let transitionStyle = _currentSkeletonConfig?.transition else { return } skeletonLayer.stopAnimation() _status = .off skeletonLayer.removeLayer(transition: transitionStyle) { self._skeletonLayer = nil self._currentSkeletonConfig = nil } } } ================================================ FILE: SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+SkeletonView.swift ================================================ // // 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+SkeletonView.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit extension UIView { func showSkeleton( skeletonConfig config: SkeletonConfig, notifyDelegate: Bool = true ) { _isSkeletonAnimated = config.animated if notifyDelegate { _flowDelegate = SkeletonFlowHandler() _flowDelegate?.willBeginShowingSkeletons(rootView: self) } recursiveShowSkeleton(skeletonConfig: config, root: self) } func updateSkeleton( skeletonConfig config: SkeletonConfig, notifyDelegate: Bool = true ) { _isSkeletonAnimated = config.animated if notifyDelegate { _flowDelegate?.willBeginUpdatingSkeletons(rootView: self) } recursiveUpdateSkeleton(skeletonConfig: config, root: self) } func recursiveLayoutSkeletonIfNeeded(root: UIView? = nil) { subviewsSkeletonables.recursiveSearch(leafBlock: { guard isSkeletonable, sk.isSkeletonActive else { return } layoutSkeletonLayerIfNeeded() if let config = _currentSkeletonConfig, config.animated, !_isSkeletonAnimated { startSkeletonAnimation(config.animation) } }) { subview in subview.recursiveLayoutSkeletonIfNeeded() } if let root = root { _flowDelegate?.didLayoutSkeletonsIfNeeded(rootView: root) } } func recursiveHideSkeleton(reloadDataAfter reload: Bool, transition: SkeletonTransitionStyle, root: UIView? = nil) { guard sk.isSkeletonActive else { return } if isHiddenWhenSkeletonIsActive { isHidden = false } _currentSkeletonConfig?.transition = transition unSwizzleLayoutSubviews() unSwizzleTraitCollectionDidChange() removeDummyDataSourceIfNeeded(reloadAfter: reload) subviewsSkeletonables.recursiveSearch(leafBlock: { recoverViewState(forced: false) removeSkeletonLayer() }) { subview in subview.recursiveHideSkeleton(reloadDataAfter: reload, transition: transition) } if let root = root { _flowDelegate?.didHideSkeletons(rootView: root) } } } private extension UIView { func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) { guard !sk.isSkeletonActive else { return } saveViewState() prepareViewForSkeleton() addSkeletonLayer(skeletonConfig: config) } func recursiveShowSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) { if isHiddenWhenSkeletonIsActive { isHidden = true } guard isSkeletonable && !sk.isSkeletonActive else { return } _currentSkeletonConfig = config swizzleLayoutSubviews() swizzleTraitCollectionDidChange() addDummyDataSourceIfNeeded() subviewsSkeletonables.recursiveSearch(leafBlock: { showSkeletonIfNotActive(skeletonConfig: config) }) { subview in subview.recursiveShowSkeleton(skeletonConfig: config) } if let root = root { _flowDelegate?.didShowSkeletons(rootView: root) } } func recursiveUpdateSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) { guard sk.isSkeletonActive else { return } _currentSkeletonConfig = config updateDummyDataSourceIfNeeded() subviewsSkeletonables.recursiveSearch(leafBlock: { if let skeletonLayer = _skeletonLayer, skeletonLayer.type != config.type { removeSkeletonLayer() addSkeletonLayer(skeletonConfig: config) } else { updateSkeletonLayer(skeletonConfig: config) } }) { subview in subview.recursiveUpdateSkeleton(skeletonConfig: config) } if let root = root { _flowDelegate?.didUpdateSkeletons(rootView: root) } } } ================================================ FILE: SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Swizzling.swift ================================================ // // 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+Swizzling.swift // // Created by Juanpe Catalán on 19/8/21. import UIKit extension UIView { @objc func skeletonLayoutSubviews() { guard Thread.isMainThread else { return } skeletonLayoutSubviews() guard sk.isSkeletonActive else { return } layoutSkeletonIfNeeded() } @objc func skeletonTraitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { skeletonTraitCollectionDidChange(previousTraitCollection) guard isSkeletonable, sk.isSkeletonActive, let config = _currentSkeletonConfig else { return } updateSkeleton(skeletonConfig: config) } func swizzleLayoutSubviews() { DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { DispatchQueue.once(token: "UIView.SkeletonView.swizzleLayoutSubviews") { swizzle(selector: #selector(UIView.layoutSubviews), with: #selector(UIView.skeletonLayoutSubviews), inClass: UIView.self, usingClass: UIView.self) self.layoutSkeletonIfNeeded() } } } 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) } } } func swizzleTraitCollectionDidChange() { DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { DispatchQueue.once(token: "UIView.SkeletonView.swizzleTraitCollectionDidChange") { swizzle(selector: #selector(UIView.traitCollectionDidChange(_:)), with: #selector(UIView.skeletonTraitCollectionDidChange(_:)), inClass: UIView.self, usingClass: UIView.self) } } } 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) } } } } ================================================ FILE: SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Transitions.swift ================================================ // Copyright © 2019 SkeletonView. All rights reserved. import UIKit extension UIView { func startTransition(transitionBlock: @escaping () -> Void) { guard let transitionStyle = _currentSkeletonConfig?.transition, transitionStyle != .none else { transitionBlock() return } if case let .crossDissolve(duration) = transitionStyle { UIView.transition(with: self, duration: duration, options: .transitionCrossDissolve, animations: transitionBlock, completion: nil) } } } ================================================ FILE: SkeletonViewCore/Sources/Supporting Files/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass ================================================ FILE: SkeletonViewCore/Sources/Supporting Files/PrivacyInfo.xcprivacy ================================================ NSPrivacyTracking NSPrivacyTrackingDomains NSPrivacyCollectedDataTypes NSPrivacyAccessedAPITypes NSPrivacyAccessedAPIType NSPrivacyAccessedAPICategoryUserDefaults NSPrivacyAccessedAPITypeReasons CA92.1 ================================================ FILE: SkeletonViewCore/Tests/Debug/SkeletonDebugTests.swift ================================================ // // 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 // // SkeletonDebugTests.swift // // Created by Juanpe Catalán on 18/8/21. import XCTest @testable import SkeletonView class SkeletonDebugTests: XCTestCase { func testSkeletonDescriptionWithViewNotSkeletonableNotReturnsSkullEmojiAndChildren() { /// given let view = UIView() let expectedDictionary: [String : Any] = [ "isSkeletonable" : false, "type" : "UIView", "reference" : "\(Unmanaged.passUnretained(view).toOpaque())" ] /// when let obtainedDictionary = view.sk.treeNode.dictionaryRepresentation /// then XCTAssertEqual(expectedDictionary.keys, obtainedDictionary.keys) } } ================================================ FILE: SkeletonViewCore/Tests/Supporting Files/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass ================================================ FILE: Translations/README_de.md ================================================ ![](../Assets/header2.jpg)

codebeat badge SkeletonView Playground

FunktionenAnleitungenInstallationVerwendungSonstigesBeitragen

**🌎 README ist auch in anderen Sprachen verfügbar: [🇬🇧](../README.md) . [🇪🇸](README_es.md) . [🇨🇳](README_zh.md) . [🇧🇷](README_pt-br.md) . [🇰🇷](README_ko.md) . [🇫🇷](README_fr.md)** Heutzutage haben fast alle Anwendungen async-Prozesse, z.B. API-Anfragen, lang laufende Prozesse, usw. Während die Prozesse arbeiten, platzieren die Entwickler in der Regel eine Ladeansicht, um den Benutzern zu zeigen, dass im Hintergrund etwas vor sich geht. **SkeletonView** wurde entwickelt, um dieses Bedürfnis zu befriedigen, indem auf eine elegante Art und Weise den Nutzern gezeigt wird, dass etwas passiert und sie gleichzeitig darauf vorbereitet, welche Inhalte sie erwarten. Viel Spaß damit! 🙂 ## - [🌟 Funktionen](#-funktionen) - [🎬 Anleitungen](#-anleitungen) - [📲 Installation](#-installation) - [🐒 Verwendung](#-verwendung) - [🌿 Sammlungen](#-sammlungen) - [🔠 Texte](#-texte) - [🦋 Erscheinungsbild](#-erscheinungsbild) - [🎨 Benutzerdefinierte Farben](#-benutzerdefinierte-farben) - [🏃‍♀️ Animationen](#️-animationen) - [🏄 Übergänge](#-übergänge) - [✨ Sonstiges](#-sonstiges) - [❤️ Beitragen](#️-beitragen) - [📢 Erwähnungen](#-erwähnungen) - [🏆 Sponsoren](#-sponsoren) - [👨🏻‍💻 Autor](#-autor) - [👮🏻 Lizenz](#-lizenz) ## 🌟 Funktionen * Einfach zu benutzen * Alle UIViews sind skelettierbar * Vollständig anpassbar * Universal (iPhone & iPad) * Freundlicher interface builder * Einfache Swift-Syntax * Leicht lesbarer code ## 🎬 Anleitungen | [![](https://img.youtube.com/vi/75kgOhWsPNA/maxresdefault.jpg)](https://youtu.be/75kgOhWsPNA)|[![](https://img.youtube.com/vi/MVCiM_VdxVA/maxresdefault.jpg)](https://youtu.be/MVCiM_VdxVA)|[![](https://img.youtube.com/vi/Qq3Evspeea8/maxresdefault.jpg)](https://youtu.be/Qq3Evspeea8)|[![](https://img.youtube.com/vi/Zx1Pg1gPfxA/maxresdefault.jpg)](https://www.youtube.com/watch?v=Zx1Pg1gPfxA) |:---: | :---: | :---: | :---: |[**SkeletonView Guides - Getting started**](https://youtu.be/75kgOhWsPNA)|[**How to Create Loading View with Skeleton View in Swift 5.2**](https://youtu.be/MVCiM_VdxVA) by iKh4ever Studio|[**Create Skeleton Loading View in App (Swift 5) - Xcode 11, 2020**](https://youtu.be/Qq3Evspeea8) by iOS Academy| [**Cómo crear una ANIMACIÓN de CARGA de DATOS en iOS**](https://www.youtube.com/watch?v=Zx1Pg1gPfxA) by MoureDev ## 📲 Installation * [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html): ```ruby pod 'SkeletonView' ``` * [Carthage](https://github.com/Carthage/Carthage): ```ruby github "Juanpe/SkeletonView" ``` * [Swift Package Manager](https://swift.org/package-manager/): ```swift dependencies: [ .package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0") ] ``` > 📣 **WICHTIG!** > > Seit Version 1.30.0 unterstützt `SkeletonView` **XCFrameworks**, wenn du es also als **XCFramework** installieren möchtest, verwende bitte stattdessen [dieses Repo](https://github.com/Juanpe/SkeletonView-XCFramework.git). ## 🐒 Verwendung Nur **3** Schritte sind erforderlich, um `SkeletonView` zu verwenden: 1️⃣ Importiere SkeletonView an der richtigen Stelle. ```swift import SkeletonView ``` 2️⃣ Lege nun fest, welche Ansichten `skelettierbar` sein sollen. Dies kannst du auf zwei Arten erreichen: **Durch code:** ```swift avatarImageView.isSkeletonable = true ``` **Durch IB/Storyboards:** ![](../Assets/storyboard.png) 3️⃣ Sobald du die Views eingestellt hast, kannst du das **Skelett** anzeigen. Dazu hast du **4** Auswahlmöglichkeiten: ```swift (1) view.showSkeleton() // Einfarbig (2) view.showGradientSkeleton() // Farbverlauf (3) view.showAnimatedSkeleton() // Einfarbig animiert (4) view.showAnimatedGradientSkeleton() // Farbverlauf animiert ``` **Vorschau**
Einfarbig
Farbverlauf
Einfarbig animiert
Farbverlauf animiert
> 📣 **WICHTIG!** > > `SkeletonView` ist rekursiv, wenn du also das Skelett in allen skelettierbaren Views anzeigen willst, musst du nur die show-Methode in der Haupt-Container-View aufrufen. Zum Beispiel mit `UIViewControllers`. ### 🌿 Sammlungen ```SkeletonView``` ist kompatibel mit ```UITableView``` und ```UICollectionView```. **UITableView** Wenn du das Skelett in ```UITableView```'s anzeigen willst, müssen diese dem ```SkeletonTableViewDataSource```-Protokoll entsprechen. ``` swift public protocol SkeletonTableViewDataSource: UITableViewDataSource { func numSections(in collectionSkeletonView: UITableView) -> Int // Standard: 1 func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? // Standard: nil func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) } ``` Wie du sehen kannst, erbt dieses Protokoll von ```UITableViewDataSource```, so dass du dieses Protokoll durch das Skelettprotokoll ersetzen kannst. Dieses Protokoll hat eine Standardimplementierung für einige Methoden. Zum Beispiel wird die Anzahl der Zeilen für jeden Abschnitt in Echtzeit berechnet: ``` swift func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int // Standard: // Es wird berechnet, wie viele Zellen benötigt werden, um die gesamte Tabellenansicht zu füllen ``` > 📣 **WICHTIG!** > > Wenn du in der obigen Methode `UITableView.automaticNumberOfSkeletonRows` zurückgibst, verhält es sich wie das Standardverhalten (d.h. es wird berechnet, wie viele Zellen benötigt werden, um den gesamten Tableview zu füllen). Es gibt nur eine Methode, die du implementieren musst, damit Skeleton die Zellen ID kennt. Diese Methode hat keine Standardimplementierung: ``` swift func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier { return "CellIdentifier" } ``` Standardmäßig entfernt die library die Zellen aus jedem indexPath, aber du kannst dies auch tun, wenn du einige Änderungen vornehmen möchtest, bevor das Skelett erscheint: ``` swift func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? { let cell = skeletonView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as? Cell cell?.textField.isHidden = indexPath.row == 0 return cell } ``` Wenn du es vorziehst, den deque-Teil der Bibliothek zu überlassen, kannst du die Zelle mit dieser Methode konfigurieren: ``` swift func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) { let cell = cell as? Cell cell?.textField.isHidden = indexPath.row == 0 } ``` Außerdem kannst du sowohl die Kopf- als auch die Fußzeilen skelettieren. Diese müssen nur dem Protokoll "SkeletonTableViewDelegate" entsprechen. ```swift public protocol SkeletonTableViewDelegate: UITableViewDelegate { func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? // standard: nil func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? // standard: nil } ``` > 📣 **WICHTIG!** > > 1️⃣ Wenn du größenvariable Zellen verwendest (**`tableView.rowHeight = UITableViewAutomaticDimension`**), ist es zwingend erforderlich, die **`estimatedRowHeight`** zu definieren. > > 2️⃣ Wenn man Elemente in einer **`UITableViewCell`** hinzufügt, sollte man sie dem **`contentView`** hinzufügen und nicht direkt in der Zelle. > > ```swift > self.contentView.addSubview(titleLabel) ✅ > self.addSubview(titleLabel) ❌ > ``` **UICollectionView** Für `UICollectionView` musst du dem Protokoll `SkeletonCollectionViewDataSource` entsprechen. ``` swift public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource { func numSections(in collectionSkeletonView: UICollectionView) -> Int // standard: 1 func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? // standard: nil func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? // standard: nil func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) func collectionSkeletonView(_ skeletonView: UICollectionView, prepareViewForSkeleton view: UICollectionReusableView, at indexPath: IndexPath) } ``` Der Rest des Prozesses ist derselbe wie bei ```UITableView``` ### 🔠 Texte ![](../Assets/multilines2.png) Wenn Elemente mit Text verwendet werden, zeichnet ```SkeletonView``` Linien, um Text zu simulieren. Du kannst einige Variablen für mehrzeilige Elemente einstellen. | Variable | Typ | Standard | Vorschau | ------- | ------- |------- | ------- | **lastLineFillPercent** | `CGFloat` | `70`| ![](../Assets/multiline_lastline.png) | **linesCornerRadius** | `Int` | `0` | ![](../Assets/multiline_corner.png) | **skeletonLineSpacing** | `CGFloat` | `10` | ![](../Assets/multiline_lineSpacing.png) | **skeletonPaddingInsets** | `UIEdgeInsets` | `.zero` | ![](../Assets/multiline_insets.png) | **skeletonTextLineHeight** | `SkeletonTextLineHeight` | `.fixed(15)` | ![](../Assets/multiline_lineHeight.png) | **skeletonTextNumberOfLines** | `SkeletonTextNumberOfLines` | `.inherited` | ![](../Assets/multiline_corner.png)
Um den Prozentsatz oder den Radius **mit Hilfe von Code** zu ändern, lege diese Variablen fest: ```swift descriptionTextView.lastLineFillPercent = 50 descriptionTextView.linesCornerRadius = 5 ``` Oder, wenn du es vorziehst, verwende **IB/Storyboard**: ![](../Assets/multiline_customize.png)
**Wie kann die Anzahl der Zeilen festgelegt werden?** Standardmäßig entspricht die Anzahl der Linien dem Wert der Variable `numberOfLines`. Und wenn es auf **null** gesetzt ist, wird berechnet, wie viele Linien benötigt werden, um das gesamte Skelett zu füllen und es zu zeichnen. Wenn du jedoch eine bestimmte Anzahl von Zeilen für das Skelett festlegen möchtest, kannst du dies mit der Variable `skeletonTextNumberOfLines` tun. Diese Variable hat zwei mögliche Werte: `inherited`, der den Wert `numberOfLines` zurückgibt, und `custom(Int)`, der die spezifische Anzahl von Zeilen zurückgibt, die als zugehöriger Wert angegeben wurde. Zum Beispiel: ```swift label.skeletonTextNumberOfLines = 3 // .custom(3) ```
> **⚠️ VERALTET!** > > **useFontLineHeight** wurde abgeschafft. Du kannst stattdessen **skeletonTextLineHeight** verwenden: > > ```swift > descriptionTextView.skeletonTextLineHeight = .relativeToFont > ``` > **📣 WICHTIG!** > > Bitte beachte, dass bei Ansichten ohne mehrere Zeilen die einzelne Zeile > als letzte Zeile betrachtet wird. ### 🦋 Erscheinungsbild Die Skelette haben ein Standardaussehen. Wenn du also die Farbe, den Farbverlauf oder Mehrlinien-Eigenschaften nicht angibst, verwendet `SkeletonView` die Standardwerte. Standardwerte: - **tintColor**: `UIColor` - *standard: `.skeletonDefault` (gleich wie `.clouds`, aber anpassungsfähig an den dunklen Modus)* - **gradient**: SkeletonGradient - *standard: `SkeletonGradient(baseColor: .skeletonDefault)`* - **multilineHeight**: `CGFloat` - *standard: 15* - **multilineSpacing**: `CGFloat` - *standard: 10* - **multilineLastLineFillPercent**: `Int` - *standard: 70* - **multilineCornerRadius**: `Int` - *standard: 0* - **skeletonCornerRadius**: `CGFloat` (IBInspectable)(Macht ihre Skelettansicht mit Ecken) - *standard: 0* Um diese Standardwerte zu erhalten, kannst du `SkeletonAppearance.default` verwenden. Mit dieser Variable kannst du auch die Werte einstellen: ```swift SkeletonAppearance.default.multilineHeight = 20 SkeletonAppearance.default.tintColor = .green ``` > **⚠️ VERALTET!** > > **useFontLineHeight** wurde abgeschafft. Du kannst stattdessen **textLineHeight** verwenden: > > ```swift > SkeletonAppearance.default.textLineHeight = .relativeToFont > ``` ### 🎨 Benutzerdefinierte Farben Du kannst entscheiden, mit welcher Farbe das Skelett eingefärbt wird. Du brauchst nur die gewünschte Farbe oder den gewünschten Farbverlauf als Parameter zu übergeben. **Verwendung von Volltonfarben** ```swift view.showSkeleton(usingColor: UIColor.gray) // Einfarbig // oder view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0)) ``` **Verwendung von Farbverläufen** ```swift let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue) view.showGradientSkeleton(usingGradient: gradient) // Farbverlauf ``` Außerdem bietet **SkeletonView** 20 flache Farben 🤙🏼. ```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...``` ![](../Assets/flatcolors.png) ###### Bild von der Website [https://flatuicolors.com](https://flatuicolors.com) entnommen ### 🏃‍♀️ Animationen **SkeletonView** hat zwei eingebaute Animationen, *pulse* für einfarbige Skelette und *sliding* für Farbverläufe. Außerdem ist es sehr einfach, eine eigene Skelettanimationen zu erstellen. Skeleton bietet die Funktion `showAnimatedSkeleton`, die eine Closure ```SkeletonLayerAnimation``` besitzt, in der du deine eigene Animation definieren kannst. ```swift public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation ``` Du kannst die Funktion wie folgt aufrufen: ```swift view.showAnimatedSkeleton { (layer) -> CAAnimation in let animation = CAAnimation() // Passe hier ihre Animation an return animation } ``` Es ist ein ```SkeletonAnimationBuilder``` verfügbar. Es ist ein Builder um ```SkeletonLayerAnimation``` zu erstellen. Heute kann man **Gleitanimationen** für Farbverläufe erstellen, indem man die **Richtung** und die **Dauer** der Animation festlegt (Standard = 1,5s). ```swift // func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight) view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation) ``` ```GradientDirection``` ist ein enum, mit den folgenden cases: | Richtung | Vorschau |------- | ------- | .leftRight | ![](../Assets/sliding_left_to_right.gif) | .rightLeft | ![](../Assets/sliding_right_to_left.gif) | .topBottom | ![](../Assets/sliding_top_to_bottom.gif) | .bottomTop | ![](../Assets/sliding_bottom_to_top.gif) | .topLeftBottomRight | ![](../Assets/sliding_topLeft_to_bottomRight.gif) | .bottomRightTopLeft | ![](../Assets/sliding_bottomRight_to_topLeft.gif) > **😉 TRICK!** > > Es gibt noch eine andere Möglichkeit, Schiebeanimationen zu erstellen, indem man einfach diese Abkürzung benutzt: > > ```swift > let animation = GradientDirection.leftToRight.slidingAnimation() > ``` ### 🏄 Übergänge **SkeletonView** hat eingebaute Übergänge, um die Skelette auf eine *ruhigere* Weise **ein- und auszublenden** 🤙. Um den Übergang zu benutzen, füge einfach den Parameter ```transition``` zu ihrer Funktion ```showSkeleton()``` oder ```hideSkeleton()``` mit der Übergangszeit hinzu, wie hier: ```swift view.showSkeleton(transition: .crossDissolve(0.25)) //Einblenden des Skeleton mit Querauflösen-Übergang mit 0,25 Sekunden Übergangszeit view.hideSkeleton(transition: .crossDissolve(0.25)) //Ausblenden des Skeleton mit Querauflösen-Übergang mit 0,25 Sekunden Übergangszeit ``` Der Standardwert ist `crossDissolve(0.25)` **Vorschau**
Keinen
Querauflösen
## ✨ Sonstiges **Hierarchie** Da ```SkeletonView``` rekursiv ist, und wir wollen, dass Skeleton sehr effizient ist, wollen wir die Rekursion so schnell wie möglich beenden. Aus diesem Grund musst du die Container-Ansicht auf `Skeletonable` setzen, denn Skeleton wird aufhören, nach `skeletonable` Unteransichten zu suchen, sobald eine Ansicht nicht skelettierbar ist, und damit die Rekursion beenden. Denn ein Bild sagt mehr als tausend Worte: In diesem Beispiel haben wir einen `UIViewController` mit einem `ContainerView` und einem `UITableView`. Wenn der View fertig ist, zeigen wir das Skelett mit dieser Methode: ``` view.showSkeleton() ``` > ```isSkeletonable```= ☠️ | Konfiguration | Ergebnis| |:-------:|:-------:| | | | | | | | | | || | | | | | | | **Skelettansichten-Layout** Manchmal kann es vorkommen, dass das Skelett-Layout nicht zu ihrem Layout passt, weil sich die Grenzen der übergeordneten Ansicht geändert haben. ~Zum Beispiel, wenn du das Gerät drehst. Du kannst die Skelettansichten wie folgt neu anordnen: ```swift override func viewDidLayoutSubviews() { view.layoutSkeletonIfNeeded() } ``` > 📣 **WICHTIG!** > > Du solltest diese Methode nicht aufrufen. Ab **Version 1.8.1** brauchst du diese Methode nicht mehr aufzurufen, die Bibliothek macht das automatisch. Du kannst diese Methode also **NUR** in den Fällen verwenden, in denen du das Layout des Skeletts manuell aktualisieren musst. **Skelett aktualisieren** Du kannst die Konfiguration des Skeletts jederzeit ändern, wie z.B. seine Farbe, Animation, etc. mit den folgenden Methoden: ```swift (1) view.updateSkeleton() // Einfarbig (2) view.updateGradientSkeleton() // Farbverlauf (3) view.updateAnimatedSkeleton() // Einfarbig animiert (4) view.updateAnimatedGradientSkeleton() // Farbverlauf animiert ``` **Ausblenden von Ansichten, wenn die Animation beginnt** Manchmal möchte man einige Ansichten ausblenden, wenn die Animation beginnt. Dafür gibt es eine praktische Variable, die man benutzen kann: ```swift view.isHiddenWhenSkeletonIsActive = true // Dies funktioniert nur, wenn isSkeletonable = true ``` **Benutzerinteraktion nicht ändern, wenn das Skelett aktiv ist** Standardmäßig ist die Benutzerinteraktion für skelettierte Elemente deaktiviert, aber wenn du den Indikator für die Benutzerinteraktion nicht ändern willst, wenn das Skelett aktiv ist, kannst du die Variable `isUserInteractionDisabledWhenSkeletonIsActive` verwenden: ```swift view.isUserInteractionDisabledWhenSkeletonIsActive = false // Die Ansicht wird aktiv sein, wenn das Skelett aktiv ist. ``` **Zeilenhöhe der Schriftart für die Skelettlinien in Labels nicht verwenden** `False`, um die automatische Anpassung des Skeletts an die Schrifthöhe für ein `UILabel` oder `UITextView` zu deaktivieren. Standardmäßig wird die Höhe der Skelettlinien automatisch an die Schrifthöhe angepasst, um den Text im Label-Rect genauer wiederzugeben, anstatt die Bounding Box zu verwenden. ```swift label.useFontLineHeight = false ``` **Skelett verzögert anzeigen** Du kannst die Darstellung des Skeletts verzögern, wenn die Ansichten schnell aktualisiert werden. ```swift func showSkeleton(usingColor: UIColor, animated: Bool, delay: TimeInterval, transition: SkeletonTransitionStyle) ``` ```swift func showGradientSkeleton(usingGradient: SkeletonGradient, animated: Bool, delay: TimeInterval, transition: SkeletonTransitionStyle) ``` **Debug** Um die Debug-Aufgaben zu erleichtern, wenn etwas nicht richtig funktioniert, hat **`SkeletonView`** einige neue Werkzeuge. Erstens, `UIView` hat eine Variable mit seinen Skelett-Informationen zur Verfügung: ```swift var sk.skeletonTreeDescription: String ``` Außerdem kannst du den neuen **Debug-Modus** aktivieren. Füge einfach die Umgebungsvariable `SKELETON_DEBUG` hinzu um ihn zu aktivieren. ![](../Assets/debug_mode.png) Wenn das Skelett dann erscheint, kannst du die Ansichtshierarchie in der Xcode-Konsole sehen. ``` { "type" : "UIView", // UITableView, UILabel... "isSkeletonable" : true, "reference" : "0x000000014751ce30", "children" : [ { "type" : "UIView", "isSkeletonable" : true, "children" : [ ... ], "reference" : "0x000000014751cfa0" } ] } ``` **Unterstützte OS & SDK-Versionen** - iOS 9.0+ - tvOS 9.0+ - Swift 5.3 ## ❤️ Beitragen Dies ist ein Open-Source-Projekt, du kannst also gerne dazu beitragen. Wie? - Eröffne ein [issue](https://github.com/Juanpe/SkeletonView/issues/new). - Sende Feedback über [email](mailto://juanpecatalan.com). - Schlage deine eigenen Korrekturen und Vorschläge vor und öffne einen Pull Request mit den Änderungen. Siehe [alle Mitwirkenden](https://github.com/Juanpe/SkeletonView/graphs/contributors) Für weitere Informationen lies bitte die [contributing guidelines](https://github.com/Juanpe/SkeletonView/blob/main/CONTRIBUTING.md). ## 📢 Erwähnungen - [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start) - [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful) - [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65) - [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9) - [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899) - [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d) - [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/) - [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204) - [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759) - [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) ## 🏆 Sponsoren Open-Source-Projekte leben nicht lange ohne ihre Hilfe. Wenn du **SkeletonView** nützlich findest, ziehe bitte in Betracht, dieses Projekt zu unterstützen, indem du ein Sponsor wirst. Werde Sponsor über [GitHub Sponsors] () :heart: ## 👨🏻‍💻 Autor [Juanpe Catalán](http://www.twitter.com/JuanpeCatalan) Buy me a coffee ## 👮🏻 Lizenz ``` MIT License Copyright (c) 2017 Juanpe Catalán Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ================================================ FILE: Translations/README_es.md ================================================ ![](../Assets/header2.jpg)

codebeat badge SkeletonView Playground

DestacadoInstalación¿Cómo funciona?MisceláneaContribuir

**🌎 README está disponible en estos idiomas: [🇬🇧](../README.md) . [🇨🇳](README_zh.md) . [🇧🇷](README_pt-br.md) . [🇰🇷](README_ko.md) . [🇫🇷](README_fr.md) . [🇩🇪](README_de.md)** Hoy en día, La mayoría de las apps tiene procesos asíncronos, como peticiones a una API, procesos que tardan mucho tiempo, etc. Mientras estos procesos se están ejecutando, se suele mostrar un aburrido spinner indicando que algo está pasando. **SkeletonView** ha sido desarrollada para cubrir esta necesidad, un elegante manera de decirle a los usarios que algo se está procesando y además prepararlos, visualmente, para el contenido que están esperando. Enjoy it! 🙂 ## - [](#) - [🌟 Destacado](#-destacado) - [🎬 Videotutoriales](#-videotutoriales) - [📲 Instalación](#-instalación) - [🐒 ¿Cómo funciona?](#-cómo-funciona) - [](#-1) - [🌿 Colecciones](#-colecciones) - [🔠 Textos](#-textos) - [🦋 Apariencia](#-apariencia) - [🎨 Colores](#-colores) - [Imagen extraída de la web https://flatuicolors.com](#imagen-extraída-de-la-web-httpsflatuicolorscom) - [🏃‍♀️ Animaciones](#️-animaciones) - [🏄 Transiciones](#-transiciones) - [✨ Miscelánea](#-miscelánea) - [❤️ Contributing](#️-contributing) - [📢 Menciones](#-menciones) - [👨🏻‍💻 Autor](#-autor) - [👮🏻 Licencia](#-licencia) ## 🌟 Destacado * Fácil de usar * Todas las UIViews son skeletonables * Personalizable * Universal (iPhone & iPad) * Interface Builder friendly ## 🎬 Videotutoriales | [![](https://img.youtube.com/vi/75kgOhWsPNA/maxresdefault.jpg)](https://youtu.be/75kgOhWsPNA)|[![](https://img.youtube.com/vi/MVCiM_VdxVA/maxresdefault.jpg)](https://youtu.be/MVCiM_VdxVA)|[![](https://img.youtube.com/vi/Qq3Evspeea8/maxresdefault.jpg)](https://youtu.be/Qq3Evspeea8)|[![](https://img.youtube.com/vi/ZOoPtBwDRT0/maxresdefault.jpg)](https://youtu.be/ZOoPtBwDRT0) |:---: | :---: |:---: | :---: |[**SkeletonView Guides - Getting started**](https://youtu.be/75kgOhWsPNA)|[**How to Create Loading View with Skeleton View in Swift 5.2**](https://youtu.be/MVCiM_VdxVA) by iKh4ever Studio|[**Create Skeleton Loading View in App (Swift 5) - Xcode 11, 2020**](https://youtu.be/Qq3Evspeea8) by iOS Academy| [**Add An Elegant Loading Animation in Swift***](https://youtu.be/ZOoPtBwDRT0) by Gary Tokman ## 📲 Instalación * [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html): ```ruby pod 'SkeletonView' ``` * [Carthage](https://github.com/Carthage/Carthage): ```ruby github "Juanpe/SkeletonView" ``` * [Swift Package Manager](https://swift.org/package-manager/): ```swift dependencies: [ .package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0") ] ``` ## 🐒 ¿Cómo funciona? Solo necesitas **3** pasos para usar `SkeletonView`: 1️⃣ Importa SkeletonView en el archivo donde vayas a utilizarlo. ```swift import SkeletonView ``` 2️⃣ Ahora, debes indicar qué elementos de tu vista son `skeletonables` **Con código:** ```swift avatarImageView.isSkeletonable = true ``` **Con IB/Storyboards:** ![](../Assets/storyboard.png) 3️⃣ Una vez indicado, solo tienes que mostrar el **skeleton**. Tienes **4** opciones: ```swift (1) view.showSkeleton() // Sólido (2) view.showGradientSkeleton() // Degradado (3) view.showAnimatedSkeleton() // Sólido animado (4) view.showAnimatedGradientSkeleton() // Degradado animado ``` **Vista previa**
Sólido
Degradado
Sólido Animado
Degradado Animado
> 📣 **¡IMPORTANTE!** > > `SkeletonView` es recursivo. Por lo que si tienes una vsita que contiene varios elementos skeletonables, solo tienes queenecu For example, with `UIViewControllers`. ## ### 🌿 Colecciones `SkeletonView` es compatible con `UITableView` and `UICollectionView`. **UITableView** Si quieres mostrar el skeleton en un `UITableView`, necesitas conformar el protocolo `SkeletonTableViewDataSource`. ``` swift public protocol SkeletonTableViewDataSource: UITableViewDataSource { // Por defecto: 1 func numSections(in collectionSkeletonView: UITableView) -> Int // Por defecto: // Calcula cuantas celdas necesita para rellenar todo el frame. func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier } ``` Este protocolo hereda de `UITableViewDataSource`, por lo que puedes reemplazar este protocolo por el protocolo de skeleton sin perder ninguna funcionalidad. El único método que es obligatorio implementar es `cellIdentifierForRowAt`, donde tienes que indicar el identificador de la celda. **Ejemplo** ``` swift func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier { return "CellIdentifier" } ``` Además, tu puedes mostrar el skeleton en las headers y en los footers, conformando el protocolo `SkeletonTableViewDelegate`. ```swift public protocol SkeletonTableViewDelegate: UITableViewDelegate { func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? // default: nil func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? // default: nil } ``` > 📣 **¡IMPORTANTE!** > > 1️⃣ Si estás usando celdas con altura dinámica (**`tableView.rowHeight = UITableViewAutomaticDimension`**), es obligatorio definir el **`estimatedRowHeight`**. > > 2️⃣ Cuando añades elemetos a una **`UITableViewCell`**, debes añadirlo al **`contentView`** y no a la celda directamente. > ```swift > cell.contentView.addSubview(titleLabel) ✅ > cell.addSubview(titleLabel) ❌ > ``` **UICollectionView** Para los `UICollectionView`, debes conformar el protocolo `SkeletonCollectionViewDataSource`. ``` swift public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource { func numSections(in collectionSkeletonView: UICollectionView) -> Int // Por defecto: 1 func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? // Por defecto: nil } ``` El resto del proceso es exactamente igual que con las `UITableView`. ### 🔠 Textos ![](../Assets/multilines2.png) Cuando usas elementos que contienen texto,`SkeletonView` dibujo líneas para simular el texto. Además, puedes decidir el número de líneas. Si `numberOfLines` es igual a **0**, se calculará automáticamente el número de líneas necesarias para ocupar todo el frame. Sin embargo, si es un número mayor que cero, solo se dibujarán esas líneas. Puedes especificar algunos atributos para estos elementos: | Atributo | Valores | Por defecto | Vista previa | ------- | ------- |------- | ------- | **Porcentaje de relleno** de la última línea. | `0...100` | `70%` | ![](../Assets/multiline_lastline.png) | **Radio de las esquinas** de las líneas. | `0...10` | `0` | ![](../Assets/multiline_corner.png) Para modificar alguno de los valores lo puedes hacer **con código**:: ```swift descriptionTextView.lastLineFillPercent = 50 descriptionTextView.linesCornerRadius = 5 ``` O usando **IB/Storyboards**: ![](../Assets/multiline_customize.png) ### 🦋 Apariencia Los skeletons tiene una apariencia por defecto. Así, cuando no especificas el color, el degradado o las propiedades para las multiíneas, `SkeletonView` usa estos valores. Valores por defecto: - **tintColor**: `UIColor` - *default: `.skeletonDefault` (igual que `.clouds` pero se adapta al dark mode)* - **gradient**: `SkeletonGradient` - *default: `SkeletonGradient(baseColor: .skeletonDefault)`* - **multilineHeight**: `CGFloat` - *default: 15* - **multilineSpacing**: `CGFloat` - *default: 10* - **multilineLastLineFillPercent**: `Int` - *default: 70* - **multilineCornerRadius**: `Int` - *default: 0* - **skeletonCornerRadius**: `CGFloat` (IBInspectable) - *default: 0* Para obtener o modificar estos valores tu puedes usar `SkeletonAppearance.default`: ```swift SkeletonAppearance.default.multilineHeight = 20 SkeletonAppearance.default.tintColor = .green ``` ### 🎨 Colores Puedes decidir de qué color se tinta tu skeleton. Solo tienes que indicarlo pasándolo como parámetro: **Usando colores sólidos** ```swift view.showSkeleton(usingColor: UIColor.gray) // o view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0)) ``` **Usando degradados** ``` swift let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue) view.showGradientSkeleton(usingGradient: gradient) ``` Además, **SkeletonView** añade 20 colores flat 🤙🏼 ```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...``` ![](../Assets/flatcolors.png) ###### Imagen extraída de la web [https://flatuicolors.com](https://flatuicolors.com) ### 🏃‍♀️ Animaciones **SkeletonView** tiene pre-definidas dos animaciones, *pulse* para skeleton sólidos y *sliding* para degradados. Además, usando el método `showAnimatedSkeleton`, podemos incluir la `animation` que es de tipo `SkeletonLayerAnimation`, un bloque donde tu puedes crear tus propias animaciones: ```swift public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation ``` Tu código quedaría así: ```swift view.showAnimatedSkeleton { (layer) -> CAAnimation in let animation = CAAnimation() // Personaliza la animación aquí return animation } ``` `SkeletonAnimationBuilder` es un builder que permite crear `SkeletonLayerAnimation`. Por ejemplo, tu puedes crear **sliding animations** para los degradados, decidiendo la **direction** y indicando la **duration** de la animación (default = 1.5s). ```swift // func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight) view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation) ``` ```GradientDirection``` es un enumerado con estos `cases`: | Dirección | Vista previa |------- | ------- | .leftRight | ![](../Assets/sliding_left_to_right.gif) | .rightLeft | ![](../Assets/sliding_right_to_left.gif) | .topBottom | ![](../Assets/sliding_top_to_bottom.gif) | .bottomTop | ![](../Assets/sliding_bottom_to_top.gif) | .topLeftBottomRight | ![](../Assets/sliding_topLeft_to_bottomRight.gif) | .bottomRightTopLeft | ![](../Assets/sliding_bottomRight_to_topLeft.gif) > **😉 ¡Truco!** > > Puedes crear una animación sliding, con este shortcut: > ```swift > let animation = GradientDirection.leftToRight.slidingAnimation() > ``` ### 🏄 Transiciones **SkeletonView** tiene algunas transiciones listas para usarse cuando **aparece** o se **oculta**. Puedes especificarla así: ```swift view.showSkeleton(transition: .crossDissolve(0.25)) view.hideSkeleton(transition: .crossDissolve(0.25)) ``` La transición por defecto es `crossDissolve(0.25)` **Vista previa**
Sin transición
Cross dissolve
## ✨ Miscelánea **Jerarquía** `SkeletonView` es recursivo, pero para que sea eficiente, tenemos que pararar la recursión tan pronto como sea posible. Por este motivo, el contenedor de las vistas debe ser **`skeletonable`**, porque `SkeletonView` parará de buscar vistas skeletonables cuando encuentre una que no lo sea, dentro de la jerarquía. Como una imagen vale más que mil palabras: En este ejemplo, tenemos un `UIViewController` con un `containerView` y una `UITableView`. Cuando la vista está lista, para mostrar el skeleton ejecutamos el método: ``` view.showSkeleton() ``` > `isSkeletonable`= ☠️ | Configuración | Resultado| |:-------:|:-------:| | | | | | | | | | || | | | | | | | **Jerarquía en las colecciones** Esta ilustración muestra como deberías específicar qué elementos son skeletonables cuando estás usando una `UITableView`: **Actualiza el skeleton** Puedes cambiar la configuración del skeleton, como el color, la animación, etc, con los siguientes métodos: ```swift (1) view.updateSkeleton() // Sólido (2) view.updateGradientSkeleton() // Degradado (3) view.updateAnimatedSkeleton() // Sólido animado (4) view.updateAnimatedGradientSkeleton() // Degradado animado ``` **Debug** Para facilitar las tareas de debug cuando algo no está funcionando bien, **`SkeletonView`** tiene una nueva herramienta. Primero, `UIView` tiene una nueva propiedad que contiene toda la info del skeleton: ```swift var skeletonDescription: String ``` Y es representada de la siguiente manera: ![](../Assets/debug_description.png) Para activar el **modo debug**. Solo tienes que añadir una variable de entorno con esta clave `SKELETON_DEBUG` y activarla. ![](../Assets/debug_mode.png) Entonces, cuando el skeleton aparece, tu podrás ver la jerarquía de vistas en la consola de Xcode.
Abre para ver un ejemplo
**OS Soportado & Versiones SDK** * iOS 9.0+ * tvOS 9.0+ * Swift 5 ## ❤️ Contributing Esto es un proyecto open source, siéntete libre de contribuir. ¿Cómo? - Abre un [issue](https://github.com/Juanpe/SkeletonView/issues/new). - Envía feedback a través del [email](mailto://juanpecatalan.com). - Propone tus propies fixes, sugerencias y abre una Pull Request con los cambios. Échale un vistazo a [los que ya han contribuído](https://github.com/Juanpe/SkeletonView/graphs/contributors) Para más información, por favor, lee la [guía de contribución](https://github.com/Juanpe/SkeletonView/blob/main/CONTRIBUTING.md). ## 📢 Menciones - [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start) - [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful) - [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65) - [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9) - [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899) - [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d) - [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/) - [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204) - [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759) - [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) ## 👨🏻‍💻 Autor [Juanpe Catalán](http://www.twitter.com/JuanpeCatalan) Buy me a coffee ## 👮🏻 Licencia ``` MIT License Copyright (c) 2017 Juanpe Catalán Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ================================================ FILE: Translations/README_fr.md ================================================ ![](../Assets/header2.jpg)

codebeat badge SkeletonView Playground
Twitter: @JuanpeCatalan PayPal Licence

**🌎 Traductions: [🇬🇧](../README.md) . [🇨🇳](README_zh.md) . [🇧🇷](README_pt-br.md) . [🇰🇷](README_ko.md) . [🇫🇷](README_fr.md) . [🇩🇪](README_de.md)** Aujourd'hui, presque toutes les applications ont des processus asynchrones, tels que les requêtes Api, les processus de longue durée, etc. Et pendant que les processus fonctionnent, les développeurs affichent généralement une vue de chargement pour montrer aux utilisateurs que quelque chose se passe. "SkeletonView" a été créé pour répondre à ce besoin, une manière élégante de montrer aux utilisateurs que quelque chose se passe et aussi de les préparer aux contenus qu'ils attendent. Profitez-en! 🙂 - [🌟 Caractéristiques](#-caractéristiques) - [🎬 Guides](#-guides) - [📲 Installation](#-installation) - [Utilisation de CocoaPods](#utilisation-de-cocoapods) - [Utilisation de Carthage](#utilisation-de-carthage) - [Utilisation du gestionnaire de paquets Swift](#utilisation-du-gestionnaire-de-paquets-swift) - [🐒 Mode d'emploi](#-mode-demploi) - [Extra](#extra) - [Mise en page des vues squelettes](#mise-en-page-des-vues-squelettes) - [Mise à jour de la configuration du squelette](#mise-à-jour-de-la-configuration-du-squelette) - [🌿 Collections](#-collections) - [UITableView](#uitableview) - [UICollectionView](#uicollectionview) - [📰 Texte multiligne](#-texte-multiligne) - [🎛 Personnaliser](#-personnaliser) - [🎨 Couleurs personnalisées](#-couleurs-personnalisées) - [Image tirée du site web https://flatuicolors.com](#image-tirée-du-site-web-httpsflatuicolorscom) - [🦋 Présentation](#-présentation) - [🤓 Animations personnalisées](#-animations-personnalisées) - [🏄 Transitions](#-transitions) - [👨👧👦 Hiérarchie](#-hiérarchie) - [🔬 Débugger](#-débugger) - [📚 Documentation](#-documentation) - [📋 Versions OS et SDK supportées](#-versions-os-et-sdk-supportées) - [📬 Prochaines étapes](#-prochaines-étapes) - [❤️ Contribuer](#️-contribuer) - [Projet généré avec SwiftPlate](#projet-généré-avec-swiftplate) - [📢 Mentions](#-mentions) - [👨🏻‍💻 Auteur](#-auteur) - [👮🏻 Licence](#-licence) ## 🌟 Caractéristiques - [x] Facile à utiliser - [x] Tous les UIViews sont squelettisables - [x] Entièrement personnalisable - [x] Universel (iPhone et iPad) - [x] Interface Builder friendly - [x] Syntaxe Swift simple - [x] Base de code légère et lisible ## 🎬 Guides [](https://youtu.be/75kgOhWsPNA) ## 📲 Installation #### Utilisation de [CocoaPods](https://cocoapods.org) Editez votre "podfile" et spécifiez la dépendance: ```ruby pod "SkeletonView" ``` #### Utilisation de [Carthage](https://github.com/carthage) Modifiez votre "Cartfile" et spécifiez la dépendance: ```bash github "Juanpe/SkeletonView" ``` #### Utilisation du [gestionnaire de paquets Swift](https://github.com/apple/swift-package-manager) Une fois que vous avez configuré votre paquet Swift, ajouter `SkeletonView` comme dépendance est aussi facile que de l'ajouter à la valeur des `dépendances` de votre `Package.swift`. ```swift dependencies: [ .package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0") ] ``` ## 🐒 Mode d'emploi Seulement **3** étapes nécessaires pour utiliser `SkeletonView` : **1.** Importer SkeletonView au bon endroit. ```swift import SkeletonView ``` **2.** Maintenant, définissez les vues qui seront `squelettisables`. Vous y arrivez de deux façons : **En utilisant du code:** ```swift avatarImageView.isSkeletonable = true ``` **Utilisation des IB/Storyboards:** ![](../Assets/storyboard.png) **Une fois que vous avez défini les vues, vous pouvez montrer le **squelette**. Pour le faire, vous avez quatre choix : ```swift (1) view.showSkeleton() // Solide (2) view.showGradientSkeleton() // Gradient (3) view.showAnimatedSkeleton() // Solide animé (4) view.showAnimatedGradientSkeleton() // Gradient animé ``` **Preview**
Solide
Gradient
Animé Solide
Animé Gradient
> **IMPORTANT!** >>```SkeletonView``` est récursif, donc si vous voulez montrer le squelette dans toutes les vues squelettables, il vous suffit d'appeler la méthode `show` dans la vue principale du conteneur. Par exemple, avec UIViewControllers ### Extra #### Mise en page des vues squelettes Il arrive que le squelette ne corresponde pas à votre mise en page parce que les limites de la vue parent ont changé. ~Par exemple, la rotation de l'appareil.~ Vous pouvez relayer les vues du squelette de cette manière : ```swift override func viewDidLayoutSubviews() { view.layoutSkeletonIfNeeded() } ``` ⚠️⚠️ Vous ne devriez pas appeler cette méthode. À partir de la *version 1.8.1*, vous n'avez pas besoin d'appeler cette méthode, la bibliothèque le fait automatiquement. Vous pouvez donc utiliser cette méthode *seulement* dans les cas où vous devez mettre à jour manuellement la présentation du squelette. #### Mise à jour de la configuration du squelette Vous pouvez modifier la configuration du squelette à tout moment comme sa couleur, son animation, etc : ```swift (1) view.updateSkeleton() // Solide (2) view.updateGradientSkeleton() // Gradient (3) view.updateAnimatedSkeleton() // Solid animated (4) view.updateAnimatedGradientSkeleton() // Gradient animé ``` ### 🌿 Collections Maintenant, `SkeletonView` est compatible avec `UITableView` et `UICollectionView`. #### UITableView Si vous voulez montrer le squelette dans un `UITableView`, vous devez vous conformer au protocole `SkeletonTableViewDataSource`. ``` swift public protocol SkeletonTableViewDataSource: UITableViewDataSource { func numSections(in collectionSkeletonView: UITableView) -> Int func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? } ``` Comme vous pouvez le voir, ce protocole hérite de `UITableViewDataSource`, vous pouvez donc remplacer ce protocole par le protocole squelette. Ce protocole a une implémentation par défaut: ``` swift func numSections(in collectionSkeletonView: UITableView) -> Int // Par défaut: 1 ``` ``` swift func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int // Par défaut: // Il calcule le nombre de cellules nécessaires pour remplir l'ensemble du tableau ``` ``` swift func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? // Par défaut: nil ``` ``` swift func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? // Par défaut: nil ``` Il n'y a qu'une seule méthode à mettre en œuvre pour faire connaître au Squelette l'identifiant de la cellule. Cette méthode n'a pas d'implémentation par défaut : ``` swift func collectionSkeletonView(_ skeletonView : UITableView, cellIdentifierForRowAt indexPath : IndexPath) -> ReusableCellIdentifier ``` **Exemple** ``` swift func collectionSkeletonView(_ skeletonView : UITableView, cellIdentifierForRowAt indexPath : IndexPath) -> ReusableCellIdentifier { return "CellIdentifier". } ``` > **IMPORTANT!** > Si vous utilisez des cellules redimensionnables (`tableView.rowHeight = UITableViewAutomaticDimension` ), il est obligatoire de définir la `estimatedRowHeight`. 👩🏼 **Comment préciser quels éléments sont squelettisables ? Voici une illustration qui montre comment vous devez spécifier quels éléments sont squelettisables lorsque vous utilisez une `UITableView` : ![](../Assets/tableview_scheme.png) Comme vous pouvez le voir, nous devons faire `skeletonable` la tableview, la cellule et les éléments de l'interface visuelle, mais nous n'avons pas besoin de faire `skeletonable` le `contentView`. #### UICollectionView Pour ```UICollectionView```, vous devez conformer le protocole `SkeletonCollectionViewDataSource`. ``` swift public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource { func numSections(in collectionSkeletonView: UICollectionView) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier } ``` Le reste du processus ressemble à une `UITableView`. ### 📰 Texte multiligne ![](../Assets/multilines2.png) Lorsqu'on utilise des éléments avec du texte, `SkeletonView` dessine des lignes pour simuler le texte. En outre, vous pouvez décider combien de lignes vous voulez. Si `numberOfLines` est réglé à zéro, il calculera le combien de lignes sont nécessaires pour remplir tout le squelette et il sera dessiné. Au contraire, si vous le réglez sur un, deux ou tout autre nombre supérieur à zéro, il ne dessinera que ce nombre de lignes. ##### 🎛 Personnaliser Vous pouvez définir certaines propriétés pour les éléments multilignes. | Propriété | Valeurs | Par défaut | Aperçu | ------- | ------- |------- | ------- | **Pourcentage de remplissage** de la dernière ligne. | `0...100` | `70%` | ![](../Assets/multiline_lastline.png) | **Corner radius** des lignes. (**NEW**) | `0...10` | `0` | ![](../Assets/multiline_corner.png) Pour modifier le pourcentage ou le rayon **à l'aide du code**, définissez les propriétés : ``` swift descriptionTextView.lastLineFillPercent = 50 descriptionTextView.linesCornerRadius = 5 ``` Ou, si vous préférez, utilisez l'**IB/Storyboard** : ![](../Assets/multiline_customize.png) ### 🎨 Couleurs personnalisées Vous pouvez décider la couleur du squelette. Il vous suffit de passer comme paramètre la couleur ou le gradient que vous souhaitez. **Utiliser des couleurs solides** ``` swift view.showSkeleton(usingColor : UIColor.gray) // Solide // ou view.showSkeleton(usingColor : UIColor(red : 25.0, green : 30.0, blue : 255.0, alpha : 1.0)) ``` **Utilisation des gradients** ``` swift let gradient = SkeletonGradient(baseColor : UIColor.midnightBlue) view.showGradientSkeleton(usingGradient : gradient) // Gradient ``` En outre, `SkeletonView` dispose de 20 couleurs unies 🤙🏼 `UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...` ![](../Assets/flatcolors.png) ###### Image tirée du site web [https://flatuicolors.com](https://flatuicolors.com) ### 🦋 Présentation **NOUVEAU** Les squelettes ont une apparence par défaut. Ainsi, lorsque vous ne spécifiez pas la couleur, le gradient ou les propriétés multilignes, `SkeletonView` utilise les valeurs par défaut. Valeurs par défaut : - **tintColor** : UIColor - *défaut : `.skeletonDefault` (comme `.clouds` mais adaptable au thème sombre)* - **gradient** : SkeletonGradient - *défaut : `SkeletonGradient(baseColor : .skeletonDefault)`* - **multilineHeight** : CGFloat - *défaut : 15* - **multilineSpacing** : CGFloat - *défaut : 10* - **multilineLastLineFillPercent** : Int - *défaut : 70* - **multilineCornerRadius** : Int - *défaut : 0* - **skeletonCornerRadius** : CGFloat (IBInspectable) (Faites votre vue squelette avec des coins) - *défaut : 0* Pour obtenir ces valeurs par défaut, vous pouvez utiliser `SkeletonAppearance.default`. En utilisant cette propriété, vous pouvez également définir les valeurs : ``` swift SkeletonAppearance.default.multilineHeight = 20 SkeletonAppearance.default.tintColor = .green ``` ### 🤓 Animations personnalisées `SkeletonView` a deux animations intégrées, *pulse* pour les squelettes solides et *sliding* pour les gradients. De plus, si vous voulez faire votre propre animation de squelette, c'est très facile. Skeleton fournit la fonction `showAnimatedSkeleton` qui possède une fermeture `SkeletonLayerAnimation` où vous pouvez définir votre animation personnalisée. ``` swift public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation ``` Vous pouvez appeler la fonction comme ceci : ```swift view.showAnimatedSkeleton { (layer) -> CAAnimation in let animation = CAAnimation() // Personnalisez ici votre animation return animation } ``` `SkeletonAnimationBuilder` est disponible. C'est un constructeur pour faire `SkeletonLayerAnimation`. Aujourd'hui, vous pouvez créer des **animations glissantes** pour les gradients, en décidant de la **direction** et en fixant la **durée** de l'animation (par défaut = 1,5s). ```swift // func makeSlidingAnimation(withDirection direction : GradientDirection, duration : CFTimeInterval = 1.5) -> SkeletonLayerAnimation let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection : .leftToRight) view.showAnimatedGradientSkeleton(usingGradient : gradient, animation : animation) ``` `GradientDirection` est une `enum`, avec ces cas : | Direction | Aperçu |------- | ------- | .leftRight | ![](../Assets/sliding_left_to_right.gif) | .rightLeft | ![](../Assets/sliding_right_to_left.gif) | .topBottom | ![](../Assets/sliding_top_to_bottom.gif) | .bottomTop | ![](../Assets/sliding_bottom_to_top.gif) | .topLeftBottomRight | ![](../Assets/sliding_topLeft_to_bottomRight.gif) | .bottomRightTopLeft | ![](../Assets/sliding_bottomRight_to_topLeft.gif) > **😉 TRICK!** Il existe une autre façon de créer des animations de glissement, en utilisant simplement ce raccourci : >>```let animation = GradientDirection.leftToRight.slidingAnimation()``` ### 🏄 Transitions `SkeletonView` a des transitions intégrées pour **montrer** ou **cacher** les squelettes d'une manière *lisse* 🤙 Pour utiliser la transition, il suffit d'ajouter le paramètre "transition" à votre fonction `showSkeleton()` ou `hideSkeleton()` avec le temps de transition, comme ceci : ```swift view.showSkeleton(transition : .crossDissolve(0.25)) //Montrer la transition de dissolution croisée du squelette avec un temps de fondu de 0,25 seconde view.hideSkeleton(transition : .crossDissolve(0.25)) //Cachez la transition de dissolution croisée du squelette avec un temps de fondu de 0,25 seconde ``` La valeur par défaut est `crossDissolve(0.25)`. **Preview**
None
Cross dissolve
### 👨👧👦 Hiérarchie Puisque `SkeletonView` est récursif, et que nous voulons que le squelette soit très efficace, nous voulons arrêter la récursivité dès que possible. Pour cette raison, vous devez définir la vue du conteneur comme `Skeletonable`, parce que Skeleton arrêtera de chercher des sous-vues `squelettisables` dès qu'une vue n'est pas `Skeletonable`, cassant alors la récursivité. Parce qu'une image vaut mille mots : Dans cet exemple, nous avons un `UIViewController` avec une `ContainerView` et une `UITableView`. Lorsque la vue est prête, nous montrons le squelette en utilisant cette méthode : ``` view.showSkeleton() ``` > ```isSkeletonable```= ☠️ | Configuration | Résultat| |:-------:|:-------:| | | | | | | | | | || | | | | | | | ### 🔬 Débugger **NOUVEAU** Afin de faciliter les tâches de debuggage lorsque quelque chose ne fonctionne pas bien. `SkeletonView` a de nouveaux outils. Tout d'abord, `UIView` a une nouvelle propriété disponible avec son squelette d'information : ```swift var skeletonDescription : String ``` La représentation du squelette ressemble à ceci : ![](../Assets/debug_description.png) En outre, vous pouvez activer le nouveau mode **debug**. Il suffit d'ajouter la variable d'environnement `SKELETON_DEBUG` et de l'activer. ![](../Assets/debug_mode.png) Ensuite, lorsque le squelette apparaît, vous pouvez voir la hiérarchie des vues dans la console Xcode.
Ouvrez pour voir un exemple de sortie
### 📚 Documentation Bientôt disponible...😅 ### 📋 Versions OS et SDK supportées * iOS 9.0+ * tvOS 9.0+ * Swift 5 ## 📬 Prochaines étapes * [x] Fixer le pourcentage de remplissage de la dernière ligne dans les éléments multilignes * [x] Ajout d'autres animations en dégradé * [x] Cellules redimensionnables prises en charge * [x] Compatible avec CollectionView * [x] Compatible avec tvOS * [x] Ajouter l'état de recouvrement * [x] Apparence personnalisée par défaut * [x] Mode de debuggage * [x] Ajouter des animations lorsqu'il montre/cache les squelettes * [ ] Compatible avec les collections personnalisées * [ ] Compatible avec MacOS et WatchOS ## ❤️ Contribuer Il s'agit d'un projet open source, alors n'hésitez pas à y contribuer. Comment ? - Ouvrez un [numéro](https://github.com/Juanpe/SkeletonView/issues/new). - Envoyez vos commentaires via [email](mailto://juanpecatalan.com). - Proposez vos propres correctifs, suggestions et ouvrez une `pull request` avec les changements. Voir [tous les contributeurs](https://github.com/Juanpe/SkeletonView/graphs/contributors) ###### Projet généré avec [SwiftPlate](https://github.com/JohnSundell/SwiftPlate) ## 📢 Mentions - [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start) - [Hacking with Swift Articles] (https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful) - [Top 10 articles Swift de novembre] (https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65) - [30 bibliothèques incroyables pour iOS Swift (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9) - [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899) - [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d) - [Bulletin d'information sur les développements Swift #113](https://andybargh.com/swiftdevelopments-113/) - [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204) - [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759) - [CocoaControls](https://www.cocoacontrols.com/controls/skeletonview) - [Bulletin d'information Génial iOS #74](https://ios.libhunt.com/newsletter/74) - [Swift News #36](https://www.youtube.com/watch?v=mAGpsQiy6so) - [Meilleurs articles sur iOS, nouveaux outils et plus](https://medium.com/flawless-app-stories/best-ios-articles-new-tools-more-fcbe673e10d) ## 👨🏻‍💻 Auteur [1.1]: http://i.imgur.com/tXSoThF.png [1]: http://www.twitter.com/JuanpeCatalan * Juanpe Catalán [![alt text][1.1]][1] Buy me a coffee ## 👮🏻 Licence ``` MIT License Copyright (c) 2017 Juanpe Catalán Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ================================================ FILE: Translations/README_ko.md ================================================ ![](../Assets/header2.jpg)

codebeat badge SkeletonView Playground
Twitter: @JuanpeCatalan PayPal License

**🌎 번역에 도움을 주신분들: [🇬🇧](../README.md) . [🇨🇳](README_zh.md) . [🇧🇷](README_pt-br.md) . [🇰🇷](README_ko.md) . [🇫🇷](README_fr.md) . [🇩🇪](README_de.md)** 오늘날 거의 대부분의 앱들은 비동기 방식의 API 호출을 사용하는 프로세스를 가지고 있습니다. 프로세스가 작동하는동안 개발자들은 작업이 실행되고 있다는것을 사용자들에게 보여주기 위해서 로딩 뷰를 배치합니다. ```SkeletonView```는 이러한 필요에 의해 고안되었고, 사용자들에게 무엇인가 로딩이 되고 있다는것을 보여주면서 기다리는 콘텐츠에 대해서도 미리 준비할 수 있게 해주는 우아하게 표현할수 있는 방법입니다 맘껏 누리세요 🙂 * [기능](#-features) * [가이드](#-guides) * [설치방법](#-installation) * [Cocoapods](#using-cocoapods) * [Carthage](#using-carthage) * [SPM](#using-swift-package-manager) * [어떻게 사용하나요?](#-how-to-use) * [Collections](#-collections) * [Multiline text](#-multiline-text) * [Custom colors](#-custom-colors) * [Appearance](#-appearance) * [Custom animations](#-custom-animations) * [Hierarchy](#-hierarchy) * [Debug](#-debug) * [문서화](#-documentation) * [지원되는 OS와 SDK 버전](#-supported-os--sdk-versions) * [Next steps](#-next-steps) * [Contributing](#-contributing) * [Mentions](#-mentions) * [개발자](#-author) * [라이센스](#-license) ## 🌟 기능 - [x] 사용이 쉽습니다 - [x] 모든 `UIView`에서 사용가능합니다 - [x] 전체 커스터마이징이 가능합니다 - [x] 공통으로 이용가능합니다 (iPhone & iPad) - [x] `Interface Builder` 에서 사용 가능합니다. - [x] 간단한 스위프트 문법 - [x] 가볍고 가독성 좋은 코드 ## 🎬 사용가이드 [](https://youtu.be/75kgOhWsPNA) ## 📲 설치 방법 #### [CocoaPods](https://cocoapods.org) 로 사용하기 당신의 프로젝트 `Podfile` 파일에 아래와 같이 입력합니다: ```ruby pod "SkeletonView" ``` #### [Carthage](https://github.com/carthage)로 사용하기 당신의 프로젝트 `Cartfile` 파일에 아래와 같이 입력합니다: ```bash github "Juanpe/SkeletonView" ``` #### [Swift Package Manager](https://github.com/apple/swift-package-manager)로 사용하기 당신의 프로젝트에 Swift package를 설정한다면, `SkeletonView` 를 `Package.swift` 파일에 있는 `dependencies`에 추가하시면 됩니다. ```swift dependencies: [ .package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.6") ] ``` ## 🐒 어떻게 사용하나요? `SkeletonView` 를 이용하기 위해서는 딱 **3** 단계만 기억하세요: **1.** 사용하고자 하는 파일에서 `SkeletonView` 를 `Import` 합니다. ```swift import SkeletonView ``` **2.** 자, 그렇다면 UIView 속성에 `skeletonables` 를 이용하실 수 있습니다. 두가지 옵션이 있습니다 **코드로 사용하는 방법:** ```swift avatarImageView.isSkeletonable = true ``` **인터페이스빌더 / 스토리보드를 이용하는 방법:** ![](../Assets/storyboard.png) **3.** 당신이 뷰를 세팅할때, **skeleton** 옵션을 사용 할 수 있습니다. 총 **4** 가지 옵션을 지원합니다: ```swift (1) view.showSkeleton() // Solid (2) view.showGradientSkeleton() // Gradient (3) view.showAnimatedSkeleton() // Solid animated (4) view.showAnimatedGradientSkeleton() // Gradient animated ``` **미리보기**
Solid
Gradient
Solid Animated
Gradient Animated
> **중요!** >>```SkeletonView``` 는 재귀적으로 되어있습니다, 만약 모든 뷰에 대해서 skeleton을 호출하고 싶다면, 메인 컨테이너 뷰에서 show `method`를 호출하여야 합니다. 예를 들자면 UIViewControllers가 있습니다. ### 🌿 Collections 현재, ```SkeletonView``` 는 ```UITableView``` 와 ```UICollectionView```에서 호환됩니다. #### UITableView 만약 ```UITableView```에서 skeleton을 호출하고 싶다면, ```SkeletonTableViewDataSource``` protocol 을 구현하여야 합니다. ``` swift public protocol SkeletonTableViewDataSource: UITableViewDataSource { func numSections(in collectionSkeletonView: UITableView) -> Int func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier } ``` 해당 프로토클은 보시다시피 ```UITableViewDataSource```를 상속받아 구현하였으므로, skeleton의 protocol과 대체 가능합니다. 프로토콜의 기본 구현은 다음과 같습니다: ``` swift func numSections(in collectionSkeletonView: UITableView) -> Int // Default: 1 ``` ``` swift func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int // Default: // 전체 테이블 뷰를 채우는데 필요한 셀 수를 계산합니다 ``` 해당 메소드는 당신이 구현하여야할 cell identifier을 아는 경우에만 사용합니다, 해당 메소드는 기본으로 구현하지 않아도됩니다 : ``` swift func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier ``` **Example** ``` swift func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier { return "CellIdentifier" } ``` > **중요!** > 만약 사이즈가 변하는 셀을 사용한다면 (`tableView.rowHeight = UITableViewAutomaticDimension` ),`estimatedRowHeight`를 무조건 정의해주세요. 👩🏼‍🏫 **어떻게 특정 요소에 skeleton 을 지정할까요?** 아래의 그림은 `UITableView` 에서 특정한 요소에 skeleton 을 지정하는 방법을 보여주는 이미지 입니다: ![](../Assets/tableview_scheme.png) 위의 이미지에서 보이듯, 테이블 뷰와 셀에 들어가는 UI 요소들에는 적용을 해야하지만, `contentView`에 skeleton을 적용할 필요는 없습니다. #### UICollectionView ```UICollectionView``` 에 적용을 하기 위해서는, ```SkeletonCollectionViewDataSource``` protocol 을 구현할 필요가 있습니다. ``` swift public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource { func numSections(in collectionSkeletonView: UICollectionView) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier } ``` ```UITableView``` 와 사용방법은 같습니다. ### 📰 Multiline text ![](../Assets/multilines2.png) 텍스트가 들어있는 요소를 사용한다면, ```SkeletonView``` 에서 텍스트의 라인을 그려줍니다. 그리고, 원하는 라인 수를 설정할 수 있습니다. 만약 ```numberOfLines``` 을 0으로 설정한다면, 자동으로 필요한 라인수를 계산해서 그려줍니다. 대신 값이 설정되어있다면 설정된 수만큼의 라인이 그려집니다. ##### 🎛 Customize 당신은 멀티라인을 위해 몇가지 옵션을 설정할 수 있습니다. | 속성 | 값 | 기본값 | 미리보기 | | ----------------------------------------------- | --------- | ----- | ---------------------------------- | | 마지막 라인의 **퍼센트** 를 지정 할 수 있습니다. | `0...100` | `70%` | ![](../Assets/multiline_lastline.png) | | 라인의 **Corner radius** 를 지정할 수 있습니다. (**새로운기능**) | `0...10` | `0` | ![](../Assets/multiline_corner.png) | 라인의 radius를 지정하기 위해서는 **코드** 를 이용합니다, 아래 처럼 코드를 작성합니다: ```swift descriptionTextView.lastLineFillPercent = 50 descriptionTextView.linesCornerRadius = 5 ``` 혹은 **IB/Storyboard** 를 이용하실 수 있습니다: ![](../Assets/multiline_customize.png) ### 🎨 Custom colors 당신은 skeleton의 색상을 지정 할 수 있습니다. 간단하게 원하는 색상을 파라미터로 넘겨주시면 됩니다. **단색 이용방법** ``` swift view.showSkeleton(usingColor: UIColor.gray) // Solid // or view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0)) ``` **그라디언트 이용 방법** ``` swift let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue) view.showGradientSkeleton(usingGradient: gradient) // Gradient ``` 게다가, ```SkeletonView``` 에서는 20가지의 기본 컬러를 지원합니다 🤙🏼 ```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...``` ![](../Assets/flatcolors.png) ###### 위 이미지는 [https://flatuicolors.com](https://flatuicolors.com) 사이트에서 발췌했습니다. ### 🦋 Appearance **새로운 사항** skeleton 은 기본설정 값이 정해져 있습니다. 만약 커스텀 컬러를 사용할 필요가 없다면, `SkeletonView` 에 지정 되어있는 기본설정을 사용하시면 됩니다. 기본 설정값: - **tintColor**: UIColor - *기본값: .clouds* - **gradient**: SkeletonGradient - *기본값: SkeletonGradient(baseColor: .clouds)* - **multilineHeight**: CGFloat - *기본값: 15* - **multilineSpacing**: CGFloat - *기본값: 10* - **multilineLastLineFillPercent**: Int - *기본값: 70* - **multilineCornerRadius**: Int - *기본값: 0* `SkeletonAppearance.default` 에는 사용 되어지는 기본 값들이 설정되어 있습니다 . 아래의 코드와 같이 사용할 수 있습니다: ```Swift SkeletonAppearance.default.multilineHeight = 20 SkeletonAppearance.default.tintColor = .green ``` ### 🤓 커스텀 애니메이션 ```SkeletonView``` 에는 두가지 애니메이션이 내장되어 있습니다, 단색 *바운스* 애니메이션과 그라디언트 *슬라이드* 애니메이션 입니다 . 게다가, 직접 애니메이션을 추가하고 싶다면 정말 간단합니다. Skeleton 에서는 `showAnimatedSkeleton` 함수를 ```SkeletonLayerAnimation```에 정의하여 맞춤형 애니메이션을 정의할 수 있도록 되어 있습니다. ```swift public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation ``` 함수는 이렇게 호출 가능합니다: ```swift view.showAnimatedSkeleton { (layer) -> CAAnimation in let animation = CAAnimation() // Customize here your animation return animation } ``` ```SkeletonAnimationBuilder```의 사용이 가능합니다. ```SkeletonLayerAnimation```을 만들기 위해 사용됩니다. 이제, 그라디언트를 위한 **슬라이딩 애니메이션** 을 만들 수 있습니다, 애니메이션을 위한 **방향** 과 **지속시간** 을 설정 할 수 있습니다. (기본값 = 1.5초). ```swift // func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight) view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation) ``` ```GradientDirection``` 는 enum 으로 정의 되어있습니다., 아래의 케이스를 참조하세요: | 방향 | 미리보기 | | ------------------- | ---------------------------------------------- | | .leftRight | ![](../Assets/sliding_left_to_right.gif) | | .rightLeft | ![](../Assets/sliding_right_to_left.gif) | | .topBottom | ![](../Assets/sliding_top_to_bottom.gif) | | .bottomTop | ![](../Assets/sliding_bottom_to_top.gif) | | .topLeftBottomRight | ![](../Assets/sliding_topLeft_to_bottomRight.gif) | | .bottomRightTopLeft | ![](../Assets/sliding_bottomRight_to_topLeft.gif) | > **😉 꿀팁!** 슬라이딩 애니메이션을 만들기 위한 또다른 방법이 있습니다, 아래의 코드를 참조하세요: >>```let animation = GradientDirection.leftToRight.slidingAnimation()``` ### 👨‍👧‍👦 계층 구조 ```SkeletonView```는 재귀적입니다 , 그리고 우리는 skeleton이 효율적으로 작동하기를 원하기 때문에, 가능한 빨리 재귀작업을 중단하기를 원합니다. 이러한 이유때문에 반드시 컨테이너 뷰를 `Skeletonable` 로 설정해야 합니다, `skeletonable` 되지 않는 뷰를 만나는 순간 재귀 작업을 중단하기 떄문입니다. 아래의 이미지를 참고하세요 이미지는 한눈에 이해되실겁니다: > ```ìsSkeletonable```= ☠️ | 설정값 | 결과 | | ----------------------------------------- | --------------------------------------------- | | ![](../Assets/no_skeletonable.png) | ![](../Assets/no_skeletonables_result.png) | | ![](../Assets/container_no_skeletonable.png) | ![](../Assets/no_skeletonables_result.png) | | ![](../Assets/container_skeletonable.png) | ![](../Assets/container_skeletonable_result.png) | | ![](../Assets/all_skeletonables.png) | ![](../Assets/all_skeletonables_result.png) | ### 🔬 디버그 **새로운소식** 어떤것들이 잘 동작 하지 않을때를 위해 디버그 작업을 용이하게 하기 위해서 `SkeletonView` 에는 몇가지 새로운 것들이 있습니다. 첫번쨰로, `UIView` 에서 skeleton 정보를 보기위해 다음과 같이 지원하고 있습니다: ```swift var skeletonDescription: String ``` skeleton은 이렇게 생겼습니다: ![](../Assets/debug_description.png) 그리고, 새로운 **디버그 모드**를 활성화 시킬 수 있습니다. 간단하게 `SKELETON_DEBUG` 이라는 환경 변수를 추가해 활성화 하면 됩니다. ![](../Assets/debug_mode.png) 그런 이후 skeleton이 나오면 Xcode 콘솔창에서 계층 구조를 볼 수 있습니다.
예제를 확인해보세요.
### 📚 문서화 조금만 기다려주세요...😅 ### 📋 지원 가능한 OS & SDK 버전 * iOS 9.0+ * tvOS 9.0+ * Swift 4.2 ## 📬 예정된 기능들 * [x] 멀티라인 에서의 마지막 라인의 채우기 비율 설정 * [x] 더많은 그라디언트 애니메이션 * [x] resizable cells 지원 * [x] CollectionView 호환 * [x] tvOS 호환 * [x] recovery state 추가 * [x] Custom default appearance * [x] 디버그 모드 * [ ] Custom collections 호환 * [ ] skeletons 가 보이거나 가려질때 애니메이션 추가 * [ ] MacOS 와 WatchOS 호환 ## ❤️ 기여하기 이 프로젝트는 오픈소스 프로젝트 입니다, 마음편하게 기여해주시면 됩니다 어떻게 하냐구요? - 새로운 [이슈](https://github.com/Juanpe/SkeletonView/issues/new)를 등록합니다. - [email](mailto://juanpecatalan.com)을 보냅니다. - 당신의 수정을 제안합니다, pull request를 포함한 수정을 권장합니다. 전체 [기여자목록](https://github.com/Juanpe/SkeletonView/graphs/contributors) ###### [SwiftPlate](https://github.com/JohnSundell/SwiftPlate)를 통해 프로젝트가 생성되었습니다 ## 📢 소식들 - [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start) - [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful) - [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65) - [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9) - [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899) - [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d) - [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/) - [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204) - [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759) - [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) ## 👨🏻‍💻 개발자 [1.1]: http://i.imgur.com/tXSoThF.png [1]: http://www.twitter.com/JuanpeCatalan * Juanpe Catalán [![alt text][1.1]][1] Buy me a coffee ## 👮🏻 라이센스 ``` MIT License Copyright (c) 2017 Juanpe Catalán Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ================================================ FILE: Translations/README_pt-br.md ================================================ ![](../Assets/header2.jpg)

codebeat badge SkeletonView Playground
Twitter: @JuanpeCatalan PayPal License

**🌎 Traduções: [🇬🇧](../README.md) . [🇨🇳](README_zh.md) . [🇧🇷](README_pt-br.md) . [🇰🇷](README_ko.md) . [🇫🇷](README_fr.md) . [🇩🇪](README_de.md)** Hoje, quase todos os apps têm processos assíncronos, como requisições de API, processos longos, etc. E enquanto os processos estão ocorrendo, normalmente os desenvolvedores usam uma view que mostra os usuarios que algo está ocorrendo. ```SkeletonView``` foi criado para essa necessidade, um jeito elegante de mostrar aos usuários que algo está acontecendo e já prepará-los para qual conteúdo será carregado. Aproveite! 🙂 - [🌟 Features](#-features) - [📋 Versões do SDK e OS suportados](#-versões-do-sdk-e-os-suportados) - [🔮 Exemplo](#-exemplo) - [📲 Instalação](#-instalação) - [Usando CocoaPods](#usando-cocoapods) - [Usando Carthage](#usando-carthage) - [🐒 Como usar](#-como-usar) - [🌿 Coleções](#-coleções) - [UITableView](#uitableview) - [UICollectionView](#uicollectionview) - [📰 Texto de várias linhas](#-texto-de-várias-linhas) - [🎛 Customização](#-customização) - [🎨 Cores customizadas](#-cores-customizadas) - [Imagem capturada do site https://flatuicolors.com](#imagem-capturada-do-site-httpsflatuicolorscom) - [🦋 Aparência](#-aparência) - [🤓 Animações customizadas](#-animações-customizadas) - [👨‍👧‍👦 Hierarquia](#-hierarquia) - [📚 Documentação](#-documentação) - [📬 Próximos passos](#-próximos-passos) - [❤️ Contribuindo](#️-contribuindo) - [Projeto gerado com SwiftPlate](#projeto-gerado-com-swiftplate) - [📢 Menções](#-menções) - [👨🏻‍💻 Autor](#-autor) - [👮🏻 Licença](#-licença) ## 🌟 Features - [x] Fácil de usar - [x] Todas as UIViews são skeletonables - [x] Completamente customizável - [x] Universal (iPhone & iPad) - [x] Interface Builder friendly - [x] Sintaxe simples em Swift - [x] Código leve e legível ### 📋 Versões do SDK e OS suportados * iOS 9.0+ * tvOS 9.0+ * Swift 4.2 ### 🔮 Exemplo Para rodar o projeto de exemplo, clone o repositório e use o target `SkeletonViewExample`. ## 📲 Instalação #### Usando [CocoaPods](https://cocoapods.org) Edite seu `Podfile` e especifíque a dependência: ```ruby pod "SkeletonView" ``` #### Usando [Carthage](https://github.com/carthage) Edite seu `Cartfile` e especifíque a dependência: ```bash github "Juanpe/SkeletonView" ``` ## 🐒 Como usar Apenas **3** passos necessários para usar `SkeletonView`: **1.** Importe SkeletonView no lugar desejado. ```swift import SkeletonView ``` **2.** Agora, especifíque quais views serão `skeletonables`. Você consegue fazer isso de duas formas: **Usando código:** ```swift avatarImageView.isSkeletonable = true ``` **Usando IB/Storyboards:** ![](../Assets/storyboard.png) **3.** Uma vez que você setou as views, você pode mostrar o **skeleton**. Para fazê-lo, você tem **4** escolhas: ```swift (1) view.showSkeleton() // Solid (2) view.showGradientSkeleton() // Gradient (3) view.showAnimatedSkeleton() // Solid animated (4) view.showAnimatedGradientSkeleton() // Gradient animated ``` **Pré-visualização**
Solid
Gradient
Solid Animated
Gradient Animated
> **IMPORTANTE!** >>```SkeletonView``` é recursivo, então se você quer mostrar o esqueleto em todas as views skeletonables, você só precisa chamar o método na main container view. Por exemplo, com UIViewControllers ### 🌿 Coleções ```SkeletonView``` é compatível com ```UITableView``` e ```UICollectionView```. ###### UITableView Se você quer mostrar o skeleton em uma ```UITableView```, você precisa conformar com o protocolo ```SkeletonTableViewDataSource```. ``` swift public protocol SkeletonTableViewDataSource: UITableViewDataSource { func numSections(in collectionSkeletonView: UITableView) -> Int func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier } ``` Como você pode ver, esse protocolo herda de ```UITableViewDataSource```, então você pode substituir esse protocolo com o protocolo do skeleton. Esse protocolo tem uma implementação padrão: ``` swift func numSections(in collectionSkeletonView: UITableView) -> Int // Default: 1 ``` ``` swift func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int // Default: // It calculates how many cells need to populate whole tableview ``` Esse é o único método que você precisa implementar para informar o skeleton sobre o cell identifier. Esse método não possui uma implementação padrão: ``` swift func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier ``` **Exemplo** ``` swift func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier { return "CellIdentifier" } ``` > **IMPORTANTE!** > Se você está usando resizable cells (`tableView.rowHeight = UITableViewAutomaticDimension` ), é obrigatório definir a `estimatedRowHeight`. ###### UICollectionView Para ```UICollectionView```, você precisa conformar com o protocolo ```SkeletonCollectionViewDataSource```. ``` swift public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource { func numSections(in collectionSkeletonView: UICollectionView) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier } ``` O resto do processo é o mesmo da ```UITableView``` ### 📰 Texto de várias linhas ![](../Assets/multilines2.png) Quando você usar elementos com texto, ```SkeletonView``` desenha linhas para simular o texto. Além disso, você pode decidir quantas linhas você quer. Se ```numberOfLines``` está setado para zero (0), haverá um cálculo para saber quantas linhas são necessárias para preencher o skeleton inteiro e será desenhado. Caso contrário, se você setar para um (1) ou qualquer outro número maior que zero, só serão desenhadas aquele número de linhas. ##### 🎛 Customização Você pode setar algumas propriedades para elementos de várias linhas. | Property | Values | Default | Preview | ------- | ------- |------- | ------- | **Filling percent** of the last line. | `0...100` | `70%` | ![](../Assets/multiline_lastline.png) | **Corner radius** of lines. (**NEW**) | `0...10` | `0` | ![](../Assets/multiline_corner.png) Para modificar a percentagem ou o raio **usando código**, especifique as propriedades: ```swift descriptionTextView.lastLineFillPercent = 50 descriptionTextView.linesCornerRadius = 5 ``` Ou, se você preferir use **IB/Storyboard**: ![](../Assets/multiline_customize.png) ### 🎨 Cores customizadas Você pode decidir que cor o skeleton esta pintado. Você só precisa parametrizar a cor e o gradiente que deseja. **Usando cores sólidas** ``` swift view.showSkeleton(usingColor: UIColor.gray) // Solid // or view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0)) ``` **Usando gradientes** ``` swift let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue) view.showGradientSkeleton(usingGradient: gradient) // Gradient ``` Além do mais, ```SkeletonView``` tem 20 cores flat 🤙🏼 ```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...``` ![](../Assets/flatcolors.png) ###### Imagem capturada do site [https://flatuicolors.com](https://flatuicolors.com) ### 🦋 Aparência **NOVIDADE** Os skeletons tem uma aparência padrão. Então, quando você não especifíca a cor, gradiente ou propriedades de várias linhas, `SkeletonView` usa os valores padrões. Valores padrões: - **tintColor**: UIColor - *default: .clouds* - **gradient**: SkeletonGradient - *default: SkeletonGradient(baseColor: .clouds)* - **multilineHeight**: CGFloat - *default: 15* - **multilineSpacing**: CGFloat - *default: 10* - **multilineLastLineFillPercent**: Int - *default: 70* - **multilineCornerRadius**: Int - *default: 0* Para obter esses valores padrões você pode usar `SkeletonAppearance.default`. Usando essa propriedade você pode declarar os valores também: ```Swift SkeletonAppearance.default.multilineHeight = 20 SkeletonAppearance.default.tintColor = .green ``` ### 🤓 Animações customizadas ```SkeletonView``` tem duas animações pré-definidas, *pulse* para skeletons solidos e *sliding* para gradientes. Além disso, se você quiser fazer suas próprias animações no skeleton, é muito fácil. Skeleton disponibiliza a função `showAnimatedSkeleton` que tem o closure ```SkeletonLayerAnimation``` onde você pode definir sua animação customizada. ```swift public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation ``` Você pode chamar esta função assim: ```swift view.showAnimatedSkeleton { (layer) -> CAAnimation in let animation = CAAnimation() // Customize here your animation return animation } ``` Está disponível ```SkeletonAnimationBuilder```. É um construtor para ```SkeletonLayerAnimation```. Hoje, você pode criar **sliding animations** para gradientes, decidindo a **direction** e setando a **duration** da animaçāo (padrão = 1.5s). ```swift // func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight) view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation) ``` ```GradientDirection``` é um enum, com os seguintes cases: | Direction | Preview |------- | ------- | .leftRight | ![](../Assets/sliding_left_to_right.gif) | .rightLeft | ![](../Assets/sliding_right_to_left.gif) | .topBottom | ![](../Assets/sliding_top_to_bottom.gif) | .bottomTop | ![](../Assets/sliding_bottom_to_top.gif) | .topLeftBottomRight | ![](../Assets/sliding_topLeft_to_bottomRight.gif) | .bottomRightTopLeft | ![](../Assets/sliding_bottomRight_to_topLeft.gif) > **😉 TRUQUE!** Existe outra forma de criar sliding animations, apenas usando este atalho: >>```let animation = GradientDirection.leftToRight.slidingAnimation()``` ### 👨‍👧‍👦 Hierarquia Já que ```SkeletonView``` é recursiva, e queremos que o skeleton seja muito eficiente, queremos parar a recursão assim que possível. Por este motivo, você deve setar a container view como `Skeletonable`, porque o Skeleton vai parar de procurar por subviews `skeletonable` assim que a view não for mais skeletonable, quebrando a recursão. Porque uma imagem vale mais que mil palavras: > ```ìsSkeletonable```= ☠️ | Configuration | Result |------- | ------- |![](../Assets/no_skeletonable.png) | ![](../Assets/no_skeletonables_result.png) |![](../Assets/container_no_skeletonable.png) | ![](../Assets/no_skeletonables_result.png) |![](../Assets/container_skeletonable.png) | ![](../Assets/container_skeletonable_result.png) |![](../Assets/all_skeletonables.png) | ![](../Assets/all_skeletonables_result.png) ### 📚 Documentação Em breve...😅 ## 📬 Próximos passos * [x] Setar o percentual de preenchimento da última linha em elementos de várias linhas * [x] Adicionar mais animações de gradiente * [x] Suporte para resizable cells * [x] Compatível com CollectionView * [x] Compatível com tvOS * [x] Adicionar recovery state * [x] Aparência padrão customizável * [ ] Compatível com coleções customizáveis * [ ] Adicionar animações quando mostra/esconde os skeletons * [ ] Compatível com MacOS e WatchOS ## ❤️ Contribuindo Este é um projeto de código aberto, então sinta-se a vontade para contribuir. Como? - Abra uma [issue](https://github.com/Juanpe/SkeletonView/issues/new). - Envie feedback por [email](mailto://juanpecatalan.com). - Proponha seus próprios fixes, sugestões e abra um pull request com as alterações. Ver [todos os contribuidores](https://github.com/Juanpe/SkeletonView/graphs/contributors) ###### Projeto gerado com [SwiftPlate](https://github.com/JohnSundell/SwiftPlate) ## 📢 Menções - [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start) - [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful) - [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65) - [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9) - [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899) - [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d) - [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/) - [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204) - [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759) - [CocoaControls](https://www.cocoacontrols.com/controls/skeletonview) - [Awesome iOS Newsletter #74](https://ios.libhunt.com/newsletter/74) ## 👨🏻‍💻 Autor [1.1]: http://i.imgur.com/tXSoThF.png [1]: http://www.twitter.com/JuanpeCatalan * Juanpe Catalán [![alt text][1.1]][1] Buy me a coffee ## 👮🏻 Licença ``` MIT License Copyright (c) 2017 Juanpe Catalán Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ================================================ FILE: Translations/README_zh.md ================================================ ![](../Assets/header2.jpg)

codebeat badge SkeletonView Playground
Twitter: @JuanpeCatalan PayPal License

**🌎 翻译: [🇬🇧](../README.md) . [🇨🇳](README_zh.md) . [🇧🇷](README_pt-br.md) . [🇰🇷](README_ko.md) . [🇫🇷](README_fr.md) . [🇩🇪](README_de.md)** 今天,几乎所有的应用程序都有异步流程,例如:Api请求、长时间运行的流程等。虽然流程正在运行,但通常开发人员会设置一个加载视图来向用户显示正在发生的事情。 ```SkeletonView``` 已经构想出来满足这种需求,这是一种优雅的方式,向用户展示正在发生的事情,并为他们等待的内容做好准备。 好好享受! 🙂 - [🌟 特征](#-特征) - [📋 版本要求](#-版本要求) - [🔮 示例](#-示例) - [📲 安装](#-安装) - [使用 CocoaPods](#使用-cocoapods) - [使用 Carthage](#使用-carthage) - [🐒 如何使用](#-如何使用) - [🌿 集合](#-集合) - [UITableView](#uitableview) - [UICollectionView](#uicollectionview) - [📰 多行文字](#-多行文字) - [🎛 定制](#-定制) - [🎨 自定义颜色](#-自定义颜色) - [从网站 https://flatuicolors.com捕获的图像](#从网站-httpsflatuicolorscom捕获的图像) - [🤓 自定义动画](#-自定义动画) - [👨‍👧‍👦 等级制度](#-等级制度) - [📚 文档](#-文档) - [📬 下一步](#-下一步) - [❤️ 特约](#️-特约) - [使用 SwiftPlate 生成的项目](#使用-swiftplate-生成的项目) - [📢 提及](#-提及) - [👨🏻‍💻 作者](#-作者) - [👮🏻 许可证](#-许可证) ## 🌟 特征 - [x] 使用方便 - [x] 支持所有 UIView - [x] 完全可定制 - [x] 通用(iPhone和iPad) - [x] Interface Builder 友好 - [x] 简单的 Swift 语法 - [x] 轻量级可读代码库 ### 📋 版本要求 * iOS 10.0+ * tvOS 10.0+ * Swift 4.2 ### 🔮 示例 要运行示例项目,请克隆并运行 `SkeletonViewExample` 项目。 ## 📲 安装 #### 使用 [CocoaPods](https://cocoapods.org) 使用 CocoaPods 编辑您的 Podfile 并指定依赖项: ```ruby pod "SkeletonView" ``` #### 使用 [Carthage](https://github.com/carthage) 编辑您的 Cartfile 并指定依赖项: ```bash github "Juanpe/SkeletonView" ``` ## 🐒 如何使用 只需 **3** 个步骤即可使用 `SkeletonView`: **1.** 在适当的位置导入SkeletonView ```swift import SkeletonView ``` **2.** 现在,您可以通过两种设置方式实现 `SkeletonView` 效果 **使用纯代码:** ```swift avatarImageView.isSkeletonable = true ``` **使用 IB/Storyboards:** ![](../Assets/storyboard.png) **3.** 设置视图后,可以显示 **skeleton**. 并且您有 **4** 种效果可供选择: ```swift (1) view.showSkeleton() // 固体 (2) view.showGradientSkeleton() // 渐变 (3) view.showAnimatedSkeleton() // 纯色动画 (4) view.showAnimatedGradientSkeleton() // 渐变动画 ``` **Preview**
固体
渐变
纯色动画
渐变动画
> **重要!** >>```SkeletonView``` 是递归的,所以如果你想在所有可骨架化的视图中显示骨架,你只需要在主容器视图中调用show方法。例如,使用UIViewControllers ### 🌿 集合 现在,```SkeletonView``` 兼容 ```UITableView``` 和 ```UICollectionView```。 ###### UITableView 如果你要显示 skeleton 在一个 ```UITableView```上,你需要符合 ```SkeletonTableViewDataSource``` 协议。 ``` swift public protocol SkeletonTableViewDataSource: UITableViewDataSource { func numSections(in collectionSkeletonView: UITableView) -> Int func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier } ``` 如您所见,此协议继承自 UITableViewDataSource,因此您可以使用骨架协议替换此协议。 该协议具有默认实现: ``` swift func numSections(in collectionSkeletonView: UITableView) -> Int // 默认值:1 ``` ``` swift func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int // 默认值: // 它计算填充整个tableview需要多少个单元格 ``` 为了让Skeleton知道单元标识符,您只需要实现一种方法。此方法没有默认实现: ``` swift func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier ``` **示例** ``` swift func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier { return "CellIdentifier" } ``` > **重要!** > 如果您使用可调整大小的单元格 (`tableView.rowHeight = UITableViewAutomaticDimension` ),则必须定义 `estimatedRowHeight`。 ###### UICollectionView 要为 ```UICollectionView``` 设置效果, 您需要符合 ```SkeletonCollectionViewDataSource``` 协议。 ``` swift public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource { func numSections(in collectionSkeletonView: UICollectionView) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier } ``` 其余操作与 ```UITableView``` 相同。 ### 📰 多行文字 ![](../Assets/multilines2.png) 使用带有文本的元素时, ```SkeletonView``` 绘制线条以模拟文本。此外,您可以决定您想要多少行。如果 ```numberOfLines``` 设置为零,它将计算填充整个骨架所需的行数,并将绘制它。相反,如果将其设置为一,二或任何大于零的数字,它将只绘制此行数。 ##### 🎛 定制 您可以为多行元素设置一些属性。 | 属性 | 值范围 | 默认 | 延时 | ------- | ------- |------- | ------- | **Filling percent** 最后一行的长度百分比 | `0...100` | `70%` | ![](../Assets/multiline_lastline.png) | **Corner radius** 条目圆角半径. (**新**) | `0...10` | `0` | ![](../Assets/multiline_corner.png) **纯代码**修改百分比或半径: ```swift descriptionTextView.lastLineFillPercent = 50 descriptionTextView.linesCornerRadius = 5 ``` 或者,如果您更喜欢使用 **IB/Storyboard**: ![](../Assets/multiline_customize.png) ### 🎨 自定义颜色 您可以决定 ```SkeletonView``` 的显示颜色。您只需要传递颜色或渐变的参数。 **使用纯色** ``` swift view.showSkeleton(usingColor: UIColor.gray) // 固体效果 // 或者 view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0)) ``` **使用渐变色** ``` swift let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue) view.showGradientSkeleton(usingGradient: gradient) // 梯度效果 ``` 此外, ```SkeletonView``` 附带的 20 种颜色 🤙🏼 ```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...``` ![](../Assets/flatcolors.png) ###### 从网站 [https://flatuicolors.com](https://flatuicolors.com)捕获的图像 ### 🤓 自定义动画 现在,```SkeletonView``` 有两个内置动画,*pulse* 脉冲效果和 *sliding* 渐变滑动效果。 此外,如果你想做自己的 skeleton 动画,那真的很容易。 Skeleton 提供了 `showAnimatedSkeleton` 一个具有 ```SkeletonLayerAnimation``` 闭包的功能,您可以在其中定义自定义动画。 ```swift public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation ``` 您可以像这样调用函数: ```swift view.showAnimatedSkeleton { (layer) -> CAAnimation in let animation = CAAnimation() // 在这里自定义你的动画 return animation } ``` **新** 它可用 ```SkeletonAnimationBuilder```。这是一个 ```SkeletonLayerAnimation```的衍生。 今天,您可以为渐变创建 **滑动动画**,确定 **方向** 并设置动画的 **持续时间** (默认值 = 1.5s)。 ```swift // func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight) view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation) ``` ```GradientDirection``` 是一个枚举,在这种情况下: | 方向 | 效果 |------- | ------- | .leftRight | ![](../Assets/sliding_left_to_right.gif) | .rightLeft | ![](../Assets/sliding_right_to_left.gif) | .topBottom | ![](../Assets/sliding_top_to_bottom.gif) | .bottomTop | ![](../Assets/sliding_bottom_to_top.gif) | .topLeftBottomRight | ![](../Assets/sliding_topLeft_to_bottomRight.gif) | .bottomRightTopLeft | ![](../Assets/sliding_bottomRight_to_topLeft.gif) > **😉 技巧!** 存在另一种创建滑动动画的方法,只需使用此快捷方式: >>```let animation = GradientDirection.leftToRight.slidingAnimation()``` ### 👨‍👧‍👦 等级制度 由于 ```SkeletonView``` 是递归的,我们希望 skeleton 效率高效, 我们希望尽快停止递归。因此,您必须将容器视图设置为 `Skeletonable` ,因为`skeletonable` 一旦视图不是 Skeletonable, Skeleton 将停止查找子视图,然后断开递归。 一图胜千言: > 设置 ```ìsSkeletonable```= ☠️ | 分组 | 结果 |------- | ------- |![](../Assets/no_skeletonable.png) | ![](../Assets/no_skeletonables_result.png) |![](../Assets/container_no_skeletonable.png) | ![](../Assets/no_skeletonables_result.png) |![](../Assets/container_skeletonable.png) | ![](../Assets/container_skeletonable_result.png) |![](../Assets/all_skeletonables.png) | ![](../Assets/all_skeletonables_result.png) ### 📚 文档 快出来...😅 ## 📬 下一步 * [x] 设置多行元素中最后一行的填充百分比 * [x] 添加更多渐变动画 * [x] 支持可调整大小的单元 * [x] CollectionView 兼容 * [x] tvOS 兼容 * [x] 添加恢复状态 * [ ] 自定义集合兼容 * [ ] 在显示/隐藏骨架时添加动画 * [ ] MacOS 和 WatchOS兼容 ## ❤️ 特约 这是一个开源项目,所以请随时贡献。怎么样? - 打开一个 [issue](https://github.com/Juanpe/SkeletonView/issues/new) - 反馈通过发送 [email](mailto://juanpecatalan.com) - 提出您自己的修复和建议,并带有拉取的请求。 查看 [所有贡献者](https://github.com/Juanpe/SkeletonView/graphs/contributors) ###### 使用 [SwiftPlate](https://github.com/JohnSundell/SwiftPlate) 生成的项目 ## 📢 提及 - [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start) - [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful) - [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65) - [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9) - [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899) - [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d) - [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/) - [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204) - [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759) - [CocoaControls](https://www.cocoacontrols.com/controls/skeletonview) - [Awesome iOS Newsletter #74](https://ios.libhunt.com/newsletter/74) ## 👨🏻‍💻 作者 [1.1]: http://i.imgur.com/tXSoThF.png [1]: http://www.twitter.com/JuanpeCatalan * Juanpe Catalán [![alt text][1.1]][1] Buy me a coffee ## 👮🏻 许可证 ``` MIT License Copyright (c) 2017 Juanpe Catalán Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ================================================ FILE: fastlane/Fastfile ================================================ default_platform(:ios) podspec_name = "SkeletonView.podspec" lane :bump_version do |options| version_bump_podspec(path: @podspec_name, version_number: options[:next_version]) end lane :release_current do version = version_get_podspec(path: @podspec_name) if git_tag_exists(tag: version) UI.user_error!("The tag #{version} already exists on the repo. To release a new version of the library bump the version on #{@podspec_name}") end pod_lib_lint add_git_tag(tag: "#{version}") push_git_tags pod_push end ================================================ FILE: fastlane/README.md ================================================ fastlane documentation ---- # Installation Make sure you have the latest version of the Xcode command line tools installed: ```sh xcode-select --install ``` For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) # Available Actions ### bump_version ```sh [bundle exec] fastlane bump_version ``` ### release_current ```sh [bundle exec] fastlane release_current ``` ---- This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).