├── .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
├── Assets
├── all_skeletonables.jpg
├── all_skeletonables_result.png
├── container_no_skeletonable.jpg
├── container_skeletonable.jpg
├── container_skeletonable_result.png
├── debug_description.png
├── debug_mode.png
├── demoApp2.png
├── flatcolors.png
├── gradient.png
├── gradient_animated.gif
├── header.jpg
├── header2.jpg
├── hierarchy_output.png
├── multiline_corner.png
├── multiline_customize.png
├── multiline_insets.png
├── multiline_lastline.png
├── multiline_lineHeight.png
├── multiline_lineSpacing.png
├── multilines2.png
├── no_skeletonable.jpg
├── no_skeletonables_result.png
├── skeleton_transition_fade.gif
├── skeleton_transition_nofade.gif
├── sliding_bottomRight_to_topLeft.gif
├── sliding_bottom_to_top.gif
├── sliding_left_to_right.gif
├── sliding_right_to_left.gif
├── sliding_topLeft_to_bottomRight.gif
├── sliding_top_to_bottom.gif
├── solid.png
├── solid_animated.gif
├── solid_animated2.gif
├── storyboard.png
├── tableview_no_skeletonable.jpg
├── tableview_no_skeletonable_result.png
├── tableview_scheme.png
├── tableview_skeletonable.jpg
├── tableview_skeletonable_result.png
└── thumb_getting_started.png
├── 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
│ │ │ └── avatar.png
│ │ └── picture.imageset
│ │ │ ├── Contents.json
│ │ │ └── picture.png
│ ├── 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
│ │ │ │ └── avatar.png
│ │ ├── 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
├── Gemfile.lock
├── 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
/.codebeatsettings:
--------------------------------------------------------------------------------
1 | {
2 | "SWIFT": {
3 | "TOO_MANY_FUNCTIONS": [50, 100, 150, 200],
4 | "TOTAL_LOC": [200, 400, 500, 600]
5 | }
6 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sh linguist-language=Swift
2 | *.podspec linguist-language=Swift
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [juanpe]
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug report"
3 | about: Report a bug or unexpected behavior while using SkeletonView
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Description
11 |
12 | Describe your issue here.
13 |
14 | ### What type of issue is this? (place an `x` in one of the `[ ]`)
15 | - [ ] bug
16 | - [ ] enhancement (feature request)
17 | - [ ] question
18 | - [ ] documentation related
19 | - [ ] discussion
20 |
21 | ### Requirements (place an `x` in each of the `[ ]`)
22 | * [ ] 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.
23 | * [ ] I've read and agree to the [Code of Conduct](https://github.com/Juanpe/SkeletonView/blob/main/CODE_OF_CONDUCT.md).
24 | * [ ] I've searched for any related issues and avoided creating a duplicate issue.
25 |
26 | ---
27 |
28 | ### Bug Report
29 |
30 | Filling out the following details about bugs will help us solve your issue sooner.
31 |
32 | ### SkeletonView Environment:
33 |
34 | **SkeletonView version:**
35 | **Xcode version:**
36 | **Swift version:**
37 |
38 | #### Steps to reproduce:
39 |
40 | *Please replace this with the steps to reproduce the behavior.*
41 |
42 | 1.
43 | 2.
44 | 3.
45 |
46 | #### Expected result:
47 |
48 | *Please replace this with what you expected to happen.*
49 |
50 | #### Actual result:
51 |
52 | *Please replace this with of what happened instead.*
53 |
54 | #### Attachments:
55 |
56 | Logs, screenshots, sample project, funny gif, etc.
57 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feedback.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F4E3 Feedback"
3 | about: Give us general feedback about the SkeletonView
4 | title: ''
5 | labels: feedback
6 | assignees: ''
7 |
8 | ---
9 |
10 | # SkeletonView Feedback
11 |
12 | You can use this template to give us structured feedback or just wipe it and leave us a note. Thank you!
13 |
14 | ## What have you loved?
15 |
16 | _eg "the nice colors"_
17 |
18 | ## What was confusing or gave you pause?
19 |
20 | _eg "it did something unexpected"_
21 |
22 | ## Are there features you'd like to see added?
23 |
24 | _eg "SkeletonView should be compatible with SwiftUI"_
25 |
26 | ## Anything else?
27 |
28 | _eg "have a nice day"_
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/submit-a-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "⭐ Submit a request"
3 | about: Surface a feature or problem that you think should be solved
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Describe the feature or problem you’d like to solve
11 |
12 | A clear and concise description of what the feature or problem is.
13 |
14 | ### Proposed solution
15 |
16 | How will it benefit SkeletonView and its users?
17 |
18 | ### Additional context
19 |
20 | Add any other context like screenshots or mockups are helpful, if applicable.
21 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### Summary
2 |
3 | Describe the goal of this PR. Mention any related Issue numbers.
4 |
5 | ### Requirements (place an `x` in each of the `[ ]`)
6 | * [ ] 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.
7 | * [ ] I've read and agree to the [Code of Conduct](https://github.com/Juanpe/SkeletonView/blob/main/CODE_OF_CONDUCT.md).
8 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: '📦 $RESOLVED_VERSION'
2 | tag-template: '$RESOLVED_VERSION'
3 | category-template: '#### $TITLE'
4 | change-template: '- **#$NUMBER**: $TITLE - @$AUTHOR'
5 | template: |
6 | $CHANGES
7 | categories:
8 | - title: '🚨 Breaking'
9 | label: 'breaking'
10 | - title: '🔬Improvements'
11 | label: '💡 enhancement'
12 | - title: '🙌 New'
13 | label: 'feature'
14 | - title: '🩹 Bug fixes'
15 | label: '🐞 bug'
16 | - title: '⚙️ Maintenance'
17 | label: '⚙️ maintenance'
18 | - title: '📚 Documentation'
19 | label: '📚 docs'
20 | - title: '💾 Dependency Updates'
21 | label: 'dependencies'
22 |
23 | version-resolver:
24 | major:
25 | labels:
26 | - 'breaking'
27 | minor:
28 | labels:
29 | - '💡 enhancement'
30 | - 'feature'
31 | patch:
32 | labels:
33 | - '🐞 bug'
34 | - '⚙️ maintenance'
35 | - '📚 docs'
36 | - 'dependencies'
37 |
38 | exclude-labels:
39 | - 'skip-changelog'
40 |
--------------------------------------------------------------------------------
/.github/workflows/CD.yml:
--------------------------------------------------------------------------------
1 | name: CD
2 |
3 | on:
4 | pull_request_target:
5 | branches: [main]
6 | types: [closed]
7 |
8 | jobs:
9 | release_version:
10 | if: github.event.pull_request.milestone == null && github.event.pull_request.merged == true
11 | runs-on: macOS-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 |
15 | - name: Publish release
16 | id: publish_release
17 | uses: release-drafter/release-drafter@v5
18 | with:
19 | publish: true
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 |
23 | - name: Update podspec
24 | run: fastlane bump_version next_version:${{ steps.publish_release.outputs.tag_name }}
25 |
26 | - name: Commit changes
27 | uses: stefanzweifel/git-auto-commit-action@v4
28 | with:
29 | branch: 'main'
30 | commit_message: 'Bump version ${{ steps.publish_release.outputs.tag_name }}'
31 | env:
32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 |
34 | - name: Deploy to Cocoapods
35 | continue-on-error: true
36 | env:
37 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
38 | run: |
39 | set -eo pipefail
40 | pod lib lint --allow-warnings
41 | pod trunk push --allow-warnings
42 |
43 | - name: Tweet the release
44 | uses: ethomson/send-tweet-action@v1
45 | with:
46 | consumer-key: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
47 | consumer-secret: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
48 | access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
49 | access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
50 | status: |
51 | 🎉 New release ${{ steps.publish_release.outputs.tag_name }} is out 🚀
52 |
53 | Check out all the changes here:
54 | ${{ steps.publish_release.outputs.html_url }}
55 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | branches: [main]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build:
10 | runs-on: macos-latest
11 | strategy:
12 | matrix:
13 | build-config:
14 | - { scheme: 'SkeletonView iOS', destination: 'platform=iOS Simulator,name=iPhone 8', sdk: 'iphonesimulator' }
15 | - { scheme: 'SkeletonView tvOS', destination: 'platform=tvOS Simulator,name=Apple TV', sdk: 'appletvsimulator' }
16 | - { scheme: 'iOS Example', destination: 'platform=iOS Simulator,name=iPhone 8', sdk: 'iphonesimulator' }
17 | - { scheme: 'tvOS Example', destination: 'platform=tvOS Simulator,name=Apple TV', sdk: 'appletvsimulator' }
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: Build
21 | run: xcodebuild clean build -workspace 'SkeletonView.xcworkspace' -scheme '${{ matrix.build-config['scheme'] }}' -sdk '${{ matrix.build-config['sdk'] }}' -destination '${{ matrix.build-config['destination'] }}'
22 |
23 |
--------------------------------------------------------------------------------
/.github/workflows/needs-attention.yml:
--------------------------------------------------------------------------------
1 | name: Issue Needs Attention
2 | # This workflow is triggered on issue comments.
3 | on:
4 | issue_comment:
5 | types: created
6 |
7 | jobs:
8 | applyNeedsAttentionLabel:
9 | name: Apply Needs Attention Label
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Apply Needs Attention Label
14 | uses: hramos/needs-attention@v1
15 | with:
16 | repo-token: ${{ secrets.GITHUB_TOKEN }}
17 | response-required-label: 'awaiting user info'
18 | needs-attention-label: 'needs triage'
19 |
--------------------------------------------------------------------------------
/.github/workflows/pod_lib_lint.yml:
--------------------------------------------------------------------------------
1 | name: Pod lint
2 | on: [workflow_dispatch]
3 |
4 | jobs:
5 | pod_lib_lint:
6 | runs-on: macOS-latest
7 | steps:
8 | - uses: actions/checkout@v2
9 |
10 | - env:
11 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
12 | run: |
13 | set -eo pipefail
14 | pod lib lint --allow-warnings
15 |
--------------------------------------------------------------------------------
/.github/workflows/pod_trunk.yml:
--------------------------------------------------------------------------------
1 | name: Pod trunk
2 | on: [workflow_dispatch]
3 |
4 | jobs:
5 | release_version:
6 | runs-on: macOS-latest
7 | steps:
8 | - uses: actions/checkout@v2
9 |
10 | - name: Deploy to Cocoapods
11 | env:
12 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
13 | run: |
14 | set -eo pipefail
15 | pod lib lint --allow-warnings
16 | pod trunk push --allow-warnings
17 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on: [workflow_dispatch]
3 |
4 | jobs:
5 | release_version:
6 | runs-on: macOS-latest
7 | steps:
8 | - uses: actions/checkout@v2
9 |
10 | - name: Publish release
11 | id: publish_release
12 | uses: release-drafter/release-drafter@v5
13 | with:
14 | publish: true
15 | env:
16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 |
18 | - name: Update podspec
19 | run: fastlane bump_version next_version:${{ steps.publish_release.outputs.tag_name }}
20 |
21 | - name: Commit changes
22 | uses: stefanzweifel/git-auto-commit-action@v4
23 | with:
24 | branch: 'main'
25 | commit_message: 'Bump version ${{ steps.publish_release.outputs.tag_name }}'
26 | env:
27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 |
29 | - name: Deploy to Cocoapods
30 | env:
31 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
32 | run: |
33 | set -eo pipefail
34 | pod lib lint --allow-warnings
35 | pod trunk push --allow-warnings
36 |
37 | - name: Tweet the release
38 | uses: ethomson/send-tweet-action@v1
39 | with:
40 | consumer-key: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
41 | consumer-secret: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
42 | access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
43 | access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
44 | status: |
45 | 🎉 New release ${{ steps.publish_release.outputs.tag_name }} is out 🚀
46 | Check out all the changes here:
47 | ${{ steps.publish_release.outputs.html_url }}
48 |
--------------------------------------------------------------------------------
/.github/workflows/release_notes.yml:
--------------------------------------------------------------------------------
1 | name: Release Notes
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | update_release_notes:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: release-drafter/release-drafter@master
13 | env:
14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: 'Close stale issues and PRs'
2 | on:
3 | schedule:
4 | - cron: '30 5 * * *'
5 |
6 | jobs:
7 | stale:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/stale@v4
11 | with:
12 | 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.'
13 | 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 🙂'
14 | days-before-stale: 5
15 | days-before-close: 3
16 | enable-statistics: true
17 | operations-per-run: 60
18 | only-labels: 'awaiting user input'
--------------------------------------------------------------------------------
/.github/workflows/validations.yml:
--------------------------------------------------------------------------------
1 | name: Validations
2 |
3 | on:
4 | pull_request_target:
5 | branches: [main]
6 | types: [opened, reoneped, edited, synchronized]
7 |
8 | # workflow_dispatch:
9 | # inputs:
10 | # commit hash:
11 | # description: "Commit hash"
12 | # required: true
13 | # default: ""
14 |
15 | jobs:
16 | lint:
17 | runs-on: macos-latest
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: Run SwiftLint
21 | run: swiftlint lint --reporter github-actions-logging
22 |
23 | danger:
24 | runs-on: ubuntu-latest
25 | steps:
26 | - uses: actions/checkout@v2
27 | - name: Danger
28 | uses: docker://frmeloni/danger-swift-with-swiftlint:1.3.1
29 | with:
30 | args: --failOnErrors --verbose
31 | env:
32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 | .DS_Store
25 |
26 | ## Obj-C/Swift specific
27 | *.hmap
28 | *.ipa
29 | *.dSYM.zip
30 | *.dSYM
31 |
32 | ## Playgrounds
33 | timeline.xctimeline
34 | playground.xcworkspace
35 |
36 | # Swift Package Manager
37 | #
38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
39 | # Packages/
40 | # Package.pins
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots
68 | fastlane/test_output
69 |
70 | # JetBrains
71 |
72 | .idea
73 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
2 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | included:
2 | - SkeletonViewCore/Sources
3 | disabled_rules:
4 | - trailing_whitespace
5 | - line_length
6 | - type_body_length
7 | - identifier_name
8 | - multiple_closures_with_trailing_closure
9 | - class_delegate_protocol
10 | - force_unwrapping
11 | - force_try
12 | - force_cast
13 | - function_parameter_count
14 | - discouraged_optional_collection
15 | - shorthand_operator
16 | - reduce_boolean
17 | - weak_delegate
18 | - nesting
19 | - closure_end_indentation
20 | - function_default_parameter_at_end
21 | - unowned_variable_capture
22 | - legacy_constructor
23 | - redundant_type_annotation
24 | - vertical_whitespace_opening_braces
25 | opt_in_rules:
26 | - multiline_arguments
27 | - multiline_parameters
28 | - closure_spacing
29 | - closure_body_length
30 | - collection_alignment
31 | - contains_over_filter_is_empty
32 | - contains_over_filter_count
33 | - contains_over_first_not_nil
34 | - contains_over_range_nil_comparison
35 | - convenience_type
36 | - discouraged_object_literal
37 | - discouraged_optional_boolean
38 | - empty_count
39 | - empty_string
40 | - fallthrough
41 | - file_name_no_space
42 | - first_where
43 | - flatmap_over_map_reduce
44 | - implicitly_unwrapped_optional
45 | - joined_default_parameter
46 | - last_where
47 | - literal_expression_end_indentation
48 | - multiline_function_chains
49 | - operator_usage_whitespace
50 | - private_action
51 | - private_outlet
52 | - redundant_optional_initialization
53 | - redundant_set_access_control
54 | - sorted_first_last
55 | - switch_case_on_newline
56 | - unneeded_parentheses_in_closure_argument
57 | - unused_declaration
58 | - unused_import
59 | - discouraged_optional_collection
60 | - enum_case_associated_values_count
61 | - legacy_multiple
62 | - legacy_random
63 | indentation: 2
64 | type_name:
65 | min_length: 2
66 | max_length:
67 | warning: 50
68 | error: 60
69 | file_length:
70 | - 2500
71 | - 3000
72 | large_tuple:
73 | - 5
74 | - 6
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Assets/all_skeletonables.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/all_skeletonables.jpg
--------------------------------------------------------------------------------
/Assets/all_skeletonables_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/all_skeletonables_result.png
--------------------------------------------------------------------------------
/Assets/container_no_skeletonable.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/container_no_skeletonable.jpg
--------------------------------------------------------------------------------
/Assets/container_skeletonable.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/container_skeletonable.jpg
--------------------------------------------------------------------------------
/Assets/container_skeletonable_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/container_skeletonable_result.png
--------------------------------------------------------------------------------
/Assets/debug_description.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/debug_description.png
--------------------------------------------------------------------------------
/Assets/debug_mode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/debug_mode.png
--------------------------------------------------------------------------------
/Assets/demoApp2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/demoApp2.png
--------------------------------------------------------------------------------
/Assets/flatcolors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/flatcolors.png
--------------------------------------------------------------------------------
/Assets/gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/gradient.png
--------------------------------------------------------------------------------
/Assets/gradient_animated.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/gradient_animated.gif
--------------------------------------------------------------------------------
/Assets/header.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/header.jpg
--------------------------------------------------------------------------------
/Assets/header2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/header2.jpg
--------------------------------------------------------------------------------
/Assets/hierarchy_output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/hierarchy_output.png
--------------------------------------------------------------------------------
/Assets/multiline_corner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/multiline_corner.png
--------------------------------------------------------------------------------
/Assets/multiline_customize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/multiline_customize.png
--------------------------------------------------------------------------------
/Assets/multiline_insets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/multiline_insets.png
--------------------------------------------------------------------------------
/Assets/multiline_lastline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/multiline_lastline.png
--------------------------------------------------------------------------------
/Assets/multiline_lineHeight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/multiline_lineHeight.png
--------------------------------------------------------------------------------
/Assets/multiline_lineSpacing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/multiline_lineSpacing.png
--------------------------------------------------------------------------------
/Assets/multilines2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/multilines2.png
--------------------------------------------------------------------------------
/Assets/no_skeletonable.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/no_skeletonable.jpg
--------------------------------------------------------------------------------
/Assets/no_skeletonables_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/no_skeletonables_result.png
--------------------------------------------------------------------------------
/Assets/skeleton_transition_fade.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/skeleton_transition_fade.gif
--------------------------------------------------------------------------------
/Assets/skeleton_transition_nofade.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/skeleton_transition_nofade.gif
--------------------------------------------------------------------------------
/Assets/sliding_bottomRight_to_topLeft.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/sliding_bottomRight_to_topLeft.gif
--------------------------------------------------------------------------------
/Assets/sliding_bottom_to_top.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/sliding_bottom_to_top.gif
--------------------------------------------------------------------------------
/Assets/sliding_left_to_right.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/sliding_left_to_right.gif
--------------------------------------------------------------------------------
/Assets/sliding_right_to_left.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/sliding_right_to_left.gif
--------------------------------------------------------------------------------
/Assets/sliding_topLeft_to_bottomRight.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/sliding_topLeft_to_bottomRight.gif
--------------------------------------------------------------------------------
/Assets/sliding_top_to_bottom.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/sliding_top_to_bottom.gif
--------------------------------------------------------------------------------
/Assets/solid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/solid.png
--------------------------------------------------------------------------------
/Assets/solid_animated.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/solid_animated.gif
--------------------------------------------------------------------------------
/Assets/solid_animated2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/solid_animated2.gif
--------------------------------------------------------------------------------
/Assets/storyboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/storyboard.png
--------------------------------------------------------------------------------
/Assets/tableview_no_skeletonable.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/tableview_no_skeletonable.jpg
--------------------------------------------------------------------------------
/Assets/tableview_no_skeletonable_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/tableview_no_skeletonable_result.png
--------------------------------------------------------------------------------
/Assets/tableview_scheme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/tableview_scheme.png
--------------------------------------------------------------------------------
/Assets/tableview_skeletonable.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/tableview_skeletonable.jpg
--------------------------------------------------------------------------------
/Assets/tableview_skeletonable_result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/tableview_skeletonable_result.png
--------------------------------------------------------------------------------
/Assets/thumb_getting_started.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Assets/thumb_getting_started.png
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | The Code of Conduct governs how we behave in public or in private
4 | whenever the project will be judged by our actions.
5 | We expect it to be honored by everyone who represents the project
6 | officially or informally,
7 | claims affiliation with the project,
8 | or participates directly.
9 |
10 | We strive to:
11 |
12 | * **Be open**: We invite anybody to participate in any aspect of our projects.
13 | Our community is open, and any responsibility can be carried
14 | by any contributor who demonstrates the required capacity and competence.
15 | * **Be empathetic**: We work together to resolve conflict,
16 | assume good intentions,
17 | and do our best to act in an empathic fashion.
18 | By understanding that humanity drops a few packets in online interactions,
19 | and adjusting accordingly,
20 | we can create a comfortable environment for everyone to share their ideas.
21 | * **Be collaborative**: We prefer to work transparently
22 | and to involve interested parties early on in the process.
23 | Wherever possible, we work closely with others in the open source community
24 | to coordinate our efforts.
25 | * **Be decisive**: We expect participants in the project to resolve disagreements constructively.
26 | When they cannot, we escalate the matter to structures
27 | with designated leaders to arbitrate and provide clarity and direction.
28 | * **Be responsible**: We hold ourselves accountable for our actions.
29 | When we make mistakes, we take responsibility for them.
30 | When we need help, we reach out to others.
31 | When it comes time to move on from a project,
32 | we take the proper steps to ensure that others can pick up where we left off.
33 |
34 | This code is not exhaustive or complete.
35 | It serves to distill our common understanding of a
36 | collaborative, shared environment and goals.
37 | We expect it to be followed in spirit as much as in the letter.
38 |
39 | ---
40 |
41 | The **SkeletonView** Code of Conduct is adapted from the [Contributor Covenant][homepage],
42 | version 2.0, available at
43 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
44 |
45 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
46 | enforcement ladder](https://github.com/mozilla/diversity).
47 |
48 | [homepage]: https://www.contributor-covenant.org
49 |
50 | For answers to common questions about this code of conduct, see the FAQ at
51 | https://www.contributor-covenant.org/faq. Translations are available at
52 | https://www.contributor-covenant.org/translations.
53 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributors Guide
2 |
3 | Interested in contributing? Awesome! Before you do though, please read our
4 | [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
5 | well.
6 |
7 | There are many ways you can contribute! :heart:
8 |
9 | ### Bug Reports and Fixes :bug:
10 | - 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,
11 | [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
12 | be reviewed.
13 | - Issues that have already been identified as a bug (note: able to reproduce) will be labelled `🐞 Bug`.
14 | - If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number.
15 |
16 | ### New Features :bulb:
17 | - 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).
18 | - Issues that have been identified as a feature request will be labelled `💡 Enhancement`.
19 | - If you'd like to implement the new feature, please wait for feedback from the project
20 | maintainers before spending too much time writing the code. In some cases, `💡 Enhancement`s may
21 | not align well with the project objectives at the time.
22 |
23 | ### Miscellaneous :sparkles:
24 | - If you have an alternative implementation of something that may have advantages over the way its currently
25 | done, or you have any other change, we would be happy to hear about it!
26 | - If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind.
27 | - If not, [open an Issue](https://github.com/Juanpe/SkeletonView/issues/new) to discuss the idea first.
28 |
29 | If you're new to our project and looking for some way to make your first contribution, look for
30 | Issues labelled `good first issue`.
31 |
32 | ## Requirements
33 |
34 | For your contribution to be accepted:
35 |
36 | - [x] The changes must be approved by code review.
37 | - [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number.
38 |
39 | 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.
40 |
41 | ## Creating a Pull Request
42 |
43 | 1. :fork_and_knife: Fork the repository on GitHub.
44 | 2. :runner: Clone/fetch your fork to your local development machine.
45 | 3. :herb: Create a new branch and check it out.
46 | 4. :crystal_ball: Make your changes and commit them locally.
47 | 5. :arrow_heading_up: Push your new branch to your fork. (e.g. `git push username fix-issue-300`).
48 | 6. :inbox_tray: Open a Pull Request on github.com from your new branch on your fork to `main` in this
49 | repository.
50 |
51 | ## Developer's Certificate of Origin 1.1
52 |
53 | By making a contribution to this project, I certify that:
54 |
55 | - (a) The contribution was created in whole or in part by me and I
56 | have the right to submit it under the open source license
57 | indicated in the file; or
58 |
59 | - (b) The contribution is based upon previous work that, to the best
60 | of my knowledge, is covered under an appropriate open source
61 | license and I have the right under that license to submit that
62 | work with modifications, whether created in whole or in part
63 | by me, under the same open source license (unless I am
64 | permitted to submit under a different license), as indicated
65 | in the file; or
66 |
67 | - (c) The contribution was provided directly to me by some other
68 | person who certified (a), (b) or (c) and I have not modified
69 | it.
70 |
71 | - (d) I understand and agree that this project and the contribution
72 | are public and that a record of the contribution (including all
73 | personal information I submit with it, including my sign-off) is
74 | maintained indefinitely and may be redistributed consistent with
75 | this project or the open source license(s) involved.
76 |
77 | *Wording of statement copied from [elinux.org](http://elinux.org/Developer_Certificate_Of_Origin)*
78 |
--------------------------------------------------------------------------------
/Dangerfile.swift:
--------------------------------------------------------------------------------
1 | import Danger
2 |
3 | let danger = Danger()
4 | let github = danger.github
5 |
6 | // Make it more obvious that a PR is a work in progress and shouldn't be merged yet
7 | if danger.github.pullRequest.title.contains("WIP") {
8 | warn("PR is classed as Work in Progress")
9 | }
10 |
11 | // Warn, asking to update all README files if only English README are updated
12 | let enReameModified = danger.git.modifiedFiles.contains { $0.contains("README.md") }
13 | let zhReameModified = danger.git.modifiedFiles.contains { $0.contains("README_zh.md") }
14 | let koReameModified = danger.git.modifiedFiles.contains { $0.contains("README_ko.md") }
15 | let ptBrReameModified = danger.git.modifiedFiles.contains { $0.contains("README_pt-br.md") }
16 | let otherLanguagesReadmeHaveBeenModified = zhReameModified && koReameModified && ptBrReameModified
17 |
18 | if (enReameModified && !otherLanguagesReadmeHaveBeenModified) {
19 | warn("Consider **also** updating the README for other languages.")
20 | }
21 |
22 | // Warn when there is a big PR
23 | if (danger.github.pullRequest.additions ?? 0) > 500 {
24 | warn("Big PR, try to keep changes smaller if you can")
25 | }
26 |
27 | // Added (or removed) library files need to be added (or removed) from the
28 | // Xcode project to avoid breaking things.
29 | let addedSwiftLibraryFiles = danger.git.createdFiles.contains { $0.fileType == .swift && $0.hasPrefix("Sources") }
30 | let deletedSwiftLibraryFiles = danger.git.deletedFiles.contains { $0.fileType == .swift && $0.hasPrefix("Sources") }
31 | let modifiedXcodeProject = danger.git.modifiedFiles.contains { $0.contains(".xcodeproj") }
32 | if (addedSwiftLibraryFiles || deletedSwiftLibraryFiles) && !modifiedXcodeProject {
33 | fail("Added or removed files require the Xcode project to be updated.")
34 | }
35 |
--------------------------------------------------------------------------------
/Examples/CollectionView/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | @UIApplicationMain
6 | class AppDelegate: UIResponder, UIApplicationDelegate {
7 |
8 | var window: UIWindow?
9 |
10 |
11 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
12 | // Override point for customization after application launch.
13 | return true
14 | }
15 |
16 | func applicationWillResignActive(_ application: UIApplication) {
17 | // 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.
18 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
19 | }
20 |
21 | func applicationDidEnterBackground(_ application: UIApplication) {
22 | // 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.
23 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
24 | }
25 |
26 | func applicationWillEnterForeground(_ application: UIApplication) {
27 | // 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.
28 | }
29 |
30 | func applicationDidBecomeActive(_ application: UIApplication) {
31 | // 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.
32 | }
33 |
34 | func applicationWillTerminate(_ application: UIApplication) {
35 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
36 | }
37 |
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/Examples/CollectionView/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Examples/CollectionView/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Examples/CollectionView/Assets.xcassets/avatar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "avatar.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Examples/CollectionView/Assets.xcassets/avatar.imageset/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Examples/CollectionView/Assets.xcassets/avatar.imageset/avatar.png
--------------------------------------------------------------------------------
/Examples/CollectionView/Assets.xcassets/picture.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "picture.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Examples/CollectionView/Assets.xcassets/picture.imageset/picture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Examples/CollectionView/Assets.xcassets/picture.imageset/picture.png
--------------------------------------------------------------------------------
/Examples/CollectionView/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Examples/CollectionView/CollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 | import SkeletonView
5 |
6 | class CollectionViewCell: UICollectionViewCell {
7 |
8 | var label: UILabel!
9 | var imageView: UIImageView!
10 |
11 | override init(frame: CGRect) {
12 | super.init(frame: frame)
13 |
14 | isSkeletonable = true
15 | createLabel()
16 | createImageView()
17 |
18 | }
19 |
20 | required init?(coder aDecoder: NSCoder) {
21 | fatalError("init(coder:) has not been implemented")
22 | }
23 |
24 | private func createImageView() {
25 | imageView = UIImageView(image: UIImage(named: "picture"))
26 | imageView.isSkeletonable = true
27 | imageView.translatesAutoresizingMaskIntoConstraints = false
28 | imageView.contentMode = .scaleAspectFit
29 | addSubview(imageView)
30 | NSLayoutConstraint.activate([
31 | imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
32 | imageView.topAnchor.constraint(equalTo: topAnchor),
33 | imageView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75),
34 | imageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.75)
35 | ])
36 |
37 |
38 | }
39 |
40 | private func createLabel() {
41 | label = UILabel()
42 | label.isSkeletonable = true
43 | label.text = "Lorem ipsum"
44 | label.textAlignment = .center
45 | label.translatesAutoresizingMaskIntoConstraints = false
46 | addSubview(label)
47 | NSLayoutConstraint.activate([
48 | label.centerXAnchor.constraint(equalTo: centerXAnchor),
49 | label.bottomAnchor.constraint(equalTo: bottomAnchor),
50 | label.heightAnchor.constraint(equalToConstant: 40),
51 | label.widthAnchor.constraint(equalToConstant: frame.width)
52 | ])
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Examples/CollectionView/SkeletonViewExampleCollectionview-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SkeletonViewExample
4 | //
5 | // Created by Juanpe Catalán on 02/11/2017.
6 | // Copyright © 2017 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // 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.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // 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.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // 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.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // 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.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "platform" : "universal",
6 | "reference" : "systemBlueColor"
7 | },
8 | "idiom" : "universal"
9 | },
10 | {
11 | "appearances" : [
12 | {
13 | "appearance" : "luminosity",
14 | "value" : "dark"
15 | }
16 | ],
17 | "color" : {
18 | "color-space" : "srgb",
19 | "components" : {
20 | "alpha" : "1.000",
21 | "blue" : "1.000",
22 | "green" : "1.000",
23 | "red" : "1.000"
24 | }
25 | },
26 | "idiom" : "universal"
27 | }
28 | ],
29 | "info" : {
30 | "author" : "xcode",
31 | "version" : 1
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/Assets.xcassets/avatar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "avatar.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/Assets.xcassets/avatar.imageset/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Juanpe/SkeletonView/30c92f0992888e7b249e788405ac31e2103f5c69/Examples/iOS Example/Sources/Assets.xcassets/avatar.imageset/avatar.png
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/Cell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Cell.swift
3 | // SkeletonViewExample
4 | //
5 | // Created by Juanpe Catalán on 03/11/2017.
6 | // Copyright © 2017 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class Cell: UITableViewCell {
12 |
13 | @IBOutlet weak var avatar: UIImageView!
14 | @IBOutlet weak var label1: UILabel!
15 | @IBOutlet weak var textField: UITextField!
16 |
17 | override func awakeFromNib() {
18 | super.awakeFromNib()
19 | setUpInputAccessoryView()
20 | }
21 |
22 | func setUpInputAccessoryView() {
23 | let bar = UIToolbar()
24 | let reset = UIBarButtonItem(title: "InputAccessoryView", style: .plain, target: self, action: #selector(resetTapped))
25 | bar.items = [reset]
26 | bar.sizeToFit()
27 | textField.inputAccessoryView = bar
28 | }
29 |
30 | @objc func resetTapped() {
31 |
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/Constants.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | 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")]
6 |
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/HeaderFooterSection.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2020 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | class HeaderFooterSection: UITableViewHeaderFooterView {
6 |
7 | lazy var titleLabel: UILabel = {
8 | let label = UILabel()
9 |
10 | label.text = " "
11 | label.isSkeletonable = true
12 | label.linesCornerRadius = 10
13 |
14 | return label
15 | }()
16 |
17 | override init(reuseIdentifier: String?) {
18 | super.init(reuseIdentifier: reuseIdentifier)
19 |
20 | isSkeletonable = true
21 |
22 | contentView.addSubview(titleLabel)
23 |
24 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
25 |
26 | NSLayoutConstraint.activate([
27 | titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
28 | titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
29 | titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
30 | titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10)
31 | ])
32 |
33 | backgroundView = UIView()
34 | if #available(iOS 13.0, *) {
35 | backgroundView?.backgroundColor = .systemBackground
36 | } else {
37 | backgroundView?.backgroundColor = .white
38 | }
39 | }
40 |
41 | required init?(coder: NSCoder) {
42 | fatalError("init(coder:) has not been implemented")
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSupportsIndirectInputEvents
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Examples/iOS Example/Sources/UITextViewByCodeViewController.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2022 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 | import SkeletonView
5 |
6 | class UITextViewByCodeViewController: UIViewController {
7 | lazy var textView: UITextView = {
8 | let tv = UITextView()
9 |
10 | tv.text = " "
11 | tv.linesCornerRadius = 10
12 | tv.isSkeletonable = true
13 | tv.translatesAutoresizingMaskIntoConstraints = false
14 |
15 | return tv
16 | }()
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 |
21 | setupUI()
22 | setupElementsConstraints()
23 | showSkeletonForElements()
24 | }
25 |
26 | override func viewWillAppear(_ animated: Bool) {
27 | super.viewWillAppear(animated)
28 | }
29 |
30 | func setupUI() {
31 | view.addSubview(textView)
32 | }
33 |
34 | func setupElementsConstraints() {
35 | textView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
36 | textView.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor, constant: 10).isActive = true
37 | textView.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
38 | textView.heightAnchor.constraint(equalToConstant: 100).isActive = true
39 | }
40 |
41 | func showSkeletonForElements() {
42 | textView.showSkeleton()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Examples/iOS Example/iOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/iOS Example/iOS Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // tvOS Example
4 | //
5 | // Created by Juanpe Catalán on 18/8/21.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | func applicationWillResignActive(_ application: UIApplication) {
22 | // 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.
23 | // 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.
24 | }
25 |
26 | func applicationDidEnterBackground(_ application: UIApplication) {
27 | // 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.
28 | }
29 |
30 | func applicationWillEnterForeground(_ application: UIApplication) {
31 | // 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.
32 | }
33 |
34 | func applicationDidBecomeActive(_ application: UIApplication) {
35 | // 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.
36 | }
37 |
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "filename" : "App Icon - App Store.imagestack",
5 | "idiom" : "tv",
6 | "role" : "primary-app-icon",
7 | "size" : "1280x768"
8 | },
9 | {
10 | "filename" : "App Icon.imagestack",
11 | "idiom" : "tv",
12 | "role" : "primary-app-icon",
13 | "size" : "400x240"
14 | },
15 | {
16 | "filename" : "Top Shelf Image Wide.imageset",
17 | "idiom" : "tv",
18 | "role" : "top-shelf-image-wide",
19 | "size" : "2320x720"
20 | },
21 | {
22 | "filename" : "Top Shelf Image.imageset",
23 | "idiom" : "tv",
24 | "role" : "top-shelf-image",
25 | "size" : "1920x720"
26 | }
27 | ],
28 | "info" : {
29 | "author" : "xcode",
30 | "version" : 1
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | arm64
30 |
31 | UIUserInterfaceStyle
32 | Automatic
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/Sources/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // tvOS Example
4 | //
5 | // Created by Juanpe Catalán on 18/8/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class ViewController: UIViewController {
11 |
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 | // Do any additional setup after loading the view.
15 | }
16 |
17 |
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/tvOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/tvOS Example/tvOS Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
4 | gem 'cocoapods', '~> 1.7.0.beta.2'
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Juanpe Catalán
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "SkeletonView",
7 | platforms: [
8 | .iOS(.v9),
9 | .tvOS(.v9)
10 | ],
11 | products: [
12 | .library(
13 | name: "SkeletonView",
14 | targets: ["SkeletonView"]
15 | )
16 | ],
17 | targets: [
18 | .target(
19 | name: "SkeletonView",
20 | path: "SkeletonViewCore/Sources",
21 | resources: [.copy("Supporting Files/PrivacyInfo.xcprivacy")]
22 | ),
23 | .testTarget(
24 | name: "SkeletonViewTests",
25 | dependencies: ["SkeletonView"],
26 | path: "SkeletonViewCore/Tests"
27 | )
28 | ],
29 | swiftLanguageVersions: [.v5]
30 | )
31 |
--------------------------------------------------------------------------------
/SkeletonVIew.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/SkeletonVIew.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SkeletonView.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "SkeletonView"
3 | s.version = "1.31.0"
4 | s.summary = "An elegant way to show users that something is happening and also prepare them to which contents he is waiting"
5 | s.description = <<-DESC
6 | 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.
7 | 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.
8 | DESC
9 | s.homepage = "https://github.com/Juanpe/SkeletonView"
10 | s.license = { :type => "MIT", :file => "LICENSE" }
11 | s.author = { "Juanpe Catalán" => "juanpecm@gmail.com" }
12 | s.social_media_url = "https://x.com/JuanpeCatalan"
13 | s.ios.deployment_target = "9.0"
14 | s.tvos.deployment_target = "9.0"
15 | s.swift_version = "5.0"
16 | s.source = { :git => "https://github.com/Juanpe/SkeletonView.git", :tag => s.version.to_s }
17 | s.source_files = "SkeletonViewCore/Sources/**/*.{swift,h}"
18 | end
19 |
--------------------------------------------------------------------------------
/SkeletonView.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/SkeletonView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SkeletonView.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SkeletonView.xcodeproj/xcshareddata/IDETemplateMacros.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FILEHEADER
6 |
7 | // Copyright SkeletonView. All Rights Reserved.
8 | //
9 | // Licensed under the MIT License (the "License");
10 | // you may not use this file except in compliance with the License.
11 | // You may obtain a copy of the License at
12 | //
13 | // https://opensource.org/licenses/MIT
14 | //
15 | // ___FILENAME___
16 | //
17 | // Created by ___FULLUSERNAME___ on ___DATE___.
18 |
19 |
20 |
--------------------------------------------------------------------------------
/SkeletonView.xcodeproj/xcshareddata/xcschemes/SkeletonView iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
63 |
64 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/SkeletonView.xcodeproj/xcshareddata/xcschemes/SkeletonView tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/AnimationBuilder/SkeletonAnimationBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SkeletonAnimationBuilder.swift
3 | // SkeletonView-iOS
4 | //
5 | // Created by Juanpe Catalán on 17/11/2017.
6 | // Copyright © 2017 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation
12 |
13 | public class SkeletonAnimationBuilder {
14 |
15 | public init() { }
16 |
17 | public func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5, autoreverses: Bool = false) -> SkeletonLayerAnimation {
18 | { _ in
19 | let startPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.startPoint))
20 | startPointAnim.fromValue = direction.startPoint.from
21 | startPointAnim.toValue = direction.startPoint.to
22 |
23 | let endPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.endPoint))
24 | endPointAnim.fromValue = direction.endPoint.from
25 | endPointAnim.toValue = direction.endPoint.to
26 |
27 | let animGroup = CAAnimationGroup()
28 | animGroup.animations = [startPointAnim, endPointAnim]
29 | animGroup.duration = duration
30 | animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
31 | animGroup.repeatCount = .infinity
32 | animGroup.autoreverses = autoreverses
33 | animGroup.isRemovedOnCompletion = false
34 |
35 | return animGroup
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/Appearance/SkeletonAppearance.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonAppearance.swift
11 | //
12 |
13 | import UIKit
14 |
15 | public enum SkeletonAppearance {
16 | public static var `default` = SkeletonViewAppearance.shared
17 | }
18 |
19 | // codebeat:disable[TOO_MANY_IVARS]
20 | public class SkeletonViewAppearance {
21 |
22 | static var shared = SkeletonViewAppearance()
23 |
24 | public var tintColor: UIColor = .skeletonDefault
25 |
26 | public var gradient = SkeletonGradient(baseColor: .skeletonDefault)
27 |
28 | public var multilineHeight: CGFloat = 15
29 |
30 | public lazy var textLineHeight: SkeletonTextLineHeight = .fixed(SkeletonAppearance.default.multilineHeight)
31 |
32 | public var multilineSpacing: CGFloat = 10
33 |
34 | public var multilineLastLineFillPercent: Int = 70
35 |
36 | public var multilineCornerRadius: Int = 0
37 |
38 | public var renderSingleLineAsView: Bool = false
39 |
40 | public var skeletonCornerRadius: Float = 0
41 |
42 | }
43 | // codebeat:enable[TOO_MANY_IVARS]
44 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/Collections/CollectionViews/SkeletonCollectionViewProtocols.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SkeletonCollectionViewProtocols.swift
3 | // SkeletonView-iOS
4 | //
5 | // Created by Juanpe Catalán on 06/11/2017.
6 | // Copyright © 2017 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
12 | func numSections(in collectionSkeletonView: UICollectionView) -> Int
13 | func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
14 | func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
15 | func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier?
16 | func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell?
17 | func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath)
18 | func collectionSkeletonView(_ skeletonView: UICollectionView, prepareViewForSkeleton view: UICollectionReusableView, at indexPath: IndexPath)
19 | }
20 |
21 | public extension SkeletonCollectionViewDataSource {
22 | func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
23 | UICollectionView.automaticNumberOfSkeletonItems
24 | }
25 |
26 | func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? {
27 | nil
28 | }
29 |
30 | func numSections(in collectionSkeletonView: UICollectionView) -> Int {
31 | 1
32 | }
33 |
34 | func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell? {
35 | nil
36 | }
37 |
38 | func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) { }
39 |
40 | func collectionSkeletonView(_ skeletonView: UICollectionView, prepareViewForSkeleton view: UICollectionReusableView, at indexPath: IndexPath) { }
41 | }
42 |
43 | public protocol SkeletonCollectionViewDelegate: UICollectionViewDelegate { }
44 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/Collections/TableViews/SkeletonTableViewProtocols.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SkeletonTableViewProtocols.swift
3 | // SkeletonView-iOS
4 | //
5 | // Created by Juanpe Catalán on 06/11/2017.
6 | // Copyright © 2017 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UITableView {
12 | public static let automaticNumberOfSkeletonRows = -1
13 | }
14 |
15 | public typealias ReusableHeaderFooterIdentifier = String
16 |
17 | public protocol SkeletonTableViewDataSource: UITableViewDataSource {
18 | func numSections(in collectionSkeletonView: UITableView) -> Int
19 | func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
20 | func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
21 | func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell?
22 | func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath)
23 | }
24 |
25 | public extension SkeletonTableViewDataSource {
26 | func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int {
27 | return UITableView.automaticNumberOfSkeletonRows
28 | }
29 |
30 | func numSections(in collectionSkeletonView: UITableView) -> Int { return 1 }
31 |
32 | /// Keeping the misspelled version around until it can be deprecated
33 | /// Right now, it just calls the new correctly spelled method and returns its result
34 | @available(*, deprecated, renamed: "collectionSkeletonView(_:cellIdentifierForRowAt:)")
35 | func collectionSkeletonView(_ skeletonView: UITableView, cellIdenfierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
36 | return collectionSkeletonView(skeletonView, cellIdentifierForRowAt: indexPath)
37 | }
38 |
39 | func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? {
40 | nil
41 | }
42 |
43 | func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) { }
44 | }
45 |
46 | public protocol SkeletonTableViewDelegate: UITableViewDelegate {
47 | func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier?
48 | func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier?
49 | }
50 |
51 | public extension SkeletonTableViewDelegate {
52 | func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? {
53 | return nil
54 | }
55 |
56 | func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? {
57 | return nil
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/Deprecated.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // Deprecated.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import UIKit
15 |
16 | public extension Notification.Name {
17 |
18 | @available(*, deprecated, renamed: "skeletonWillAppear")
19 | static let willBeginShowingSkeletons = Notification.Name.skeletonWillAppearNotification
20 |
21 | @available(*, deprecated, renamed: "skeletonDidAppear")
22 | static let didShowSkeletons = Notification.Name.skeletonDidAppearNotification
23 |
24 | @available(*, deprecated, renamed: "skeletonWillUpdate")
25 | static let willBeginUpdatingSkeletons = Notification.Name.skeletonWillUpdateNotification
26 |
27 | @available(*, deprecated, renamed: "skeletonDidUpdate")
28 | static let didUpdateSkeletons = Notification.Name.skeletonDidUpdateNotification
29 |
30 | @available(*, deprecated, renamed: "skeletonWillDisappear")
31 | static let willBeginHidingSkeletons = Notification.Name.skeletonWillDisappearNotification
32 |
33 | @available(*, deprecated, renamed: "skeletonDidDisappear")
34 | static let didHideSkeletons = Notification.Name.skeletonDidDisappearNotification
35 |
36 | }
37 |
38 | public extension UIView {
39 |
40 | @available(*, deprecated, renamed: "sk.treeNodesDescription")
41 | var skeletonDescription: String {
42 | sk.skeletonTreeDescription
43 | }
44 |
45 | @available(*, deprecated, renamed: "sk.isSkeletonActive")
46 | var isSkeletonActive: Bool {
47 | sk.isSkeletonActive
48 | }
49 |
50 | }
51 |
52 | public extension UILabel {
53 |
54 | @IBInspectable
55 | @available(*, deprecated, renamed: "skeletonTextLineHeight")
56 | var useFontLineHeight: Bool {
57 | get {
58 | textLineHeight == .relativeToFont
59 | }
60 | set {
61 | textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight)
62 | }
63 | }
64 |
65 | }
66 |
67 | public extension UITextView {
68 |
69 | @IBInspectable
70 | @available(*, deprecated, renamed: "skeletonTextLineHeight")
71 | var useFontLineHeight: Bool {
72 | get {
73 | textLineHeight == .relativeToFont
74 | }
75 | set {
76 | textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight)
77 | }
78 | }
79 |
80 | }
81 |
82 | public extension SkeletonViewAppearance {
83 |
84 | @available(*, deprecated, renamed: "textLineHeight")
85 | var useFontLineHeight: Bool {
86 | get {
87 | textLineHeight == .relativeToFont
88 | }
89 | set {
90 | textLineHeight = newValue ? .relativeToFont : .fixed(SkeletonAppearance.default.multilineHeight)
91 | }
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/FoundationExtensions/Notification+SkeletonFlow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // Notification+SkeletonFlow.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import Foundation
15 |
16 | public extension Notification.Name {
17 |
18 | static let skeletonWillAppearNotification = Notification.Name("skeletonWillAppear")
19 | static let skeletonDidAppearNotification = Notification.Name("skeletonDidAppear")
20 | static let skeletonWillUpdateNotification = Notification.Name("skeletonWillUpdate")
21 | static let skeletonDidUpdateNotification = Notification.Name("skeletonDidUpdate")
22 | static let skeletonWillDisappearNotification = Notification.Name("skeletonWillDisappear")
23 | static let skeletonDidDisappearNotification = Notification.Name("skeletonDidDisappear")
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/Models/GradientDirection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // GradientDirection.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import UIKit
15 |
16 | public enum GradientDirection {
17 |
18 | case leftRight
19 | case rightLeft
20 | case topBottom
21 | case bottomTop
22 | case topLeftBottomRight
23 | case bottomRightTopLeft
24 |
25 | public func slidingAnimation(duration: CFTimeInterval = 1.5, autoreverses: Bool = false) -> SkeletonLayerAnimation {
26 | return SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: self, duration: duration, autoreverses: autoreverses)
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/Models/SkeletonGradient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonGradient.swift
11 | //
12 | // Created by Juanpe Catalán on 05/11/2017.
13 |
14 | import UIKit
15 |
16 | public struct SkeletonGradient {
17 |
18 | private let gradientColors: [UIColor]
19 |
20 | public var colors: [UIColor] {
21 | return gradientColors
22 | }
23 |
24 | public init(baseColor: UIColor, secondaryColor: UIColor? = nil) {
25 | if let secondary = secondaryColor {
26 | self.gradientColors = [baseColor, secondary, baseColor]
27 | } else {
28 | self.gradientColors = baseColor.makeGradient()
29 | }
30 | }
31 |
32 | public init(colors: [UIColor]) {
33 | self.gradientColors = colors
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/Models/SkeletonTextLineHeight.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonTextLineHeight.swift
11 | //
12 | // Created by Juanpe Catalán on 22/11/21.
13 |
14 | import UIKit
15 |
16 | public enum SkeletonTextLineHeight: Equatable {
17 |
18 | /// Calculates the line height based on the font line height.
19 | case relativeToFont
20 |
21 | /// Calculates the line height based on the height constraints.
22 | ///
23 | /// If no constraints exist, the height will be set to the `multilineHeight`
24 | /// value defined in the `SkeletonAppearance`.
25 | case relativeToConstraints
26 |
27 | /// Returns the specific height specified as the associated value.
28 | case fixed(CGFloat)
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/Models/SkeletonTextNumberOfLines.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonTextNumberOfLines.swift
11 | //
12 | // Created by Juanpe Catalán on 10/1/22.
13 |
14 | import UIKit
15 |
16 | public enum SkeletonTextNumberOfLines: Equatable, ExpressibleByIntegerLiteral {
17 |
18 | /// Returns `numberOfLines` value.
19 | case inherited
20 |
21 | /// Returns the specific number of lines specified as the associated value.
22 | case custom(Int)
23 |
24 | }
25 |
26 | public extension SkeletonTextNumberOfLines {
27 |
28 | init(integerLiteral value: Int) {
29 | self = .custom(value)
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/Models/SkeletonTransitionStyle.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2019 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | public enum SkeletonTransitionStyle: Equatable {
6 | case none
7 | case crossDissolve(TimeInterval)
8 | }
9 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/Models/SkeletonType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonType.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import UIKit
15 |
16 | public enum SkeletonType {
17 |
18 | case solid
19 | case gradient
20 |
21 | var layer: CALayer {
22 | switch self {
23 | case .solid:
24 | return CALayer()
25 | case .gradient:
26 | return CAGradientLayer()
27 | }
28 | }
29 |
30 | func defaultLayerAnimation(isRTL: Bool) -> SkeletonLayerAnimation {
31 | switch self {
32 | case .solid:
33 | return { $0.pulse }
34 | case .gradient:
35 | return { SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: isRTL ? .rightLeft : .leftRight) }()
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/SkeletonExtended.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonExtended.swift
11 | //
12 | // Created by Juanpe Catalán on 23/8/21.
13 |
14 | import Foundation
15 |
16 | /// Type that acts as a generic extension point for all `SkeletonViewExtended` types.
17 | public struct SkeletonViewExtension {
18 | /// Stores the type or meta-type of any extended type.
19 | public private(set) var type: ExtendedType
20 |
21 | /// Create an instance from the provided value.
22 | ///
23 | /// - Parameter type: Instance being extended.
24 | public init(_ type: ExtendedType) {
25 | self.type = type
26 | }
27 | }
28 |
29 | /// Protocol describing the `sk` extension points for SkeletonView extended types.
30 | public protocol SkeletonViewExtended {
31 | /// Type being extended.
32 | associatedtype ExtendedType
33 |
34 | /// Instance SkeletonView extension point.
35 | var sk: SkeletonViewExtension { get set }
36 | }
37 |
38 | extension SkeletonViewExtended {
39 | /// Instance SkeletonView extension point.
40 | public var sk: SkeletonViewExtension {
41 | get { SkeletonViewExtension(self) }
42 | // swiftlint:disable:next unused_setter_value
43 | set {}
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/UIKitExtensions/CALayer+Animations.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // CALayer+Animations.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import UIKit
15 |
16 | public extension CALayer {
17 |
18 | var pulse: CAAnimation {
19 | let pulseAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.backgroundColor))
20 | pulseAnimation.fromValue = backgroundColor
21 |
22 | // swiftlint:disable:next force_unwrapping
23 | pulseAnimation.toValue = UIColor(cgColor: backgroundColor!).complementaryColor.cgColor
24 | pulseAnimation.duration = 1
25 | pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
26 | pulseAnimation.autoreverses = true
27 | pulseAnimation.repeatCount = .infinity
28 | pulseAnimation.isRemovedOnCompletion = false
29 | return pulseAnimation
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/UIKitExtensions/UICollectionView+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UICollectionView+Extensions.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import UIKit
15 |
16 | public extension UICollectionView {
17 |
18 | static let automaticNumberOfSkeletonItems = -1
19 |
20 | func prepareSkeleton(completion: @escaping (Bool) -> Void) {
21 | guard let originalDataSource = self.dataSource as? SkeletonCollectionViewDataSource,
22 | !(originalDataSource is SkeletonCollectionDataSource)
23 | else { return }
24 |
25 | let dataSource = SkeletonCollectionDataSource(collectionViewDataSource: originalDataSource, rowHeight: 0.0)
26 | self.skeletonDataSource = dataSource
27 | performBatchUpdates({
28 | self.reloadData()
29 | }) { done in
30 | completion(done)
31 |
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/UIKitExtensions/UILabel+IBInspectable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UILabel+IBInspectable.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import UIKit
15 |
16 | public extension UILabel {
17 |
18 | @IBInspectable
19 | var lastLineFillPercent: Int {
20 | get { return lastLineFillingPercent }
21 | set { lastLineFillingPercent = min(newValue, 100) }
22 | }
23 |
24 | @IBInspectable
25 | var linesCornerRadius: Int {
26 | get { return multilineCornerRadius }
27 | set { multilineCornerRadius = newValue }
28 | }
29 |
30 | @IBInspectable
31 | var skeletonLineSpacing: CGFloat {
32 | get { return multilineSpacing }
33 | set { multilineSpacing = newValue }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/UIKitExtensions/UILabel+SKExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UILabel+SKExtensions.swift
11 | //
12 | // Created by Juanpe Catalán on 23/8/21.
13 |
14 | import UIKit
15 |
16 | public extension UILabel {
17 |
18 | /// Defines the skeleton paddings.
19 | var skeletonPaddingInsets: UIEdgeInsets {
20 | get {
21 | paddingInsets
22 | }
23 | set {
24 | paddingInsets = newValue
25 | }
26 | }
27 |
28 | /// Defines the logic for calculating the height of the skeleton lines.
29 | /// Default: `SkeletonAppearance.default.textLineHeight`
30 | var skeletonTextLineHeight: SkeletonTextLineHeight {
31 | get {
32 | textLineHeight
33 | }
34 | set {
35 | textLineHeight = newValue
36 | }
37 | }
38 |
39 | /// Defines the logic for calculating the number of lines of the skeleton.
40 | /// Default: `inherited`
41 | var skeletonTextNumberOfLines: SkeletonTextNumberOfLines {
42 | get {
43 | skeletonNumberOfLines
44 | }
45 | set {
46 | skeletonNumberOfLines = newValue
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/UIKitExtensions/UITextView+IBInspectable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UITextView+IBInspectable.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import UIKit
15 |
16 | public extension UITextView {
17 |
18 | @IBInspectable
19 | var lastLineFillPercent: Int {
20 | get { return lastLineFillingPercent }
21 | set { lastLineFillingPercent = min(newValue, 100) }
22 | }
23 |
24 | @IBInspectable
25 | var linesCornerRadius: Int {
26 | get { return multilineCornerRadius }
27 | set { multilineCornerRadius = newValue }
28 | }
29 |
30 | @IBInspectable
31 | var skeletonLineSpacing: CGFloat {
32 | get { return multilineSpacing }
33 | set { multilineSpacing = newValue }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/UIKitExtensions/UITextView+SKExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UITextView+SKExtensions.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import UIKit
15 |
16 | public extension UITextView {
17 |
18 | /// Defines the skeleton paddings.
19 | var skeletonPaddingInsets: UIEdgeInsets {
20 | get {
21 | paddingInsets
22 | }
23 | set {
24 | paddingInsets = newValue
25 | }
26 | }
27 |
28 | /// Defines the logic for calculating the height of the skeleton lines.
29 | /// Default: `SkeletonAppearance.default.textLineHeight`
30 | var skeletonTextLineHeight: SkeletonTextLineHeight {
31 | get {
32 | textLineHeight
33 | }
34 | set {
35 | textLineHeight = newValue
36 | }
37 | }
38 |
39 | /// Defines the logic for calculating the number of lines of the skeleton.
40 | /// Default: `inherited`
41 | var skeletonTextNumberOfLines: SkeletonTextNumberOfLines {
42 | get {
43 | skeletonNumberOfLines
44 | }
45 | set {
46 | skeletonNumberOfLines = newValue
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+IBInspectable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UIView+IBInspectable.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import UIKit
15 |
16 | public extension UIView {
17 |
18 | @IBInspectable
19 | var isSkeletonable: Bool {
20 | get { _skeletonable }
21 | set { _skeletonable = newValue }
22 | }
23 |
24 | @IBInspectable
25 | var isHiddenWhenSkeletonIsActive: Bool {
26 | get { _hiddenWhenSkeletonIsActive }
27 | set { _hiddenWhenSkeletonIsActive = newValue }
28 | }
29 |
30 | @IBInspectable
31 | var isUserInteractionDisabledWhenSkeletonIsActive: Bool {
32 | get { _disabledWhenSkeletonIsActive }
33 | set { _disabledWhenSkeletonIsActive = newValue }
34 | }
35 |
36 | @IBInspectable
37 | var skeletonCornerRadius: Float {
38 | get { _skeletonableCornerRadius }
39 | set { _skeletonableCornerRadius = newValue }
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/API/UIKitExtensions/UIView+SKExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UIView+SKExtensions.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import UIKit
15 |
16 | public extension SkeletonViewExtension where ExtendedType: UIView {
17 |
18 | /// Returns a string that describes the hierarchy of the skeleton, indicating
19 | /// whether the receiver is skeletonable and all skeletonable children.
20 | var skeletonTreeDescription: String {
21 | guard let theJSONData = try? JSONSerialization.data(withJSONObject: treeNode.dictionaryRepresentation, options: [.prettyPrinted]) else {
22 | skeletonLog("Skeleton tree generation has failed!")
23 | return ""
24 | }
25 |
26 | return String(data: theJSONData, encoding: .utf8)!
27 | }
28 |
29 | var isSkeletonActive: Bool {
30 | type._status == .on || type.subviewsSkeletonables.contains(where: { $0.sk.isSkeletonActive })
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/Collections/CollectionSkeleton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollectionSkeleton.swift
3 | // SkeletonView-iOS
4 | //
5 | // Created by Juanpe Catalán on 02/11/2017.
6 | // Copyright © 2017 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | enum CollectionAssociatedKeys {
12 | static var dummyDataSource = "dummyDataSource"
13 | static var dummyDelegate = "dummyDelegate"
14 | }
15 |
16 | protocol CollectionSkeleton {
17 |
18 | var skeletonDataSource: SkeletonCollectionDataSource? { get set }
19 | var skeletonDelegate: SkeletonCollectionDelegate? { get set }
20 | var estimatedNumberOfRows: Int { get }
21 |
22 | func addDummyDataSource()
23 | func updateDummyDataSource()
24 | func removeDummyDataSource(reloadAfter: Bool)
25 | func disableUserInteraction()
26 | func enableUserInteraction()
27 |
28 | }
29 |
30 | extension CollectionSkeleton where Self: UIScrollView {
31 |
32 | var estimatedNumberOfRows: Int { return 0 }
33 | func addDummyDataSource() {}
34 | func removeDummyDataSource(reloadAfter: Bool) {}
35 |
36 | func disableUserInteraction() {
37 | if isUserInteractionDisabledWhenSkeletonIsActive {
38 | isUserInteractionEnabled = false
39 | isScrollEnabled = false
40 | }
41 | }
42 |
43 | func enableUserInteraction() {
44 | if isUserInteractionDisabledWhenSkeletonIsActive {
45 | isUserInteractionEnabled = true
46 | isScrollEnabled = true
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SkeletonCollectionDataSource.swift
3 | // SkeletonView-iOS
4 | //
5 | // Created by Juanpe Catalán on 02/11/2017.
6 | // Copyright © 2017 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public typealias ReusableCellIdentifier = String
12 |
13 | class SkeletonCollectionDataSource: NSObject {
14 | weak var originalTableViewDataSource: SkeletonTableViewDataSource?
15 | weak var originalCollectionViewDataSource: SkeletonCollectionViewDataSource?
16 | var rowHeight: CGFloat = 0.0
17 | var originalRowHeight: CGFloat = 0.0
18 |
19 | convenience init(tableViewDataSource: SkeletonTableViewDataSource? = nil, collectionViewDataSource: SkeletonCollectionViewDataSource? = nil, rowHeight: CGFloat = 0.0, originalRowHeight: CGFloat = 0.0) {
20 | self.init()
21 | self.originalTableViewDataSource = tableViewDataSource
22 | self.originalCollectionViewDataSource = collectionViewDataSource
23 | self.rowHeight = rowHeight
24 | self.originalRowHeight = originalRowHeight
25 | }
26 | }
27 |
28 | // MARK: - UITableViewDataSource
29 | extension SkeletonCollectionDataSource: UITableViewDataSource {
30 | func numberOfSections(in tableView: UITableView) -> Int {
31 | originalTableViewDataSource?.numSections(in: tableView) ?? 0
32 | }
33 |
34 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
35 | guard let originalTableViewDataSource = originalTableViewDataSource else {
36 | return 0
37 | }
38 |
39 | let numberOfRows = originalTableViewDataSource.collectionSkeletonView(tableView, numberOfRowsInSection: section)
40 |
41 | if numberOfRows == UITableView.automaticNumberOfSkeletonRows {
42 | return tableView.estimatedNumberOfRows
43 | } else {
44 | return numberOfRows
45 | }
46 | }
47 |
48 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
49 | guard let cell = originalTableViewDataSource?.collectionSkeletonView(tableView, skeletonCellForRowAt: indexPath) else {
50 | let cellIdentifier = originalTableViewDataSource?.collectionSkeletonView(tableView, cellIdentifierForRowAt: indexPath) ?? ""
51 | let fakeCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
52 |
53 | originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: fakeCell, at: indexPath)
54 | skeletonizeViewIfContainerSkeletonIsActive(container: tableView, view: fakeCell)
55 |
56 | return fakeCell
57 | }
58 |
59 | originalTableViewDataSource?.collectionSkeletonView(tableView, prepareCellForSkeleton: cell, at: indexPath)
60 | skeletonizeViewIfContainerSkeletonIsActive(container: tableView, view: cell)
61 | return cell
62 | }
63 | }
64 |
65 | // MARK: - UICollectionViewDataSource
66 | extension SkeletonCollectionDataSource: UICollectionViewDataSource {
67 | func numberOfSections(in collectionView: UICollectionView) -> Int {
68 | originalCollectionViewDataSource?.numSections(in: collectionView) ?? 0
69 | }
70 |
71 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
72 | guard let originalCollectionViewDataSource = originalCollectionViewDataSource else {
73 | return 0
74 | }
75 |
76 | let numberOfItems = originalCollectionViewDataSource.collectionSkeletonView(collectionView, numberOfItemsInSection: section)
77 |
78 | if numberOfItems == UICollectionView.automaticNumberOfSkeletonItems {
79 | return collectionView.estimatedNumberOfRows
80 | } else {
81 | return numberOfItems
82 | }
83 | }
84 |
85 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
86 | guard let cell = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, skeletonCellForItemAt: indexPath) else {
87 | let cellIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, cellIdentifierForItemAt: indexPath) ?? ""
88 | let fakeCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
89 |
90 | originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: fakeCell, at: indexPath)
91 | skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: fakeCell)
92 |
93 | return fakeCell
94 | }
95 |
96 | originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareCellForSkeleton: cell, at: indexPath)
97 | skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: cell)
98 | return cell
99 | }
100 |
101 | func collectionView(_ collectionView: UICollectionView,
102 | viewForSupplementaryElementOfKind kind: String,
103 | at indexPath: IndexPath) -> UICollectionReusableView {
104 | if let viewIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, supplementaryViewIdentifierOfKind: kind, at: indexPath) {
105 | let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: viewIdentifier, for: indexPath)
106 |
107 | originalCollectionViewDataSource?.collectionSkeletonView(collectionView, prepareViewForSkeleton: view, at: indexPath)
108 | skeletonizeViewIfContainerSkeletonIsActive(container: collectionView, view: view)
109 | return view
110 | }
111 |
112 | return originalCollectionViewDataSource?.collectionView?(collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath) ?? UICollectionReusableView()
113 | }
114 |
115 | }
116 |
117 | extension SkeletonCollectionDataSource {
118 | private func skeletonizeViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
119 | guard container.sk.isSkeletonActive,
120 | let skeletonConfig = container._currentSkeletonConfig else {
121 | return
122 | }
123 |
124 | view.showSkeleton(
125 | skeletonConfig: skeletonConfig,
126 | notifyDelegate: false
127 | )
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/Collections/SkeletonCollectionDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SkeletonCollectionDelegate.swift
3 | // SkeletonView-iOS
4 | //
5 | // Created by Juanpe Catalán on 30/03/2018.
6 | // Copyright © 2018 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SkeletonCollectionDelegate: NSObject {
12 |
13 | weak var originalTableViewDelegate: SkeletonTableViewDelegate?
14 | weak var originalCollectionViewDelegate: SkeletonCollectionViewDelegate?
15 |
16 | init(
17 | tableViewDelegate: SkeletonTableViewDelegate? = nil,
18 | collectionViewDelegate: SkeletonCollectionViewDelegate? = nil
19 | ) {
20 | self.originalTableViewDelegate = tableViewDelegate
21 | self.originalCollectionViewDelegate = collectionViewDelegate
22 | }
23 |
24 | }
25 |
26 | // MARK: - UITableViewDelegate
27 | extension SkeletonCollectionDelegate: UITableViewDelegate {
28 |
29 | func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
30 | headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForHeaderInSection: section))
31 | }
32 |
33 | func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
34 | headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForFooterInSection: section))
35 | }
36 |
37 | func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) {
38 | view.hideSkeleton()
39 | originalTableViewDelegate?.tableView?(tableView, didEndDisplayingHeaderView: view, forSection: section)
40 | }
41 |
42 | func tableView(_ tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int) {
43 | view.hideSkeleton()
44 | originalTableViewDelegate?.tableView?(tableView, didEndDisplayingFooterView: view, forSection: section)
45 | }
46 |
47 | func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
48 | cell.hideSkeleton()
49 | originalTableViewDelegate?.tableView?(tableView, didEndDisplaying: cell, forRowAt: indexPath)
50 | }
51 |
52 | }
53 |
54 | // MARK: - UICollectionViewDelegate
55 | extension SkeletonCollectionDelegate: UICollectionViewDelegate { }
56 |
57 | private extension SkeletonCollectionDelegate {
58 |
59 | func skeletonizeViewIfContainerSkeletonIsActive(container: UIView, view: UIView) {
60 | guard container.sk.isSkeletonActive,
61 | let skeletonConfig = container._currentSkeletonConfig
62 | else {
63 | return
64 | }
65 |
66 | view.showSkeleton(
67 | skeletonConfig: skeletonConfig,
68 | notifyDelegate: false
69 | )
70 | }
71 |
72 | func headerOrFooterView(_ tableView: UITableView, for viewIdentifier: String? ) -> UIView? {
73 | guard let viewIdentifier = viewIdentifier,
74 | let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier)
75 | else {
76 | return nil
77 | }
78 |
79 | skeletonizeViewIfContainerSkeletonIsActive(
80 | container: tableView,
81 | view: header
82 | )
83 |
84 | return header
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/Collections/SkeletonReusableCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SkeletonReusableCell.swift
3 | // SkeletonView-iOS
4 | //
5 | // Created by Juanpe Catalán on 30/03/2018.
6 | // Copyright © 2018 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol SkeletonReusableCell { }
12 |
13 | extension UITableViewCell: SkeletonReusableCell { }
14 |
15 | extension UICollectionViewCell: SkeletonReusableCell { }
16 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/Debug/SkeletonDebug.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonDebug.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import Foundation
15 | import UIKit
16 |
17 | enum SkeletonEnvironmentKey: String {
18 | case debugMode = "SKELETON_DEBUG"
19 | }
20 |
21 | extension Dictionary {
22 | subscript (_ key: SkeletonEnvironmentKey) -> Value? {
23 | // swiftlint:disable:next force_cast
24 | return self[key.rawValue as! Key]
25 | }
26 | }
27 |
28 | func skeletonLog(_ message: String) {
29 | #if DEBUG
30 | if ProcessInfo.processInfo.environment[.debugMode] != nil {
31 | print(message)
32 | }
33 | #endif
34 | }
35 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/FoundationExtensions/DispatchQueue+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // DispatchQueue+Extensions.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import Foundation
15 |
16 | extension DispatchQueue {
17 |
18 | private static var _onceTracker = [String]()
19 |
20 | class func once(token: String, block: () -> Void) {
21 | objc_sync_enter(self)
22 | defer { objc_sync_exit(self) }
23 | guard !_onceTracker.contains(token) else { return }
24 |
25 | _onceTracker.append(token)
26 | block()
27 | }
28 |
29 | class func removeOnce(token: String, block: () -> Void) {
30 | objc_sync_enter(self)
31 | defer { objc_sync_exit(self) }
32 | guard let index = _onceTracker.firstIndex(of: token) else { return }
33 | _onceTracker.remove(at: index)
34 | block()
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/FoundationExtensions/Int+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // Int+Extensions.swift
11 | //
12 |
13 | import Foundation
14 |
15 | extension Int {
16 |
17 | var whitespace: String {
18 | whitespaces
19 | }
20 |
21 | var whitespaces: String {
22 | String(repeating: " ", count: self)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/FoundationExtensions/Notification+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // Notification+Extensions.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import UIKit
15 |
16 | extension Notification.Name {
17 |
18 | static let applicationDidBecomeActiveNotification = UIApplication.didBecomeActiveNotification
19 | static let applicationWillTerminateNotification = UIApplication.willTerminateNotification
20 | static let applicationDidEnterForegroundNotification = UIApplication.didEnterBackgroundNotification
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/FoundationExtensions/ProcessInfo+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // ProcessInfo+Extensions.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import Foundation
15 |
16 | extension ProcessInfo {
17 |
18 | enum Constants {
19 | static let testConfigurationFilePathKey = "XCTestConfigurationFilePath"
20 | }
21 |
22 | static var isRunningXCTest: Bool {
23 | return processInfo.environment[Constants.testConfigurationFilePathKey] != nil
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/Helpers/AssociationPolicy.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 SkeletonView. All rights reserved.
2 |
3 | import Foundation
4 |
5 | // Partially copy/pasted from https://github.com/jameslintaylor/AssociatedObjects/blob/master/AssociatedObjects/AssociatedObjects.swift
6 | enum AssociationPolicy: UInt {
7 | // raw values map to objc_AssociationPolicy's raw values
8 | case assign = 0
9 | case copy = 771
10 | case copyNonatomic = 3
11 | case retain = 769
12 | case retainNonatomic = 1
13 |
14 | var objc: objc_AssociationPolicy {
15 | // swiftlint:disable:next force_unwrapping
16 | return objc_AssociationPolicy(rawValue: rawValue)!
17 | }
18 | }
19 |
20 | protocol AssociatedObjects: AnyObject { }
21 |
22 | extension AssociatedObjects {
23 | /// wrapper around `objc_getAssociatedObject`
24 | func ao_get(pkey: UnsafeRawPointer) -> Any? {
25 | return objc_getAssociatedObject(self, pkey)
26 | }
27 |
28 | /// wrapper around `objc_setAssociatedObject`
29 | func ao_setOptional(_ value: Any?, pkey: UnsafeRawPointer, policy: AssociationPolicy = .retainNonatomic) {
30 | guard let value = value else { return }
31 | objc_setAssociatedObject(self, pkey, value, policy.objc)
32 | }
33 |
34 | /// wrapper around `objc_setAssociatedObject`
35 | func ao_set(_ value: Any, pkey: UnsafeRawPointer, policy: AssociationPolicy = .retainNonatomic) {
36 | objc_setAssociatedObject(self, pkey, value, policy.objc)
37 | }
38 |
39 | /// wrapper around 'objc_removeAssociatedObjects'
40 | func ao_removeAll() {
41 | objc_removeAssociatedObjects(self)
42 | }
43 | }
44 |
45 | extension NSObject: AssociatedObjects { }
46 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/Helpers/Recursive.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | typealias VoidBlock = () -> Void
6 | typealias RecursiveBlock = (T) -> Void
7 |
8 | protocol IterableElement {}
9 | extension UIView: IterableElement {}
10 | extension CALayer: IterableElement {}
11 |
12 | // MARK: Recursive
13 | protocol Recursive {
14 | associatedtype Element: IterableElement
15 | func recursiveSearch(leafBlock: VoidBlock, recursiveBlock: RecursiveBlock)
16 | }
17 |
18 | extension Array: Recursive where Element: IterableElement {
19 | func recursiveSearch(leafBlock: VoidBlock, recursiveBlock: RecursiveBlock) {
20 | guard !isEmpty else {
21 | leafBlock()
22 | return
23 | }
24 | forEach { recursiveBlock($0) }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/Helpers/Swizzling.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2019 SkeletonView. All rights reserved.
2 |
3 | import Foundation
4 |
5 | func swizzle(selector originalSelector: Selector, with swizzledSelector: Selector, inClass: AnyClass, usingClass: AnyClass) {
6 | guard let originalMethod = class_getInstanceMethod(inClass, originalSelector),
7 | let swizzledMethod = class_getInstanceMethod(usingClass, swizzledSelector)
8 | else { return }
9 |
10 | if class_addMethod(inClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) {
11 | class_replaceMethod(inClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
12 | } else {
13 | method_exchangeImplementations(originalMethod, swizzledMethod)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/Models/RecoverableViewState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecoverableViewState.swift
3 | // SkeletonView
4 | //
5 | // Created by Juanpe Catalán on 13/05/2018.
6 | // Copyright © 2018 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | struct RecoverableViewState {
12 |
13 | var backgroundColor: UIColor?
14 | var cornerRadius: CGFloat
15 | var clipToBounds: Bool
16 | var isUserInteractionsEnabled: Bool
17 |
18 | init(view: UIView) {
19 | self.backgroundColor = view.backgroundColor
20 | self.clipToBounds = view.layer.masksToBounds
21 | self.cornerRadius = view.layer.cornerRadius
22 | self.isUserInteractionsEnabled = view.isUserInteractionEnabled
23 | }
24 |
25 | }
26 |
27 | struct RecoverableLabelState {
28 | var attributedText: NSAttributedString? // we mess with `textColor`, which impacts attributed string if defined
29 | var text: String? // we mess with `text` if the label is within a `UIStackView`
30 | var textColor: UIColor?
31 |
32 | init(view: UILabel) {
33 | if let attributedText = view.attributedText {
34 | self.attributedText = attributedText
35 | } else {
36 | self.text = view.text
37 | }
38 | self.textColor = view.textColor
39 | }
40 | }
41 |
42 | struct RecoverableTextViewState {
43 | var attributedText: NSAttributedString? // we mess with `textColor`, which impacts attributed string if defined
44 | var textColor: UIColor?
45 |
46 | init(view: UITextView) {
47 | self.attributedText = view.attributedText
48 | self.textColor = view.textColor
49 | }
50 | }
51 |
52 | struct RecoverableTextFieldState {
53 | var attributedText: NSAttributedString? // we mess with `textColor`, which impacts attributed string if defined
54 | var textColor: UIColor?
55 | var placeholder: String?
56 |
57 | init(view: UITextField) {
58 | self.attributedText = view.attributedText
59 | self.textColor = view.textColor
60 | self.placeholder = view.placeholder
61 | }
62 | }
63 |
64 | struct RecoverableImageViewState {
65 | var image: UIImage?
66 |
67 | init(view: UIImageView) {
68 | self.image = view.image
69 | }
70 | }
71 |
72 | struct RecoverableButtonViewState {
73 | var title: String?
74 |
75 | init(view: UIButton) {
76 | self.title = view.titleLabel?.text
77 | }
78 | }
79 |
80 | struct RecoverableTableViewHeaderFooterViewState {
81 | var backgroundViewColor: UIColor?
82 |
83 | init(view: UITableViewHeaderFooterView) {
84 | self.backgroundViewColor = view.backgroundView?.backgroundColor
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/Models/SkeletonLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SkeletonLayer.swift
3 | // SkeletonView-iOS
4 | //
5 | // Created by Juanpe Catalán on 02/11/2017.
6 | // Copyright © 2017 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | struct SkeletonLayer {
12 |
13 | private var maskLayer: CALayer
14 | private weak var holder: UIView?
15 |
16 | var type: SkeletonType {
17 | return maskLayer is CAGradientLayer ? .gradient : .solid
18 | }
19 |
20 | var contentLayer: CALayer {
21 | return maskLayer
22 | }
23 |
24 | init(type: SkeletonType, colors: [UIColor], skeletonHolder holder: UIView) {
25 | self.holder = holder
26 | self.maskLayer = type.layer
27 | self.maskLayer.anchorPoint = .zero
28 | self.maskLayer.bounds = holder.definedMaxBounds
29 | self.maskLayer.cornerRadius = CGFloat(holder.skeletonCornerRadius)
30 | addTextLinesIfNeeded()
31 | self.maskLayer.tint(withColors: colors, traitCollection: holder.traitCollection)
32 | }
33 |
34 | func update(usingColors colors: [UIColor]) {
35 | layoutIfNeeded()
36 | maskLayer.tint(withColors: colors, traitCollection: holder?.traitCollection)
37 | }
38 |
39 | func layoutIfNeeded() {
40 | if let bounds = holder?.definedMaxBounds {
41 | maskLayer.bounds = bounds
42 | }
43 | updateLinesIfNeeded()
44 | }
45 |
46 | func removeLayer(transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
47 | switch transition {
48 | case .none:
49 | maskLayer.removeFromSuperlayer()
50 | completion?()
51 | case .crossDissolve(let duration):
52 | maskLayer.setOpacity(from: 1, to: 0, duration: duration) {
53 | self.maskLayer.removeFromSuperlayer()
54 | completion?()
55 | }
56 | }
57 | }
58 |
59 | /// If there is more than one line, or custom preferences have been set for a single line, draw custom layers
60 | func addTextLinesIfNeeded() {
61 | guard let textView = holderAsTextView else { return }
62 | let config = SkeletonMultilinesLayerConfig(lines: textView.estimatedNumberOfLines,
63 | lineHeight: textView.estimatedLineHeight,
64 | type: type,
65 | lastLineFillPercent: textView.lastLineFillingPercent,
66 | multilineCornerRadius: textView.multilineCornerRadius,
67 | multilineSpacing: textView.multilineSpacing,
68 | paddingInsets: textView.paddingInsets,
69 | alignment: textView.textAlignment,
70 | isRTL: holder?.isRTL ?? false,
71 | shouldCenterVertically: textView.shouldCenterTextVertically)
72 |
73 | maskLayer.addMultilinesLayers(for: config)
74 | }
75 |
76 | func updateLinesIfNeeded() {
77 | guard let textView = holderAsTextView else { return }
78 | let config = SkeletonMultilinesLayerConfig(lines: textView.estimatedNumberOfLines,
79 | lineHeight: textView.estimatedLineHeight,
80 | type: type,
81 | lastLineFillPercent: textView.lastLineFillingPercent,
82 | multilineCornerRadius: textView.multilineCornerRadius,
83 | multilineSpacing: textView.multilineSpacing,
84 | paddingInsets: textView.paddingInsets,
85 | alignment: textView.textAlignment,
86 | isRTL: holder?.isRTL ?? false,
87 | shouldCenterVertically: textView.shouldCenterTextVertically)
88 |
89 | maskLayer.updateMultilinesLayers(for: config)
90 | }
91 |
92 | var holderAsTextView: SkeletonTextNode? {
93 | guard let textView = holder as? SkeletonTextNode,
94 | (textView.estimatedNumberOfLines == -1 || textView.estimatedNumberOfLines == 0 || textView.estimatedNumberOfLines > 1 || textView.estimatedNumberOfLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
95 | return nil
96 | }
97 | return textView
98 | }
99 |
100 | }
101 |
102 | extension SkeletonLayer {
103 |
104 | func start(_ anim: SkeletonLayerAnimation? = nil, completion: (() -> Void)? = nil) {
105 | let animation = anim ?? type.defaultLayerAnimation(isRTL: holder?.isRTL ?? false)
106 | contentLayer.playAnimation(animation, key: "skeletonAnimation", completion: completion)
107 | }
108 |
109 | func stopAnimation() {
110 | contentLayer.stopAnimation(forKey: "skeletonAnimation")
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/SkeletonConfigs/SkeletonConfig.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | /// Used to store all config needed to activate the skeleton layer.
6 | struct SkeletonConfig {
7 | /// Type of skeleton layer
8 | let type: SkeletonType
9 |
10 | /// Colors used in skeleton layer
11 | let colors: [UIColor]
12 |
13 | /// If type is gradient, which gradient direction
14 | let gradientDirection: GradientDirection?
15 |
16 | /// Specify if skeleton is animated or not
17 | let animated: Bool
18 |
19 | /// Used to execute a custom animation
20 | let animation: SkeletonLayerAnimation?
21 |
22 | /// Transition style
23 | var transition: SkeletonTransitionStyle
24 |
25 | init(type: SkeletonType,
26 | colors: [UIColor],
27 | gradientDirection: GradientDirection? = nil,
28 | animated: Bool = false,
29 | animation: SkeletonLayerAnimation? = nil,
30 | transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
31 | self.type = type
32 | self.colors = colors
33 | self.gradientDirection = gradientDirection
34 | self.animated = animated
35 | self.animation = animation
36 | self.transition = transition
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/SkeletonConfigs/SkeletonMultilinesLayerConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonMultilinesLayerConfig.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import UIKit
15 |
16 | struct SkeletonMultilinesLayerConfig {
17 |
18 | var lines: Int
19 | var lineHeight: CGFloat
20 | var type: SkeletonType
21 | var lastLineFillPercent: Int
22 | var multilineCornerRadius: Int
23 | var multilineSpacing: CGFloat
24 | var paddingInsets: UIEdgeInsets
25 | var alignment: NSTextAlignment
26 | var isRTL: Bool
27 | var shouldCenterVertically: Bool
28 |
29 | /// Returns padding insets taking into account if the RTL is activated
30 | var calculatedPaddingInsets: UIEdgeInsets {
31 | UIEdgeInsets(top: paddingInsets.top,
32 | left: isRTL ? paddingInsets.right : paddingInsets.left,
33 | bottom: paddingInsets.bottom,
34 | right: isRTL ? paddingInsets.left : paddingInsets.right)
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/SkeletonExtensions/GradientDirection+Animations.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // GradientDirection+Animations.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import UIKit
15 |
16 | typealias GradientAnimationPoint = (from: CGPoint, to: CGPoint)
17 |
18 | extension GradientDirection {
19 |
20 | // codebeat:disable[ABC]
21 | var startPoint: GradientAnimationPoint {
22 | switch self {
23 | case .leftRight:
24 | return (from: CGPoint(x: -1, y: 0.5), to: CGPoint(x: 1, y: 0.5))
25 | case .rightLeft:
26 | return (from: CGPoint(x: 1, y: 0.5), to: CGPoint(x: -1, y: 0.5))
27 | case .topBottom:
28 | return (from: CGPoint(x: 0.5, y: -1), to: CGPoint(x: 0.5, y: 1))
29 | case .bottomTop:
30 | return (from: CGPoint(x: 0.5, y: 1), to: CGPoint(x: 0.5, y: -1))
31 | case .topLeftBottomRight:
32 | return (from: CGPoint(x: -1, y: -1), to: CGPoint(x: 1, y: 1))
33 | case .bottomRightTopLeft:
34 | return (from: CGPoint(x: 1, y: 1), to: CGPoint(x: -1, y: -1))
35 | }
36 | }
37 |
38 | var endPoint: GradientAnimationPoint {
39 | switch self {
40 | case .leftRight:
41 | return (from: CGPoint(x: 0, y: 0.5), to: CGPoint(x: 2, y: 0.5))
42 | case .rightLeft:
43 | return ( from: CGPoint(x: 2, y: 0.5), to: CGPoint(x: 0, y: 0.5))
44 | case .topBottom:
45 | return ( from: CGPoint(x: 0.5, y: 0), to: CGPoint(x: 0.5, y: 2))
46 | case .bottomTop:
47 | return ( from: CGPoint(x: 0.5, y: 2), to: CGPoint(x: 0.5, y: 0))
48 | case .topLeftBottomRight:
49 | return ( from: CGPoint(x: 0, y: 0), to: CGPoint(x: 2, y: 2))
50 | case .bottomRightTopLeft:
51 | return ( from: CGPoint(x: 2, y: 2), to: CGPoint(x: 0, y: 0))
52 | }
53 | }
54 | // codebeat:enable[ABC]
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/SkeletonExtensions/PrepareViewForSkeleton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // PrepareViewForSkeleton.swift
11 | //
12 | // Created by Juanpe Catalán on 04/11/2017.
13 |
14 | import UIKit
15 |
16 | extension UIView {
17 |
18 | @objc func prepareViewForSkeleton() {
19 | if isUserInteractionDisabledWhenSkeletonIsActive {
20 | isUserInteractionEnabled = false
21 | }
22 |
23 | startTransition { [weak self] in
24 | self?.backgroundColor = .clear
25 | }
26 | }
27 |
28 | }
29 |
30 | extension UILabel {
31 |
32 | override func prepareViewForSkeleton() {
33 | backgroundColor = .clear
34 |
35 | if isUserInteractionDisabledWhenSkeletonIsActive {
36 | isUserInteractionEnabled = false
37 | }
38 |
39 | resignFirstResponder()
40 | startTransition { [weak self] in
41 | self?.updateHeightConstraintsIfNeeded()
42 | self?.textColor = .clear
43 | }
44 | }
45 | }
46 |
47 | extension UITextView {
48 |
49 | override func prepareViewForSkeleton() {
50 | backgroundColor = .clear
51 |
52 | if isUserInteractionDisabledWhenSkeletonIsActive {
53 | isUserInteractionEnabled = false
54 | }
55 |
56 | resignFirstResponder()
57 | startTransition { [weak self] in
58 | self?.textColor = .clear
59 | }
60 | }
61 |
62 | }
63 |
64 | extension UITextField {
65 |
66 | override func prepareViewForSkeleton() {
67 | backgroundColor = .clear
68 | resignFirstResponder()
69 |
70 | startTransition { [weak self] in
71 | self?.textColor = .clear
72 | self?.placeholder = nil
73 | }
74 | }
75 |
76 | }
77 |
78 | extension UIImageView {
79 |
80 | override func prepareViewForSkeleton() {
81 | backgroundColor = .clear
82 |
83 | if isUserInteractionDisabledWhenSkeletonIsActive {
84 | isUserInteractionEnabled = false
85 | }
86 |
87 | startTransition { [weak self] in
88 | self?.image = nil
89 | }
90 | }
91 |
92 | }
93 |
94 | extension UIButton {
95 |
96 | override func prepareViewForSkeleton() {
97 | backgroundColor = .clear
98 |
99 | if isUserInteractionDisabledWhenSkeletonIsActive {
100 | isUserInteractionEnabled = false
101 | }
102 |
103 | startTransition { [weak self] in
104 | self?.setTitle(nil, for: .normal)
105 | }
106 | }
107 |
108 | }
109 |
110 | extension UITableViewHeaderFooterView {
111 |
112 | override func prepareViewForSkeleton() {
113 | backgroundView?.backgroundColor = .clear
114 |
115 | if isUserInteractionDisabledWhenSkeletonIsActive {
116 | isUserInteractionEnabled = false
117 | }
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/SkeletonExtensions/SubviewsSkeletonables.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | extension UIView {
6 |
7 | @objc var subviewsSkeletonables: [UIView] {
8 | subviewsToSkeleton.filter { $0.isSkeletonable }
9 | }
10 |
11 | @objc var subviewsToSkeleton: [UIView] {
12 | subviews
13 | }
14 |
15 | }
16 |
17 | extension UITableView {
18 |
19 | override var subviewsToSkeleton: [UIView] {
20 | // on `UIViewController'S onViewDidLoad`, the window is still nil.
21 | // Some developer trying to call `view.showAnimatedSkeleton()`
22 | // when the request or data is loading which sometimes happens before the ViewDidAppear
23 | guard window != nil else { return [] }
24 |
25 | var result = [UIView]()
26 |
27 | for subview in subviews {
28 | if String(describing: type(of: subview)) == "UITableViewWrapperView" {
29 | result.append(contentsOf: subview.subviews)
30 | } else {
31 | result.append(subview)
32 | }
33 | }
34 |
35 | return result
36 | }
37 |
38 | }
39 |
40 | extension UITableViewCell {
41 | override var subviewsToSkeleton: [UIView] {
42 | contentView.subviews
43 | }
44 | }
45 |
46 | extension UITableViewHeaderFooterView {
47 | override var subviewsToSkeleton: [UIView] {
48 | contentView.subviews
49 | }
50 | }
51 |
52 | extension UICollectionView {
53 | override var subviewsToSkeleton: [UIView] {
54 | subviews
55 | }
56 | }
57 |
58 | extension UICollectionViewCell {
59 | override var subviewsToSkeleton: [UIView] {
60 | contentView.subviews
61 | }
62 | }
63 |
64 | extension UIStackView {
65 | override var subviewsToSkeleton: [UIView] {
66 | arrangedSubviews
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/SkeletonFlowHandler.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | protocol SkeletonFlowDelegate: AnyObject {
6 | func willBeginShowingSkeletons(rootView: UIView)
7 | func didShowSkeletons(rootView: UIView)
8 | func willBeginUpdatingSkeletons(rootView: UIView)
9 | func didUpdateSkeletons(rootView: UIView)
10 | func willBeginLayingSkeletonsIfNeeded(rootView: UIView)
11 | func didLayoutSkeletonsIfNeeded(rootView: UIView)
12 | func willBeginHidingSkeletons(rootView: UIView)
13 | func didHideSkeletons(rootView: UIView)
14 | }
15 |
16 | class SkeletonFlowHandler: SkeletonFlowDelegate {
17 | func willBeginShowingSkeletons(rootView: UIView) {
18 | NotificationCenter.default.post(name: .skeletonWillAppearNotification, object: rootView, userInfo: nil)
19 | rootView.startObservingAppLifecycleNotifications()
20 | }
21 |
22 | func didShowSkeletons(rootView: UIView) {
23 | skeletonLog(rootView.sk.skeletonTreeDescription)
24 | NotificationCenter.default.post(name: .skeletonDidAppearNotification, object: rootView, userInfo: nil)
25 | }
26 |
27 | func willBeginUpdatingSkeletons(rootView: UIView) {
28 | NotificationCenter.default.post(name: .skeletonWillUpdateNotification, object: rootView, userInfo: nil)
29 | }
30 |
31 | func didUpdateSkeletons(rootView: UIView) {
32 | NotificationCenter.default.post(name: .skeletonDidUpdateNotification, object: rootView, userInfo: nil)
33 | }
34 |
35 | func willBeginLayingSkeletonsIfNeeded(rootView: UIView) {
36 | }
37 |
38 | func didLayoutSkeletonsIfNeeded(rootView: UIView) {
39 | }
40 |
41 | func willBeginHidingSkeletons(rootView: UIView) {
42 | NotificationCenter.default.post(name: .skeletonWillDisappearNotification, object: rootView, userInfo: nil)
43 | rootView.stopObservingAppLifecycleNotications()
44 | }
45 |
46 | func didHideSkeletons(rootView: UIView) {
47 | rootView._flowDelegate = nil
48 | NotificationCenter.default.post(name: .skeletonDidDisappearNotification, object: rootView, userInfo: nil)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/SkeletonLayerBuilders/SkeletonLayerBuilder.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | /// Object that facilitates the creation of skeleton layers,
6 | /// based on the builder pattern
7 | class SkeletonLayerBuilder {
8 |
9 | var skeletonType: SkeletonType?
10 | var colors: [UIColor] = []
11 | var holder: UIView?
12 |
13 | @discardableResult
14 | func setSkeletonType(_ type: SkeletonType) -> SkeletonLayerBuilder {
15 | self.skeletonType = type
16 | return self
17 | }
18 |
19 | @discardableResult
20 | func addColor(_ color: UIColor) -> SkeletonLayerBuilder {
21 | addColors([color])
22 | }
23 |
24 | @discardableResult
25 | func addColors(_ colors: [UIColor]) -> SkeletonLayerBuilder {
26 | self.colors.append(contentsOf: colors)
27 | return self
28 | }
29 |
30 | @discardableResult
31 | func setHolder(_ holder: UIView) -> SkeletonLayerBuilder {
32 | self.holder = holder
33 | return self
34 | }
35 |
36 | @discardableResult
37 | func build() -> SkeletonLayer? {
38 | guard let type = skeletonType,
39 | let holder = holder
40 | else { return nil }
41 |
42 | return SkeletonLayer(type: type,
43 | colors: colors,
44 | skeletonHolder: holder)
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/SkeletonLayerBuilders/SkeletonMultilineLayerBuilder.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | /// Object that facilitates the creation of skeleton layers for multiline
6 | /// elements, based on the builder pattern
7 | class SkeletonMultilineLayerBuilder {
8 |
9 | var skeletonType: SkeletonType?
10 | var index: Int?
11 | var height: CGFloat?
12 | var width: CGFloat?
13 | var cornerRadius: Int?
14 | var multilineSpacing: CGFloat = SkeletonAppearance.default.multilineSpacing
15 | var paddingInsets: UIEdgeInsets = .zero
16 | var alignment: NSTextAlignment = .natural
17 | var isRTL: Bool = false
18 |
19 | @discardableResult
20 | func setSkeletonType(_ type: SkeletonType) -> SkeletonMultilineLayerBuilder {
21 | self.skeletonType = type
22 | return self
23 | }
24 |
25 | @discardableResult
26 | func setIndex(_ index: Int) -> SkeletonMultilineLayerBuilder {
27 | self.index = index
28 | return self
29 | }
30 |
31 | @discardableResult
32 | func setHeight(_ height: CGFloat) -> SkeletonMultilineLayerBuilder {
33 | self.height = height
34 | return self
35 | }
36 |
37 | @discardableResult
38 | func setWidth(_ width: CGFloat) -> SkeletonMultilineLayerBuilder {
39 | self.width = width
40 | return self
41 | }
42 |
43 | @discardableResult
44 | func setCornerRadius(_ radius: Int) -> SkeletonMultilineLayerBuilder {
45 | self.cornerRadius = radius
46 | return self
47 | }
48 |
49 | @discardableResult
50 | func setMultilineSpacing(_ spacing: CGFloat) -> SkeletonMultilineLayerBuilder {
51 | self.multilineSpacing = spacing
52 | return self
53 | }
54 |
55 | @discardableResult
56 | func setPadding(_ insets: UIEdgeInsets) -> SkeletonMultilineLayerBuilder {
57 | self.paddingInsets = insets
58 | return self
59 | }
60 |
61 | @discardableResult
62 | func setAlignment(_ alignment: NSTextAlignment) -> SkeletonMultilineLayerBuilder {
63 | self.alignment = alignment
64 | return self
65 | }
66 |
67 | @discardableResult
68 | func setIsRTL(_ isRTL: Bool) -> SkeletonMultilineLayerBuilder {
69 | self.isRTL = isRTL
70 | return self
71 | }
72 |
73 | func build() -> CALayer? {
74 | guard let type = skeletonType,
75 | let index = index,
76 | let width = width,
77 | let height = height,
78 | let radius = cornerRadius
79 | else { return nil }
80 |
81 | let layer = type.layer
82 | layer.anchorPoint = .zero
83 | layer.name = CALayer.Constants.skeletonSubLayersName
84 | layer.updateLayerFrame(for: index,
85 | totalLines: layer.skeletonSublayers.count,
86 | size: CGSize(width: width, height: height),
87 | multilineSpacing: multilineSpacing,
88 | paddingInsets: paddingInsets,
89 | alignment: alignment,
90 | isRTL: isRTL)
91 |
92 | layer.cornerRadius = CGFloat(radius)
93 | layer.masksToBounds = true
94 |
95 | return layer
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/SkeletonTree/SkeletonTreeNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonTreeNode.swift
11 | //
12 | // Created by Juanpe Catalán on 23/8/21.
13 |
14 | import UIKit
15 |
16 | public struct SkeletonTreeNode {
17 | /// Base object to extend.
18 | let base: Base
19 |
20 | /// Creates extensions with base object.
21 | ///
22 | /// - parameter base: Base object.
23 | init(_ base: Base) {
24 | self.base = base
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/UIKitExtensions/SkeletonTreeNode+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonTreeNode+Extensions.swift
11 | //
12 | // Created by Juanpe Catalán on 23/8/21.
13 |
14 | import UIKit
15 |
16 | extension UIView: SkeletonViewExtended { }
17 |
18 | extension SkeletonTreeNode where Base: UIView {
19 |
20 | var children: [SkeletonTreeNode] {
21 | base.subviewsSkeletonables.map { $0.sk.treeNode }
22 | }
23 |
24 | var parent: SkeletonTreeNode? {
25 | base.superview?.sk.treeNode
26 | }
27 |
28 | }
29 |
30 | // MARK: Debug
31 |
32 | extension SkeletonTreeNode where Base: UIView {
33 |
34 | var dictionaryRepresentation: [String: Any] {
35 | let skeletonableChildren = children
36 |
37 | var nodeInfo: [String: Any] = [
38 | "type": "\(type(of: base))",
39 | "reference": "\(Unmanaged.passUnretained(base).toOpaque())",
40 | "isSkeletonable": base.isSkeletonable
41 | ]
42 |
43 | if !skeletonableChildren.isEmpty {
44 | nodeInfo["children"] = skeletonableChildren.map { $0.dictionaryRepresentation }
45 | }
46 |
47 | return nodeInfo
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/UIKitExtensions/UICollectionView+CollectionSkeleton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+CollectionSkeleton.swift
3 | // SkeletonView-iOS
4 | //
5 | // Created by Juanpe Catalán on 02/02/2018.
6 | // Copyright © 2018 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UICollectionView: CollectionSkeleton {
12 |
13 | var estimatedNumberOfRows: Int {
14 | guard let flowlayout = collectionViewLayout as? UICollectionViewFlowLayout else { return 0 }
15 | switch flowlayout.scrollDirection {
16 | case .vertical:
17 | return Int(ceil(frame.height / flowlayout.itemSize.height))
18 | case .horizontal:
19 | return Int(ceil(frame.width / flowlayout.itemSize.width))
20 | default:
21 | return 0
22 | }
23 | }
24 |
25 | var skeletonDataSource: SkeletonCollectionDataSource? {
26 | get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
27 | set {
28 | ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDataSource)
29 | self.dataSource = newValue
30 | }
31 | }
32 |
33 | var skeletonDelegate: SkeletonCollectionDelegate? {
34 | get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDelegate) as? SkeletonCollectionDelegate }
35 | set {
36 | ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDelegate)
37 | self.delegate = newValue
38 | }
39 | }
40 |
41 | func addDummyDataSource() {
42 | guard let originalDataSource = self.dataSource as? SkeletonCollectionViewDataSource,
43 | !(originalDataSource is SkeletonCollectionDataSource)
44 | else { return }
45 |
46 | let dataSource = SkeletonCollectionDataSource(collectionViewDataSource: originalDataSource)
47 | self.skeletonDataSource = dataSource
48 | reloadData()
49 | }
50 |
51 | func updateDummyDataSource() {
52 | if (dataSource as? SkeletonCollectionDataSource) != nil {
53 | reloadData()
54 | } else {
55 | addDummyDataSource()
56 | }
57 | }
58 |
59 | func removeDummyDataSource(reloadAfter: Bool) {
60 | guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
61 | self.skeletonDataSource = nil
62 | self.dataSource = dataSource.originalCollectionViewDataSource
63 | if reloadAfter { self.reloadData() }
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIColor+Skeleton.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2017 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | // codebeat:disable[TOO_MANY_IVARS]
6 | public extension UIColor {
7 |
8 | static var greenSea = UIColor(0x16a085)
9 | static var turquoise = UIColor(0x1abc9c)
10 | static var emerald = UIColor(0x2ecc71)
11 | static var peterRiver = UIColor(0x3498db)
12 | static var amethyst = UIColor(0x9b59b6)
13 | static var wetAsphalt = UIColor(0x34495e)
14 | static var nephritis = UIColor(0x27ae60)
15 | static var belizeHole = UIColor(0x2980b9)
16 | static var wisteria = UIColor(0x8e44ad)
17 | static var midnightBlue = UIColor(0x2c3e50)
18 | static var sunFlower = UIColor(0xf1c40f)
19 | static var carrot = UIColor(0xe67e22)
20 | static var alizarin = UIColor(0xe74c3c)
21 | static var clouds = UIColor(0xecf0f1)
22 | static var darkClouds = UIColor(0x1c2325)
23 | static var concrete = UIColor(0x95a5a6)
24 | static var flatOrange = UIColor(0xf39c12)
25 | static var pumpkin = UIColor(0xd35400)
26 | static var pomegranate = UIColor(0xc0392b)
27 | static var silver = UIColor(0xbdc3c7)
28 | static var asbestos = UIColor(0x7f8c8d)
29 |
30 | static var skeletonDefault: UIColor {
31 | if #available(iOS 13, tvOS 13, *) {
32 | return UIColor { traitCollection in
33 | switch traitCollection.userInterfaceStyle {
34 | case .dark:
35 | return .darkClouds
36 | default:
37 | return .clouds
38 | }
39 | }
40 | } else {
41 | return .clouds
42 | }
43 | }
44 |
45 | var complementaryColor: UIColor {
46 | if #available(iOS 13, tvOS 13, *) {
47 | return UIColor { _ in
48 | self.isLight ? self.darker : self.lighter
49 | }
50 | } else {
51 | return isLight ? darker : lighter
52 | }
53 | }
54 |
55 | var lighter: UIColor {
56 | adjust(by: 1.35)
57 | }
58 |
59 | var darker: UIColor {
60 | adjust(by: 0.94)
61 | }
62 |
63 | }
64 |
65 | extension UIColor {
66 |
67 | convenience init(_ hex: UInt) {
68 | self.init(
69 | red: CGFloat((hex & 0xFF0000) >> 16) / 255.0,
70 | green: CGFloat((hex & 0x00FF00) >> 8) / 255.0,
71 | blue: CGFloat(hex & 0x0000FF) / 255.0,
72 | alpha: CGFloat(1.0)
73 | )
74 | }
75 |
76 | var isLight: Bool {
77 | guard let components = cgColor.components,
78 | components.count >= 3 else { return false }
79 | let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
80 | return !(brightness < 0.5)
81 | }
82 |
83 | func adjust(by percent: CGFloat) -> UIColor {
84 | var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
85 | getHue(&h, saturation: &s, brightness: &b, alpha: &a)
86 | return UIColor(hue: h, saturation: s, brightness: b * percent, alpha: a)
87 | }
88 |
89 | func makeGradient() -> [UIColor] {
90 | [self, self.complementaryColor, self]
91 | }
92 |
93 | }
94 | // codebeat:enable[TOO_MANY_IVARS]
95 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/UIKitExtensions/UILabel+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UILabel+Extensions.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import UIKit
15 |
16 | extension UILabel {
17 |
18 | var desiredHeightBasedOnNumberOfLines: CGFloat {
19 | let spaceNeededForEachLine = estimatedLineHeight * CGFloat(estimatedNumberOfLines)
20 | let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(estimatedNumberOfLines - 1)
21 | let padding = paddingInsets.top + paddingInsets.bottom
22 |
23 | return spaceNeededForEachLine + spaceNeededForSpaces + padding
24 | }
25 |
26 | func updateHeightConstraintsIfNeeded() {
27 | guard estimatedNumberOfLines > 1 || estimatedNumberOfLines == 0 else { return }
28 |
29 | // Workaround to simulate content when the label is contained in a `UIStackView`.
30 | if isSuperviewAStackView, bounds.height == 0, (text?.isEmpty ?? true) {
31 | // 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.
32 | text = " "
33 | }
34 |
35 | let desiredHeight = desiredHeightBasedOnNumberOfLines
36 | if desiredHeight > definedMaxHeight {
37 | backupHeightConstraints = heightConstraints
38 | NSLayoutConstraint.deactivate(heightConstraints)
39 | setHeight(equalToConstant: desiredHeight)
40 | }
41 | }
42 |
43 | func restoreBackupHeightConstraintsIfNeeded() {
44 | guard !backupHeightConstraints.isEmpty else { return }
45 | NSLayoutConstraint.activate(backupHeightConstraints)
46 | backupHeightConstraints.removeAll()
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/UIKitExtensions/UITableView+CollectionSkeleton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+CollectionSkeleton.swift
3 | // SkeletonView-iOS
4 | //
5 | // Created by Juanpe Catalán on 02/02/2018.
6 | // Copyright © 2018 SkeletonView. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UITableView: CollectionSkeleton {
12 |
13 | var estimatedNumberOfRows: Int {
14 | return Int(ceil(frame.height / rowHeight))
15 | }
16 |
17 | var skeletonDataSource: SkeletonCollectionDataSource? {
18 | get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDataSource) as? SkeletonCollectionDataSource }
19 | set {
20 | ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDataSource)
21 | self.dataSource = newValue
22 | }
23 | }
24 |
25 | var skeletonDelegate: SkeletonCollectionDelegate? {
26 | get { return ao_get(pkey: &CollectionAssociatedKeys.dummyDelegate) as? SkeletonCollectionDelegate }
27 | set {
28 | ao_setOptional(newValue, pkey: &CollectionAssociatedKeys.dummyDelegate)
29 | self.delegate = newValue
30 | }
31 | }
32 |
33 | func addDummyDataSource() {
34 | guard let originalDataSource = self.dataSource as? SkeletonTableViewDataSource,
35 | !(originalDataSource is SkeletonCollectionDataSource)
36 | else { return }
37 | let calculatedRowHeight = calculateRowHeight()
38 | let dataSource = SkeletonCollectionDataSource(tableViewDataSource: originalDataSource,
39 | rowHeight: rowHeight,
40 | originalRowHeight: self.rowHeight)
41 | rowHeight = calculatedRowHeight
42 | self.skeletonDataSource = dataSource
43 |
44 | if let originalDelegate = self.delegate as? SkeletonTableViewDelegate,
45 | !(originalDelegate is SkeletonCollectionDelegate) {
46 | let delegate = SkeletonCollectionDelegate(tableViewDelegate: originalDelegate)
47 | self.skeletonDelegate = delegate
48 | }
49 |
50 | reloadData()
51 | }
52 |
53 | func updateDummyDataSource() {
54 | if (dataSource as? SkeletonCollectionDataSource) != nil {
55 | reloadData()
56 | } else {
57 | addDummyDataSource()
58 | }
59 | }
60 |
61 | func removeDummyDataSource(reloadAfter: Bool) {
62 | guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
63 | restoreRowHeight()
64 | self.skeletonDataSource = nil
65 | self.dataSource = dataSource.originalTableViewDataSource
66 |
67 | if let delegate = self.delegate as? SkeletonCollectionDelegate {
68 | self.skeletonDelegate = nil
69 | self.delegate = delegate.originalTableViewDelegate
70 | }
71 |
72 | if reloadAfter { self.reloadData() }
73 | }
74 |
75 | }
76 |
77 | private extension UITableView {
78 |
79 | func restoreRowHeight() {
80 | guard let dataSource = self.dataSource as? SkeletonCollectionDataSource else { return }
81 | rowHeight = dataSource.originalRowHeight
82 | }
83 |
84 | func calculateRowHeight() -> CGFloat {
85 | guard rowHeight == UITableView.automaticDimension else { return rowHeight }
86 | return estimatedRowHeight
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/UIKitExtensions/UITableView+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UITableView+Extensions.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import UIKit
15 |
16 | extension UITableView {
17 |
18 | var indexesOfVisibleSections: [Int] {
19 | (0.. {
19 | SkeletonTreeNode(self.type)
20 | }
21 |
22 | }
23 |
24 | extension UIView {
25 |
26 | /// Flags
27 |
28 | var isSuperviewAStackView: Bool {
29 | superview is UIStackView
30 | }
31 |
32 | var isRTL: Bool {
33 | if #available(iOS 10.0, *), #available(tvOS 10.0, *) {
34 | return effectiveUserInterfaceLayoutDirection == .rightToLeft
35 | } else {
36 | return false
37 | }
38 | }
39 |
40 | /// Math
41 |
42 | var definedMaxBounds: CGRect {
43 | if let parentStackView = (superview as? UIStackView) {
44 | var origin: CGPoint = .zero
45 | switch parentStackView.alignment {
46 | case .trailing:
47 | origin.x = definedMaxWidth
48 | default:
49 | break
50 | }
51 | return CGRect(origin: origin, size: definedMaxSize)
52 | }
53 | return CGRect(origin: .zero, size: definedMaxSize)
54 | }
55 |
56 | var definedMaxSize: CGSize {
57 | CGSize(width: definedMaxWidth, height: definedMaxHeight)
58 | }
59 |
60 | var definedMaxWidth: CGFloat {
61 | let constraintsMaxWidth = widthConstraints
62 | .map { $0.constant }
63 | .max() ?? 0
64 |
65 | return max(frame.size.width, constraintsMaxWidth)
66 | }
67 |
68 | var definedMaxHeight: CGFloat {
69 | let constraintsMaxHeight = heightConstraints
70 | .map { $0.constant }
71 | .max() ?? 0
72 |
73 | return max(frame.size.height, constraintsMaxHeight)
74 | }
75 |
76 | /// Autolayout
77 |
78 | var widthConstraints: [NSLayoutConstraint] {
79 | nonContentSizeLayoutConstraints.filter { $0.firstAttribute == NSLayoutConstraint.Attribute.width }
80 | }
81 |
82 | var heightConstraints: [NSLayoutConstraint] {
83 | nonContentSizeLayoutConstraints.filter { $0.firstAttribute == NSLayoutConstraint.Attribute.height }
84 | }
85 |
86 | var skeletonHeightConstraints: [NSLayoutConstraint] {
87 | nonContentSizeLayoutConstraints.filter {
88 | $0.firstAttribute == NSLayoutConstraint.Attribute.height
89 | && $0.identifier?.contains("SkeletonView.Constraint.Height") ?? false
90 | }
91 | }
92 |
93 | @discardableResult
94 | func setHeight(equalToConstant constant: CGFloat) -> NSLayoutConstraint {
95 | let heightConstraint = heightAnchor.constraint(equalToConstant: constant)
96 | heightConstraint.identifier = "SkeletonView.Constraint.Height.\(constant)"
97 | NSLayoutConstraint.activate([heightConstraint])
98 | return heightConstraint
99 | }
100 |
101 | var nonContentSizeLayoutConstraints: [NSLayoutConstraint] {
102 | constraints.filter({ "\(type(of: $0))" != "NSContentSizeLayoutConstraint" })
103 | }
104 |
105 | /// Animations
106 |
107 | func startSkeletonLayerAnimationBlock(_ anim: SkeletonLayerAnimation? = nil) -> VoidBlock {
108 | {
109 | self._isSkeletonAnimated = true
110 | guard let layer = self._skeletonLayer else { return }
111 | layer.start(anim) { [weak self] in
112 | self?._isSkeletonAnimated = false
113 | }
114 | }
115 | }
116 |
117 | var stopSkeletonLayerAnimationBlock: VoidBlock {
118 | {
119 | self._isSkeletonAnimated = false
120 | guard let layer = self._skeletonLayer else { return }
121 | layer.stopAnimation()
122 | }
123 | }
124 |
125 | /// Skeleton Layer
126 |
127 | func addSkeletonLayer(skeletonConfig config: SkeletonConfig) {
128 | guard let skeletonLayer = SkeletonLayerBuilder()
129 | .setSkeletonType(config.type)
130 | .addColors(config.colors)
131 | .setHolder(self)
132 | .build()
133 | else { return }
134 |
135 | self._skeletonLayer = skeletonLayer
136 | layer.insertSkeletonLayer(
137 | skeletonLayer,
138 | atIndex: UInt32.max,
139 | transition: config.transition
140 | ) { [weak self] in
141 | guard let self = self else { return }
142 |
143 | // Workaround to fix the problem when inserting a sublayer and
144 | // the content offset is modified by the system.
145 | (self as? UITextView)?.setContentOffset(.zero, animated: false)
146 |
147 | if config.animated {
148 | self.startSkeletonAnimation(config.animation)
149 | }
150 | }
151 | _status = .on
152 | }
153 |
154 | func updateSkeletonLayer(skeletonConfig config: SkeletonConfig) {
155 | guard let skeletonLayer = _skeletonLayer else { return }
156 | skeletonLayer.update(usingColors: config.colors)
157 | if config.animated {
158 | startSkeletonAnimation(config.animation)
159 | } else {
160 | skeletonLayer.stopAnimation()
161 | }
162 | }
163 |
164 | func layoutSkeletonLayerIfNeeded() {
165 | guard let skeletonLayer = _skeletonLayer else { return }
166 | skeletonLayer.layoutIfNeeded()
167 | }
168 |
169 | func removeSkeletonLayer() {
170 | guard sk.isSkeletonActive,
171 | let skeletonLayer = _skeletonLayer,
172 | let transitionStyle = _currentSkeletonConfig?.transition else { return }
173 | skeletonLayer.stopAnimation()
174 | _status = .off
175 | skeletonLayer.removeLayer(transition: transitionStyle) {
176 | self._skeletonLayer = nil
177 | self._currentSkeletonConfig = nil
178 | }
179 | }
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+SkeletonView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UIView+SkeletonView.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import UIKit
15 |
16 | extension UIView {
17 |
18 | func showSkeleton(
19 | skeletonConfig config: SkeletonConfig,
20 | notifyDelegate: Bool = true
21 | ) {
22 | _isSkeletonAnimated = config.animated
23 |
24 | if notifyDelegate {
25 | _flowDelegate = SkeletonFlowHandler()
26 | _flowDelegate?.willBeginShowingSkeletons(rootView: self)
27 | }
28 |
29 | recursiveShowSkeleton(skeletonConfig: config, root: self)
30 | }
31 |
32 | func updateSkeleton(
33 | skeletonConfig config: SkeletonConfig,
34 | notifyDelegate: Bool = true
35 | ) {
36 | _isSkeletonAnimated = config.animated
37 |
38 | if notifyDelegate {
39 | _flowDelegate?.willBeginUpdatingSkeletons(rootView: self)
40 | }
41 |
42 | recursiveUpdateSkeleton(skeletonConfig: config, root: self)
43 | }
44 |
45 | func recursiveLayoutSkeletonIfNeeded(root: UIView? = nil) {
46 | subviewsSkeletonables.recursiveSearch(leafBlock: {
47 | guard isSkeletonable, sk.isSkeletonActive else { return }
48 | layoutSkeletonLayerIfNeeded()
49 | if let config = _currentSkeletonConfig, config.animated, !_isSkeletonAnimated {
50 | startSkeletonAnimation(config.animation)
51 | }
52 | }) { subview in
53 | subview.recursiveLayoutSkeletonIfNeeded()
54 | }
55 |
56 | if let root = root {
57 | _flowDelegate?.didLayoutSkeletonsIfNeeded(rootView: root)
58 | }
59 | }
60 |
61 | func recursiveHideSkeleton(reloadDataAfter reload: Bool, transition: SkeletonTransitionStyle, root: UIView? = nil) {
62 | guard sk.isSkeletonActive else { return }
63 | if isHiddenWhenSkeletonIsActive {
64 | isHidden = false
65 | }
66 | _currentSkeletonConfig?.transition = transition
67 | unSwizzleLayoutSubviews()
68 | unSwizzleTraitCollectionDidChange()
69 | removeDummyDataSourceIfNeeded(reloadAfter: reload)
70 | subviewsSkeletonables.recursiveSearch(leafBlock: {
71 | recoverViewState(forced: false)
72 | removeSkeletonLayer()
73 | }) { subview in
74 | subview.recursiveHideSkeleton(reloadDataAfter: reload, transition: transition)
75 | }
76 |
77 | if let root = root {
78 | _flowDelegate?.didHideSkeletons(rootView: root)
79 | }
80 | }
81 |
82 | }
83 |
84 | private extension UIView {
85 |
86 | func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
87 | guard !sk.isSkeletonActive else { return }
88 | saveViewState()
89 |
90 | prepareViewForSkeleton()
91 | addSkeletonLayer(skeletonConfig: config)
92 | }
93 |
94 | func recursiveShowSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
95 | if isHiddenWhenSkeletonIsActive {
96 | isHidden = true
97 | }
98 | guard isSkeletonable && !sk.isSkeletonActive else { return }
99 | _currentSkeletonConfig = config
100 | swizzleLayoutSubviews()
101 | swizzleTraitCollectionDidChange()
102 | addDummyDataSourceIfNeeded()
103 | subviewsSkeletonables.recursiveSearch(leafBlock: {
104 | showSkeletonIfNotActive(skeletonConfig: config)
105 | }) { subview in
106 | subview.recursiveShowSkeleton(skeletonConfig: config)
107 | }
108 |
109 | if let root = root {
110 | _flowDelegate?.didShowSkeletons(rootView: root)
111 | }
112 | }
113 |
114 | func recursiveUpdateSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
115 | guard sk.isSkeletonActive else { return }
116 | _currentSkeletonConfig = config
117 | updateDummyDataSourceIfNeeded()
118 | subviewsSkeletonables.recursiveSearch(leafBlock: {
119 | if let skeletonLayer = _skeletonLayer,
120 | skeletonLayer.type != config.type {
121 | removeSkeletonLayer()
122 | addSkeletonLayer(skeletonConfig: config)
123 | } else {
124 | updateSkeletonLayer(skeletonConfig: config)
125 | }
126 | }) { subview in
127 | subview.recursiveUpdateSkeleton(skeletonConfig: config)
128 | }
129 |
130 | if let root = root {
131 | _flowDelegate?.didUpdateSkeletons(rootView: root)
132 | }
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Swizzling.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // UIView+Swizzling.swift
11 | //
12 | // Created by Juanpe Catalán on 19/8/21.
13 |
14 | import UIKit
15 |
16 | extension UIView {
17 |
18 | @objc func skeletonLayoutSubviews() {
19 | guard Thread.isMainThread else { return }
20 | skeletonLayoutSubviews()
21 | guard sk.isSkeletonActive else { return }
22 | layoutSkeletonIfNeeded()
23 | }
24 |
25 | @objc func skeletonTraitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
26 | skeletonTraitCollectionDidChange(previousTraitCollection)
27 | guard isSkeletonable, sk.isSkeletonActive, let config = _currentSkeletonConfig else { return }
28 | updateSkeleton(skeletonConfig: config)
29 | }
30 |
31 | func swizzleLayoutSubviews() {
32 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
33 | DispatchQueue.once(token: "UIView.SkeletonView.swizzleLayoutSubviews") {
34 | swizzle(selector: #selector(UIView.layoutSubviews),
35 | with: #selector(UIView.skeletonLayoutSubviews),
36 | inClass: UIView.self,
37 | usingClass: UIView.self)
38 | self.layoutSkeletonIfNeeded()
39 | }
40 | }
41 | }
42 |
43 | func unSwizzleLayoutSubviews() {
44 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
45 | DispatchQueue.removeOnce(token: "UIView.SkeletonView.swizzleLayoutSubviews") {
46 | swizzle(selector: #selector(UIView.skeletonLayoutSubviews),
47 | with: #selector(UIView.layoutSubviews),
48 | inClass: UIView.self,
49 | usingClass: UIView.self)
50 | }
51 | }
52 | }
53 |
54 | func swizzleTraitCollectionDidChange() {
55 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
56 | DispatchQueue.once(token: "UIView.SkeletonView.swizzleTraitCollectionDidChange") {
57 | swizzle(selector: #selector(UIView.traitCollectionDidChange(_:)),
58 | with: #selector(UIView.skeletonTraitCollectionDidChange(_:)),
59 | inClass: UIView.self,
60 | usingClass: UIView.self)
61 | }
62 | }
63 | }
64 |
65 | func unSwizzleTraitCollectionDidChange() {
66 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
67 | DispatchQueue.removeOnce(token: "UIView.SkeletonView.swizzleTraitCollectionDidChange") {
68 | swizzle(selector: #selector(UIView.skeletonTraitCollectionDidChange(_:)),
69 | with: #selector(UIView.traitCollectionDidChange(_:)),
70 | inClass: UIView.self,
71 | usingClass: UIView.self)
72 | }
73 | }
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Internal/UIKitExtensions/UIView+Transitions.swift:
--------------------------------------------------------------------------------
1 | // Copyright © 2019 SkeletonView. All rights reserved.
2 |
3 | import UIKit
4 |
5 | extension UIView {
6 |
7 | func startTransition(transitionBlock: @escaping () -> Void) {
8 | guard let transitionStyle = _currentSkeletonConfig?.transition,
9 | transitionStyle != .none else {
10 | transitionBlock()
11 | return
12 | }
13 |
14 | if case let .crossDissolve(duration) = transitionStyle {
15 | UIView.transition(with: self,
16 | duration: duration,
17 | options: .transitionCrossDissolve,
18 | animations: transitionBlock,
19 | completion: nil)
20 | }
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Sources/Supporting Files/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyTracking
6 |
7 | NSPrivacyTrackingDomains
8 |
9 | NSPrivacyCollectedDataTypes
10 |
11 | NSPrivacyAccessedAPITypes
12 |
13 |
14 | NSPrivacyAccessedAPIType
15 | NSPrivacyAccessedAPICategoryUserDefaults
16 | NSPrivacyAccessedAPITypeReasons
17 |
18 | CA92.1
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Tests/Debug/SkeletonDebugTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright SkeletonView. All Rights Reserved.
3 | //
4 | // Licensed under the MIT License (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // https://opensource.org/licenses/MIT
9 | //
10 | // SkeletonDebugTests.swift
11 | //
12 | // Created by Juanpe Catalán on 18/8/21.
13 |
14 | import XCTest
15 | @testable import SkeletonView
16 |
17 | class SkeletonDebugTests: XCTestCase {
18 |
19 | func testSkeletonDescriptionWithViewNotSkeletonableNotReturnsSkullEmojiAndChildren() {
20 | /// given
21 | let view = UIView()
22 | let expectedDictionary: [String : Any] = [
23 | "isSkeletonable" : false,
24 | "type" : "UIView",
25 | "reference" : "\(Unmanaged.passUnretained(view).toOpaque())"
26 | ]
27 |
28 | /// when
29 | let obtainedDictionary = view.sk.treeNode.dictionaryRepresentation
30 |
31 | /// then
32 | XCTAssertEqual(expectedDictionary.keys, obtainedDictionary.keys)
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/SkeletonViewCore/Tests/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | BNDL
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | default_platform(:ios)
2 | podspec_name = "SkeletonView.podspec"
3 |
4 | lane :bump_version do |options|
5 | version_bump_podspec(path: @podspec_name, version_number: options[:next_version])
6 | end
7 |
8 | lane :release_current do
9 | version = version_get_podspec(path: @podspec_name)
10 | if git_tag_exists(tag: version)
11 | 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}")
12 | end
13 | pod_lib_lint
14 | add_git_tag(tag: "#{version}")
15 | push_git_tags
16 | pod_push
17 | end
18 |
--------------------------------------------------------------------------------
/fastlane/README.md:
--------------------------------------------------------------------------------
1 | fastlane documentation
2 | ----
3 |
4 | # Installation
5 |
6 | Make sure you have the latest version of the Xcode command line tools installed:
7 |
8 | ```sh
9 | xcode-select --install
10 | ```
11 |
12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
13 |
14 | # Available Actions
15 |
16 | ### bump_version
17 |
18 | ```sh
19 | [bundle exec] fastlane bump_version
20 | ```
21 |
22 |
23 |
24 | ### release_current
25 |
26 | ```sh
27 | [bundle exec] fastlane release_current
28 | ```
29 |
30 |
31 |
32 | ----
33 |
34 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
35 |
36 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
37 |
38 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
39 |
--------------------------------------------------------------------------------