Compare commits
201 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f78f5c378 | |||
| c8fdd6998d | |||
| 134463e529 | |||
| ee59239c59 | |||
| f1e61aa9c0 | |||
| 135778aa1a | |||
| e8d5eb61d8 | |||
| c2a029ed51 | |||
| a1c8276980 | |||
| e9ac3a5ab3 | |||
| fb83a62f7b | |||
| 12521c1d87 | |||
| c266035888 | |||
| 74b5172ea5 | |||
| 318e629d04 | |||
| 62193db76f | |||
| 19e7866d3d | |||
| 36668f450b | |||
| 1bdb3b9c72 | |||
| 6c4e9091a7 | |||
| 61a6efbc7b | |||
| 4143a6065c | |||
| f3baeedc14 | |||
| 29f7d8fd6a | |||
| 83e1600a73 | |||
| 450cecbd9c | |||
| 1676f8720a | |||
| 32ab6d3852 | |||
| 7ce5e0623a | |||
| f0c7d2d6a0 | |||
| 26476fd3ed | |||
| f0c2cf4ab4 | |||
| cadf8bb136 | |||
| 91c8f953ca | |||
| 0a91aebf2a | |||
| 7d8a057b64 | |||
| 06f7d240ff | |||
| c4c725eea3 | |||
| cc9dbdb499 | |||
| c6dfcf9c1a | |||
| dd7e5243bd | |||
| 7e002ddce0 | |||
| 6dcfb325dc | |||
| 7b5739295a | |||
| 2e1808ea50 | |||
| cec3aeb781 | |||
| b16400294d | |||
| abb8ea55f6 | |||
| c833da20f8 | |||
| 292fadf5b0 | |||
| 2ce041901c | |||
| 961cc77661 | |||
| 11b623fb13 | |||
| 4899802b01 | |||
| 0dfe1ca43b | |||
| 499d01c232 | |||
| f8f368ab8a | |||
| 023a57a763 | |||
| d426a0916e | |||
| 611c248c51 | |||
| 6f20c34d56 | |||
| be25b572f8 | |||
| 3c67812453 | |||
| 0431eb2a7d | |||
| fa0b95d923 | |||
| 523cd1c657 | |||
| 29e65c70d3 | |||
| 4b7360a011 | |||
| 2753c54216 | |||
| c7b3bc67be | |||
| 0c8bbf1fed | |||
| 2e99bc98bf | |||
| d5d52d1416 | |||
| 91c7735d0c | |||
| 18c5c1deb8 | |||
| 1ecfa31743 | |||
| 82b70ea1ee | |||
| d8ee06f1c5 | |||
| 13ce7426dd | |||
| 7d5039536c | |||
| b7a7731784 | |||
| 07f92d098a | |||
| f45405c9d5 | |||
| 768354ca35 | |||
| d5819763a1 | |||
| a73fc993aa | |||
| 0d027b5927 | |||
| bc482f58b3 | |||
| 78e941e01c | |||
| 7282008606 | |||
| 52d657faa0 | |||
| cba096becf | |||
| c095f49028 | |||
| 89e292c11b | |||
| 5089295771 | |||
| 39971af22d | |||
| 81d182259d | |||
| 1d82545a3d | |||
| 9086d7e874 | |||
| 666ebdf472 | |||
| 5a5d7b66e7 | |||
| 78f2bbe9f1 | |||
| c7133a6711 | |||
| aee5cdfd8f | |||
| 638c535767 | |||
| 782dd45b33 | |||
| 073baa6483 | |||
| a26dbf7c89 | |||
| 7ebde2b030 | |||
| 4009ed2b1e | |||
| a246c16cfe | |||
| ef8e4578d7 | |||
| 46e2180c4b | |||
| f32c420165 | |||
| 0ca38d8ea1 | |||
| 9ed337ba53 | |||
| 932a1f6e6e | |||
| 5cd08fb77c | |||
| f7cb021a93 | |||
| 4fbfbeca55 | |||
| c92461ae5c | |||
| efed2e7f32 | |||
| 52cbd3ae2c | |||
| a349f7174c | |||
| a58c97eb1a | |||
| 8e6fea2de3 | |||
| bf417d6bf8 | |||
| c3295531be | |||
| a87f7302c0 | |||
| 5e22f38454 | |||
| 0dbdb9064d | |||
| c8efc4ccb1 | |||
| 01733b358c | |||
| 027143499c | |||
| ae1b5ee790 | |||
| 0c0a0ea451 | |||
| 8d090904b4 | |||
| 8d00d5fa16 | |||
| f2bc1cab27 | |||
| 83b99f16b1 | |||
| 7a2cc5d51d | |||
| c50547ef5f | |||
| 0a7f80e6d0 | |||
| 29f0726397 | |||
| 6db57ee5fb | |||
| 709dec5fa0 | |||
| 3796d96039 | |||
| fab5e2e36b | |||
| a6a6dc2440 | |||
| 58e9ace96d | |||
| 030da2437c | |||
| 95b1b7d512 | |||
| 2060d1a4a5 | |||
| 4874e7c95c | |||
| f42ce31a7d | |||
| 805d3bc12a | |||
| a8c61c8ed2 | |||
| 90bb2cb22a | |||
| 8fddc20ed3 | |||
| a7137ac29d | |||
| a72acbae5b | |||
| bb5bdf2d0d | |||
| beeb303db8 | |||
| 4ea81526b6 | |||
| 3b8c0114de | |||
| e1a74ad0b8 | |||
| 39d7af9980 | |||
| 796702671b | |||
| df5fbb4faa | |||
| 072f085da4 | |||
| 88521db1a1 | |||
| ffac0dd0e4 | |||
| d8ccc8ec7a | |||
| 3dcfebda82 | |||
| e2afb6aba7 | |||
| b267a2c675 | |||
| ced07a1ca1 | |||
| 8d09e6646a | |||
| 7d710bbd74 | |||
| 4970073190 | |||
| 4ecfa226b6 | |||
| a256001f61 | |||
| 8b3b8c1d71 | |||
| 4e605d2fd4 | |||
| 6feffdb41b | |||
| 0adc9683ee | |||
| 0521be6d6e | |||
| 4167579d8a | |||
| 1bd4d3d960 | |||
| b79219ac88 | |||
| 39ad7aa264 | |||
| 997c62b7d1 | |||
| d4aa1743e6 | |||
| 770be4fc06 | |||
| cdd30bd623 | |||
| 0c500a943c | |||
| 8258b73b35 | |||
| 3d9e9063c5 | |||
| b443dcc17c | |||
| 13b07d9edd | |||
| 5a27b37e81 |
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: "\U0001F41B Bug report"
|
||||
about: Report a bug or unexpected behavior while using SkeletonView
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Description
|
||||
|
||||
Describe your issue here.
|
||||
|
||||
### What type of issue is this? (place an `x` in one of the `[ ]`)
|
||||
- [ ] bug
|
||||
- [ ] enhancement (feature request)
|
||||
- [ ] question
|
||||
- [ ] documentation related
|
||||
- [ ] discussion
|
||||
|
||||
### Requirements (place an `x` in each of the `[ ]`)
|
||||
* [ ] I've read and understood the [Contributing guidelines](https://github.com/Juanpe/SkeletonView/blob/main/CONTRIBUTING.md) and have done my best effort to follow them.
|
||||
* [ ] I've read and agree to the [Code of Conduct](https://github.com/Juanpe/SkeletonView/blob/main/CODE_OF_CONDUCT.md).
|
||||
* [ ] I've searched for any related issues and avoided creating a duplicate issue.
|
||||
|
||||
---
|
||||
|
||||
### Bug Report
|
||||
|
||||
Filling out the following details about bugs will help us solve your issue sooner.
|
||||
|
||||
### SkeletonView Environment:
|
||||
|
||||
**SkeletonView version:**
|
||||
**Xcode version:**
|
||||
**Swift version:**
|
||||
|
||||
#### Steps to reproduce:
|
||||
|
||||
*Please replace this with the steps to reproduce the behavior.*
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
#### Expected result:
|
||||
|
||||
*Please replace this with what you expected to happen.*
|
||||
|
||||
#### Actual result:
|
||||
|
||||
*Please replace this with of what happened instead.*
|
||||
|
||||
#### Attachments:
|
||||
|
||||
Logs, screenshots, sample project, funny gif, etc.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: "\U0001F4E3 Feedback"
|
||||
about: Give us general feedback about the SkeletonView
|
||||
title: ''
|
||||
labels: feedback
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
# SkeletonView Feedback
|
||||
|
||||
You can use this template to give us structured feedback or just wipe it and leave us a note. Thank you!
|
||||
|
||||
## What have you loved?
|
||||
|
||||
_eg "the nice colors"_
|
||||
|
||||
## What was confusing or gave you pause?
|
||||
|
||||
_eg "it did something unexpected"_
|
||||
|
||||
## Are there features you'd like to see added?
|
||||
|
||||
_eg "SkeletonView should be compatible with SwiftUI"_
|
||||
|
||||
## Anything else?
|
||||
|
||||
_eg "have a nice day"_
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: "⭐ Submit a request"
|
||||
about: Surface a feature or problem that you think should be solved
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Describe the feature or problem you’d like to solve
|
||||
|
||||
A clear and concise description of what the feature or problem is.
|
||||
|
||||
### Proposed solution
|
||||
|
||||
How will it benefit SkeletonView and its users?
|
||||
|
||||
### Additional context
|
||||
|
||||
Add any other context like screenshots or mockups are helpful, if applicable.
|
||||
@@ -1,25 +0,0 @@
|
||||
⚠️ Please fill out this template when filing an issue.
|
||||
|
||||
#### 🙏🏼 *Please check if it already exists other issue related with yours.*
|
||||
|
||||
### What did you do?
|
||||
|
||||
*Please replace this with what you did.*
|
||||
|
||||
### What did you expect to happen?
|
||||
|
||||
*Please replace this with what you expected to happen.*
|
||||
|
||||
### What happened instead?
|
||||
|
||||
*Please replace this with of what happened instead.*
|
||||
|
||||
### Steps to reproduce the behavior
|
||||
|
||||
*Please replace this with the steps to reproduce the behavior.*
|
||||
|
||||
### SkeletonView Environment
|
||||
|
||||
**SkeletonView version:**
|
||||
**Xcode version:**
|
||||
**Swift version:**
|
||||
@@ -0,0 +1,7 @@
|
||||
### Summary
|
||||
|
||||
Describe the goal of this PR. Mention any related Issue numbers.
|
||||
|
||||
### Requirements (place an `x` in each of the `[ ]`)
|
||||
* [ ] I've read and understood the [Contributing guidelines](https://github.com/Juanpe/SkeletonView/blob/main/CONTRIBUTING.md) and have done my best effort to follow them.
|
||||
* [ ] I've read and agree to the [Code of Conduct](https://github.com/Juanpe/SkeletonView/blob/main/CODE_OF_CONDUCT.md).
|
||||
@@ -0,0 +1,39 @@
|
||||
name-template: '📦 $RESOLVED_VERSION'
|
||||
tag-template: '$RESOLVED_VERSION'
|
||||
category-template: '#### $TITLE'
|
||||
change-template: '- **#$NUMBER**: $TITLE - @$AUTHOR'
|
||||
template: |
|
||||
$CHANGES
|
||||
categories:
|
||||
- title: '🚨 Breaking'
|
||||
label: 'breaking'
|
||||
- title: '🔬Improvements'
|
||||
label: '💡 enhancement'
|
||||
- title: '🙌 New'
|
||||
label: 'feature'
|
||||
- title: '🩹 Bug fixes'
|
||||
label: '🐞 bug'
|
||||
- title: '⚙️ Maintenance'
|
||||
label: '⚙️ maintenance'
|
||||
- title: '📚 Documentation'
|
||||
label: '📚 docs'
|
||||
- title: '💾 Dependency Updates'
|
||||
label: 'dependencies'
|
||||
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- 'breaking'
|
||||
minor:
|
||||
labels:
|
||||
- '💡 enhancement'
|
||||
- 'feature'
|
||||
patch:
|
||||
labels:
|
||||
- '🐞 bug'
|
||||
- '⚙️ maintenance'
|
||||
- '📚 docs'
|
||||
- 'dependencies'
|
||||
|
||||
exclude-labels:
|
||||
- 'skip-changelog'
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
daysUntilStale: 20
|
||||
daysUntilStale: 7
|
||||
daysUntilClose: 7
|
||||
onlyLabels:
|
||||
- awaiting user input
|
||||
staleLabel: given up
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
🤖 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.
|
||||
for your contributions 🙂
|
||||
closeComment: false
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: xcodebuild
|
||||
run: |
|
||||
xcodebuild -project SkeletonView.xcodeproj -target SkeletonView-iOS -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 7,OS=12'
|
||||
xcodebuild -project SkeletonView.xcodeproj -target SkeletonView-tvOS -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV,OS=12'
|
||||
xcodebuild -project SkeletonView.xcodeproj -target SkeletonViewExample -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 7,OS=12'
|
||||
@@ -1,13 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
danger:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Execute danger
|
||||
uses: danger/swift@2.0.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,19 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
build-config:
|
||||
- { target: 'SkeletonView-iOS', destination: 'platform=iOS Simulator,name=iPhone 8', sdk: 'iphonesimulator' }
|
||||
- { target: 'SkeletonView-tvOS', destination: 'platform=tvOS Simulator,name=Apple TV', sdk: 'appletvsimulator' }
|
||||
- { target: 'SkeletonViewExample', destination: 'platform=iOS Simulator,name=iPhone 8', sdk: 'iphonesimulator' }
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: xcodebuild clean build -target '${{ matrix.build-config['target'] }}' -sdk '${{ matrix.build-config['sdk'] }}' -destination '${{ matrix.build-config['destination'] }}'
|
||||
@@ -0,0 +1,14 @@
|
||||
name: Pod lint
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
pod_lib_lint:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- env:
|
||||
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
|
||||
run: |
|
||||
set -eo pipefail
|
||||
pod lib lint --allow-warnings
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Release
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
release_version:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup fastlane
|
||||
run: brew install fastlane
|
||||
|
||||
- name: Publish release
|
||||
id: publish_release
|
||||
uses: release-drafter/release-drafter@v5
|
||||
with:
|
||||
publish: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update podspec
|
||||
run: fastlane bump_version next_version:${{ steps.publish_release.outputs.tag_name }}
|
||||
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
branch: 'main'
|
||||
commit_message: 'Bump version ${{ steps.publish_release.outputs.tag_name }}'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Cocoapods
|
||||
env:
|
||||
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
|
||||
run: |
|
||||
set -eo pipefail
|
||||
pod lib lint --allow-warnings
|
||||
pod trunk push --allow-warnings
|
||||
@@ -0,0 +1,14 @@
|
||||
name: Release Notes
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
update_release_notes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Validations
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [main]
|
||||
types: [opened, reoneped, edited, synchronized]
|
||||
|
||||
# workflow_dispatch:
|
||||
# inputs:
|
||||
# commit hash:
|
||||
# description: "Commit hash"
|
||||
# required: true
|
||||
# default: ""
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run SwiftLint
|
||||
run: swiftlint lint --reporter github-actions-logging
|
||||
|
||||
danger:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Danger
|
||||
uses: docker://frmeloni/danger-swift-with-swiftlint:1.3.1
|
||||
with:
|
||||
args: --failOnErrors --verbose
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
included:
|
||||
- Sources
|
||||
disabled_rules:
|
||||
- trailing_whitespace
|
||||
- line_length
|
||||
- type_body_length
|
||||
- identifier_name
|
||||
- multiple_closures_with_trailing_closure
|
||||
- class_delegate_protocol
|
||||
- force_unwrapping
|
||||
- force_try
|
||||
- force_cast
|
||||
- function_parameter_count
|
||||
- discouraged_optional_collection
|
||||
- shorthand_operator
|
||||
- reduce_boolean
|
||||
- weak_delegate
|
||||
- nesting
|
||||
- closure_end_indentation
|
||||
- function_default_parameter_at_end
|
||||
- unowned_variable_capture
|
||||
- legacy_constructor
|
||||
opt_in_rules:
|
||||
- multiline_arguments
|
||||
- multiline_parameters
|
||||
- closure_spacing
|
||||
- closure_body_length
|
||||
- collection_alignment
|
||||
- contains_over_filter_is_empty
|
||||
- contains_over_filter_count
|
||||
- contains_over_first_not_nil
|
||||
- contains_over_range_nil_comparison
|
||||
- convenience_type
|
||||
- discouraged_object_literal
|
||||
- discouraged_optional_boolean
|
||||
- empty_count
|
||||
- empty_string
|
||||
- fallthrough
|
||||
- file_name_no_space
|
||||
- first_where
|
||||
- flatmap_over_map_reduce
|
||||
- implicitly_unwrapped_optional
|
||||
- joined_default_parameter
|
||||
- last_where
|
||||
- literal_expression_end_indentation
|
||||
- multiline_function_chains
|
||||
- operator_usage_whitespace
|
||||
- private_action
|
||||
- private_outlet
|
||||
- redundant_optional_initialization
|
||||
- redundant_set_access_control
|
||||
- redundant_type_annotation
|
||||
- sorted_first_last
|
||||
- switch_case_on_newline
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
- unused_declaration
|
||||
- unused_import
|
||||
- vertical_whitespace_opening_braces
|
||||
- discouraged_optional_collection
|
||||
- enum_case_associated_values_counts
|
||||
- legacy_multiple
|
||||
- legacy_random
|
||||
indentation: 2
|
||||
file_length:
|
||||
- 2500
|
||||
- 3000
|
||||
large_tuple:
|
||||
- 5
|
||||
- 6
|
||||
+61
-22
@@ -3,7 +3,46 @@ All notable changes to this project will be documented in this file
|
||||
|
||||
### Next version
|
||||
|
||||
### 📦 [1.8.8](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.8)
|
||||
#### 🙌 New
|
||||
|
||||
#### 🔬 Improvements
|
||||
* [**369**](https://github.com/Juanpe/SkeletonView/pull/369): remove useless corner radius constraint - [@Juanpe](https://github.com/Juanpe)
|
||||
* [**357**](https://github.com/Juanpe/SkeletonView/pull/357): Removed duplicate code in SkeletonCollectionDelegate. - [@keshavamurthy1](https://github.com/keshavamurthy1)
|
||||
|
||||
#### 🩹 Bug fixes
|
||||
* [**359**](https://github.com/Juanpe/SkeletonView/pull/359): SkeletonView respecting Font's height, rather than the `UIView` actual height. - [@keshavamurthy1](https://github.com/keshavamurthy1)
|
||||
|
||||
|
||||
## 📦 [1.11.0](https://github.com/Juanpe/SkeletonView/releases/tag/1.11.0)
|
||||
|
||||
#### 🙌 New
|
||||
* [**339**](https://github.com/Juanpe/SkeletonView/pull/339): Add `hiddenWhenSkeletonIsActive` property - [@mohn93](https://github.com/mohn93)
|
||||
* [**341**](https://github.com/Juanpe/SkeletonView/pull/341): Support autoreverses in gradient animations - [@Juanpe](https://github.com/Juanpe)
|
||||
|
||||
#### 🔬Improvements
|
||||
* [**344**](https://github.com/Juanpe/SkeletonView/pull/344): Resize labels based on number of lines - [@Juanpe](https://github.com/Juanpe)
|
||||
|
||||
#### 🩹 Bug fixes
|
||||
* [**340**](https://github.com/Juanpe/SkeletonView/pull/340): Fixed incorrect padding, and incorrect multiline layer frame calculation - [@yzhao198](https://github.com/yzhao198)
|
||||
|
||||
## 📦 [1.10.0](https://github.com/Juanpe/SkeletonView/releases/tag/1.10.0)
|
||||
|
||||
#### 🙌 New
|
||||
* [**327**](https://github.com/Juanpe/SkeletonView/pull/327): Add SwiftLint - [@Juanpe](https://github.com/Juanpe)
|
||||
* [**329**](https://github.com/Juanpe/SkeletonView/pull/329): Spanish README 🇪🇸 - [@Juanpe](https://github.com/Juanpe)
|
||||
|
||||
#### 🩹 Bug fixes
|
||||
* [**336**](https://github.com/Juanpe/SkeletonView/pull/336): Not replace text when the skeleton disappears. Solved issues: [#296](https://github.com/Juanpe/SkeletonView/issues/296), [#330](https://github.com/Juanpe/SkeletonView/issues/330) - [@Juanpe](https://github.com/Juanpe)
|
||||
* [**337**](https://github.com/Juanpe/SkeletonView/pull/337): RTL support. Solved issues: [#143](https://github.com/Juanpe/SkeletonView/issues/143) - [@Juanpe](https://github.com/Juanpe)
|
||||
|
||||
## 📦 [1.9](https://github.com/Juanpe/SkeletonView/releases/tag/1.9)
|
||||
|
||||
#### 🩹 Bug fixes
|
||||
* [**319**](https://github.com/Juanpe/SkeletonView/pull/319): Fix to consider the top and bottom edge insets when updating the skeleton layer height - [@xpereta](https://github.com/xpereta)
|
||||
* [**320**](https://github.com/Juanpe/SkeletonView/pull/320): Fix Single line customisation - [@Juanpe](https://github.com/juanpe)
|
||||
* [**323**](https://github.com/Juanpe/SkeletonView/pull/323): Save and restore view state for UIButton - [@Juanpe](https://github.com/juanpe)
|
||||
|
||||
## 📦 [1.8.8](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.8)
|
||||
|
||||
#### 🙌 New
|
||||
* [**304**](https://github.com/Juanpe/SkeletonView/pull/304): French README 🇫🇷 - [@OmarJalil](https://github.com/OmarJalil)
|
||||
@@ -17,7 +56,7 @@ All notable changes to this project will be documented in this file
|
||||
* [**308**](https://github.com/Juanpe/SkeletonView/pull/308): Fix example backgroundColor in DarkMode - [@toshi0383](https://github.com/toshi0383)
|
||||
* [**307**](https://github.com/Juanpe/SkeletonView/pull/307): Prevent incorrect skeletonLayer to be added when updating skeleton - [@wsalim1610](https://github.com/wsalim1610)
|
||||
|
||||
### 📦 [1.8.7](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.7)
|
||||
## 📦 [1.8.7](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.7)
|
||||
|
||||
#### 🔬Improvements
|
||||
* [**271**](https://github.com/Juanpe/SkeletonView/pull/271): Add corner radius for skeletonView as IBInspectable (CGFloat) default is 0.0 - [@paulanatoleclaudot-betclic](https://github.com/paulanatoleclaudot-betclic)
|
||||
@@ -27,7 +66,7 @@ All notable changes to this project will be documented in this file
|
||||
* [**274**](https://github.com/Juanpe/SkeletonView/pull/274): Fix: hiding skeleton when header and footer views of section would not hide it - [@darkside999](https://github.com/darkside999)
|
||||
* [**273**](https://github.com/Juanpe/SkeletonView/pull/273): Fix: in vertical stack view with center alignment show incorrect position - [@koooootake](https://github.com/koooootake)
|
||||
|
||||
### 📦 [1.8.6](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.6)
|
||||
## 📦 [1.8.6](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.6)
|
||||
|
||||
#### 🔬Improvements
|
||||
* [**242**](https://github.com/Juanpe/SkeletonView/pull/242): Offscreen table view layout issue fixed - [@Cacodemon](https://github.com/Cacodemon)
|
||||
@@ -41,16 +80,16 @@ All notable changes to this project will be documented in this file
|
||||
* [**257**](https://github.com/Juanpe/SkeletonView/issues/257): Unit test problem when using SkeletonView
|
||||
|
||||
|
||||
### 📦 [1.8.3](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.3)
|
||||
## 📦 [1.8.3](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.3)
|
||||
|
||||
- Support for iOS 13 dark mode. (thanks @Wilsonator5000)
|
||||
|
||||
### 📦 [1.8.2](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.2)
|
||||
## 📦 [1.8.2](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.2)
|
||||
|
||||
#### 🙌 New
|
||||
- Add ability to customize line spacing per label. (thanks @gshahbazian)
|
||||
|
||||
### 📦 [LayoutSkeleton (1.8.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.1)
|
||||
## 📦 [LayoutSkeleton (1.8.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.8.1)
|
||||
|
||||
#### 🔬Improvements
|
||||
- Fix completion call in .none transition style while hide skeletons. (thanks @aadudyrev)
|
||||
@@ -66,7 +105,7 @@ All notable changes to this project will be documented in this file
|
||||
- Update layout subviews when the original method is called.
|
||||
- Issues: [#88, #149]
|
||||
|
||||
### 📦 [Transitions (1.8)](https://github.com/Juanpe/SkeletonView/releases/tag/1.8)
|
||||
## 📦 [Transitions (1.8)](https://github.com/Juanpe/SkeletonView/releases/tag/1.8)
|
||||
|
||||
#### 🙌 New
|
||||
|
||||
@@ -80,7 +119,7 @@ All notable changes to this project will be documented in this file
|
||||
- Solved issues.
|
||||
[#175](https://github.com/Juanpe/SkeletonView/issues/175) Swift Package Manager version format
|
||||
|
||||
### 📦 [Layout update (1.7)](https://github.com/Juanpe/SkeletonView/releases/tag/1.7)
|
||||
## 📦 [Layout update (1.7)](https://github.com/Juanpe/SkeletonView/releases/tag/1.7)
|
||||
|
||||
#### 🙌 New
|
||||
|
||||
@@ -90,7 +129,7 @@ All notable changes to this project will be documented in this file
|
||||
|
||||
- Allow updating skeleton layers without recreating them: `updateSkeleton`, `updateGradientSkeleton`, `updateAnimatedSkeleton`, `updateAnimatedGradientSkeleton`. (thanks @eduardbosch)
|
||||
|
||||
### 📦 [Debug (1.4)](https://github.com/Juanpe/SkeletonView/releases/tag/1.4)
|
||||
## 📦 [Debug (1.4)](https://github.com/Juanpe/SkeletonView/releases/tag/1.4)
|
||||
|
||||
#### 🙌 New
|
||||
|
||||
@@ -104,7 +143,7 @@ All notable changes to this project will be documented in this file
|
||||
#### 🩹 Bug fixes
|
||||
- Solved issue [#86](https://github.com/Juanpe/SkeletonView/issues/86) (thanks @reececomo)
|
||||
|
||||
### 📦 [Custom defaults (1.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.3)
|
||||
## 📦 [Custom defaults (1.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.3)
|
||||
|
||||
#### 🙌 New
|
||||
|
||||
@@ -114,7 +153,7 @@ All notable changes to this project will be documented in this file
|
||||
#### 🩹 Bug fixes
|
||||
- Solved issue [#41](https://github.com/Juanpe/SkeletonView/issues/41). Now, Skeleton works if UICollectionView cell's Nib is registered in code. (thanks @kjoneandrei)
|
||||
|
||||
### 📦 [Typo (1.2.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2.3)
|
||||
## 📦 [Typo (1.2.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2.3)
|
||||
|
||||
#### Fixes
|
||||
|
||||
@@ -124,7 +163,7 @@ All notable changes to this project will be documented in this file
|
||||
|
||||
- Now it takes in account the `UIStackView` to calculate the `SkeletonLayer` bounds (thanks @giantramen)
|
||||
|
||||
### 📦 [New face (1.2.2)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2.2)
|
||||
## 📦 [New face (1.2.2)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2.2)
|
||||
|
||||
#### 🙌 New
|
||||
|
||||
@@ -133,7 +172,7 @@ All notable changes to this project will be documented in this file
|
||||
#### 🩹 Bug fixes
|
||||
- Solved issue [#23](https://github.com/Juanpe/SkeletonView/issues/23). Problem with UIStackView. (thanks @giantramen)
|
||||
|
||||
### 📦 [State (1.2.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2.1)
|
||||
## 📦 [State (1.2.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2.1)
|
||||
|
||||
#### 🙌 New
|
||||
|
||||
@@ -143,7 +182,7 @@ All notable changes to this project will be documented in this file
|
||||
#### 🩹 Bug fixes
|
||||
- Solved issue [#51](https://github.com/Juanpe/SkeletonView/issues/51). Support inspectable properties when using Carthage. (thanks @eduardbosch)
|
||||
|
||||
### 📦 [On TV (1.2)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2)
|
||||
## 📦 [On TV (1.2)](https://github.com/Juanpe/SkeletonView/releases/tag/1.2)
|
||||
|
||||
#### 🙌 New
|
||||
- Now ```SkeletonView``` is **tvOS** compatible! 🎉. (thanks @mihai8804858)
|
||||
@@ -152,12 +191,12 @@ All notable changes to this project will be documented in this file
|
||||
- Solved issue [#46](https://github.com/Juanpe/SkeletonView/issues/46). It crashes the application when tap on it, didSelect called and crash.
|
||||
|
||||
|
||||
### 📦 [Hotfix (1.1.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.1.1)
|
||||
## 📦 [Hotfix (1.1.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.1.1)
|
||||
|
||||
#### 🩹 Bug fixes
|
||||
- Now yes, solved issue [#39](https://github.com/Juanpe/SkeletonView/issues/39)
|
||||
|
||||
### 📦 [Needed (1.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.1)
|
||||
## 📦 [Needed (1.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.1)
|
||||
|
||||
#### 🙌 New
|
||||
- Now ```SkeletonView```supports **UICollectionViews**! 🎉. (thanks @Renatdz)
|
||||
@@ -166,7 +205,7 @@ All notable changes to this project will be documented in this file
|
||||
- Solved issue [#39](https://github.com/Juanpe/SkeletonView/issues/39). Gradient animation did not work when app becomes active.
|
||||
|
||||
|
||||
### 📦 [Resizable (1.0.5)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.5)
|
||||
## 📦 [Resizable (1.0.5)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.5)
|
||||
|
||||
#### 🙌 New
|
||||
- Now you can use table views with resizable cells.
|
||||
@@ -177,7 +216,7 @@ All notable changes to this project will be documented in this file
|
||||
[#30](https://github.com/Juanpe/SkeletonView/issues/30),
|
||||
[#34](https://github.com/Juanpe/SkeletonView/issues/34).
|
||||
|
||||
### 📦 [Filled or not (1.0.4)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.4)
|
||||
## 📦 [Filled or not (1.0.4)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.4)
|
||||
|
||||
#### 🙌 New
|
||||
- You can set the filling percent of the last line in multiline elements (thanks @jontelang!)
|
||||
@@ -185,22 +224,22 @@ All notable changes to this project will be documented in this file
|
||||
#### 🩹 Bug fixes
|
||||
- Solved issue [#14](https://github.com/Juanpe/SkeletonView/issues/14). You could edit text views with skeleton active.
|
||||
|
||||
### 📦 [In all directions (1.0.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.3)
|
||||
## 📦 [In all directions (1.0.3)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.3)
|
||||
|
||||
#### 🙌 New
|
||||
- Create ```SkeletonAnimationBuilder```, to facilitate the creation of layer animations.
|
||||
```GradientDirection``` enum.
|
||||
|
||||
### 📦 [Retro (1.0.2)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.2)
|
||||
## 📦 [Retro (1.0.2)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.2)
|
||||
|
||||
#### 🙌 New
|
||||
- Change some private keywords, to be Swift 3 compatible
|
||||
|
||||
### 📦 [Early bird bug (1.0.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.2)
|
||||
## 📦 [Early bird bug (1.0.1)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0.2)
|
||||
|
||||
#### 🩹 Bug fixes
|
||||
- It was not removing the skeleton layer
|
||||
|
||||
### 📦 [Starter (1.0)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0)
|
||||
## 📦 [Starter (1.0)](https://github.com/Juanpe/SkeletonView/releases/tag/1.0)
|
||||
|
||||
- First release
|
||||
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
# Code of Conduct
|
||||
|
||||
The Code of Conduct governs how we behave in public or in private
|
||||
whenever the project will be judged by our actions.
|
||||
We expect it to be honored by everyone who represents the project
|
||||
officially or informally,
|
||||
claims affiliation with the project,
|
||||
or participates directly.
|
||||
|
||||
We strive to:
|
||||
|
||||
* **Be open**: We invite anybody to participate in any aspect of our projects.
|
||||
Our community is open, and any responsibility can be carried
|
||||
by any contributor who demonstrates the required capacity and competence.
|
||||
* **Be empathetic**: We work together to resolve conflict,
|
||||
assume good intentions,
|
||||
and do our best to act in an empathic fashion.
|
||||
By understanding that humanity drops a few packets in online interactions,
|
||||
and adjusting accordingly,
|
||||
we can create a comfortable environment for everyone to share their ideas.
|
||||
* **Be collaborative**: We prefer to work transparently
|
||||
and to involve interested parties early on in the process.
|
||||
Wherever possible, we work closely with others in the open source community
|
||||
to coordinate our efforts.
|
||||
* **Be decisive**: We expect participants in the project to resolve disagreements constructively.
|
||||
When they cannot, we escalate the matter to structures
|
||||
with designated leaders to arbitrate and provide clarity and direction.
|
||||
* **Be responsible**: We hold ourselves accountable for our actions.
|
||||
When we make mistakes, we take responsibility for them.
|
||||
When we need help, we reach out to others.
|
||||
When it comes time to move on from a project,
|
||||
we take the proper steps to ensure that others can pick up where we left off.
|
||||
|
||||
This code is not exhaustive or complete.
|
||||
It serves to distill our common understanding of a
|
||||
collaborative, shared environment and goals.
|
||||
We expect it to be followed in spirit as much as in the letter.
|
||||
|
||||
---
|
||||
|
||||
The **SkeletonView** Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
# Contributors Guide
|
||||
|
||||
Interested in contributing? Awesome! Before you do though, please read our
|
||||
[Code of Conduct](https://github.com/Juanpe/SkeletonView/blob/main/CODE_OF_CONDUCT.md). We take it very seriously, and expect that you will as
|
||||
well.
|
||||
|
||||
There are many ways you can contribute! :heart:
|
||||
|
||||
### Bug Reports and Fixes :bug:
|
||||
- If you find a bug, please search for it in the [Issues](https://github.com/Juanpe/SkeletonView/issues), and if it isn't already tracked,
|
||||
[create a new issue](https://github.com/slackhq/PanModal/issues/new). Fill out the "Bug Report" section of the issue template. Even if an Issue is closed, feel free to comment and add details, it will still
|
||||
be reviewed.
|
||||
- Issues that have already been identified as a bug (note: able to reproduce) will be labelled `🐞 Bug`.
|
||||
- If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number.
|
||||
|
||||
### New Features :bulb:
|
||||
- If you'd like to add new functionality to this project, describe the problem you want to solve in a [new Issue](https://github.com/Juanpe/SkeletonView/issues/new).
|
||||
- Issues that have been identified as a feature request will be labelled `💡 Enhancement`.
|
||||
- If you'd like to implement the new feature, please wait for feedback from the project
|
||||
maintainers before spending too much time writing the code. In some cases, `💡 Enhancement`s may
|
||||
not align well with the project objectives at the time.
|
||||
|
||||
### Miscellaneous :sparkles:
|
||||
- If you have an alternative implementation of something that may have advantages over the way its currently
|
||||
done, or you have any other change, we would be happy to hear about it!
|
||||
- If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind.
|
||||
- If not, [open an Issue](https://github.com/Juanpe/SkeletonView/issues/new) to discuss the idea first.
|
||||
|
||||
If you're new to our project and looking for some way to make your first contribution, look for
|
||||
Issues labelled `good first issue`.
|
||||
|
||||
## Requirements
|
||||
|
||||
For your contribution to be accepted:
|
||||
|
||||
- [x] The changes must be approved by code review.
|
||||
- [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number.
|
||||
|
||||
If the contribution doesn't meet the above criteria, you may fail our automated checks or a maintainer will discuss it with you. You can continue to improve a Pull Request by adding commits to the branch from which the PR was created.
|
||||
|
||||
## Creating a Pull Request
|
||||
|
||||
1. :fork_and_knife: Fork the repository on GitHub.
|
||||
2. :runner: Clone/fetch your fork to your local development machine.
|
||||
3. :herb: Create a new branch and check it out.
|
||||
4. :crystal_ball: Make your changes and commit them locally.
|
||||
5. :arrow_heading_up: Push your new branch to your fork. (e.g. `git push username fix-issue-300`).
|
||||
6. :inbox_tray: Open a Pull Request on github.com from your new branch on your fork to `main` in this
|
||||
repository.
|
||||
|
||||
## Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
- (a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
- (b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
- (c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
- (d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
|
||||
*Wording of statement copied from [elinux.org](http://elinux.org/Developer_Certificate_Of_Origin)*
|
||||
@@ -3,16 +3,6 @@ import Danger
|
||||
let danger = Danger()
|
||||
let github = danger.github
|
||||
|
||||
// Changelog entries are required for changes to library files.
|
||||
let allSourceFiles = danger.git.modifiedFiles + danger.git.createdFiles
|
||||
let noChangelogEntry = !allSourceFiles.contains("CHANGELOG.md")
|
||||
let sourceChanges = allSourceFiles.contains { $0.hasPrefix("Sources") }
|
||||
let isNotTrivial = !danger.github.pullRequest.title.contains("#trivial")
|
||||
|
||||
if isNotTrivial && noChangelogEntry && sourceChanges {
|
||||
warn("Any changes to library code should be reflected in the Changelog.")
|
||||
}
|
||||
|
||||
// Make it more obvious that a PR is a work in progress and shouldn't be merged yet
|
||||
if danger.github.pullRequest.title.contains("WIP") {
|
||||
warn("PR is classed as Work in Progress")
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
|
||||
<device id="retina5_9" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15510"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -25,7 +26,7 @@
|
||||
<constraint firstAttribute="height" constant="78" id="gF5-G1-lKI"/>
|
||||
</constraints>
|
||||
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </string>
|
||||
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" systemColor="labelColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
@@ -39,7 +40,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="nMj-pU-5wJ">
|
||||
<rect key="frame" x="141" y="20" width="93" height="93"/>
|
||||
<rect key="frame" x="45" y="20" width="93" height="93"/>
|
||||
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="93" id="gw9-nu-cKo"/>
|
||||
@@ -49,16 +50,35 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CJW-A4-Fb8">
|
||||
<rect key="frame" x="166" y="27" width="166" height="12"/>
|
||||
<color key="backgroundColor" red="0.92156862750000001" green="0.16862745100000001" blue="0.54901960780000003" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="10"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="linesCornerRadius">
|
||||
<integer key="value" value="5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="skeletonLineSpacing">
|
||||
<real key="value" value="8"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="nMj-pU-5wJ" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" id="9X4-2r-AKx"/>
|
||||
<constraint firstItem="CJW-A4-Fb8" firstAttribute="leading" secondItem="nMj-pU-5wJ" secondAttribute="trailing" constant="28" id="Drg-cD-6E8"/>
|
||||
<constraint firstItem="e9V-mk-xH0" firstAttribute="leading" secondItem="F9K-jU-100" secondAttribute="leading" constant="45" id="HvQ-HY-zYU"/>
|
||||
<constraint firstItem="e9V-mk-xH0" firstAttribute="centerX" secondItem="F9K-jU-100" secondAttribute="centerX" constant="1" id="KcB-tG-NXa"/>
|
||||
<constraint firstAttribute="height" constant="243" id="MIj-xq-gr1"/>
|
||||
<constraint firstItem="nMj-pU-5wJ" firstAttribute="leading" secondItem="F9K-jU-100" secondAttribute="leading" constant="45" id="TK6-Ws-2xY"/>
|
||||
<constraint firstItem="e9V-mk-xH0" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="142" id="Wcx-nZ-1lR"/>
|
||||
<constraint firstAttribute="trailing" secondItem="e9V-mk-xH0" secondAttribute="trailing" constant="43" id="XbU-Og-rht"/>
|
||||
<constraint firstItem="CJW-A4-Fb8" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="27" id="ceh-gB-7Et"/>
|
||||
<constraint firstItem="nMj-pU-5wJ" firstAttribute="top" secondItem="F9K-jU-100" secondAttribute="top" constant="20" id="hQL-cr-MaN"/>
|
||||
<constraint firstAttribute="trailing" secondItem="CJW-A4-Fb8" secondAttribute="trailing" constant="43" id="nfT-a5-z36"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
@@ -76,7 +96,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="avatar" translatesAutoresizingMaskIntoConstraints="NO" id="oiE-tt-nc2">
|
||||
<rect key="frame" x="15" y="18" width="82" height="82"/>
|
||||
<rect key="frame" x="16" y="18" width="82" height="82"/>
|
||||
<color key="backgroundColor" red="0.56078431370000004" green="0.59607843140000005" blue="0.7843137255" alpha="0.90709546230000004" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="82" id="4j0-PU-CmN"/>
|
||||
@@ -86,10 +106,11 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhU-1t-AaI" userLabel="Label">
|
||||
<rect key="frame" x="118" y="29" width="237" height="18"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhU-1t-AaI" userLabel="Label">
|
||||
<rect key="frame" x="119" y="29" width="235" height="20"/>
|
||||
<color key="backgroundColor" red="0.92156862750000001" green="0.16862745100000001" blue="0.54901960780000003" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="71" id="HRL-cI-ieC"/>
|
||||
<constraint firstAttribute="height" constant="20" id="HRL-cI-ieC"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<nil key="textColor"/>
|
||||
@@ -104,14 +125,25 @@
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="placeholder" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="dha-bH-Ipf">
|
||||
<rect key="frame" x="119" y="57" width="235" height="34"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="dha-bH-Ipf" firstAttribute="top" secondItem="VhU-1t-AaI" secondAttribute="bottom" constant="8" symbolic="YES" id="1Ek-1L-ZVs"/>
|
||||
<constraint firstItem="oiE-tt-nc2" firstAttribute="leading" secondItem="7IN-F3-Mr6" secondAttribute="leadingMargin" id="1be-ak-AH1"/>
|
||||
<constraint firstAttribute="bottom" secondItem="oiE-tt-nc2" secondAttribute="bottom" constant="20" id="CKt-oA-eBI"/>
|
||||
<constraint firstItem="oiE-tt-nc2" firstAttribute="top" secondItem="7IN-F3-Mr6" secondAttribute="topMargin" constant="7" id="EKn-ST-LDX"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="VhU-1t-AaI" secondAttribute="trailing" constant="5" id="I7C-Bq-mfK"/>
|
||||
<constraint firstItem="VhU-1t-AaI" firstAttribute="leading" secondItem="oiE-tt-nc2" secondAttribute="trailing" constant="21" id="Ojr-Kz-1k6"/>
|
||||
<constraint firstItem="VhU-1t-AaI" firstAttribute="top" secondItem="7IN-F3-Mr6" secondAttribute="topMargin" constant="18" id="ZW6-JY-S4c"/>
|
||||
<constraint firstItem="dha-bH-Ipf" firstAttribute="trailing" secondItem="VhU-1t-AaI" secondAttribute="trailing" id="baX-Nw-8sB"/>
|
||||
<constraint firstItem="dha-bH-Ipf" firstAttribute="leading" secondItem="VhU-1t-AaI" secondAttribute="leading" id="kzA-mV-IDt"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
@@ -123,6 +155,7 @@
|
||||
<connections>
|
||||
<outlet property="avatar" destination="oiE-tt-nc2" id="Dkh-R5-Qhu"/>
|
||||
<outlet property="label1" destination="VhU-1t-AaI" id="kUW-HV-KrD"/>
|
||||
<outlet property="textField" destination="dha-bH-Ipf" id="OHI-6P-tuU"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
@@ -183,7 +216,7 @@
|
||||
<action selector="btnChangeColorTouchUpInside:" destination="BYZ-38-t0r" eventType="touchUpInside" id="cB8-Ik-LIJ"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Tdu-YQ-saq">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Tdu-YQ-saq">
|
||||
<rect key="frame" x="263" y="69" width="94" height="30"/>
|
||||
<state key="normal" title="Hide skeleton"/>
|
||||
<connections>
|
||||
@@ -203,7 +236,7 @@
|
||||
</connections>
|
||||
</stepper>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="l4N-LL-ZrJ" firstAttribute="leading" secondItem="mrw-PM-jJJ" secondAttribute="trailing" constant="8" id="5iU-dO-qVc"/>
|
||||
<constraint firstItem="mrw-PM-jJJ" firstAttribute="centerY" secondItem="l4N-LL-ZrJ" secondAttribute="centerY" id="9OM-mx-4Jo"/>
|
||||
@@ -228,7 +261,8 @@
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="F9K-jU-100" firstAttribute="trailing" secondItem="6Tk-OE-BBY" secondAttribute="trailing" id="1es-nY-bd3"/>
|
||||
<constraint firstItem="F9K-jU-100" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="A3E-iv-1qp"/>
|
||||
@@ -241,7 +275,6 @@
|
||||
<constraint firstItem="UCB-SP-lQk" firstAttribute="trailing" secondItem="6Tk-OE-BBY" secondAttribute="trailing" id="o6z-Dj-ppC"/>
|
||||
<constraint firstItem="XgY-1a-UGc" firstAttribute="bottom" secondItem="6Tk-OE-BBY" secondAttribute="bottom" id="vnZ-9k-MfI"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
@@ -270,8 +303,8 @@
|
||||
<view key="view" contentMode="scaleToFill" id="Jwx-gI-Qod">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="Ao1-hk-zrH"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" title="Item" id="iKp-9S-aib"/>
|
||||
</viewController>
|
||||
@@ -301,5 +334,11 @@
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="avatar" width="215" height="211"/>
|
||||
<systemColor name="labelColor">
|
||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -9,11 +9,25 @@
|
||||
import UIKit
|
||||
|
||||
class Cell: UITableViewCell {
|
||||
|
||||
|
||||
@IBOutlet weak var avatar: UIImageView!
|
||||
@IBOutlet weak var label1: UILabel!
|
||||
|
||||
@IBOutlet weak var textField: UITextField!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
setUpInputAccessoryView()
|
||||
}
|
||||
|
||||
func setUpInputAccessoryView() {
|
||||
let bar = UIToolbar()
|
||||
let reset = UIBarButtonItem(title: "InputAccessoryView", style: .plain, target: self, action: #selector(resetTapped))
|
||||
bar.items = [reset]
|
||||
bar.sizeToFit()
|
||||
textField.inputAccessoryView = bar
|
||||
}
|
||||
|
||||
@objc func resetTapped() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,11 +52,11 @@ class ViewController: UIViewController {
|
||||
super.viewDidLoad()
|
||||
tableview.isSkeletonable = true
|
||||
transitionDurationStepper.value = 0.25
|
||||
view.showAnimatedSkeleton()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
view.showAnimatedSkeleton()
|
||||
}
|
||||
|
||||
@IBAction func changeAnimated(_ sender: Any) {
|
||||
@@ -180,7 +180,7 @@ extension ViewController: SkeletonTableViewDataSource {
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as! Cell
|
||||
cell.label1.text = "cell => \(indexPath.row)"
|
||||
cell.label1.text = "cell -> \(indexPath.row)"
|
||||
return cell
|
||||
}
|
||||
}
|
||||
@@ -193,7 +193,7 @@ extension ViewController: SkeletonTableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let header = tableView
|
||||
.dequeueReusableHeaderFooterView(withIdentifier: "HeaderIdentifier") as! HeaderFooterSection
|
||||
header.titleLabel.text = "header => \(section)"
|
||||
header.titleLabel.text = "header -> \(section)"
|
||||
return header
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ extension ViewController: SkeletonTableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
let footer = tableView
|
||||
.dequeueReusableHeaderFooterView(withIdentifier: "FooterIdentifier") as! HeaderFooterSection
|
||||
footer.titleLabel.text = "footer => \(section)"
|
||||
footer.titleLabel.text = "footer -> \(section)"
|
||||
return footer
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +1,104 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/workflows/build">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/build/badge.svg">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/CI/badge.svg">
|
||||
</a>
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<img src="http://img.shields.io/badge/dependency%20manager-swiftpm%2Bcocoapods%2Bcarthage-green" />
|
||||
<img src="https://img.shields.io/badge/platforms-ios%2Btvos-green" />
|
||||
<a href="https://badge.bow-swift.io/recipe?name=SkeletonView&description=An%20elegant%20way%20to%20show%20users%20that%20something%20is%20happening%20and%20also%20prepare%20them%20to%20which%20contents%20he%20is%20waiting&url=https://github.com/juanpe/skeletonview&owner=Juanpe&avatar=https://avatars0.githubusercontent.com/u/1409041?v=4&tag=1.8.7"><img src="https://raw.githubusercontent.com/bow-swift/bow-art/master/badges/nef-playgrounds-badge.svg" alt="SkeletonView Playground" style="height:20px"></a>
|
||||
<br/>
|
||||
<a href="https://twitter.com/JuanpeCatalan">
|
||||
<img src="https://img.shields.io/badge/contact-@JuanpeCatalan-blue.svg?style=flat" alt="Twitter: @JuanpeCatalan" />
|
||||
</a>
|
||||
<a href="https://twitter.com/intent/tweet?text=Wow%20This%20library%20is%20awesome:&url=https%3A%2F%2Fgithub.com%2FJuanpe%2FSkeletonView">
|
||||
<img src="https://img.shields.io/twitter/url/https/github.com/Juanpe/SkeletonView.svg?style=social" alt="License" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="#-features">Features</a>
|
||||
• <a href="#-guides">Guides</a>
|
||||
• <a href="#-installation">Installation</a>
|
||||
• <a href="#-usage">Usage</a>
|
||||
• <a href="#-miscellaneous">Miscellaneous</a>
|
||||
• <a href="#️-contributing">Contributing</a>
|
||||
</p>
|
||||
|
||||
**🌎 README is available in other languages: [🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) [🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) [🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) [🇫🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_fr.md)**
|
||||
**🌎 README is available in other languages: [🇪🇸](https://github.com/Juanpe/SkeletonView/blob/main/README_es.md) . [🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) . [🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) . [🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) . [🇫🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_fr.md)**
|
||||
|
||||
Today almost all apps have async processes, such as API requests, long running processes, etc. While the processes are working, usually developers place a loading view to show users that something is going on.
|
||||
|
||||
```SkeletonView``` has been conceived to address this need, an elegant way to show users that something is happening and also prepare them for which contents are waiting.
|
||||
**SkeletonView** has been conceived to address this need, an elegant way to show users that something is happening and also prepare them for which contents are waiting.
|
||||
|
||||
Enjoy it! 🙂
|
||||
|
||||
* [Features](#-features)
|
||||
* [Guides](#-guides)
|
||||
* [Installation](#-installation)
|
||||
* [Cocoapods](#using-cocoapods)
|
||||
* [Carthage](#using-carthage)
|
||||
* [SPM](#using-swift-package-manager)
|
||||
* [How to use](#-how-to-use)
|
||||
* [Collections](#-collections)
|
||||
* [Multiline text](#-multiline-text)
|
||||
* [Custom colors](#-custom-colors)
|
||||
* [Appearance](#-appearance)
|
||||
* [Custom animations](#-custom-animations)
|
||||
* [Transitions](#-transitions)
|
||||
* [Hierarchy](#-hierarchy)
|
||||
* [Debug](#-debug)
|
||||
* [Documentation](#-documentation)
|
||||
* [Supported OS & SDK Versions](#-supported-os--sdk-versions)
|
||||
* [Next steps](#-next-steps)
|
||||
* [Contributing](#%EF%B8%8F-contributing)
|
||||
* [Mentions](#-mentions)
|
||||
* [Author](#-author)
|
||||
* [License](#-license)
|
||||
|
||||
##
|
||||
- [🌟 Features](#-features)
|
||||
- [🎬 Guides](#-guides)
|
||||
- [📲 Installation](#-installation)
|
||||
- [🐒 Usage](#-usage)
|
||||
- [🌿 Collections](#-collections)
|
||||
- [🔠 Texts](#-texts)
|
||||
- [🦋 Appearance](#-appearance)
|
||||
- [🎨 Custom colors](#-custom-colors)
|
||||
- [Image captured from website https://flatuicolors.com](#image-captured-from-website-httpsflatuicolorscom)
|
||||
- [🏃♀️ Animations](#️-animations)
|
||||
- [🏄 Transitions](#-transitions)
|
||||
- [✨ Miscellaneous](#-miscellaneous)
|
||||
- [❤️ Contributing](#️-contributing)
|
||||
- [📢 Mentions](#-mentions)
|
||||
- [👨🏻💻 Author](#-author)
|
||||
- [👮🏻 License](#-license)
|
||||
|
||||
|
||||
|
||||
## 🌟 Features
|
||||
|
||||
- [x] Easy to use
|
||||
- [x] All UIViews are skeletonables
|
||||
- [x] Fully customizable
|
||||
- [x] Universal (iPhone & iPad)
|
||||
- [x] Interface Builder friendly
|
||||
- [x] Simple Swift syntax
|
||||
- [x] Lightweight readable codebase
|
||||
* Easy to use
|
||||
* All UIViews are skeletonables
|
||||
* Fully customizable
|
||||
* Universal (iPhone & iPad)
|
||||
* Interface Builder friendly
|
||||
* Simple Swift syntax
|
||||
* Lightweight readable codebase
|
||||
|
||||
|
||||
## 🎬 Guides
|
||||
|
||||
| [](https://youtu.be/75kgOhWsPNA)|[](https://youtu.be/MVCiM_VdxVA)|[](https://youtu.be/Qq3Evspeea8)|[](https://youtu.be/ZOoPtBwDRT0)
|
||||
|:---: | :---: |:---: | :---:
|
||||
|[**SkeletonView Guides - Getting started**](https://youtu.be/75kgOhWsPNA)|[**How to Create Loading View with Skeleton View in Swift 5.2**](https://youtu.be/MVCiM_VdxVA) <br/> by iKh4ever Studio|[**Create Skeleton Loading View in App (Swift 5) - Xcode 11, 2020**](https://youtu.be/Qq3Evspeea8) <br/> by iOS Academy| [**Add An Elegant Loading Animation in Swift***](https://youtu.be/ZOoPtBwDRT0) <br/> by Gary Tokman
|
||||
| [](https://youtu.be/75kgOhWsPNA)|[](https://youtu.be/MVCiM_VdxVA)|[](https://youtu.be/Qq3Evspeea8)|[](https://youtu.be/ZOoPtBwDRT0)|[](https://www.youtube.com/watch?v=Zx1Pg1gPfxA)
|
||||
|:---: | :---: |:---: | :---: | :---:
|
||||
|[**SkeletonView Guides - Getting started**](https://youtu.be/75kgOhWsPNA)|[**How to Create Loading View with Skeleton View in Swift 5.2**](https://youtu.be/MVCiM_VdxVA) by iKh4ever Studio|[**Create Skeleton Loading View in App (Swift 5) - Xcode 11, 2020**](https://youtu.be/Qq3Evspeea8) by iOS Academy| [**Add An Elegant Loading Animation in Swift***](https://youtu.be/ZOoPtBwDRT0) by Gary Tokman| [**Cómo crear una ANIMACIÓN de CARGA de DATOS en iOS**](https://www.youtube.com/watch?v=Zx1Pg1gPfxA) by MoureDev
|
||||
|
||||
|
||||
## 📲 Installation
|
||||
|
||||
#### Using [CocoaPods](https://cocoapods.org)
|
||||
|
||||
Edit your `Podfile` and specify the dependency:
|
||||
* [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html):
|
||||
|
||||
```ruby
|
||||
pod "SkeletonView"
|
||||
pod 'SkeletonView'
|
||||
```
|
||||
|
||||
#### Using [Carthage](https://github.com/carthage)
|
||||
* [Carthage](https://github.com/Carthage/Carthage):
|
||||
|
||||
Edit your `Cartfile` and specify the dependency:
|
||||
|
||||
```bash
|
||||
```ruby
|
||||
github "Juanpe/SkeletonView"
|
||||
```
|
||||
|
||||
#### Using [Swift Package Manager](https://github.com/apple/swift-package-manager)
|
||||
|
||||
Once you have your Swift package set up, adding `SkeletonView` as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
|
||||
* [Swift Package Manager](https://swift.org/package-manager/):
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0")
|
||||
]
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0")
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 🐒 How to use
|
||||
## 🐒 Usage
|
||||
|
||||
Only **3** steps needed to use `SkeletonView`:
|
||||
|
||||
**1.** Import SkeletonView in proper place.
|
||||
1️⃣ Import SkeletonView in proper place.
|
||||
```swift
|
||||
import SkeletonView
|
||||
```
|
||||
|
||||
**2.** Now, set which views will be `skeletonables`. You achieve this in two ways:
|
||||
2️⃣ Now, set which views will be `skeletonables`. You achieve this in two ways:
|
||||
|
||||
**Using code:**
|
||||
```swift
|
||||
@@ -117,7 +108,7 @@ avatarImageView.isSkeletonable = true
|
||||
|
||||

|
||||
|
||||
**3.** Once you've set the views, you can show the **skeleton**. To do so, you have **4** choices:
|
||||
3️⃣ Once you've set the views, you can show the **skeleton**. To do so, you have **4** choices:
|
||||
|
||||
```swift
|
||||
(1) view.showSkeleton() // Solid
|
||||
@@ -159,41 +150,20 @@ avatarImageView.isSkeletonable = true
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> **IMPORTANT!**
|
||||
>>```SkeletonView``` is recursive, so if you want show the skeleton in all skeletonable views, you only need to call the show method in the main container view. For example, with UIViewControllers
|
||||
|
||||
### Extra
|
||||
> 📣 **IMPORTANT!**
|
||||
>
|
||||
> `SkeletonView` is recursive, so if you want show the skeleton in all skeletonable views, you only need to call the show method in the main container view. For example, with `UIViewControllers`.
|
||||
|
||||
#### Skeleton views layout
|
||||
|
||||
|
||||
Sometimes skeleton layout may not fit your layout because the parent view bounds have changed. ~For example, rotating the device.~
|
||||
|
||||
You can relayout the skeleton views like so:
|
||||
|
||||
```swift
|
||||
override func viewDidLayoutSubviews() {
|
||||
view.layoutSkeletonIfNeeded()
|
||||
}
|
||||
```
|
||||
|
||||
⚠️⚠️ You shouldn't call this method. From *version 1.8.1* you don't need to call this method, the library does automatically. So, you can use this method *ONLY* in the cases when you need to update the layout of the skeleton manually.
|
||||
|
||||
#### Update skeleton configuration
|
||||
|
||||
You can change the skeleton configuration at any time like its colour, animation, etc. with the following methods:
|
||||
|
||||
```swift
|
||||
(1) view.updateSkeleton() // Solid
|
||||
(2) view.updateGradientSkeleton() // Gradient
|
||||
(3) view.updateAnimatedSkeleton() // Solid animated
|
||||
(4) view.updateAnimatedGradientSkeleton() // Gradient animated
|
||||
```
|
||||
|
||||
### 🌿 Collections
|
||||
|
||||
```SkeletonView``` is compatible with ```UITableView``` and ```UICollectionView```.
|
||||
|
||||
#### UITableView
|
||||
|
||||
**UITableView**
|
||||
|
||||
If you want to show the skeleton in a ```UITableView```, you need to conform to ```SkeletonTableViewDataSource``` protocol.
|
||||
|
||||
@@ -202,8 +172,6 @@ public protocol SkeletonTableViewDataSource: UITableViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier?
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier?
|
||||
}
|
||||
```
|
||||
As you can see, this protocol inherits from ```UITableViewDataSource```, so you can replace this protocol with the skeleton protocol.
|
||||
@@ -221,15 +189,9 @@ func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection s
|
||||
// It calculates how many cells need to populate whole tableview
|
||||
```
|
||||
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier?
|
||||
// Default: nil
|
||||
```
|
||||
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier?
|
||||
// Default: nil
|
||||
```
|
||||
> 📣 **IMPORTANT!**
|
||||
>
|
||||
> If you return `UITableView.automaticNumberOfSkeletonRows` in the above method, it acts like the default behavior (i.e. it calculates how many cells needed to populate the whole tableview).
|
||||
|
||||
There is only one method you need to implement to let Skeleton know the cell identifier. This method doesn't have default implementation:
|
||||
``` swift
|
||||
@@ -242,42 +204,51 @@ There is only one method you need to implement to let Skeleton know the cell ide
|
||||
return "CellIdentifier"
|
||||
}
|
||||
```
|
||||
|
||||
Besides, you can skeletonize both the headers and footers. You need to conform to `SkeletonTableViewDelegate` protocol.
|
||||
|
||||
> **IMPORTANT!**
|
||||
> If you are using resizable cells (`tableView.rowHeight = UITableViewAutomaticDimension` ), it's mandatory define the `estimatedRowHeight`.
|
||||
```swift
|
||||
public protocol SkeletonTableViewDelegate: UITableViewDelegate {
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? // default: nil
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? // default: nil
|
||||
}
|
||||
```
|
||||
|
||||
👩🏼🏫 **How specify which elements are skeletonables?**
|
||||
> 📣 **IMPORTANT!**
|
||||
>
|
||||
> 1️⃣ If you are using resizable cells (**`tableView.rowHeight = UITableViewAutomaticDimension`**), it's mandatory define the **`estimatedRowHeight`**.
|
||||
>
|
||||
> 2️⃣ When you add elements in a **`UITableViewCell`** you should add it to **`contentView`** and not to the cell directly.
|
||||
> ```swift
|
||||
> self.contentView.addSubview(titleLabel) ✅
|
||||
> self.addSubview(titleLabel) ❌
|
||||
> ```
|
||||
|
||||
Here is an illustration that shows how you should specify which elements are skeletonables when you are using an `UITableView`:
|
||||
|
||||
|
||||

|
||||
**UICollectionView**
|
||||
|
||||
As you can see, we have to make skeletonable the tableview, the cell and the UI elements, but we don't need to set as skeletonable the `contentView`
|
||||
|
||||
#### UICollectionView
|
||||
|
||||
For ```UICollectionView```, you need to conform to ```SkeletonCollectionViewDataSource``` protocol.
|
||||
For `UICollectionView`, you need to conform to `SkeletonCollectionViewDataSource` protocol.
|
||||
|
||||
``` swift
|
||||
public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int // default: 1
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? // default: nil
|
||||
}
|
||||
```
|
||||
|
||||
The rest of the process is the same as ```UITableView```
|
||||
|
||||
### 📰 Multiline text
|
||||
|
||||
### 🔠 Texts
|
||||
|
||||

|
||||
|
||||
When using elements with text, ```SkeletonView``` draws lines to simulate text.
|
||||
Besides, you can decide how many lines you want. If ```numberOfLines``` is set to zero, it will calculate how many lines needed to populate the whole skeleton and it will be drawn. Instead, if you set it to one, two or any number greater than zero, it will only draw this number of lines.
|
||||
|
||||
##### 🎛 Customize
|
||||
|
||||
You can set some properties for multilines elements.
|
||||
|
||||
|
||||
@@ -298,32 +269,10 @@ Or, if you prefer use **IB/Storyboard**:
|
||||
|
||||

|
||||
|
||||
### 🎨 Custom colors
|
||||
|
||||
You can decide which color the skeleton is tinted with. You only need to pass as a parameter the color or gradient you want.
|
||||
|
||||
**Using solid colors**
|
||||
``` swift
|
||||
view.showSkeleton(usingColor: UIColor.gray) // Solid
|
||||
// or
|
||||
view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0))
|
||||
```
|
||||
**Using gradients**
|
||||
``` swift
|
||||
let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue)
|
||||
view.showGradientSkeleton(usingGradient: gradient) // Gradient
|
||||
```
|
||||
|
||||
Besides, ```SkeletonView``` features 20 flat colors 🤙🏼
|
||||
|
||||
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
|
||||
|
||||

|
||||
###### Image captured from website [https://flatuicolors.com](https://flatuicolors.com)
|
||||
|
||||
### 🦋 Appearance
|
||||
|
||||
**NEW** The skeletons have a default appearance. So, when you don't specify the color, gradient or multilines properties, `SkeletonView` uses the default values.
|
||||
The skeletons have a default appearance. So, when you don't specify the color, gradient or multilines properties, `SkeletonView` uses the default values.
|
||||
|
||||
Default values:
|
||||
- **tintColor**: UIColor
|
||||
@@ -342,7 +291,7 @@ Default values:
|
||||
- *default: 0*
|
||||
|
||||
To get these default values you can use `SkeletonAppearance.default`. Using this property you can set the values as well:
|
||||
```Swift
|
||||
```swift
|
||||
SkeletonAppearance.default.multilineHeight = 20
|
||||
SkeletonAppearance.default.tintColor = .green
|
||||
```
|
||||
@@ -354,9 +303,33 @@ You can also specifiy these line appearance properties on a per-label basis:
|
||||
- **skeletonPaddingInsets**: UIEdgeInsets
|
||||
|
||||
|
||||
### 🤓 Custom animations
|
||||
### 🎨 Custom colors
|
||||
|
||||
```SkeletonView``` has two built-in animations, *pulse* for solid skeletons and *sliding* for gradients.
|
||||
You can decide which color the skeleton is tinted with. You only need to pass as a parameter the color or gradient you want.
|
||||
|
||||
**Using solid colors**
|
||||
```swift
|
||||
view.showSkeleton(usingColor: UIColor.gray) // Solid
|
||||
// or
|
||||
view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0))
|
||||
```
|
||||
**Using gradients**
|
||||
``` swift
|
||||
let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue)
|
||||
view.showGradientSkeleton(usingGradient: gradient) // Gradient
|
||||
```
|
||||
|
||||
Besides, **SkeletonView** features 20 flat colors 🤙🏼
|
||||
|
||||
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
|
||||
|
||||

|
||||
###### Image captured from website [https://flatuicolors.com](https://flatuicolors.com)
|
||||
|
||||
|
||||
### 🏃♀️ Animations
|
||||
|
||||
**SkeletonView** has two built-in animations, *pulse* for solid skeletons and *sliding* for gradients.
|
||||
|
||||
Besides, if you want to do your own skeleton animation, it's really easy.
|
||||
|
||||
@@ -390,7 +363,7 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
|
||||
|
||||
```
|
||||
|
||||
```GradientDirection``` is an enum, with this cases:
|
||||
```GradientDirection``` is an enum, with theses cases:
|
||||
|
||||
| Direction | Preview
|
||||
|------- | -------
|
||||
@@ -402,14 +375,17 @@ view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
|
||||
| .bottomRightTopLeft | 
|
||||
|
||||
> **😉 TRICK!**
|
||||
Exist another way to create sliding animations, just using this shortcut:
|
||||
>>```let animation = GradientDirection.leftToRight.slidingAnimation()```
|
||||
|
||||
>
|
||||
> Exist another way to create sliding animations, just using this shortcut:
|
||||
> ```swift
|
||||
> let animation = GradientDirection.leftToRight.slidingAnimation()
|
||||
> ```
|
||||
|
||||
|
||||
|
||||
### 🏄 Transitions
|
||||
|
||||
```SkeletonView``` has build-in transitions to **show** or **hide** the skeletons in a *smoother* way 🤙
|
||||
**SkeletonView** has built-in transitions to **show** or **hide** the skeletons in a *smoother* way 🤙
|
||||
|
||||
To use the transition, simply add the ```transition``` parameter to your ```showSkeleton()``` or ```hideSkeleton()``` function with the transition time, like this:
|
||||
|
||||
@@ -443,7 +419,11 @@ The default value is `crossDissolve(0.25)`
|
||||
</table>
|
||||
|
||||
|
||||
### 👨👧👦 Hierarchy
|
||||
## ✨ Miscellaneous
|
||||
|
||||
|
||||
|
||||
**Hierarchy**
|
||||
|
||||
Since ```SkeletonView``` is recursive, and we want skeleton to be very efficient, we want to stop recursion as soon as possible. For this reason, you must set the container view as `Skeletonable`, because Skeleton will stop looking for `skeletonable` subviews as soon as a view is not Skeletonable, breaking then the recursion.
|
||||
|
||||
@@ -454,7 +434,7 @@ In this example we have a `UIViewController` with a `ContainerView` and a `UITab
|
||||
view.showSkeleton()
|
||||
```
|
||||
|
||||
> ```ìsSkeletonable```= ☠️
|
||||
> ```isSkeletonable```= ☠️
|
||||
|
||||
| Configuration | Result|
|
||||
|:-------:|:-------:|
|
||||
@@ -465,10 +445,68 @@ view.showSkeleton()
|
||||
|<img src="Assets/tableview_no_skeletonable.jpg" width="350"/> | <img src="Assets/tableview_no_skeletonable_result.png" height="350"/>|
|
||||
|<img src="Assets/tableview_skeletonable.jpg" width="350"/> | <img src="Assets/tableview_skeletonable_result.png" height="350"/>|
|
||||
|
||||
|
||||
|
||||
### 🔬 Debug
|
||||
**Hierarchy in collections**
|
||||
|
||||
**NEW** In order to facilitate the debug tasks when something is not working fine. `SkeletonView` has some new tools.
|
||||
Here is an illustration that shows how you should specify which elements are skeletonables when you are using an `UITableView`:
|
||||
|
||||
<img src="Assets/tableview_scheme.png" width="700px">
|
||||
|
||||
As you can see, we have to make skeletonable the tableview, the cell and the UI elements, but we don't need to set as skeletonable the `contentView`
|
||||
|
||||
|
||||
|
||||
**Skeleton views layout**
|
||||
|
||||
Sometimes skeleton layout may not fit your layout because the parent view bounds have changed. ~For example, rotating the device.~
|
||||
|
||||
You can relayout the skeleton views like so:
|
||||
|
||||
```swift
|
||||
override func viewDidLayoutSubviews() {
|
||||
view.layoutSkeletonIfNeeded()
|
||||
}
|
||||
```
|
||||
|
||||
> 📣 **IMPORTANT!**
|
||||
>
|
||||
> You shouldn't call this method. From **version 1.8.1** you don't need to call this method, the library does automatically. So, you can use this method **ONLY** in the cases when you need to update the layout of the skeleton manually.
|
||||
|
||||
|
||||
|
||||
|
||||
**Update skeleton**
|
||||
|
||||
You can change the skeleton configuration at any time like its colour, animation, etc. with the following methods:
|
||||
|
||||
```swift
|
||||
(1) view.updateSkeleton() // Solid
|
||||
(2) view.updateGradientSkeleton() // Gradient
|
||||
(3) view.updateAnimatedSkeleton() // Solid animated
|
||||
(4) view.updateAnimatedGradientSkeleton() // Gradient animated
|
||||
```
|
||||
|
||||
**Hiding views when the animation starts**
|
||||
|
||||
Sometimes you wanna hide some view when the animation starts, so there is a quick property that you can use to make this happen:
|
||||
|
||||
```swift
|
||||
view.isHiddenWhenSkeletonIsActive = true // This works only when isSkeletonable = true
|
||||
```
|
||||
|
||||
**Don't modify user interaction when the skeleton is active**
|
||||
|
||||
|
||||
By default, the user interaction is disabled for skeletonized items, but if you don't want to modify the user interaction indicator when skeleton is active, you can use the `isUserInteractionDisabledWhenSkeletonIsActive` property:
|
||||
|
||||
```swift
|
||||
view.isUserInteractionDisabledWhenSkeletonIsActive = false // The view will be active when the skeleton will be active.
|
||||
```
|
||||
|
||||
**Debug**
|
||||
|
||||
To facilitate the debug tasks when something is not working fine. **`SkeletonView`** has some new tools.
|
||||
|
||||
First, `UIView` has available a new property with his skeleton info:
|
||||
```swift
|
||||
@@ -490,30 +528,13 @@ Then, when the skeleton appears, you can see the view hierarchy in the Xcode con
|
||||
<img src="Assets/hierarchy_output.png" />
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
### 📚 Documentation
|
||||
Coming soon...😅
|
||||
|
||||
### 📋 Supported OS & SDK Versions
|
||||
|
||||
**Supported OS & SDK Versions**
|
||||
|
||||
* iOS 9.0+
|
||||
* tvOS 9.0+
|
||||
* Swift 5
|
||||
|
||||
## 📬 Next steps
|
||||
|
||||
* [x] Set the filling percent of the last line in multiline elements
|
||||
* [x] Add more gradient animations
|
||||
* [x] Supported resizable cells
|
||||
* [x] CollectionView compatible
|
||||
* [x] tvOS compatible
|
||||
* [x] Add recovery state
|
||||
* [x] Custom default appearance
|
||||
* [x] Debug mode
|
||||
* [x] Add animations when it shows/hides the skeletons
|
||||
* [ ] Custom collections compatible
|
||||
* [ ] MacOS and WatchOS compatible
|
||||
|
||||
## ❤️ Contributing
|
||||
This is an open source project, so feel free to contribute. How?
|
||||
@@ -523,7 +544,8 @@ This is an open source project, so feel free to contribute. How?
|
||||
|
||||
See [all contributors](https://github.com/Juanpe/SkeletonView/graphs/contributors)
|
||||
|
||||
###### Project generated with [SwiftPlate](https://github.com/JohnSundell/SwiftPlate)
|
||||
For more information, please read the [contributing guidelines](https://github.com/Juanpe/SkeletonView/blob/main/CONTRIBUTING.md).
|
||||
|
||||
|
||||
## 📢 Mentions
|
||||
|
||||
@@ -544,13 +566,12 @@ See [all contributors](https://github.com/Juanpe/SkeletonView/graphs/contributor
|
||||
|
||||
|
||||
## 👨🏻💻 Author
|
||||
[1.1]: http://i.imgur.com/tXSoThF.png
|
||||
[1]: http://www.twitter.com/JuanpeCatalan
|
||||
|
||||
* Juanpe Catalán [![alt text][1.1]][1]
|
||||
[Juanpe Catalán](http://www.twitter.com/JuanpeCatalan)
|
||||
|
||||
<a class="bmc-button" target="_blank" href="https://www.buymeacoffee.com/CDou4xtIK"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy me a coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;"><span style="margin-left:5px"></span></a>
|
||||
|
||||
|
||||
## 👮🏻 License
|
||||
|
||||
```
|
||||
|
||||
+532
@@ -0,0 +1,532 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/CI/badge.svg">
|
||||
</a>
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<img src="http://img.shields.io/badge/dependency%20manager-swiftpm%2Bcocoapods%2Bcarthage-green" />
|
||||
<img src="https://img.shields.io/badge/platforms-ios%2Btvos-green" />
|
||||
<a href="https://badge.bow-swift.io/recipe?name=SkeletonView&description=An%20elegant%20way%20to%20show%20users%20that%20something%20is%20happening%20and%20also%20prepare%20them%20to%20which%20contents%20he%20is%20waiting&url=https://github.com/juanpe/skeletonview&owner=Juanpe&avatar=https://avatars0.githubusercontent.com/u/1409041?v=4&tag=1.8.7"><img src="https://raw.githubusercontent.com/bow-swift/bow-art/master/badges/nef-playgrounds-badge.svg" alt="SkeletonView Playground" style="height:20px"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="#-destacado">Destacado</a>
|
||||
• <a href="#-instalación">Instalación</a>
|
||||
• <a href="#-cómo-funciona">¿Cómo funciona?</a>
|
||||
• <a href="#-miscelánea">Miscelánea</a>
|
||||
• <a href="#️-contribuir">Contribuir</a>
|
||||
</p>
|
||||
|
||||
**🌎 README está disponible en estos idiomas: [🇬🇧](https://github.com/Juanpe/SkeletonView/blob/master/README.md) . [🇨🇳](https://github.com/Juanpe/SkeletonView/blob/master/README_zh.md) . [🇧🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_pt-br.md) . [🇰🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_ko.md) . [🇫🇷](https://github.com/Juanpe/SkeletonView/blob/master/README_fr.md)**
|
||||
|
||||
Hoy en día, La mayoría de las apps tiene procesos asíncronos, como peticiones a una API, procesos que tardan mucho tiempo, etc. Mientras estos procesos se están ejecutando, se suele mostrar un aburrido spinner indicando que algo está pasando.
|
||||
|
||||
**SkeletonView** ha sido desarrollada para cubrir esta necesidad, un elegante manera de decirle a los usarios que algo se está procesando y además prepararlos, visualmente, para el contenido que están esperando.
|
||||
|
||||
Enjoy it! 🙂
|
||||
|
||||
##
|
||||
- [](#)
|
||||
- [🌟 Destacado](#-destacado)
|
||||
- [🎬 Videotutoriales](#-videotutoriales)
|
||||
- [📲 Instalación](#-instalación)
|
||||
- [🐒 ¿Cómo funciona?](#-cómo-funciona)
|
||||
- [](#-1)
|
||||
- [🌿 Colecciones](#-colecciones)
|
||||
- [🔠 Textos](#-textos)
|
||||
- [🦋 Apariencia](#-apariencia)
|
||||
- [🎨 Colores](#-colores)
|
||||
- [Imagen extraída de la web https://flatuicolors.com](#imagen-extraída-de-la-web-httpsflatuicolorscom)
|
||||
- [🏃♀️ Animaciones](#️-animaciones)
|
||||
- [🏄 Transiciones](#-transiciones)
|
||||
- [✨ Miscelánea](#-miscelánea)
|
||||
- [❤️ Contributing](#️-contributing)
|
||||
- [📢 Menciones](#-menciones)
|
||||
- [👨🏻💻 Autor](#-autor)
|
||||
- [👮🏻 Licencia](#-licencia)
|
||||
|
||||
|
||||
|
||||
## 🌟 Destacado
|
||||
|
||||
* Fácil de usar
|
||||
* Todas las UIViews son skeletonables
|
||||
* Personalizable
|
||||
* Universal (iPhone & iPad)
|
||||
* Interface Builder friendly
|
||||
|
||||
|
||||
## 🎬 Videotutoriales
|
||||
|
||||
| [](https://youtu.be/75kgOhWsPNA)|[](https://youtu.be/MVCiM_VdxVA)|[](https://youtu.be/Qq3Evspeea8)|[](https://youtu.be/ZOoPtBwDRT0)
|
||||
|:---: | :---: |:---: | :---:
|
||||
|[**SkeletonView Guides - Getting started**](https://youtu.be/75kgOhWsPNA)|[**How to Create Loading View with Skeleton View in Swift 5.2**](https://youtu.be/MVCiM_VdxVA) by iKh4ever Studio|[**Create Skeleton Loading View in App (Swift 5) - Xcode 11, 2020**](https://youtu.be/Qq3Evspeea8) by iOS Academy| [**Add An Elegant Loading Animation in Swift***](https://youtu.be/ZOoPtBwDRT0) by Gary Tokman
|
||||
|
||||
|
||||
## 📲 Instalación
|
||||
|
||||
* [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html):
|
||||
|
||||
```ruby
|
||||
pod 'SkeletonView'
|
||||
```
|
||||
|
||||
* [Carthage](https://github.com/Carthage/Carthage):
|
||||
|
||||
```ruby
|
||||
github "Juanpe/SkeletonView"
|
||||
```
|
||||
|
||||
* [Swift Package Manager](https://swift.org/package-manager/):
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0")
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
## 🐒 ¿Cómo funciona?
|
||||
|
||||
Solo necesitas **3** pasos para usar `SkeletonView`:
|
||||
|
||||
1️⃣ Importa SkeletonView en el archivo donde vayas a utilizarlo.
|
||||
```swift
|
||||
import SkeletonView
|
||||
```
|
||||
|
||||
2️⃣ Ahora, debes indicar qué elementos de tu vista son `skeletonables`
|
||||
|
||||
**Con código:**
|
||||
```swift
|
||||
avatarImageView.isSkeletonable = true
|
||||
```
|
||||
**Con IB/Storyboards:**
|
||||
|
||||

|
||||
|
||||
3️⃣ Una vez indicado, solo tienes que mostrar el **skeleton**. Tienes **4** opciones:
|
||||
|
||||
```swift
|
||||
(1) view.showSkeleton() // Sólido
|
||||
(2) view.showGradientSkeleton() // Degradado
|
||||
(3) view.showAnimatedSkeleton() // Sólido animado
|
||||
(4) view.showAnimatedGradientSkeleton() // Degradado animado
|
||||
```
|
||||
|
||||
**Vista previa**
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="25%">
|
||||
<center>Sólido</center>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<center>Degradado</center>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<center>Sólido Animado</center>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<center>Degradado Animado</center>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="25%">
|
||||
<img src="Assets/solid.png"></img>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<img src="Assets/gradient.png"></img>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<img src="Assets/solid_animated.gif"></img>
|
||||
</td>
|
||||
<td width="25%">
|
||||
<img src="Assets/gradient_animated.gif"></img>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
> 📣 **¡IMPORTANTE!**
|
||||
>
|
||||
> `SkeletonView` es recursivo. Por lo que si tienes una vsita que contiene varios elementos skeletonables, solo tienes queenecu For example, with `UIViewControllers`.
|
||||
|
||||
|
||||
##
|
||||
### 🌿 Colecciones
|
||||
|
||||
`SkeletonView` es compatible con `UITableView` and `UICollectionView`.
|
||||
|
||||
|
||||
**UITableView**
|
||||
|
||||
Si quieres mostrar el skeleton en un `UITableView`, necesitas conformar el protocolo `SkeletonTableViewDataSource`.
|
||||
|
||||
``` swift
|
||||
public protocol SkeletonTableViewDataSource: UITableViewDataSource {
|
||||
// Por defecto: 1
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int
|
||||
|
||||
// Por defecto:
|
||||
// Calcula cuantas celdas necesita para rellenar todo el frame.
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
}
|
||||
```
|
||||
|
||||
Este protocolo hereda de `UITableViewDataSource`, por lo que puedes reemplazar este protocolo por el protocolo de skeleton sin perder ninguna funcionalidad. El único método que es obligatorio implementar es `cellIdentifierForRowAt`, donde tienes que indicar el identificador de la celda.
|
||||
|
||||
**Ejemplo**
|
||||
``` swift
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
|
||||
return "CellIdentifier"
|
||||
}
|
||||
```
|
||||
|
||||
Además, tu puedes mostrar el skeleton en las headers y en los footers, conformando el protocolo `SkeletonTableViewDelegate`.
|
||||
|
||||
```swift
|
||||
public protocol SkeletonTableViewDelegate: UITableViewDelegate {
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? // default: nil
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? // default: nil
|
||||
}
|
||||
```
|
||||
|
||||
> 📣 **¡IMPORTANTE!**
|
||||
>
|
||||
> 1️⃣ Si estás usando celdas con altura dinámica (**`tableView.rowHeight = UITableViewAutomaticDimension`**), es obligatorio definir el **`estimatedRowHeight`**.
|
||||
>
|
||||
> 2️⃣ Cuando añades elemetos a una **`UITableViewCell`**, debes añadirlo al **`contentView`** y no a la celda directamente.
|
||||
> ```swift
|
||||
> cell.contentView.addSubview(titleLabel) ✅
|
||||
> cell.addSubview(titleLabel) ❌
|
||||
> ```
|
||||
|
||||
|
||||
|
||||
**UICollectionView**
|
||||
|
||||
Para los `UICollectionView`, debes conformar el protocolo `SkeletonCollectionViewDataSource`.
|
||||
|
||||
``` swift
|
||||
public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int // Por defecto: 1
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? // Por defecto: nil
|
||||
}
|
||||
```
|
||||
|
||||
El resto del proceso es exactamente igual que con las `UITableView`.
|
||||
|
||||
|
||||
### 🔠 Textos
|
||||
|
||||

|
||||
|
||||
Cuando usas elementos que contienen texto,`SkeletonView` dibujo líneas para simular el texto.
|
||||
|
||||
Además, puedes decidir el número de líneas. Si `numberOfLines` es igual a **0**, se calculará automáticamente el número de líneas necesarias para ocupar todo el frame. Sin embargo, si es un número mayor que cero, solo se dibujarán esas líneas.
|
||||
|
||||
Puedes especificar algunos atributos para estos elementos:
|
||||
|
||||
|
||||
| Atributo | Valores | Por defecto | Vista previa
|
||||
| ------- | ------- |------- | -------
|
||||
| **Porcentaje de relleno** de la última línea. | `0...100` | `70%` | 
|
||||
| **Radio de las esquinas** de las líneas. | `0...10` | `0` | 
|
||||
|
||||
Para modificar alguno de los valores lo puedes hacer **con código**::
|
||||
```swift
|
||||
descriptionTextView.lastLineFillPercent = 50
|
||||
descriptionTextView.linesCornerRadius = 5
|
||||
```
|
||||
|
||||
O usando **IB/Storyboards**:
|
||||
|
||||

|
||||
|
||||
|
||||
### 🦋 Apariencia
|
||||
|
||||
Los skeletons tiene una apariencia por defecto. Así, cuando no especificas el color, el degradado o las propiedades para las multiíneas, `SkeletonView` usa estos valores.
|
||||
|
||||
Valores por defecto:
|
||||
- **tintColor**: `UIColor`
|
||||
- *default: `.skeletonDefault` (igual que `.clouds` pero se adapta al dark mode)*
|
||||
- **gradient**: `SkeletonGradient`
|
||||
- *default: `SkeletonGradient(baseColor: .skeletonDefault)`*
|
||||
- **multilineHeight**: `CGFloat`
|
||||
- *default: 15*
|
||||
- **multilineSpacing**: `CGFloat`
|
||||
- *default: 10*
|
||||
- **multilineLastLineFillPercent**: `Int`
|
||||
- *default: 70*
|
||||
- **multilineCornerRadius**: `Int`
|
||||
- *default: 0*
|
||||
- **skeletonCornerRadius**: `CGFloat` (IBInspectable)
|
||||
- *default: 0*
|
||||
|
||||
Para obtener o modificar estos valores tu puedes usar `SkeletonAppearance.default`:
|
||||
```swift
|
||||
SkeletonAppearance.default.multilineHeight = 20
|
||||
SkeletonAppearance.default.tintColor = .green
|
||||
```
|
||||
|
||||
|
||||
### 🎨 Colores
|
||||
|
||||
Puedes decidir de qué color se tinta tu skeleton. Solo tienes que indicarlo pasándolo como parámetro:
|
||||
|
||||
**Usando colores sólidos**
|
||||
```swift
|
||||
view.showSkeleton(usingColor: UIColor.gray)
|
||||
// o
|
||||
view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0))
|
||||
```
|
||||
**Usando degradados**
|
||||
``` swift
|
||||
let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue)
|
||||
view.showGradientSkeleton(usingGradient: gradient)
|
||||
```
|
||||
|
||||
Además, **SkeletonView** añade 20 colores flat 🤙🏼
|
||||
|
||||
```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...```
|
||||
|
||||

|
||||
###### Imagen extraída de la web [https://flatuicolors.com](https://flatuicolors.com)
|
||||
|
||||
|
||||
### 🏃♀️ Animaciones
|
||||
|
||||
**SkeletonView** tiene pre-definidas dos animaciones, *pulse* para skeleton sólidos y *sliding* para degradados.
|
||||
|
||||
Además, usando el método `showAnimatedSkeleton`, podemos incluir la `animation` que es de tipo `SkeletonLayerAnimation`, un bloque donde tu puedes crear tus propias animaciones:
|
||||
|
||||
```swift
|
||||
public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation
|
||||
```
|
||||
|
||||
Tu código quedaría así:
|
||||
|
||||
```swift
|
||||
view.showAnimatedSkeleton { (layer) -> CAAnimation in
|
||||
let animation = CAAnimation()
|
||||
// Personaliza la animación aquí
|
||||
|
||||
return animation
|
||||
}
|
||||
```
|
||||
|
||||
`SkeletonAnimationBuilder` es un builder que permite crear `SkeletonLayerAnimation`.
|
||||
|
||||
Por ejemplo, tu puedes crear **sliding animations** para los degradados, decidiendo la **direction** y indicando la **duration** de la animación (default = 1.5s).
|
||||
|
||||
```swift
|
||||
// func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation
|
||||
|
||||
let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight)
|
||||
view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)
|
||||
|
||||
```
|
||||
|
||||
```GradientDirection``` es un enumerado con estos `cases`:
|
||||
|
||||
| Dirección | Vista previa
|
||||
|------- | -------
|
||||
| .leftRight | 
|
||||
| .rightLeft | 
|
||||
| .topBottom | 
|
||||
| .bottomTop | 
|
||||
| .topLeftBottomRight | 
|
||||
| .bottomRightTopLeft | 
|
||||
|
||||
> **😉 ¡Truco!**
|
||||
>
|
||||
> Puedes crear una animación sliding, con este shortcut:
|
||||
> ```swift
|
||||
> let animation = GradientDirection.leftToRight.slidingAnimation()
|
||||
> ```
|
||||
|
||||
|
||||
|
||||
### 🏄 Transiciones
|
||||
|
||||
**SkeletonView** tiene algunas transiciones listas para usarse cuando **aparece** o se **oculta**. Puedes especificarla así:
|
||||
|
||||
```swift
|
||||
view.showSkeleton(transition: .crossDissolve(0.25))
|
||||
view.hideSkeleton(transition: .crossDissolve(0.25))
|
||||
|
||||
```
|
||||
|
||||
La transición por defecto es `crossDissolve(0.25)`
|
||||
|
||||
**Vista previa**
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<center>Sin transición</center>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<center>Cross dissolve</center>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<img src="Assets/skeleton_transition_nofade.gif"></img>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<img src="Assets/skeleton_transition_fade.gif"></img>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## ✨ Miscelánea
|
||||
|
||||
|
||||
|
||||
**Jerarquía**
|
||||
|
||||
|
||||
`SkeletonView` es recursivo, pero para que sea eficiente, tenemos que pararar la recursión tan pronto como sea posible. Por este motivo, el contenedor de las vistas debe ser **`skeletonable`**, porque `SkeletonView` parará de buscar vistas skeletonables cuando encuentre una que no lo sea, dentro de la jerarquía.
|
||||
|
||||
Como una imagen vale más que mil palabras:
|
||||
|
||||
En este ejemplo, tenemos un `UIViewController` con un `containerView` y una `UITableView`. Cuando la vista está lista, para mostrar el skeleton ejecutamos el método:
|
||||
```
|
||||
view.showSkeleton()
|
||||
```
|
||||
|
||||
> `isSkeletonable`= ☠️
|
||||
|
||||
| Configuración | Resultado|
|
||||
|:-------:|:-------:|
|
||||
|<img src="Assets/no_skeletonable.jpg" width="350"/> | <img src="Assets/no_skeletonables_result.png" width="350"/>|
|
||||
|<img src="Assets/container_no_skeletonable.jpg" width="350"/> | <img src="Assets/no_skeletonables_result.png" width="350"/>|
|
||||
|<img src="Assets/container_skeletonable.jpg" width="350"/> | <img src="Assets/container_skeletonable_result.png" width="350"/>|
|
||||
|<img src="Assets/all_skeletonables.jpg" width="350"/>| <img src="Assets/all_skeletonables_result.png" width="350"/>|
|
||||
|<img src="Assets/tableview_no_skeletonable.jpg" width="350"/> | <img src="Assets/tableview_no_skeletonable_result.png" height="350"/>|
|
||||
|<img src="Assets/tableview_skeletonable.jpg" width="350"/> | <img src="Assets/tableview_skeletonable_result.png" height="350"/>|
|
||||
|
||||
|
||||
|
||||
**Jerarquía en las colecciones**
|
||||
|
||||
Esta ilustración muestra como deberías específicar qué elementos son skeletonables cuando estás usando una `UITableView`:
|
||||
|
||||
<img src="Assets/tableview_scheme.png" width="700px">
|
||||
|
||||
|
||||
**Actualiza el skeleton**
|
||||
|
||||
Puedes cambiar la configuración del skeleton, como el color, la animación, etc, con los siguientes métodos:
|
||||
|
||||
```swift
|
||||
(1) view.updateSkeleton() // Sólido
|
||||
(2) view.updateGradientSkeleton() // Degradado
|
||||
(3) view.updateAnimatedSkeleton() // Sólido animado
|
||||
(4) view.updateAnimatedGradientSkeleton() // Degradado animado
|
||||
```
|
||||
|
||||
|
||||
**Debug**
|
||||
|
||||
Para facilitar las tareas de debug cuando algo no está funcionando bien, **`SkeletonView`** tiene una nueva herramienta.
|
||||
|
||||
Primero, `UIView` tiene una nueva propiedad que contiene toda la info del skeleton:
|
||||
```swift
|
||||
var skeletonDescription: String
|
||||
|
||||
```
|
||||
Y es representada de la siguiente manera:
|
||||
|
||||

|
||||
|
||||
Para activar el **modo debug**. Solo tienes que añadir una variable de entorno con esta clave `SKELETON_DEBUG` y activarla.
|
||||
|
||||

|
||||
|
||||
Entonces, cuando el skeleton aparece, tu podrás ver la jerarquía de vistas en la consola de Xcode.
|
||||
|
||||
<details>
|
||||
<summary>Abre para ver un ejemplo </summary>
|
||||
<img src="Assets/hierarchy_output.png" />
|
||||
</details>
|
||||
|
||||
|
||||
**OS Soportado & Versiones SDK**
|
||||
|
||||
* iOS 9.0+
|
||||
* tvOS 9.0+
|
||||
* Swift 5
|
||||
|
||||
|
||||
## ❤️ Contributing
|
||||
|
||||
Esto es un proyecto open source, siéntete libre de contribuir. ¿Cómo?
|
||||
- Abre un [issue](https://github.com/Juanpe/SkeletonView/issues/new).
|
||||
- Envía feedback a través del [email](mailto://juanpecatalan.com).
|
||||
- Propone tus propies fixes, sugerencias y abre una Pull Request con los cambios.
|
||||
|
||||
Échale un vistazo a [los que ya han contribuído](https://github.com/Juanpe/SkeletonView/graphs/contributors)
|
||||
|
||||
Para más información, por favor, lee la [guía de contribución](https://github.com/Juanpe/SkeletonView/blob/main/CONTRIBUTING.md).
|
||||
|
||||
|
||||
## 📢 Menciones
|
||||
|
||||
- [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start)
|
||||
- [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful)
|
||||
- [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65)
|
||||
- [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9)
|
||||
- [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899)
|
||||
- [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d)
|
||||
- [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/)
|
||||
- [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204)
|
||||
- [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759)
|
||||
- [CocoaControls](https://www.cocoacontrols.com/controls/skeletonview)
|
||||
- [Awesome iOS Newsletter #74](https://ios.libhunt.com/newsletter/74)
|
||||
- [Swift News #36](https://www.youtube.com/watch?v=mAGpsQiy6so)
|
||||
- [Best iOS articles, new tools & more](https://medium.com/flawless-app-stories/best-ios-articles-new-tools-more-fcbe673e10d)
|
||||
|
||||
|
||||
|
||||
## 👨🏻💻 Autor
|
||||
|
||||
[Juanpe Catalán](http://www.twitter.com/JuanpeCatalan)
|
||||
|
||||
<a class="bmc-button" target="_blank" href="https://www.buymeacoffee.com/CDou4xtIK"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy me a coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;"><span style="margin-left:5px"></span></a>
|
||||
|
||||
|
||||
## 👮🏻 Licencia
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Juanpe Catalán
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
+32
-24
@@ -1,8 +1,8 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/workflows/build">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/build/badge.svg">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/CI/badge.svg">
|
||||
</a>
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<img src="https://img.shields.io/badge/Swift-5-orange.svg" />
|
||||
@@ -37,28 +37,36 @@ Aujourd'hui, presque toutes les applications ont des processus asynchrones, tels
|
||||
|
||||
Profitez-en! 🙂
|
||||
|
||||
* [Caractéristiques](#-caractéristiques)
|
||||
* [Guides](#-guides)
|
||||
* [Installation](#-installation)
|
||||
* [Cocoapods](#utilisation-de-cocoapods)
|
||||
* [Carthage](#utilisation-de-carthage)
|
||||
* [SPM](#utilisation-du-gestionnaire-de-paquets-Swift)
|
||||
* [Comment utiliser](#-mode-d'emploi)
|
||||
* [Collections](#-collections)
|
||||
* [Texte Multiligne](#-texte-multiligne)
|
||||
* [Couleurs personnalisées](#-couleurs-personnalisées)
|
||||
* [Présentation](#-présentation)
|
||||
* [Animations personnalisées](#-animations-personnalisées)
|
||||
* [Transitions](#-transitions)
|
||||
* [Hiérarchie](#-hiérarchie)
|
||||
* [Débugger](#-débugger)
|
||||
* [Documentation](#-documentation)
|
||||
* [Versions OS & SDK supportées](#-versions-os-et-sdk-supportées)
|
||||
* [Prochaines étapes](#-prochaines-étapes)
|
||||
* [Contribuer](#-contribuer)
|
||||
* [Mentions](#-mentions)
|
||||
* [Auteur](#-auteur)
|
||||
* [Licence](#-licence)
|
||||
- [🌟 Caractéristiques](#-caractéristiques)
|
||||
- [🎬 Guides](#-guides)
|
||||
- [📲 Installation](#-installation)
|
||||
- [Utilisation de CocoaPods](#utilisation-de-cocoapods)
|
||||
- [Utilisation de Carthage](#utilisation-de-carthage)
|
||||
- [Utilisation du gestionnaire de paquets Swift](#utilisation-du-gestionnaire-de-paquets-swift)
|
||||
- [🐒 Mode d'emploi](#-mode-demploi)
|
||||
- [Extra](#extra)
|
||||
- [Mise en page des vues squelettes](#mise-en-page-des-vues-squelettes)
|
||||
- [Mise à jour de la configuration du squelette](#mise-à-jour-de-la-configuration-du-squelette)
|
||||
- [🌿 Collections](#-collections)
|
||||
- [UITableView](#uitableview)
|
||||
- [UICollectionView](#uicollectionview)
|
||||
- [📰 Texte multiligne](#-texte-multiligne)
|
||||
- [🎛 Personnaliser](#-personnaliser)
|
||||
- [🎨 Couleurs personnalisées](#-couleurs-personnalisées)
|
||||
- [Image tirée du site web https://flatuicolors.com](#image-tirée-du-site-web-httpsflatuicolorscom)
|
||||
- [🦋 Présentation](#-présentation)
|
||||
- [🤓 Animations personnalisées](#-animations-personnalisées)
|
||||
- [🏄 Transitions](#-transitions)
|
||||
- [👨👧👦 Hiérarchie](#-hiérarchie)
|
||||
- [🔬 Débugger](#-débugger)
|
||||
- [📚 Documentation](#-documentation)
|
||||
- [📋 Versions OS et SDK supportées](#-versions-os-et-sdk-supportées)
|
||||
- [📬 Prochaines étapes](#-prochaines-étapes)
|
||||
- [❤️ Contribuer](#️-contribuer)
|
||||
- [Projet généré avec SwiftPlate](#projet-généré-avec-swiftplate)
|
||||
- [📢 Mentions](#-mentions)
|
||||
- [👨🏻💻 Auteur](#-auteur)
|
||||
- [👮🏻 Licence](#-licence)
|
||||
|
||||
## 🌟 Caractéristiques
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/workflows/build">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/build/badge.svg">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/CI/badge.svg">
|
||||
</a>
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<img src="https://img.shields.io/badge/Swift-5-orange.svg" />
|
||||
|
||||
+26
-21
@@ -1,8 +1,8 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/workflows/build">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/build/badge.svg">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/CI/badge.svg">
|
||||
</a>
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<img src="https://img.shields.io/badge/Swift-5-orange.svg" />
|
||||
@@ -36,25 +36,30 @@ Hoje, quase todos os apps têm processos assíncronos, como requisições de API
|
||||
|
||||
Aproveite! 🙂
|
||||
|
||||
* [Features](#-features)
|
||||
* [Requerimentos](#-supported-os--sdk-versions)
|
||||
* [Projeto de exemplo](#-example)
|
||||
* [Instalação](#-installation)
|
||||
* [Cocoapods](#using-cocoapods)
|
||||
* [Carthage](#using-carthage)
|
||||
* [Como usar](#-how-to-use)
|
||||
* [Coleções](#-collections)
|
||||
* [Texto em várias linhas](#-multiline-text)
|
||||
* [Cores customizadas](#-custom-colors)
|
||||
* [Aparência](#-appearance)
|
||||
* [Animaçōes customizadas](#-custom-animations)
|
||||
* [Hierarquia](#-hierarchy)
|
||||
* [Documentação](#-documentation)
|
||||
* [Próximos passos](#-next-steps)
|
||||
* [Contribuindo](#-contributing)
|
||||
* [Menções](#-mentions)
|
||||
* [Autor](#-author)
|
||||
* [Licença](#-license)
|
||||
- [🌟 Features](#-features)
|
||||
- [📋 Versões do SDK e OS suportados](#-versões-do-sdk-e-os-suportados)
|
||||
- [🔮 Exemplo](#-exemplo)
|
||||
- [📲 Instalação](#-instalação)
|
||||
- [Usando CocoaPods](#usando-cocoapods)
|
||||
- [Usando Carthage](#usando-carthage)
|
||||
- [🐒 Como usar](#-como-usar)
|
||||
- [🌿 Coleções](#-coleções)
|
||||
- [UITableView](#uitableview)
|
||||
- [UICollectionView](#uicollectionview)
|
||||
- [📰 Texto de várias linhas](#-texto-de-várias-linhas)
|
||||
- [🎛 Customização](#-customização)
|
||||
- [🎨 Cores customizadas](#-cores-customizadas)
|
||||
- [Imagem capturada do site https://flatuicolors.com](#imagem-capturada-do-site-httpsflatuicolorscom)
|
||||
- [🦋 Aparência](#-aparência)
|
||||
- [🤓 Animações customizadas](#-animações-customizadas)
|
||||
- [👨👧👦 Hierarquia](#-hierarquia)
|
||||
- [📚 Documentação](#-documentação)
|
||||
- [📬 Próximos passos](#-próximos-passos)
|
||||
- [❤️ Contribuindo](#️-contribuindo)
|
||||
- [Projeto gerado com SwiftPlate](#projeto-gerado-com-swiftplate)
|
||||
- [📢 Menções](#-menções)
|
||||
- [👨🏻💻 Autor](#-autor)
|
||||
- [👮🏻 Licença](#-licença)
|
||||
|
||||
|
||||
## 🌟 Features
|
||||
|
||||
+25
-20
@@ -1,8 +1,8 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/workflows/build">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/build/badge.svg">
|
||||
<a href="https://github.com/Juanpe/SkeletonView/actions?query=workflow%3ACI">
|
||||
<img src="https://github.com/Juanpe/SkeletonView/workflows/CI/badge.svg">
|
||||
</a>
|
||||
<a href="https://codebeat.co/projects/github-com-juanpe-skeletonview-master"><img alt="codebeat badge" src="https://codebeat.co/badges/f854fdfd-31e5-4689-ba04-075d83653e60" /></a>
|
||||
<img src="https://img.shields.io/badge/Swift-5-orange.svg" />
|
||||
@@ -36,24 +36,29 @@
|
||||
|
||||
好好享受! 🙂
|
||||
|
||||
* [特征](#-特征)
|
||||
* [版本要求](#-版本要求)
|
||||
* [示例项目](#-示例)
|
||||
* [安装](#-安装)
|
||||
* [Cocoapods](#使用-cocoapods)
|
||||
* [Carthage](#使用-carthage)
|
||||
* [如何使用](#-如何使用)
|
||||
* [集合](#-集合)
|
||||
* [多行文字](#-多行文字)
|
||||
* [自定义颜色](#-自定义颜色)
|
||||
* [自定义动画](#-自定义动画)
|
||||
* [等级制度](#-等级制度)
|
||||
* [文档](#-文档)
|
||||
* [下一步](#-下一步)
|
||||
* [特约](#-特约)
|
||||
* [提及](#-提及)
|
||||
* [作者](#-作者)
|
||||
* [许可证](#-许可证)
|
||||
- [🌟 特征](#-特征)
|
||||
- [📋 版本要求](#-版本要求)
|
||||
- [🔮 示例](#-示例)
|
||||
- [📲 安装](#-安装)
|
||||
- [使用 CocoaPods](#使用-cocoapods)
|
||||
- [使用 Carthage](#使用-carthage)
|
||||
- [🐒 如何使用](#-如何使用)
|
||||
- [🌿 集合](#-集合)
|
||||
- [UITableView](#uitableview)
|
||||
- [UICollectionView](#uicollectionview)
|
||||
- [📰 多行文字](#-多行文字)
|
||||
- [🎛 定制](#-定制)
|
||||
- [🎨 自定义颜色](#-自定义颜色)
|
||||
- [从网站 https://flatuicolors.com捕获的图像](#从网站-httpsflatuicolorscom捕获的图像)
|
||||
- [🤓 自定义动画](#-自定义动画)
|
||||
- [👨👧👦 等级制度](#-等级制度)
|
||||
- [📚 文档](#-文档)
|
||||
- [📬 下一步](#-下一步)
|
||||
- [❤️ 特约](#️-特约)
|
||||
- [使用 SwiftPlate 生成的项目](#使用-swiftplate-生成的项目)
|
||||
- [📢 提及](#-提及)
|
||||
- [👨🏻💻 作者](#-作者)
|
||||
- [👮🏻 许可证](#-许可证)
|
||||
|
||||
|
||||
## 🌟 特征
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SkeletonView"
|
||||
s.version = "1.8.8"
|
||||
s.version = "1.17.1"
|
||||
s.summary = "An elegant way to show users that something is happening and also prepare them to which contents he is waiting"
|
||||
s.description = <<-DESC
|
||||
Today almost all apps have async processes, as API requests, long runing processes, etc. And while the processes are working, usually developers place a loading view to show users that something is going on.
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
17DD0E1D207FB32100C56334 /* ContainsMultilineText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E3A1FB123C100EE67C5 /* ContainsMultilineText.swift */; };
|
||||
17DD0E1E207FB32100C56334 /* PrepareForSkeletonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F56B94451FAE20AF0095662F /* PrepareForSkeletonProtocol.swift */; };
|
||||
17DD0E1F207FB32100C56334 /* RecursiveProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5307E311FB0F42F00EE67C5 /* RecursiveProtocol.swift */; };
|
||||
1E291F3E2540655D0018D602 /* UIView+Autolayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E291F3D2540655D0018D602 /* UIView+Autolayout.swift */; };
|
||||
1E291F3F2540655D0018D602 /* UIView+Autolayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E291F3D2540655D0018D602 /* UIView+Autolayout.swift */; };
|
||||
1E6C67A2230E76CC0019D87B /* SkeletonTransitionStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4CE587C22EEE65200333067 /* SkeletonTransitionStyle.swift */; };
|
||||
1E6C67A3230E76CE0019D87B /* UIView+Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4CE587B22EEE63100333067 /* UIView+Transitions.swift */; };
|
||||
1EE42E1F23FF25CC00BF665A /* ProcessInfo+XCTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE42E1E23FF25CC00BF665A /* ProcessInfo+XCTest.swift */; };
|
||||
@@ -161,6 +163,7 @@
|
||||
17DD0E00207FB27400C56334 /* SkeletonView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SkeletonView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
17DD0E1A207FB2C200C56334 /* SkeletonView-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SkeletonView-tvOS.plist"; sourceTree = "<group>"; };
|
||||
17DD0E1B207FB2C200C56334 /* SkeletonView-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SkeletonView-iOS.plist"; sourceTree = "<group>"; };
|
||||
1E291F3D2540655D0018D602 /* UIView+Autolayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Autolayout.swift"; sourceTree = "<group>"; };
|
||||
1EE42E1E23FF25CC00BF665A /* ProcessInfo+XCTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+XCTest.swift"; sourceTree = "<group>"; };
|
||||
42ABD06D210B548200BEEFF4 /* SkeletonViewExampleUICollectionView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SkeletonViewExampleUICollectionView.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
42ABD070210B54E100BEEFF4 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -443,6 +446,7 @@
|
||||
F5307E2D1FB0E5E400EE67C5 /* UIView+Frame.swift */,
|
||||
F5804771230ECD0000066D02 /* UIView+IBInspectable.swift */,
|
||||
F587FB85202CEC95002DB5FE /* UIView+UIApplicationDelegate.swift */,
|
||||
1E291F3D2540655D0018D602 /* UIView+Autolayout.swift */,
|
||||
5600784323FD293D00669AD6 /* UITableView+VisibleSections.swift */,
|
||||
1EE42E1E23FF25CC00BF665A /* ProcessInfo+XCTest.swift */,
|
||||
);
|
||||
@@ -526,6 +530,7 @@
|
||||
17DD0DFC207FB27400C56334 /* Frameworks */,
|
||||
17DD0DFD207FB27400C56334 /* Headers */,
|
||||
17DD0DFE207FB27400C56334 /* Resources */,
|
||||
1E3CFFD5250AD9A400DDB852 /* SwiftLint */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -563,6 +568,7 @@
|
||||
52D6D9781BEFF229002C0205 /* Frameworks */,
|
||||
52D6D9791BEFF229002C0205 /* Headers */,
|
||||
52D6D97A1BEFF229002C0205 /* Resources */,
|
||||
1E3CFFD3250AD95300DDB852 /* SwiftLint */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -613,6 +619,7 @@
|
||||
52D6D97B1BEFF229002C0205 = {
|
||||
CreatedOnToolsVersion = 7.1;
|
||||
LastSwiftMigration = 1000;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
F5F899F11FABA607002E8FDA = {
|
||||
CreatedOnToolsVersion = 9.1;
|
||||
@@ -679,12 +686,52 @@
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
1E3CFFD3250AD95300DDB852 /* SwiftLint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = SwiftLint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||
};
|
||||
1E3CFFD5250AD9A400DDB852 /* SwiftLint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = SwiftLint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
17DD0DFB207FB27400C56334 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
872D5A5721C177E20037D763 /* UIView+Extension.swift in Sources */,
|
||||
1E291F3F2540655D0018D602 /* UIView+Autolayout.swift in Sources */,
|
||||
17DD0E1F207FB32100C56334 /* RecursiveProtocol.swift in Sources */,
|
||||
872D5A5D21C24EDD0037D763 /* UILabel+Multiline.swift in Sources */,
|
||||
17DD0E1E207FB32100C56334 /* PrepareForSkeletonProtocol.swift in Sources */,
|
||||
@@ -745,6 +792,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E4CE588B22EEF70D00333067 /* UIView+Transitions.swift in Sources */,
|
||||
1E291F3E2540655D0018D602 /* UIView+Autolayout.swift in Sources */,
|
||||
872D5A5621C177E20037D763 /* UIView+Extension.swift in Sources */,
|
||||
872D5A5C21C24EDD0037D763 /* UILabel+Multiline.swift in Sources */,
|
||||
F54CF5E42024CEB000330B0D /* UIView+CollectionSkeleton.swift in Sources */,
|
||||
@@ -1070,8 +1118,10 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1084,6 +1134,8 @@
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.SkeletonView.SkeletonView-iOS";
|
||||
PRODUCT_NAME = SkeletonView;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -1095,8 +1147,10 @@
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1108,6 +1162,8 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.SkeletonView.SkeletonView-iOS";
|
||||
PRODUCT_NAME = SkeletonView;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
@@ -22,7 +22,7 @@ class SkeletonViewAppearance: Appearance {
|
||||
|
||||
var tintColor: UIColor = .skeletonDefault
|
||||
|
||||
var gradient: SkeletonGradient = SkeletonGradient(baseColor: .skeletonDefault)
|
||||
var gradient = SkeletonGradient(baseColor: .skeletonDefault)
|
||||
|
||||
var multilineHeight: CGFloat = 15
|
||||
|
||||
|
||||
@@ -9,25 +9,30 @@ class SkeletonLayerBuilder {
|
||||
var colors: [UIColor] = []
|
||||
var holder: UIView?
|
||||
|
||||
@discardableResult
|
||||
func setSkeletonType(_ type: SkeletonType) -> SkeletonLayerBuilder {
|
||||
self.skeletonType = type
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func addColor(_ color: UIColor) -> SkeletonLayerBuilder {
|
||||
return addColors([color])
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func addColors(_ colors: [UIColor]) -> SkeletonLayerBuilder {
|
||||
self.colors.append(contentsOf: colors)
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setHolder(_ holder: UIView) -> SkeletonLayerBuilder {
|
||||
self.holder = holder
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func build() -> SkeletonLayer? {
|
||||
guard let type = skeletonType,
|
||||
let holder = holder
|
||||
|
||||
@@ -12,41 +12,55 @@ class SkeletonMultilineLayerBuilder {
|
||||
var cornerRadius: Int?
|
||||
var multilineSpacing: CGFloat = SkeletonAppearance.default.multilineSpacing
|
||||
var paddingInsets: UIEdgeInsets = .zero
|
||||
var isRTL: Bool = false
|
||||
|
||||
@discardableResult
|
||||
func setSkeletonType(_ type: SkeletonType) -> SkeletonMultilineLayerBuilder {
|
||||
self.skeletonType = type
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setIndex(_ index: Int) -> SkeletonMultilineLayerBuilder {
|
||||
self.index = index
|
||||
return self
|
||||
}
|
||||
|
||||
func setHeight(_ height: CGFloat) -> SkeletonMultilineLayerBuilder {
|
||||
self.height = height
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setHeight(_ height: CGFloat) -> SkeletonMultilineLayerBuilder {
|
||||
self.height = height
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setWidth(_ width: CGFloat) -> SkeletonMultilineLayerBuilder {
|
||||
self.width = width
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setCornerRadius(_ radius: Int) -> SkeletonMultilineLayerBuilder {
|
||||
self.cornerRadius = radius
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setMultilineSpacing(_ spacing: CGFloat) -> SkeletonMultilineLayerBuilder {
|
||||
self.multilineSpacing = spacing
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setPadding(_ insets: UIEdgeInsets) -> SkeletonMultilineLayerBuilder {
|
||||
self.paddingInsets = insets
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setIsRTL(_ isRTL: Bool) -> SkeletonMultilineLayerBuilder {
|
||||
self.isRTL = isRTL
|
||||
return self
|
||||
}
|
||||
|
||||
func build() -> CALayer? {
|
||||
guard let type = skeletonType,
|
||||
@@ -59,7 +73,11 @@ class SkeletonMultilineLayerBuilder {
|
||||
let layer = type.layer
|
||||
layer.anchorPoint = .zero
|
||||
layer.name = CALayer.skeletonSubLayersName
|
||||
layer.updateLayerFrame(for: index, size: CGSize(width: width, height: height), multilineSpacing: self.multilineSpacing, paddingInsets: paddingInsets)
|
||||
layer.updateLayerFrame(for: index,
|
||||
size: CGSize(width: width, height: height),
|
||||
multilineSpacing: multilineSpacing,
|
||||
paddingInsets: paddingInsets,
|
||||
isRTL: isRTL)
|
||||
|
||||
layer.cornerRadius = CGFloat(radius)
|
||||
layer.masksToBounds = true
|
||||
|
||||
@@ -29,6 +29,18 @@ extension CollectionSkeleton where Self: UIScrollView {
|
||||
var estimatedNumberOfRows: Int { return 0 }
|
||||
func addDummyDataSource() {}
|
||||
func removeDummyDataSource(reloadAfter: Bool) {}
|
||||
func disableUserInteraction() { isUserInteractionEnabled = false; isScrollEnabled = false }
|
||||
func enableUserInteraction() { isUserInteractionEnabled = true; isScrollEnabled = true }
|
||||
|
||||
func disableUserInteraction() {
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
isScrollEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
func enableUserInteraction() {
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = true
|
||||
isScrollEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
func numSections(in collectionSkeletonView: UICollectionView) -> Int
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
@@ -18,7 +17,7 @@ public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
|
||||
|
||||
public extension SkeletonCollectionViewDataSource {
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return skeletonView.estimatedNumberOfRows
|
||||
return UICollectionView.automaticNumberOfSkeletonItems
|
||||
}
|
||||
|
||||
func collectionSkeletonView(_ skeletonView: UICollectionView,
|
||||
@@ -31,4 +30,3 @@ public extension SkeletonCollectionViewDataSource {
|
||||
}
|
||||
|
||||
public protocol SkeletonCollectionViewDelegate: UICollectionViewDelegate { }
|
||||
|
||||
|
||||
@@ -7,11 +7,20 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
extension UICollectionView: CollectionSkeleton {
|
||||
public static let automaticNumberOfSkeletonItems = -1
|
||||
|
||||
var estimatedNumberOfRows: Int {
|
||||
guard let flowlayout = collectionViewLayout as? UICollectionViewFlowLayout else { return 0 }
|
||||
return Int(ceil(frame.height/flowlayout.itemSize.height))
|
||||
switch flowlayout.scrollDirection {
|
||||
case .vertical:
|
||||
return Int(ceil(frame.height / flowlayout.itemSize.height))
|
||||
case .horizontal:
|
||||
return Int(ceil(frame.width / flowlayout.itemSize.width))
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
var skeletonDataSource: SkeletonCollectionDataSource? {
|
||||
@@ -70,7 +79,7 @@ public extension UICollectionView {
|
||||
self.skeletonDataSource = dataSource
|
||||
performBatchUpdates({
|
||||
self.reloadData()
|
||||
}) { (done) in
|
||||
}) { done in
|
||||
completion(done)
|
||||
|
||||
}
|
||||
|
||||
@@ -28,11 +28,21 @@ class SkeletonCollectionDataSource: NSObject {
|
||||
// MARK: - UITableViewDataSource
|
||||
extension SkeletonCollectionDataSource: UITableViewDataSource {
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return originalTableViewDataSource?.numSections(in: tableView) ?? 0
|
||||
originalTableViewDataSource?.numSections(in: tableView) ?? 0
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return originalTableViewDataSource?.collectionSkeletonView(tableView, numberOfRowsInSection: section) ?? 0
|
||||
guard let originalTableViewDataSource = originalTableViewDataSource else {
|
||||
return 0
|
||||
}
|
||||
|
||||
let numberOfRows = originalTableViewDataSource.collectionSkeletonView(tableView, numberOfRowsInSection: section)
|
||||
|
||||
if numberOfRows == UITableView.automaticNumberOfSkeletonRows {
|
||||
return tableView.estimatedNumberOfRows
|
||||
} else {
|
||||
return numberOfRows
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
@@ -46,11 +56,21 @@ extension SkeletonCollectionDataSource: UITableViewDataSource {
|
||||
// MARK: - UICollectionViewDataSource
|
||||
extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
return originalCollectionViewDataSource?.numSections(in: collectionView) ?? 0
|
||||
originalCollectionViewDataSource?.numSections(in: collectionView) ?? 0
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return originalCollectionViewDataSource?.collectionSkeletonView(collectionView, numberOfItemsInSection: section) ?? 0
|
||||
guard let originalCollectionViewDataSource = originalCollectionViewDataSource else {
|
||||
return 0
|
||||
}
|
||||
|
||||
let numberOfItems = originalCollectionViewDataSource.collectionSkeletonView(collectionView, numberOfItemsInSection: section)
|
||||
|
||||
if numberOfItems == UICollectionView.automaticNumberOfSkeletonItems {
|
||||
return collectionView.estimatedNumberOfRows
|
||||
} else {
|
||||
return numberOfItems
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
@@ -63,9 +83,8 @@ extension SkeletonCollectionDataSource: UICollectionViewDataSource {
|
||||
func collectionView(_ collectionView: UICollectionView,
|
||||
viewForSupplementaryElementOfKind kind: String,
|
||||
at indexPath: IndexPath) -> UICollectionReusableView {
|
||||
|
||||
if let viewIdentifier = originalCollectionViewDataSource?.collectionSkeletonView(collectionView, supplementaryViewIdentifierOfKind: kind, at: indexPath) {
|
||||
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: viewIdentifier, for: indexPath)
|
||||
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: viewIdentifier, for: indexPath)
|
||||
skeletonViewIfContainerSkeletonIsActive(container: collectionView, view: view)
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -21,25 +21,11 @@ class SkeletonCollectionDelegate: NSObject {
|
||||
// MARK: - UITableViewDelegate
|
||||
extension SkeletonCollectionDelegate: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
if let viewIdentifier = originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForHeaderInSection: section),
|
||||
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier) {
|
||||
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: header)
|
||||
return header
|
||||
}
|
||||
|
||||
return nil
|
||||
headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForHeaderInSection: section))
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
if let viewIdentifier = originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForFooterInSection: section),
|
||||
let footer = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier) {
|
||||
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: footer)
|
||||
return footer
|
||||
}
|
||||
|
||||
return nil
|
||||
headerOrFooterView(tableView, for: originalTableViewDelegate?.collectionSkeletonView(tableView, identifierForFooterInSection: section))
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) {
|
||||
@@ -56,6 +42,12 @@ extension SkeletonCollectionDelegate: UITableViewDelegate {
|
||||
cell.hideSkeleton()
|
||||
originalTableViewDelegate?.tableView?(tableView, didEndDisplaying: cell, forRowAt: indexPath)
|
||||
}
|
||||
|
||||
private func headerOrFooterView(_ tableView: UITableView, for viewIdentifier: String? ) -> UIView? {
|
||||
guard let viewIdentifier = viewIdentifier, let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: viewIdentifier) else { return nil }
|
||||
skeletonViewIfContainerSkeletonIsActive(container: tableView, view: header)
|
||||
return header
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDelegate
|
||||
|
||||
@@ -16,7 +16,7 @@ public protocol SkeletonTableViewDataSource: UITableViewDataSource {
|
||||
|
||||
public extension SkeletonTableViewDataSource {
|
||||
func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return skeletonView.estimatedNumberOfRows
|
||||
return UITableView.automaticNumberOfSkeletonRows
|
||||
}
|
||||
|
||||
func numSections(in collectionSkeletonView: UITableView) -> Int { return 1 }
|
||||
|
||||
@@ -11,8 +11,10 @@ import UIKit
|
||||
public typealias ReusableHeaderFooterIdentifier = String
|
||||
|
||||
extension UITableView: CollectionSkeleton {
|
||||
public static let automaticNumberOfSkeletonRows = -1
|
||||
|
||||
var estimatedNumberOfRows: Int {
|
||||
return Int(ceil(frame.height/rowHeight))
|
||||
return Int(ceil(frame.height / rowHeight))
|
||||
}
|
||||
|
||||
var skeletonDataSource: SkeletonCollectionDataSource? {
|
||||
@@ -43,8 +45,7 @@ extension UITableView: CollectionSkeleton {
|
||||
self.skeletonDataSource = dataSource
|
||||
|
||||
if let originalDelegate = self.delegate as? SkeletonTableViewDelegate,
|
||||
!(originalDelegate is SkeletonCollectionDelegate)
|
||||
{
|
||||
!(originalDelegate is SkeletonCollectionDelegate) {
|
||||
let delegate = SkeletonCollectionDelegate(tableViewDelegate: originalDelegate)
|
||||
self.skeletonDelegate = delegate
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
// Copyright © 2018 SkeletonView. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
@@ -10,6 +9,7 @@ enum SkeletonEnvironmentKey: String {
|
||||
|
||||
extension Dictionary {
|
||||
subscript (_ key: SkeletonEnvironmentKey) -> Value? {
|
||||
// swiftlint:disable:next force_cast
|
||||
return self[key.rawValue as! Key]
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ func printSkeletonHierarchy(in view: UIView) {
|
||||
}
|
||||
|
||||
func skeletonLog(_ message: String) {
|
||||
if let _ = ProcessInfo.processInfo.environment[.debugMode] {
|
||||
if ProcessInfo.processInfo.environment[.debugMode] != nil {
|
||||
print(message)
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ extension UIView {
|
||||
public var skeletonDescription: String {
|
||||
var description = "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())"
|
||||
let subSkeletons = subviewsSkeletonables
|
||||
if subSkeletons.count != 0 {
|
||||
if !subSkeletons.isEmpty {
|
||||
description += " | (\(subSkeletons.count)) subSkeletons"
|
||||
}
|
||||
if isSkeletonable {
|
||||
|
||||
@@ -30,15 +30,23 @@ extension CAGradientLayer {
|
||||
|
||||
struct SkeletonMultilinesLayerConfig {
|
||||
var lines: Int
|
||||
var lineHeight: CGFloat? = nil
|
||||
var lineHeight: CGFloat
|
||||
var type: SkeletonType
|
||||
var lastLineFillPercent: Int
|
||||
var multilineCornerRadius: Int
|
||||
var multilineSpacing: CGFloat
|
||||
var paddingInsets: UIEdgeInsets
|
||||
var isRTL: Bool
|
||||
|
||||
/// Returns padding insets taking into account if the RTL is activated
|
||||
var calculatedPaddingInsets: UIEdgeInsets {
|
||||
UIEdgeInsets(top: paddingInsets.top,
|
||||
left: isRTL ? paddingInsets.right : paddingInsets.left,
|
||||
bottom: paddingInsets.bottom,
|
||||
right: isRTL ? paddingInsets.left : paddingInsets.right)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Skeleton sublayers
|
||||
extension CALayer {
|
||||
static let skeletonSubLayersName = "SkeletonSubLayersName"
|
||||
@@ -48,9 +56,10 @@ extension CALayer {
|
||||
}
|
||||
|
||||
func addMultilinesLayers(for config: SkeletonMultilinesLayerConfig) {
|
||||
let numberOfSublayers = config.lines == 1 ? 1 : calculateNumLines(for: config)
|
||||
var height = config.lineHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
if numberOfSublayers == 1 {
|
||||
let numberOfSublayers = config.lines > 0 ? config.lines : calculateNumLines(for: config)
|
||||
var height = config.lineHeight
|
||||
|
||||
if numberOfSublayers == 1 && SkeletonAppearance.default.renderSingleLineAsView {
|
||||
height = bounds.height
|
||||
}
|
||||
|
||||
@@ -60,6 +69,7 @@ extension CALayer {
|
||||
.setMultilineSpacing(config.multilineSpacing)
|
||||
.setPadding(config.paddingInsets)
|
||||
.setHeight(height)
|
||||
.setIsRTL(config.isRTL)
|
||||
|
||||
(0..<numberOfSublayers).forEach { index in
|
||||
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: config.lastLineFillPercent, paddingInsets: config.paddingInsets)
|
||||
@@ -76,16 +86,21 @@ extension CALayer {
|
||||
let currentSkeletonSublayers = skeletonSublayers
|
||||
let numberOfSublayers = currentSkeletonSublayers.count
|
||||
let lastLineFillPercent = config.lastLineFillPercent
|
||||
let paddingInsets = config.paddingInsets
|
||||
let paddingInsets = config.calculatedPaddingInsets
|
||||
let multilineSpacing = config.multilineSpacing
|
||||
var height = config.lineHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
if numberOfSublayers == 1 {
|
||||
var height = config.lineHeight
|
||||
|
||||
if numberOfSublayers == 1 && SkeletonAppearance.default.renderSingleLineAsView {
|
||||
height = bounds.height
|
||||
}
|
||||
|
||||
for (index, layer) in currentSkeletonSublayers.enumerated() {
|
||||
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: lastLineFillPercent, paddingInsets: paddingInsets)
|
||||
layer.updateLayerFrame(for: index, size: CGSize(width: width, height: height), multilineSpacing: multilineSpacing, paddingInsets: paddingInsets)
|
||||
layer.updateLayerFrame(for: index,
|
||||
size: CGSize(width: width, height: height),
|
||||
multilineSpacing: multilineSpacing,
|
||||
paddingInsets: paddingInsets,
|
||||
isRTL: config.isRTL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,16 +112,42 @@ extension CALayer {
|
||||
return width
|
||||
}
|
||||
|
||||
func updateLayerFrame(for index: Int, size: CGSize, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets) {
|
||||
let spaceRequiredForEachLine = SkeletonAppearance.default.multilineHeight + multilineSpacing
|
||||
frame = CGRect(x: paddingInsets.left, y: CGFloat(index) * spaceRequiredForEachLine + paddingInsets.top, width: size.width, height: size.height)
|
||||
func updateLayerFrame(for index: Int, size: CGSize, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets, isRTL: Bool) {
|
||||
let spaceRequiredForEachLine = size.height + multilineSpacing
|
||||
let newFrame = CGRect(x: paddingInsets.left,
|
||||
y: CGFloat(index) * spaceRequiredForEachLine + paddingInsets.top,
|
||||
width: size.width,
|
||||
height: size.height)
|
||||
|
||||
frame = flipRectForRTLIfNeeded(newFrame, isRTL: isRTL)
|
||||
}
|
||||
|
||||
private func calculateNumLines(for config: SkeletonMultilinesLayerConfig) -> Int {
|
||||
let definedNumberOfLines = config.lines
|
||||
let requiredSpaceForEachLine = config.lineHeight + config.multilineSpacing
|
||||
let neededLines = round(CGFloat(bounds.height - config.paddingInsets.top - config.paddingInsets.bottom) / CGFloat(requiredSpaceForEachLine))
|
||||
guard neededLines.isNormal else {
|
||||
return 0
|
||||
}
|
||||
|
||||
private func calculateNumLines(for config: SkeletonMultilinesLayerConfig) -> Int {
|
||||
let requiredSpaceForEachLine = (config.lineHeight ?? SkeletonAppearance.default.multilineHeight) + config.multilineSpacing
|
||||
var numberOfSublayers = Int(round(CGFloat(bounds.height - config.paddingInsets.top - config.paddingInsets.bottom)/CGFloat(requiredSpaceForEachLine)))
|
||||
if config.lines != 0, config.lines <= numberOfSublayers { numberOfSublayers = config.lines }
|
||||
return numberOfSublayers
|
||||
let calculatedNumberOfLines = Int(neededLines)
|
||||
guard calculatedNumberOfLines > 0 else {
|
||||
return 1
|
||||
}
|
||||
|
||||
if definedNumberOfLines > 0, definedNumberOfLines <= calculatedNumberOfLines {
|
||||
return definedNumberOfLines
|
||||
}
|
||||
|
||||
return calculatedNumberOfLines
|
||||
}
|
||||
|
||||
private func flipRectForRTLIfNeeded(_ rect: CGRect, isRTL: Bool) -> CGRect {
|
||||
var newRect = rect
|
||||
if isRTL {
|
||||
newRect.origin.x = (superlayer?.bounds.width ?? 0) - rect.origin.x - rect.width
|
||||
}
|
||||
return newRect
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +156,8 @@ public extension CALayer {
|
||||
var pulse: CAAnimation {
|
||||
let pulseAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.backgroundColor))
|
||||
pulseAnimation.fromValue = backgroundColor
|
||||
|
||||
// swiftlint:disable:next force_unwrapping
|
||||
pulseAnimation.toValue = UIColor(cgColor: backgroundColor!).complementaryColor.cgColor
|
||||
pulseAnimation.duration = 1
|
||||
pulseAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
@@ -124,25 +167,6 @@ public extension CALayer {
|
||||
return pulseAnimation
|
||||
}
|
||||
|
||||
var sliding: CAAnimation {
|
||||
let startPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.startPoint))
|
||||
startPointAnim.fromValue = CGPoint(x: -1, y: 0.5)
|
||||
startPointAnim.toValue = CGPoint(x:1, y: 0.5)
|
||||
|
||||
let endPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.endPoint))
|
||||
endPointAnim.fromValue = CGPoint(x: 0, y: 0.5)
|
||||
endPointAnim.toValue = CGPoint(x:2, y: 0.5)
|
||||
|
||||
let animGroup = CAAnimationGroup()
|
||||
animGroup.animations = [startPointAnim, endPointAnim]
|
||||
animGroup.duration = 1.5
|
||||
animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
|
||||
animGroup.repeatCount = .infinity
|
||||
animGroup.isRemovedOnCompletion = false
|
||||
|
||||
return animGroup
|
||||
}
|
||||
|
||||
func playAnimation(_ anim: SkeletonLayerAnimation, key: String, completion: (() -> Void)? = nil) {
|
||||
skeletonSublayers.recursiveSearch(leafBlock: {
|
||||
DispatchQueue.main.async { CATransaction.begin() }
|
||||
|
||||
@@ -4,10 +4,10 @@ import Foundation
|
||||
|
||||
extension Int {
|
||||
var whitespace: String {
|
||||
return whitespaces
|
||||
whitespaces
|
||||
}
|
||||
|
||||
var whitespaces: String {
|
||||
return String(repeating: " ", count: self)
|
||||
String(repeating: " ", count: self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ extension UIColor {
|
||||
|
||||
public var complementaryColor: UIColor {
|
||||
if #available(iOS 13, tvOS 13, *) {
|
||||
return UIColor { traitCollection in
|
||||
return UIColor { _ in
|
||||
return self.isLight() ? self.darker : self.lighter
|
||||
}
|
||||
} else {
|
||||
@@ -31,11 +31,11 @@ extension UIColor {
|
||||
}
|
||||
|
||||
public var lighter: UIColor {
|
||||
return adjust(by: 1.35)
|
||||
adjust(by: 1.35)
|
||||
}
|
||||
|
||||
public var darker: UIColor {
|
||||
return adjust(by: 0.94)
|
||||
adjust(by: 0.94)
|
||||
}
|
||||
|
||||
func adjust(by percent: CGFloat) -> UIColor {
|
||||
@@ -45,11 +45,12 @@ extension UIColor {
|
||||
}
|
||||
|
||||
func makeGradient() -> [UIColor] {
|
||||
return [self, self.complementaryColor, self]
|
||||
[self, self.complementaryColor, self]
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIColor {
|
||||
// swiftlint:disable operator_usage_whitespace
|
||||
static var greenSea = UIColor(0x16a085)
|
||||
static var turquoise = UIColor(0x1abc9c)
|
||||
static var emerald = UIColor(0x2ecc71)
|
||||
@@ -71,13 +72,16 @@ public extension UIColor {
|
||||
static var pomegranate = UIColor(0xc0392b)
|
||||
static var silver = UIColor(0xbdc3c7)
|
||||
static var asbestos = UIColor(0x7f8c8d)
|
||||
|
||||
// swiftlint:enable operator_usage_whitespace
|
||||
|
||||
static var skeletonDefault: UIColor {
|
||||
if #available(iOS 13, tvOS 13, *) {
|
||||
return UIColor { traitCollection in
|
||||
switch traitCollection.userInterfaceStyle {
|
||||
case .dark: return .darkClouds
|
||||
default: return .clouds
|
||||
case .dark:
|
||||
return .darkClouds
|
||||
default:
|
||||
return .clouds
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -13,15 +13,13 @@ extension UITableView {
|
||||
headerRect = rectForHeader(inSection: $1)
|
||||
}
|
||||
|
||||
if headerRect != nil {
|
||||
let visiblePartOfTableView: CGRect = CGRect(
|
||||
x: contentOffset.x,
|
||||
y: contentOffset.y,
|
||||
width: bounds.size.width,
|
||||
height: bounds.size.height
|
||||
)
|
||||
if let headerRect = headerRect {
|
||||
let visiblePartOfTableView = CGRect(x: contentOffset.x,
|
||||
y: contentOffset.y,
|
||||
width: bounds.size.width,
|
||||
height: bounds.size.height)
|
||||
|
||||
if (visiblePartOfTableView.intersects(headerRect!)) {
|
||||
if visiblePartOfTableView.intersects(headerRect) {
|
||||
return $0 + [$1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright © 2020 SkeletonView. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
// MARK: Frame
|
||||
extension UIView {
|
||||
var widthConstraints: [NSLayoutConstraint] {
|
||||
nonContentSizeLayoutConstraints.filter { $0.firstAttribute == NSLayoutConstraint.Attribute.width }
|
||||
}
|
||||
|
||||
var heightConstraints: [NSLayoutConstraint] {
|
||||
nonContentSizeLayoutConstraints.filter { $0.firstAttribute == NSLayoutConstraint.Attribute.height }
|
||||
}
|
||||
|
||||
var skeletonHeightConstraints: [NSLayoutConstraint] {
|
||||
nonContentSizeLayoutConstraints.filter {
|
||||
$0.firstAttribute == NSLayoutConstraint.Attribute.height
|
||||
&& $0.identifier?.contains("SkeletonView.Constraint.Height") ?? false
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setHeight(equalToConstant constant: CGFloat) -> NSLayoutConstraint {
|
||||
let heightConstraint = heightAnchor.constraint(equalToConstant: constant)
|
||||
heightConstraint.identifier = "SkeletonView.Constraint.Height.\(constant)"
|
||||
NSLayoutConstraint.activate([heightConstraint])
|
||||
return heightConstraint
|
||||
}
|
||||
|
||||
var nonContentSizeLayoutConstraints: [NSLayoutConstraint] {
|
||||
constraints.filter({ "\(type(of: $0))" != "NSContentSizeLayoutConstraint" })
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import UIKit
|
||||
// codebeat:disable[TOO_MANY_IVARS]
|
||||
enum ViewAssociatedKeys {
|
||||
static var skeletonable = "skeletonable"
|
||||
static var hiddenWhenSkeletonIsActive = "hiddenWhenSkeletonIsActive"
|
||||
static var status = "status"
|
||||
static var skeletonLayer = "layer"
|
||||
static var flowDelegate = "flowDelegate"
|
||||
@@ -12,8 +13,10 @@ enum ViewAssociatedKeys {
|
||||
static var viewState = "viewState"
|
||||
static var labelViewState = "labelViewState"
|
||||
static var imageViewState = "imageViewState"
|
||||
static var buttonViewState = "buttonViewState"
|
||||
static var currentSkeletonConfig = "currentSkeletonConfig"
|
||||
static var skeletonCornerRadius = "skeletonCornerRadius"
|
||||
static var disabledWhenSkeletonIsActive = "disabledWhenSkeletonIsActive"
|
||||
}
|
||||
// codebeat:enable[TOO_MANY_IVARS]
|
||||
|
||||
@@ -38,13 +41,17 @@ extension UIView {
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.currentSkeletonConfig) }
|
||||
}
|
||||
|
||||
var status: Status! {
|
||||
var status: Status {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.status) as? Status ?? .off }
|
||||
set { ao_set(newValue ?? .off, pkey: &ViewAssociatedKeys.status) }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.status) }
|
||||
}
|
||||
|
||||
var isSkeletonAnimated: Bool! {
|
||||
var isSkeletonAnimated: Bool {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.isSkeletonAnimated) as? Bool ?? false }
|
||||
set { ao_set(newValue ?? false, pkey: &ViewAssociatedKeys.isSkeletonAnimated) }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.isSkeletonAnimated) }
|
||||
}
|
||||
|
||||
var isSuperviewAStackView: Bool {
|
||||
superview is UIStackView
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,32 +10,38 @@ import UIKit
|
||||
|
||||
// MARK: Frame
|
||||
extension UIView {
|
||||
var maxBoundsEstimated: CGRect {
|
||||
var definedMaxBounds: CGRect {
|
||||
if let parentStackView = (superview as? UIStackView) {
|
||||
var origin: CGPoint = .zero
|
||||
switch parentStackView.alignment {
|
||||
case .trailing:
|
||||
origin.x = maxWidthEstimated
|
||||
origin.x = definedMaxWidth
|
||||
default:
|
||||
break
|
||||
}
|
||||
return CGRect(origin: origin, size: maxSizeEstimated)
|
||||
return CGRect(origin: origin, size: definedMaxSize)
|
||||
}
|
||||
return CGRect(origin: .zero, size: maxSizeEstimated)
|
||||
return CGRect(origin: .zero, size: definedMaxSize)
|
||||
}
|
||||
|
||||
var maxSizeEstimated: CGSize {
|
||||
return CGSize(width: maxWidthEstimated, height: maxHeightEstimated)
|
||||
var definedMaxSize: CGSize {
|
||||
CGSize(width: definedMaxWidth, height: definedMaxHeight)
|
||||
}
|
||||
|
||||
var maxWidthEstimated: CGFloat {
|
||||
let constraintsWidth = nonContentSizeLayoutConstraints.filter({ $0.firstAttribute == NSLayoutConstraint.Attribute.width })
|
||||
return max(between: frame.size.width, andContantsOf: constraintsWidth)
|
||||
var definedMaxWidth: CGFloat {
|
||||
max(between: frame.size.width, andContantsOf: widthConstraints)
|
||||
}
|
||||
|
||||
var maxHeightEstimated: CGFloat {
|
||||
let constraintsHeight = nonContentSizeLayoutConstraints.filter({ $0.firstAttribute == NSLayoutConstraint.Attribute.height })
|
||||
return max(between: frame.size.height, andContantsOf: constraintsHeight)
|
||||
var definedMaxHeight: CGFloat {
|
||||
max(between: frame.size.height, andContantsOf: heightConstraints)
|
||||
}
|
||||
|
||||
var isRTL: Bool {
|
||||
if #available(iOS 10.0, *), #available(tvOS 10.0, *) {
|
||||
return effectiveUserInterfaceLayoutDirection == .rightToLeft
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func max(between value: CGFloat, andContantsOf constraints: [NSLayoutConstraint]) -> CGFloat {
|
||||
@@ -46,8 +52,4 @@ extension UIView {
|
||||
})
|
||||
return max
|
||||
}
|
||||
|
||||
var nonContentSizeLayoutConstraints: [NSLayoutConstraint] {
|
||||
return constraints.filter({ "\(type(of: $0))" != "NSContentSizeLayoutConstraint" })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,18 @@ public extension UIView {
|
||||
get { return skeletonable }
|
||||
set { skeletonable = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var isHiddenWhenSkeletonIsActive: Bool {
|
||||
get { return hiddenWhenSkeletonIsActive }
|
||||
set { hiddenWhenSkeletonIsActive = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var isUserInteractionDisabledWhenSkeletonIsActive: Bool {
|
||||
get { return disabledWhenSkeletonIsActive }
|
||||
set { disabledWhenSkeletonIsActive = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var skeletonCornerRadius: Float {
|
||||
@@ -16,17 +28,26 @@ public extension UIView {
|
||||
}
|
||||
|
||||
var isSkeletonActive: Bool {
|
||||
return status == .on || (subviewsSkeletonables.first(where: { $0.isSkeletonActive }) != nil)
|
||||
return status == .on || subviewsSkeletonables.contains(where: { $0.isSkeletonActive })
|
||||
}
|
||||
|
||||
private var skeletonable: Bool! {
|
||||
private var skeletonable: Bool {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.skeletonable) as? Bool ?? false }
|
||||
set { ao_set(newValue ?? false, pkey: &ViewAssociatedKeys.skeletonable) }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.skeletonable) }
|
||||
}
|
||||
|
||||
private var hiddenWhenSkeletonIsActive: Bool {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.hiddenWhenSkeletonIsActive) as? Bool ?? false }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.hiddenWhenSkeletonIsActive) }
|
||||
}
|
||||
|
||||
private var disabledWhenSkeletonIsActive: Bool {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.disabledWhenSkeletonIsActive) as? Bool ?? true }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.disabledWhenSkeletonIsActive) }
|
||||
}
|
||||
|
||||
private var skeletonableCornerRadius: Float! {
|
||||
private var skeletonableCornerRadius: Float {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.skeletonCornerRadius) as? Float ?? 0.0 }
|
||||
set { ao_set(newValue ?? 0.0, pkey: &ViewAssociatedKeys.skeletonCornerRadius) }
|
||||
set { ao_set(newValue, pkey: &ViewAssociatedKeys.skeletonCornerRadius) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
//Partially copy/pasted from https://github.com/jameslintaylor/AssociatedObjects/blob/master/AssociatedObjects/AssociatedObjects.swift
|
||||
// Partially copy/pasted from https://github.com/jameslintaylor/AssociatedObjects/blob/master/AssociatedObjects/AssociatedObjects.swift
|
||||
enum AssociationPolicy: UInt {
|
||||
// raw values map to objc_AssociationPolicy's raw values
|
||||
case assign = 0
|
||||
@@ -12,11 +12,12 @@ enum AssociationPolicy: UInt {
|
||||
case retainNonatomic = 1
|
||||
|
||||
var objc: objc_AssociationPolicy {
|
||||
// swiftlint:disable:next force_unwrapping
|
||||
return objc_AssociationPolicy(rawValue: rawValue)!
|
||||
}
|
||||
}
|
||||
|
||||
protocol AssociatedObjects: class { }
|
||||
protocol AssociatedObjects: AnyObject { }
|
||||
|
||||
// transparent wrappers
|
||||
extension AssociatedObjects {
|
||||
|
||||
@@ -10,6 +10,10 @@ import UIKit
|
||||
|
||||
extension UIView {
|
||||
@objc func prepareViewForSkeleton() {
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
startTransition { [weak self] in
|
||||
self?.backgroundColor = .clear
|
||||
}
|
||||
@@ -17,10 +21,48 @@ extension UIView {
|
||||
}
|
||||
|
||||
extension UILabel {
|
||||
var desiredHeightBasedOnNumberOfLines: CGFloat {
|
||||
let lineHeight = constraintHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
let spaceNeededForEachLine = lineHeight * CGFloat(numberOfLines)
|
||||
let spaceNeededForSpaces = skeletonLineSpacing * CGFloat(numberOfLines - 1)
|
||||
let padding = paddingInsets.top + paddingInsets.bottom
|
||||
|
||||
return spaceNeededForEachLine + spaceNeededForSpaces + padding
|
||||
}
|
||||
|
||||
func updateHeightConstraintsIfNeeded() {
|
||||
guard numberOfLines > 1 || numberOfLines == 0 else { return }
|
||||
|
||||
// Workaround to simulate content when the label is contained in a `UIStackView`.
|
||||
if isSuperviewAStackView, bounds.height == 0 {
|
||||
// This is a placeholder text to simulate content because it's contained in a stack view in order to prevent that the content size will be zero.
|
||||
text = " "
|
||||
}
|
||||
|
||||
let desiredHeight = desiredHeightBasedOnNumberOfLines
|
||||
if desiredHeight > definedMaxHeight {
|
||||
backupHeightConstraints = heightConstraints
|
||||
NSLayoutConstraint.deactivate(heightConstraints)
|
||||
setHeight(equalToConstant: desiredHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func restoreBackupHeightConstraintsIfNeeded() {
|
||||
guard !backupHeightConstraints.isEmpty else { return }
|
||||
NSLayoutConstraint.activate(backupHeightConstraints)
|
||||
backupHeightConstraints.removeAll()
|
||||
}
|
||||
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
resignFirstResponder()
|
||||
startTransition { [weak self] in
|
||||
self?.updateHeightConstraintsIfNeeded()
|
||||
self?.textColor = .clear
|
||||
}
|
||||
}
|
||||
@@ -29,6 +71,11 @@ extension UILabel {
|
||||
extension UITextView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
resignFirstResponder()
|
||||
startTransition { [weak self] in
|
||||
self?.textColor = .clear
|
||||
@@ -36,11 +83,42 @@ extension UITextView {
|
||||
}
|
||||
}
|
||||
|
||||
extension UITextField {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
resignFirstResponder()
|
||||
|
||||
startTransition { [weak self] in
|
||||
self?.textColor = .clear
|
||||
self?.placeholder = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIImageView {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
startTransition { [weak self] in
|
||||
self?.image = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIButton {
|
||||
override func prepareViewForSkeleton() {
|
||||
backgroundColor = .clear
|
||||
|
||||
if isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
startTransition { [weak self] in
|
||||
self?.setTitle(nil, for: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ protocol IterableElement {}
|
||||
extension UIView: IterableElement {}
|
||||
extension CALayer: IterableElement {}
|
||||
|
||||
//MARK: Recursive
|
||||
// MARK: Recursive
|
||||
protocol Recursive {
|
||||
associatedtype Element: IterableElement
|
||||
func recursiveSearch(leafBlock: VoidBlock, recursiveBlock: RecursiveBlock<Element>)
|
||||
@@ -17,12 +17,10 @@ protocol Recursive {
|
||||
|
||||
extension Array: Recursive where Element: IterableElement {
|
||||
func recursiveSearch(leafBlock: VoidBlock, recursiveBlock: RecursiveBlock<Element>) {
|
||||
guard count > 0 else {
|
||||
guard !isEmpty else {
|
||||
leafBlock()
|
||||
return
|
||||
}
|
||||
forEach { recursiveBlock($0) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,22 +4,29 @@ import Foundation
|
||||
|
||||
extension DispatchQueue {
|
||||
private static var _onceTracker = [String]()
|
||||
|
||||
class func once(token: String, block:()->Void) {
|
||||
|
||||
class func once(token: String, block: () -> Void) {
|
||||
objc_sync_enter(self); defer { objc_sync_exit(self) }
|
||||
guard !_onceTracker.contains(token) else { return }
|
||||
|
||||
|
||||
_onceTracker.append(token)
|
||||
block()
|
||||
}
|
||||
|
||||
class func removeOnce(token: String, block: () -> Void) {
|
||||
objc_sync_enter(self); defer { objc_sync_exit(self) }
|
||||
guard let index = _onceTracker.firstIndex(of: token) else { return }
|
||||
_onceTracker.remove(at: index)
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
func swizzle(selector originalSelector: Selector, with swizzledSelector: Selector, inClass: AnyClass, usingClass: AnyClass) {
|
||||
guard let originalMethod = class_getInstanceMethod(inClass, originalSelector),
|
||||
let swizzledMethod = class_getInstanceMethod(usingClass, swizzledSelector)
|
||||
else { return }
|
||||
|
||||
if (class_addMethod(inClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))) {
|
||||
|
||||
if class_addMethod(inClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) {
|
||||
class_replaceMethod(inClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
|
||||
} else {
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
|
||||
@@ -7,17 +7,14 @@ enum MultilineAssociatedKeys {
|
||||
static var multilineCornerRadius = "multilineCornerRadius"
|
||||
static var multilineSpacing = "multilineSpacing"
|
||||
static var paddingInsets = "paddingInsets"
|
||||
static var backupHeightConstraints = "backupHeightConstraints"
|
||||
}
|
||||
|
||||
protocol ContainsMultilineText {
|
||||
var multilineTextFont: UIFont? { get }
|
||||
var constraintHeight: CGFloat? { get }
|
||||
var numLines: Int { get }
|
||||
var lastLineFillingPercent: Int { get }
|
||||
var multilineCornerRadius: Int { get }
|
||||
var multilineSpacing: CGFloat { get }
|
||||
var paddingInsets: UIEdgeInsets { get }
|
||||
}
|
||||
|
||||
extension ContainsMultilineText {
|
||||
var numLines: Int { return 0 }
|
||||
}
|
||||
|
||||
@@ -8,17 +8,19 @@ public extension UILabel {
|
||||
get { return lastLineFillingPercent }
|
||||
set { lastLineFillingPercent = min(newValue, 100) }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var linesCornerRadius: Int {
|
||||
get { return multilineCornerRadius }
|
||||
set { multilineCornerRadius = min(newValue, 10) }
|
||||
set { multilineCornerRadius = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var skeletonLineSpacing: CGFloat {
|
||||
get { return multilineSpacing }
|
||||
set { multilineSpacing = min(newValue, 10) }
|
||||
set { multilineSpacing = newValue }
|
||||
}
|
||||
@IBInspectable
|
||||
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
@@ -26,10 +28,10 @@ public extension UILabel {
|
||||
}
|
||||
|
||||
extension UILabel: ContainsMultilineText {
|
||||
var multilineTextFont: UIFont? {
|
||||
return font
|
||||
}
|
||||
|
||||
var constraintHeight: CGFloat? {
|
||||
backupHeightConstraints.first?.constant
|
||||
}
|
||||
|
||||
var numLines: Int {
|
||||
return numberOfLines
|
||||
}
|
||||
@@ -53,4 +55,9 @@ extension UILabel: ContainsMultilineText {
|
||||
get { return ao_get(pkey: &MultilineAssociatedKeys.paddingInsets) as? UIEdgeInsets ?? .zero }
|
||||
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.paddingInsets) }
|
||||
}
|
||||
|
||||
var backupHeightConstraints: [NSLayoutConstraint] {
|
||||
get { return ao_get(pkey: &MultilineAssociatedKeys.backupHeightConstraints) as? [NSLayoutConstraint] ?? [] }
|
||||
set { ao_set(newValue, pkey: &MultilineAssociatedKeys.backupHeightConstraints) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,15 @@ public extension UITextView {
|
||||
@IBInspectable
|
||||
var linesCornerRadius: Int {
|
||||
get { return multilineCornerRadius }
|
||||
set { multilineCornerRadius = min(newValue, 10) }
|
||||
set { multilineCornerRadius = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var skeletonLineSpacing: CGFloat {
|
||||
get { return multilineSpacing }
|
||||
set { multilineSpacing = min(newValue, 10) }
|
||||
set { multilineSpacing = newValue }
|
||||
}
|
||||
|
||||
@IBInspectable
|
||||
var skeletonPaddingInsets: UIEdgeInsets {
|
||||
get { return paddingInsets }
|
||||
set { paddingInsets = newValue }
|
||||
@@ -29,9 +28,13 @@ public extension UITextView {
|
||||
}
|
||||
|
||||
extension UITextView: ContainsMultilineText {
|
||||
var multilineTextFont: UIFont? {
|
||||
return font
|
||||
}
|
||||
var constraintHeight: CGFloat? {
|
||||
heightConstraints.first?.constant
|
||||
}
|
||||
|
||||
var numLines: Int {
|
||||
-1
|
||||
}
|
||||
|
||||
var lastLineFillingPercent: Int {
|
||||
get {
|
||||
|
||||
@@ -24,20 +24,26 @@ extension UIView: Recoverable {
|
||||
}
|
||||
|
||||
@objc func recoverViewState(forced: Bool) {
|
||||
guard let safeViewState = viewState else { return }
|
||||
guard let storedViewState = viewState else { return }
|
||||
|
||||
startTransition { [weak self] in
|
||||
self?.layer.cornerRadius = safeViewState.cornerRadius
|
||||
self?.layer.masksToBounds = safeViewState.clipToBounds
|
||||
guard let self = self else { return }
|
||||
|
||||
if safeViewState.backgroundColor != self?.backgroundColor || forced {
|
||||
self?.backgroundColor = safeViewState.backgroundColor
|
||||
self.layer.cornerRadius = storedViewState.cornerRadius
|
||||
self.layer.masksToBounds = storedViewState.clipToBounds
|
||||
|
||||
if self.isUserInteractionDisabledWhenSkeletonIsActive {
|
||||
self.isUserInteractionEnabled = storedViewState.isUserInteractionsEnabled
|
||||
}
|
||||
|
||||
if self.backgroundColor == .clear || forced {
|
||||
self.backgroundColor = storedViewState.backgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UILabel{
|
||||
extension UILabel {
|
||||
var labelState: RecoverableTextViewState? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.labelViewState) as? RecoverableTextViewState }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.labelViewState) }
|
||||
@@ -51,14 +57,22 @@ extension UILabel{
|
||||
override func recoverViewState(forced: Bool) {
|
||||
super.recoverViewState(forced: forced)
|
||||
startTransition { [weak self] in
|
||||
self?.textColor = self?.labelState?.textColor
|
||||
self?.text = self?.labelState?.text
|
||||
self?.isUserInteractionEnabled = self?.labelState?.isUserInteractionsEnabled ?? false
|
||||
guard let self = self,
|
||||
let storedLabelState = self.labelState else {
|
||||
return
|
||||
}
|
||||
|
||||
NSLayoutConstraint.deactivate(self.skeletonHeightConstraints)
|
||||
self.restoreBackupHeightConstraintsIfNeeded()
|
||||
|
||||
if self.textColor == .clear || forced {
|
||||
self.textColor = storedLabelState.textColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UITextView{
|
||||
extension UITextView {
|
||||
var textState: RecoverableTextViewState? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.labelViewState) as? RecoverableTextViewState }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.labelViewState) }
|
||||
@@ -72,9 +86,38 @@ extension UITextView{
|
||||
override func recoverViewState(forced: Bool) {
|
||||
super.recoverViewState(forced: forced)
|
||||
startTransition { [weak self] in
|
||||
self?.textColor = self?.textState?.textColor
|
||||
self?.text = self?.textState?.text
|
||||
self?.isUserInteractionEnabled = self?.textState?.isUserInteractionsEnabled ?? false
|
||||
guard let storedLabelState = self?.textState else { return }
|
||||
|
||||
if self?.textColor == .clear || forced {
|
||||
self?.textColor = storedLabelState.textColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UITextField {
|
||||
var textState: RecoverableTextFieldState? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.labelViewState) as? RecoverableTextFieldState }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.labelViewState) }
|
||||
}
|
||||
|
||||
override func saveViewState() {
|
||||
super.saveViewState()
|
||||
textState = RecoverableTextFieldState(view: self)
|
||||
}
|
||||
|
||||
override func recoverViewState(forced: Bool) {
|
||||
super.recoverViewState(forced: forced)
|
||||
startTransition { [weak self] in
|
||||
guard let storedLabelState = self?.textState else { return }
|
||||
|
||||
if self?.textColor == .clear || forced {
|
||||
self?.textColor = storedLabelState.textColor
|
||||
}
|
||||
|
||||
if self?.placeholder == nil || forced {
|
||||
self?.placeholder = storedLabelState.placeholder
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,3 +140,24 @@ extension UIImageView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIButton {
|
||||
var buttonState: RecoverableButtonViewState? {
|
||||
get { return ao_get(pkey: &ViewAssociatedKeys.buttonViewState) as? RecoverableButtonViewState }
|
||||
set { ao_setOptional(newValue, pkey: &ViewAssociatedKeys.buttonViewState) }
|
||||
}
|
||||
|
||||
override func saveViewState() {
|
||||
super.saveViewState()
|
||||
buttonState = RecoverableButtonViewState(view: self)
|
||||
}
|
||||
|
||||
override func recoverViewState(forced: Bool) {
|
||||
super.recoverViewState(forced: forced)
|
||||
startTransition { [weak self] in
|
||||
if self?.title(for: .normal) == nil {
|
||||
self?.setTitle(self?.buttonState?.title, for: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,29 +12,35 @@ struct RecoverableViewState {
|
||||
var backgroundColor: UIColor?
|
||||
var cornerRadius: CGFloat
|
||||
var clipToBounds: Bool
|
||||
var isUserInteractionsEnabled: Bool
|
||||
|
||||
init(view: UIView) {
|
||||
self.backgroundColor = view.backgroundColor
|
||||
self.clipToBounds = view.layer.masksToBounds
|
||||
self.cornerRadius = view.layer.cornerRadius
|
||||
self.isUserInteractionsEnabled = view.isUserInteractionEnabled
|
||||
}
|
||||
}
|
||||
|
||||
struct RecoverableTextViewState {
|
||||
var text: String?
|
||||
var textColor: UIColor?
|
||||
var isUserInteractionsEnabled: Bool
|
||||
|
||||
init(view: UILabel) {
|
||||
self.textColor = view.textColor
|
||||
self.text = view.text
|
||||
self.isUserInteractionsEnabled = view.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(view: UITextView) {
|
||||
self.textColor = view.textColor
|
||||
self.text = view.text
|
||||
self.isUserInteractionsEnabled = view.isUserInteractionEnabled
|
||||
}
|
||||
}
|
||||
|
||||
struct RecoverableTextFieldState {
|
||||
var textColor: UIColor?
|
||||
var placeholder: String?
|
||||
|
||||
init(view: UITextField) {
|
||||
self.textColor = view.textColor
|
||||
self.placeholder = view.placeholder
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,3 +51,11 @@ struct RecoverableImageViewState {
|
||||
self.image = view.image
|
||||
}
|
||||
}
|
||||
|
||||
struct RecoverableButtonViewState {
|
||||
var title: String?
|
||||
|
||||
init(view: UIButton) {
|
||||
self.title = view.titleLabel?.text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,42 +18,42 @@ public enum GradientDirection {
|
||||
case topLeftBottomRight
|
||||
case bottomRightTopLeft
|
||||
|
||||
public func slidingAnimation(duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation {
|
||||
return SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: self, duration: duration)
|
||||
public func slidingAnimation(duration: CFTimeInterval = 1.5, autoreverses: Bool = false) -> SkeletonLayerAnimation {
|
||||
return SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: self, duration: duration, autoreverses: autoreverses)
|
||||
}
|
||||
|
||||
// codebeat:disable[ABC]
|
||||
var startPoint: GradientAnimationPoint {
|
||||
switch self {
|
||||
case .leftRight:
|
||||
return (from: CGPoint(x:-1, y:0.5), to: CGPoint(x:1, y:0.5))
|
||||
return (from: CGPoint(x: -1, y: 0.5), to: CGPoint(x: 1, y: 0.5))
|
||||
case .rightLeft:
|
||||
return (from: CGPoint(x:1, y:0.5), to: CGPoint(x:-1, y:0.5))
|
||||
return (from: CGPoint(x: 1, y: 0.5), to: CGPoint(x: -1, y: 0.5))
|
||||
case .topBottom:
|
||||
return (from: CGPoint(x:0.5, y:-1), to: CGPoint(x:0.5, y:1))
|
||||
return (from: CGPoint(x: 0.5, y: -1), to: CGPoint(x: 0.5, y: 1))
|
||||
case .bottomTop:
|
||||
return (from: CGPoint(x:0.5, y:1), to: CGPoint(x:0.5, y:-1))
|
||||
return (from: CGPoint(x: 0.5, y: 1), to: CGPoint(x: 0.5, y: -1))
|
||||
case .topLeftBottomRight:
|
||||
return (from: CGPoint(x:-1, y:-1), to: CGPoint(x:1, y:1))
|
||||
return (from: CGPoint(x: -1, y: -1), to: CGPoint(x: 1, y: 1))
|
||||
case .bottomRightTopLeft:
|
||||
return (from: CGPoint(x:1, y:1), to: CGPoint(x:-1, y:-1))
|
||||
return (from: CGPoint(x: 1, y: 1), to: CGPoint(x: -1, y: -1))
|
||||
}
|
||||
}
|
||||
|
||||
var endPoint: GradientAnimationPoint {
|
||||
switch self {
|
||||
case .leftRight:
|
||||
return (from: CGPoint(x:0, y:0.5), to: CGPoint(x:2, y:0.5))
|
||||
return (from: CGPoint(x: 0, y: 0.5), to: CGPoint(x: 2, y: 0.5))
|
||||
case .rightLeft:
|
||||
return ( from: CGPoint(x:2, y:0.5), to: CGPoint(x:0, y:0.5))
|
||||
return ( from: CGPoint(x: 2, y: 0.5), to: CGPoint(x: 0, y: 0.5))
|
||||
case .topBottom:
|
||||
return ( from: CGPoint(x:0.5, y:0), to: CGPoint(x:0.5, y:2))
|
||||
return ( from: CGPoint(x: 0.5, y: 0), to: CGPoint(x: 0.5, y: 2))
|
||||
case .bottomTop:
|
||||
return ( from: CGPoint(x:0.5, y:2), to: CGPoint(x:0.5, y:0))
|
||||
return ( from: CGPoint(x: 0.5, y: 2), to: CGPoint(x: 0.5, y: 0))
|
||||
case .topLeftBottomRight:
|
||||
return ( from: CGPoint(x:0, y:0), to: CGPoint(x:2, y:2))
|
||||
return ( from: CGPoint(x: 0, y: 0), to: CGPoint(x: 2, y: 2))
|
||||
case .bottomRightTopLeft:
|
||||
return ( from: CGPoint(x:2, y:2), to: CGPoint(x:0, y:0))
|
||||
return ( from: CGPoint(x: 2, y: 2), to: CGPoint(x: 0, y: 0))
|
||||
}
|
||||
}
|
||||
// codebeat:enable[ABC]
|
||||
@@ -62,9 +62,8 @@ public enum GradientDirection {
|
||||
public class SkeletonAnimationBuilder {
|
||||
public init() { }
|
||||
|
||||
public func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation {
|
||||
return { layer in
|
||||
|
||||
public func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5, autoreverses: Bool = false) -> SkeletonLayerAnimation {
|
||||
return { _ in
|
||||
let startPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.startPoint))
|
||||
startPointAnim.fromValue = direction.startPoint.from
|
||||
startPointAnim.toValue = direction.startPoint.to
|
||||
@@ -78,6 +77,7 @@ public class SkeletonAnimationBuilder {
|
||||
animGroup.duration = duration
|
||||
animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
|
||||
animGroup.repeatCount = .infinity
|
||||
animGroup.autoreverses = autoreverses
|
||||
animGroup.isRemovedOnCompletion = false
|
||||
|
||||
return animGroup
|
||||
|
||||
@@ -22,14 +22,12 @@ struct SkeletonConfig {
|
||||
/// Transition style
|
||||
var transition: SkeletonTransitionStyle
|
||||
|
||||
init(
|
||||
type: SkeletonType,
|
||||
colors: [UIColor],
|
||||
gradientDirection: GradientDirection? = nil,
|
||||
animated: Bool = false,
|
||||
animation: SkeletonLayerAnimation? = nil,
|
||||
transition: SkeletonTransitionStyle = .crossDissolve(0.25)
|
||||
) {
|
||||
init(type: SkeletonType,
|
||||
colors: [UIColor],
|
||||
gradientDirection: GradientDirection? = nil,
|
||||
animated: Bool = false,
|
||||
animation: SkeletonLayerAnimation? = nil,
|
||||
transition: SkeletonTransitionStyle = .crossDissolve(0.25)) {
|
||||
self.type = type
|
||||
self.colors = colors
|
||||
self.gradientDirection = gradientDirection
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol SkeletonFlowDelegate {
|
||||
protocol SkeletonFlowDelegate: AnyObject {
|
||||
func willBeginShowingSkeletons(rootView: UIView)
|
||||
func didShowSkeletons(rootView: UIView)
|
||||
func willBeginUpdatingSkeletons(rootView: UIView)
|
||||
|
||||
+14
-11
@@ -23,12 +23,12 @@ public enum SkeletonType {
|
||||
}
|
||||
}
|
||||
|
||||
var layerAnimation: SkeletonLayerAnimation {
|
||||
func defaultLayerAnimation(isRTL: Bool) -> SkeletonLayerAnimation {
|
||||
switch self {
|
||||
case .solid:
|
||||
return { $0.pulse }
|
||||
case .gradient:
|
||||
return { $0.sliding }
|
||||
return { SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: isRTL ? .rightLeft : .leftRight) }()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ struct SkeletonLayer {
|
||||
self.holder = holder
|
||||
self.maskLayer = type.layer
|
||||
self.maskLayer.anchorPoint = .zero
|
||||
self.maskLayer.bounds = holder.maxBoundsEstimated
|
||||
self.maskLayer.bounds = holder.definedMaxBounds
|
||||
self.maskLayer.cornerRadius = CGFloat(holder.skeletonCornerRadius)
|
||||
addTextLinesIfNeeded()
|
||||
self.maskLayer.tint(withColors: colors)
|
||||
@@ -61,7 +61,7 @@ struct SkeletonLayer {
|
||||
}
|
||||
|
||||
func layoutIfNeeded() {
|
||||
if let bounds = holder?.maxBoundsEstimated {
|
||||
if let bounds = holder?.definedMaxBounds {
|
||||
maskLayer.bounds = bounds
|
||||
}
|
||||
updateLinesIfNeeded()
|
||||
@@ -83,34 +83,37 @@ struct SkeletonLayer {
|
||||
/// If there is more than one line, or custom preferences have been set for a single line, draw custom layers
|
||||
func addTextLinesIfNeeded() {
|
||||
guard let textView = holderAsTextView else { return }
|
||||
|
||||
let lineHeight = textView.constraintHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numLines,
|
||||
lineHeight: textView.multilineTextFont?.lineHeight,
|
||||
lineHeight: lineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
multilineCornerRadius: textView.multilineCornerRadius,
|
||||
multilineSpacing: textView.multilineSpacing,
|
||||
paddingInsets: textView.paddingInsets)
|
||||
paddingInsets: textView.paddingInsets,
|
||||
isRTL: holder?.isRTL ?? false)
|
||||
|
||||
maskLayer.addMultilinesLayers(for: config)
|
||||
}
|
||||
|
||||
func updateLinesIfNeeded() {
|
||||
guard let textView = holderAsTextView else { return }
|
||||
let lineHeight = textView.constraintHeight ?? SkeletonAppearance.default.multilineHeight
|
||||
let config = SkeletonMultilinesLayerConfig(lines: textView.numLines,
|
||||
lineHeight: textView.multilineTextFont?.lineHeight,
|
||||
lineHeight: lineHeight,
|
||||
type: type,
|
||||
lastLineFillPercent: textView.lastLineFillingPercent,
|
||||
multilineCornerRadius: textView.multilineCornerRadius,
|
||||
multilineSpacing: textView.multilineSpacing,
|
||||
paddingInsets: textView.paddingInsets)
|
||||
paddingInsets: textView.paddingInsets,
|
||||
isRTL: holder?.isRTL ?? false)
|
||||
|
||||
maskLayer.updateMultilinesLayers(for: config)
|
||||
}
|
||||
|
||||
var holderAsTextView: ContainsMultilineText? {
|
||||
guard let textView = holder as? ContainsMultilineText,
|
||||
(textView.numLines == 0 || textView.numLines > 1 || textView.numLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
|
||||
(textView.numLines == -1 || textView.numLines == 0 || textView.numLines > 1 || textView.numLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
|
||||
return nil
|
||||
}
|
||||
return textView
|
||||
@@ -119,7 +122,7 @@ struct SkeletonLayer {
|
||||
|
||||
extension SkeletonLayer {
|
||||
func start(_ anim: SkeletonLayerAnimation? = nil, completion: (() -> Void)? = nil) {
|
||||
let animation = anim ?? type.layerAnimation
|
||||
let animation = anim ?? type.defaultLayerAnimation(isRTL: holder?.isRTL ?? false)
|
||||
contentLayer.playAnimation(animation, key: "skeletonAnimation", completion: completion)
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,9 @@ extension UIView {
|
||||
}
|
||||
|
||||
private func recursiveShowSkeleton(skeletonConfig config: SkeletonConfig, root: UIView? = nil) {
|
||||
if isHiddenWhenSkeletonIsActive {
|
||||
isHidden = true
|
||||
}
|
||||
guard isSkeletonable && !isSkeletonActive else { return }
|
||||
currentSkeletonConfig = config
|
||||
swizzleLayoutSubviews()
|
||||
@@ -121,7 +124,7 @@ extension UIView {
|
||||
addDummyDataSourceIfNeeded()
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
showSkeletonIfNotActive(skeletonConfig: config)
|
||||
}){ subview in
|
||||
}) { subview in
|
||||
subview.recursiveShowSkeleton(skeletonConfig: config)
|
||||
}
|
||||
|
||||
@@ -133,7 +136,7 @@ extension UIView {
|
||||
private func showSkeletonIfNotActive(skeletonConfig config: SkeletonConfig) {
|
||||
guard !isSkeletonActive else { return }
|
||||
saveViewState()
|
||||
isUserInteractionEnabled = false
|
||||
|
||||
prepareViewForSkeleton()
|
||||
addSkeletonLayer(skeletonConfig: config)
|
||||
}
|
||||
@@ -183,8 +186,12 @@ extension UIView {
|
||||
|
||||
private func recursiveHideSkeleton(reloadDataAfter reload: Bool, transition: SkeletonTransitionStyle, root: UIView? = nil) {
|
||||
guard isSkeletonActive else { return }
|
||||
if isHiddenWhenSkeletonIsActive {
|
||||
isHidden = false
|
||||
}
|
||||
currentSkeletonConfig?.transition = transition
|
||||
isUserInteractionEnabled = true
|
||||
unSwizzleLayoutSubviews()
|
||||
unSwizzleTraitCollectionDidChange()
|
||||
removeDummyDataSourceIfNeeded(reloadAfter: reload)
|
||||
subviewsSkeletonables.recursiveSearch(leafBlock: {
|
||||
recoverViewState(forced: false)
|
||||
@@ -228,6 +235,17 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
private func unSwizzleLayoutSubviews() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
DispatchQueue.removeOnce(token: "UIView.SkeletonView.swizzleLayoutSubviews") {
|
||||
swizzle(selector: #selector(UIView.skeletonLayoutSubviews),
|
||||
with: #selector(UIView.layoutSubviews),
|
||||
inClass: UIView.self,
|
||||
usingClass: UIView.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func swizzleTraitCollectionDidChange() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
DispatchQueue.once(token: "UIView.SkeletonView.swizzleTraitCollectionDidChange") {
|
||||
@@ -238,6 +256,17 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func unSwizzleTraitCollectionDidChange() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
DispatchQueue.removeOnce(token: "UIView.SkeletonView.swizzleTraitCollectionDidChange") {
|
||||
swizzle(selector: #selector(UIView.skeletonTraitCollectionDidChange(_:)),
|
||||
with: #selector(UIView.traitCollectionDidChange(_:)),
|
||||
inClass: UIView.self,
|
||||
usingClass: UIView.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
@@ -4,46 +4,50 @@ import UIKit
|
||||
|
||||
extension UIView {
|
||||
@objc var subviewsSkeletonables: [UIView] {
|
||||
return subviewsToSkeleton.filter { $0.isSkeletonable }
|
||||
subviewsToSkeleton.filter { $0.isSkeletonable }
|
||||
}
|
||||
|
||||
@objc var subviewsToSkeleton: [UIView] {
|
||||
return subviews
|
||||
subviews
|
||||
}
|
||||
}
|
||||
|
||||
extension UITableView {
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return visibleCells + visibleSectionHeaders + visibleSectionFooters
|
||||
// on `UIViewController'S onViewDidLoad`, the window is still nil.
|
||||
// Some developer trying to call `view.showAnimatedSkeleton()`
|
||||
// when the request or data is loading which sometimes happens before the ViewDidAppear
|
||||
guard window != nil else { return [] }
|
||||
return subviews
|
||||
}
|
||||
}
|
||||
|
||||
extension UITableViewCell {
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return contentView.subviews
|
||||
contentView.subviews
|
||||
}
|
||||
}
|
||||
|
||||
extension UITableViewHeaderFooterView {
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return contentView.subviews
|
||||
contentView.subviews
|
||||
}
|
||||
}
|
||||
|
||||
extension UICollectionView {
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return subviews
|
||||
subviews
|
||||
}
|
||||
}
|
||||
|
||||
extension UICollectionViewCell {
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return contentView.subviews
|
||||
contentView.subviews
|
||||
}
|
||||
}
|
||||
|
||||
extension UIStackView {
|
||||
override var subviewsToSkeleton: [UIView] {
|
||||
return arrangedSubviews
|
||||
arrangedSubviews
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ extension CALayer {
|
||||
switch transition {
|
||||
case .none:
|
||||
completion?()
|
||||
break
|
||||
case .crossDissolve(let duration):
|
||||
layer.contentLayer.setOpacity(from: 0, to: 1, duration: duration, completion: completion)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
default_platform(:ios)
|
||||
podspec_name = "SkeletonView.podspec"
|
||||
|
||||
lane :bump_version do |options|
|
||||
version_bump_podspec(path: @podspec_name, version_number: options[:next_version])
|
||||
end
|
||||
|
||||
lane :release_current do
|
||||
version = version_get_podspec(path: @podspec_name)
|
||||
if git_tag_exists(tag: version)
|
||||
|
||||
+6
-1
@@ -12,9 +12,14 @@ Install _fastlane_ using
|
||||
```
|
||||
[sudo] gem install fastlane -NV
|
||||
```
|
||||
or alternatively using `brew cask install fastlane`
|
||||
or alternatively using `brew install fastlane`
|
||||
|
||||
# Available Actions
|
||||
### bump_version
|
||||
```
|
||||
fastlane bump_version
|
||||
```
|
||||
|
||||
### release_current
|
||||
```
|
||||
fastlane release_current
|
||||
|
||||
Reference in New Issue
Block a user