![](../Assets/header2.jpg)

codebeat badge SkeletonView Playground
Twitter: @JuanpeCatalan PayPal License

**๐ŸŒŽ ๋ฒˆ์—ญ์— ๋„์›€์„ ์ฃผ์‹ ๋ถ„๋“ค: [๐Ÿ‡ฌ๐Ÿ‡ง](../README.md) . [๐Ÿ‡จ๐Ÿ‡ณ](README_zh.md) . [๐Ÿ‡ง๐Ÿ‡ท](README_pt-br.md) . [๐Ÿ‡ฐ๐Ÿ‡ท](README_ko.md) . [๐Ÿ‡ซ๐Ÿ‡ท](README_fr.md) . [๐Ÿ‡ฉ๐Ÿ‡ช](README_de.md)** ์˜ค๋Š˜๋‚  ๊ฑฐ์˜ ๋Œ€๋ถ€๋ถ„์˜ ์•ฑ๋“ค์€ ๋น„๋™๊ธฐ ๋ฐฉ์‹์˜ API ํ˜ธ์ถœ์„ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ž‘๋™ํ•˜๋Š”๋™์•ˆ ๊ฐœ๋ฐœ์ž๋“ค์€ ์ž‘์—…์ด ์‹คํ–‰๋˜๊ณ  ์žˆ๋‹ค๋Š”๊ฒƒ์„ ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด์„œ ๋กœ๋”ฉ ๋ทฐ๋ฅผ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค. ```SkeletonView```๋Š” ์ด๋Ÿฌํ•œ ํ•„์š”์— ์˜ํ•ด ๊ณ ์•ˆ๋˜์—ˆ๊ณ , ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ๋ฌด์—‡์ธ๊ฐ€ ๋กœ๋”ฉ์ด ๋˜๊ณ  ์žˆ๋‹ค๋Š”๊ฒƒ์„ ๋ณด์—ฌ์ฃผ๋ฉด์„œ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ฝ˜ํ…์ธ ์— ๋Œ€ํ•ด์„œ๋„ ๋ฏธ๋ฆฌ ์ค€๋น„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์šฐ์•„ํ•˜๊ฒŒ ํ‘œํ˜„ํ• ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค ๋ง˜๊ป ๋ˆ„๋ฆฌ์„ธ์š” ๐Ÿ™‚ * [๊ธฐ๋Šฅ](#-features) * [๊ฐ€์ด๋“œ](#-guides) * [์„ค์น˜๋ฐฉ๋ฒ•](#-installation) * [Cocoapods](#using-cocoapods) * [Carthage](#using-carthage) * [SPM](#using-swift-package-manager) * [์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋‚˜์š”?](#-how-to-use) * [Collections](#-collections) * [Multiline text](#-multiline-text) * [Custom colors](#-custom-colors) * [Appearance](#-appearance) * [Custom animations](#-custom-animations) * [Hierarchy](#-hierarchy) * [Debug](#-debug) * [๋ฌธ์„œํ™”](#-documentation) * [์ง€์›๋˜๋Š” OS์™€ SDK ๋ฒ„์ „](#-supported-os--sdk-versions) * [Next steps](#-next-steps) * [Contributing](#-contributing) * [Mentions](#-mentions) * [๊ฐœ๋ฐœ์ž](#-author) * [๋ผ์ด์„ผ์Šค](#-license) ## ๐ŸŒŸ ๊ธฐ๋Šฅ - [x] ์‚ฌ์šฉ์ด ์‰ฝ์Šต๋‹ˆ๋‹ค - [x] ๋ชจ๋“  `UIView`์—์„œ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค - [x] ์ „์ฒด ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค - [x] ๊ณตํ†ต์œผ๋กœ ์ด์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค (iPhone & iPad) - [x] `Interface Builder` ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. - [x] ๊ฐ„๋‹จํ•œ ์Šค์œ„ํ”„ํŠธ ๋ฌธ๋ฒ• - [x] ๊ฐ€๋ณ๊ณ  ๊ฐ€๋…์„ฑ ์ข‹์€ ์ฝ”๋“œ ## ๐ŸŽฌ ์‚ฌ์šฉ๊ฐ€์ด๋“œ [](https://youtu.be/75kgOhWsPNA) ## ๐Ÿ“ฒ ์„ค์น˜ ๋ฐฉ๋ฒ• #### [CocoaPods](https://cocoapods.org) ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ๋‹น์‹ ์˜ ํ”„๋กœ์ ํŠธ `Podfile` ํŒŒ์ผ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค: ```ruby pod "SkeletonView" ``` #### [Carthage](https://github.com/carthage)๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ๋‹น์‹ ์˜ ํ”„๋กœ์ ํŠธ `Cartfile` ํŒŒ์ผ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค: ```bash github "Juanpe/SkeletonView" ``` #### [Swift Package Manager](https://github.com/apple/swift-package-manager)๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ๋‹น์‹ ์˜ ํ”„๋กœ์ ํŠธ์— Swift package๋ฅผ ์„ค์ •ํ•œ๋‹ค๋ฉด, `SkeletonView` ๋ฅผ `Package.swift` ํŒŒ์ผ์— ์žˆ๋Š” `dependencies`์— ์ถ”๊ฐ€ํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ```swift dependencies: [ .package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.6") ] ``` ## ๐Ÿ’ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋‚˜์š”? `SkeletonView` ๋ฅผ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋”ฑ **3** ๋‹จ๊ณ„๋งŒ ๊ธฐ์–ตํ•˜์„ธ์š”: **1.** ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ํŒŒ์ผ์—์„œ `SkeletonView` ๋ฅผ `Import` ํ•ฉ๋‹ˆ๋‹ค. ```swift import SkeletonView ``` **2.** ์ž, ๊ทธ๋ ‡๋‹ค๋ฉด UIView ์†์„ฑ์— `skeletonables` ๋ฅผ ์ด์šฉํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‘๊ฐ€์ง€ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค **์ฝ”๋“œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•:** ```swift avatarImageView.isSkeletonable = true ``` **์ธํ„ฐํŽ˜์ด์Šค๋นŒ๋” / ์Šคํ† ๋ฆฌ๋ณด๋“œ๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•:** ![](../Assets/storyboard.png) **3.** ๋‹น์‹ ์ด ๋ทฐ๋ฅผ ์„ธํŒ…ํ• ๋•Œ, **skeleton** ์˜ต์…˜์„ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด **4** ๊ฐ€์ง€ ์˜ต์…˜์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค: ```swift (1) view.showSkeleton() // Solid (2) view.showGradientSkeleton() // Gradient (3) view.showAnimatedSkeleton() // Solid animated (4) view.showAnimatedGradientSkeleton() // Gradient animated ``` **๋ฏธ๋ฆฌ๋ณด๊ธฐ**
Solid
Gradient
Solid Animated
Gradient Animated
> **์ค‘์š”!** >>```SkeletonView``` ๋Š” ์žฌ๊ท€์ ์œผ๋กœ ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค, ๋งŒ์•ฝ ๋ชจ๋“  ๋ทฐ์— ๋Œ€ํ•ด์„œ skeleton์„ ํ˜ธ์ถœํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ๋ฉ”์ธ ์ปจํ…Œ์ด๋„ˆ ๋ทฐ์—์„œ show `method`๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์ž๋ฉด UIViewControllers๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ### ๐ŸŒฟ Collections ํ˜„์žฌ, ```SkeletonView``` ๋Š” ```UITableView``` ์™€ ```UICollectionView```์—์„œ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. #### UITableView ๋งŒ์•ฝ ```UITableView```์—์„œ skeleton์„ ํ˜ธ์ถœํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ```SkeletonTableViewDataSource``` protocol ์„ ๊ตฌํ˜„ํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ``` swift public protocol SkeletonTableViewDataSource: UITableViewDataSource { func numSections(in collectionSkeletonView: UITableView) -> Int func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier } ``` ํ•ด๋‹น ํ”„๋กœํ† ํด์€ ๋ณด์‹œ๋‹ค์‹œํ”ผ ```UITableViewDataSource```๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ตฌํ˜„ํ•˜์˜€์œผ๋ฏ€๋กœ, skeleton์˜ protocol๊ณผ ๋Œ€์ฒด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœํ† ์ฝœ์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: ``` swift func numSections(in collectionSkeletonView: UITableView) -> Int // Default: 1 ``` ``` swift func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int // Default: // ์ „์ฒด ํ…Œ์ด๋ธ” ๋ทฐ๋ฅผ ์ฑ„์šฐ๋Š”๋ฐ ํ•„์š”ํ•œ ์…€ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค ``` ํ•ด๋‹น ๋ฉ”์†Œ๋“œ๋Š” ๋‹น์‹ ์ด ๊ตฌํ˜„ํ•˜์—ฌ์•ผํ•  cell identifier์„ ์•„๋Š” ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค, ํ•ด๋‹น ๋ฉ”์†Œ๋“œ๋Š” ๊ธฐ๋ณธ์œผ๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•„๋„๋ฉ๋‹ˆ๋‹ค : ``` swift func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier ``` **Example** ``` swift func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier { return "CellIdentifier" } ``` > **์ค‘์š”!** > ๋งŒ์•ฝ ์‚ฌ์ด์ฆˆ๊ฐ€ ๋ณ€ํ•˜๋Š” ์…€์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด (`tableView.rowHeight = UITableViewAutomaticDimension` ),`estimatedRowHeight`๋ฅผ ๋ฌด์กฐ๊ฑด ์ •์˜ํ•ด์ฃผ์„ธ์š”. ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿซ **์–ด๋–ป๊ฒŒ ํŠน์ • ์š”์†Œ์— skeleton ์„ ์ง€์ •ํ• ๊นŒ์š”?** ์•„๋ž˜์˜ ๊ทธ๋ฆผ์€ `UITableView` ์—์„œ ํŠน์ •ํ•œ ์š”์†Œ์— skeleton ์„ ์ง€์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ฃผ๋Š” ์ด๋ฏธ์ง€ ์ž…๋‹ˆ๋‹ค: ![](../Assets/tableview_scheme.png) ์œ„์˜ ์ด๋ฏธ์ง€์—์„œ ๋ณด์ด๋“ฏ, ํ…Œ์ด๋ธ” ๋ทฐ์™€ ์…€์— ๋“ค์–ด๊ฐ€๋Š” UI ์š”์†Œ๋“ค์—๋Š” ์ ์šฉ์„ ํ•ด์•ผํ•˜์ง€๋งŒ, `contentView`์— skeleton์„ ์ ์šฉํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. #### UICollectionView ```UICollectionView``` ์— ์ ์šฉ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, ```SkeletonCollectionViewDataSource``` protocol ์„ ๊ตฌํ˜„ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ``` swift public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource { func numSections(in collectionSkeletonView: UICollectionView) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier } ``` ```UITableView``` ์™€ ์‚ฌ์šฉ๋ฐฉ๋ฒ•์€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ### ๐Ÿ“ฐ Multiline text ![](../Assets/multilines2.png) ํ…์ŠคํŠธ๊ฐ€ ๋“ค์–ด์žˆ๋Š” ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ```SkeletonView``` ์—์„œ ํ…์ŠคํŠธ์˜ ๋ผ์ธ์„ ๊ทธ๋ ค์ค๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ , ์›ํ•˜๋Š” ๋ผ์ธ ์ˆ˜๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ```numberOfLines``` ์„ 0์œผ๋กœ ์„ค์ •ํ•œ๋‹ค๋ฉด, ์ž๋™์œผ๋กœ ํ•„์š”ํ•œ ๋ผ์ธ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•ด์„œ ๊ทธ๋ ค์ค๋‹ˆ๋‹ค. ๋Œ€์‹  ๊ฐ’์ด ์„ค์ •๋˜์–ด์žˆ๋‹ค๋ฉด ์„ค์ •๋œ ์ˆ˜๋งŒํผ์˜ ๋ผ์ธ์ด ๊ทธ๋ ค์ง‘๋‹ˆ๋‹ค. ##### ๐ŸŽ› Customize ๋‹น์‹ ์€ ๋ฉ€ํ‹ฐ๋ผ์ธ์„ ์œ„ํ•ด ๋ช‡๊ฐ€์ง€ ์˜ต์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. | ์†์„ฑ | ๊ฐ’ | ๊ธฐ๋ณธ๊ฐ’ | ๋ฏธ๋ฆฌ๋ณด๊ธฐ | | ----------------------------------------------- | --------- | ----- | ---------------------------------- | | ๋งˆ์ง€๋ง‰ ๋ผ์ธ์˜ **ํผ์„ผํŠธ** ๋ฅผ ์ง€์ • ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. | `0...100` | `70%` | ![](../Assets/multiline_lastline.png) | | ๋ผ์ธ์˜ **Corner radius** ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (**์ƒˆ๋กœ์šด๊ธฐ๋Šฅ**) | `0...10` | `0` | ![](../Assets/multiline_corner.png) | ๋ผ์ธ์˜ radius๋ฅผ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” **์ฝ”๋“œ** ๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค, ์•„๋ž˜ ์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค: ```swift descriptionTextView.lastLineFillPercent = 50 descriptionTextView.linesCornerRadius = 5 ``` ํ˜น์€ **IB/Storyboard** ๋ฅผ ์ด์šฉํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ![](../Assets/multiline_customize.png) ### ๐ŸŽจ Custom colors ๋‹น์‹ ์€ skeleton์˜ ์ƒ‰์ƒ์„ ์ง€์ • ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•˜๊ฒŒ ์›ํ•˜๋Š” ์ƒ‰์ƒ์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. **๋‹จ์ƒ‰ ์ด์šฉ๋ฐฉ๋ฒ•** ``` swift view.showSkeleton(usingColor: UIColor.gray) // Solid // or view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0)) ``` **๊ทธ๋ผ๋””์–ธํŠธ ์ด์šฉ ๋ฐฉ๋ฒ•** ``` swift let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue) view.showGradientSkeleton(usingGradient: gradient) // Gradient ``` ๊ฒŒ๋‹ค๊ฐ€, ```SkeletonView``` ์—์„œ๋Š” 20๊ฐ€์ง€์˜ ๊ธฐ๋ณธ ์ปฌ๋Ÿฌ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค ๐Ÿค™๐Ÿผ ```UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...``` ![](../Assets/flatcolors.png) ###### ์œ„ ์ด๋ฏธ์ง€๋Š” [https://flatuicolors.com](https://flatuicolors.com) ์‚ฌ์ดํŠธ์—์„œ ๋ฐœ์ทŒํ–ˆ์Šต๋‹ˆ๋‹ค. ### ๐Ÿฆ‹ Appearance **์ƒˆ๋กœ์šด ์‚ฌํ•ญ** skeleton ์€ ๊ธฐ๋ณธ์„ค์ • ๊ฐ’์ด ์ •ํ•ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ปค์Šคํ…€ ์ปฌ๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด, `SkeletonView` ์— ์ง€์ • ๋˜์–ด์žˆ๋Š” ๊ธฐ๋ณธ์„ค์ •์„ ์‚ฌ์šฉํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ์„ค์ •๊ฐ’: - **tintColor**: UIColor - *๊ธฐ๋ณธ๊ฐ’: .clouds* - **gradient**: SkeletonGradient - *๊ธฐ๋ณธ๊ฐ’: SkeletonGradient(baseColor: .clouds)* - **multilineHeight**: CGFloat - *๊ธฐ๋ณธ๊ฐ’: 15* - **multilineSpacing**: CGFloat - *๊ธฐ๋ณธ๊ฐ’: 10* - **multilineLastLineFillPercent**: Int - *๊ธฐ๋ณธ๊ฐ’: 70* - **multilineCornerRadius**: Int - *๊ธฐ๋ณธ๊ฐ’: 0* `SkeletonAppearance.default` ์—๋Š” ์‚ฌ์šฉ ๋˜์–ด์ง€๋Š” ๊ธฐ๋ณธ ๊ฐ’๋“ค์ด ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค . ์•„๋ž˜์˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: ```Swift SkeletonAppearance.default.multilineHeight = 20 SkeletonAppearance.default.tintColor = .green ``` ### ๐Ÿค“ ์ปค์Šคํ…€ ์• ๋‹ˆ๋ฉ”์ด์…˜ ```SkeletonView``` ์—๋Š” ๋‘๊ฐ€์ง€ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค, ๋‹จ์ƒ‰ *๋ฐ”์šด์Šค* ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ๊ทธ๋ผ๋””์–ธํŠธ *์Šฌ๋ผ์ด๋“œ* ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ž…๋‹ˆ๋‹ค . ๊ฒŒ๋‹ค๊ฐ€, ์ง์ ‘ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ •๋ง ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. Skeleton ์—์„œ๋Š” `showAnimatedSkeleton` ํ•จ์ˆ˜๋ฅผ ```SkeletonLayerAnimation```์— ์ •์˜ํ•˜์—ฌ ๋งž์ถคํ˜• ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ```swift public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation ``` ํ•จ์ˆ˜๋Š” ์ด๋ ‡๊ฒŒ ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค: ```swift view.showAnimatedSkeleton { (layer) -> CAAnimation in let animation = CAAnimation() // Customize here your animation return animation } ``` ```SkeletonAnimationBuilder```์˜ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ```SkeletonLayerAnimation```์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด์ œ, ๊ทธ๋ผ๋””์–ธํŠธ๋ฅผ ์œ„ํ•œ **์Šฌ๋ผ์ด๋”ฉ ์• ๋‹ˆ๋ฉ”์ด์…˜** ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค, ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์œ„ํ•œ **๋ฐฉํ–ฅ** ๊ณผ **์ง€์†์‹œ๊ฐ„** ์„ ์„ค์ • ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๊ธฐ๋ณธ๊ฐ’ = 1.5์ดˆ). ```swift // func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight) view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation) ``` ```GradientDirection``` ๋Š” enum ์œผ๋กœ ์ •์˜ ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค., ์•„๋ž˜์˜ ์ผ€์ด์Šค๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”: | ๋ฐฉํ–ฅ | ๋ฏธ๋ฆฌ๋ณด๊ธฐ | | ------------------- | ---------------------------------------------- | | .leftRight | ![](../Assets/sliding_left_to_right.gif) | | .rightLeft | ![](../Assets/sliding_right_to_left.gif) | | .topBottom | ![](../Assets/sliding_top_to_bottom.gif) | | .bottomTop | ![](../Assets/sliding_bottom_to_top.gif) | | .topLeftBottomRight | ![](../Assets/sliding_topLeft_to_bottomRight.gif) | | .bottomRightTopLeft | ![](../Assets/sliding_bottomRight_to_topLeft.gif) | > **๐Ÿ˜‰ ๊ฟ€ํŒ!** ์Šฌ๋ผ์ด๋”ฉ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋˜๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค, ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”: >>```let animation = GradientDirection.leftToRight.slidingAnimation()``` ### ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๊ณ„์ธต ๊ตฌ์กฐ ```SkeletonView```๋Š” ์žฌ๊ท€์ ์ž…๋‹ˆ๋‹ค , ๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ๋Š” skeleton์ด ํšจ์œจ์ ์œผ๋กœ ์ž‘๋™ํ•˜๊ธฐ๋ฅผ ์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ฐ€๋Šฅํ•œ ๋นจ๋ฆฌ ์žฌ๊ท€์ž‘์—…์„ ์ค‘๋‹จํ•˜๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ด์œ ๋•Œ๋ฌธ์— ๋ฐ˜๋“œ์‹œ ์ปจํ…Œ์ด๋„ˆ ๋ทฐ๋ฅผ `Skeletonable` ๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค, `skeletonable` ๋˜์ง€ ์•Š๋Š” ๋ทฐ๋ฅผ ๋งŒ๋‚˜๋Š” ์ˆœ๊ฐ„ ์žฌ๊ท€ ์ž‘์—…์„ ์ค‘๋‹จํ•˜๊ธฐ ๋–„๋ฌธ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ์ด๋ฏธ์ง€๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š” ์ด๋ฏธ์ง€๋Š” ํ•œ๋ˆˆ์— ์ดํ•ด๋˜์‹ค๊ฒ๋‹ˆ๋‹ค: > ```รฌsSkeletonable```= โ˜ ๏ธ | ์„ค์ •๊ฐ’ | ๊ฒฐ๊ณผ | | ----------------------------------------- | --------------------------------------------- | | ![](../Assets/no_skeletonable.png) | ![](../Assets/no_skeletonables_result.png) | | ![](../Assets/container_no_skeletonable.png) | ![](../Assets/no_skeletonables_result.png) | | ![](../Assets/container_skeletonable.png) | ![](../Assets/container_skeletonable_result.png) | | ![](../Assets/all_skeletonables.png) | ![](../Assets/all_skeletonables_result.png) | ### ๐Ÿ”ฌ ๋””๋ฒ„๊ทธ **์ƒˆ๋กœ์šด์†Œ์‹** ์–ด๋–ค๊ฒƒ๋“ค์ด ์ž˜ ๋™์ž‘ ํ•˜์ง€ ์•Š์„๋•Œ๋ฅผ ์œ„ํ•ด ๋””๋ฒ„๊ทธ ์ž‘์—…์„ ์šฉ์ดํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ `SkeletonView` ์—๋Š” ๋ช‡๊ฐ€์ง€ ์ƒˆ๋กœ์šด ๊ฒƒ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒซ๋ฒˆ์จฐ๋กœ, `UIView` ์—์„œ skeleton ์ •๋ณด๋ฅผ ๋ณด๊ธฐ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ง€์›ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค: ```swift var skeletonDescription: String ``` skeleton์€ ์ด๋ ‡๊ฒŒ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค: ![](../Assets/debug_description.png) ๊ทธ๋ฆฌ๊ณ , ์ƒˆ๋กœ์šด **๋””๋ฒ„๊ทธ ๋ชจ๋“œ**๋ฅผ ํ™œ์„ฑํ™” ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•˜๊ฒŒ `SKELETON_DEBUG` ์ด๋ผ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ด ํ™œ์„ฑํ™” ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ![](../Assets/debug_mode.png) ๊ทธ๋Ÿฐ ์ดํ›„ skeleton์ด ๋‚˜์˜ค๋ฉด Xcode ์ฝ˜์†”์ฐฝ์—์„œ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ์ œ๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”.
### ๐Ÿ“š ๋ฌธ์„œํ™” ์กฐ๊ธˆ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”...๐Ÿ˜… ### ๐Ÿ“‹ ์ง€์› ๊ฐ€๋Šฅํ•œ OS & SDK ๋ฒ„์ „ * iOS 9.0+ * tvOS 9.0+ * Swift 4.2 ## ๐Ÿ“ฌ ์˜ˆ์ •๋œ ๊ธฐ๋Šฅ๋“ค * [x] ๋ฉ€ํ‹ฐ๋ผ์ธ ์—์„œ์˜ ๋งˆ์ง€๋ง‰ ๋ผ์ธ์˜ ์ฑ„์šฐ๊ธฐ ๋น„์œจ ์„ค์ • * [x] ๋”๋งŽ์€ ๊ทธ๋ผ๋””์–ธํŠธ ์• ๋‹ˆ๋ฉ”์ด์…˜ * [x] resizable cells ์ง€์› * [x] CollectionView ํ˜ธํ™˜ * [x] tvOS ํ˜ธํ™˜ * [x] recovery state ์ถ”๊ฐ€ * [x] Custom default appearance * [x] ๋””๋ฒ„๊ทธ ๋ชจ๋“œ * [ ] Custom collections ํ˜ธํ™˜ * [ ] skeletons ๊ฐ€ ๋ณด์ด๊ฑฐ๋‚˜ ๊ฐ€๋ ค์งˆ๋•Œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ถ”๊ฐ€ * [ ] MacOS ์™€ WatchOS ํ˜ธํ™˜ ## โค๏ธ ๊ธฐ์—ฌํ•˜๊ธฐ ์ด ํ”„๋กœ์ ํŠธ๋Š” ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ ์ž…๋‹ˆ๋‹ค, ๋งˆ์ŒํŽธํ•˜๊ฒŒ ๊ธฐ์—ฌํ•ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค ์–ด๋–ป๊ฒŒ ํ•˜๋ƒ๊ตฌ์š”? - ์ƒˆ๋กœ์šด [์ด์Šˆ](https://github.com/Juanpe/SkeletonView/issues/new)๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. - [email](mailto://juanpecatalan.com)์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. - ๋‹น์‹ ์˜ ์ˆ˜์ •์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค, pull request๋ฅผ ํฌํ•จํ•œ ์ˆ˜์ •์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด [๊ธฐ์—ฌ์ž๋ชฉ๋ก](https://github.com/Juanpe/SkeletonView/graphs/contributors) ###### [SwiftPlate](https://github.com/JohnSundell/SwiftPlate)๋ฅผ ํ†ตํ•ด ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค ## ๐Ÿ“ข ์†Œ์‹๋“ค - [iOS Dev Weekly #327](https://iosdevweekly.com/issues/327#start) - [Hacking with Swift Articles](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful) - [Top 10 Swift Articles November](https://medium.mybridge.co/swift-top-10-articles-for-the-past-month-v-nov-2017-dfed7861cd65) - [30 Amazing iOS Swift Libraries (v2018)](https://medium.mybridge.co/30-amazing-ios-swift-libraries-for-the-past-year-v-2018-7cf15027eee9) - [AppCoda Weekly #44](http://digest.appcoda.com/issues/appcoda-weekly-issue-44-81899) - [iOS Cookies Newsletter #103](https://us11.campaign-archive.com/?u=cd1f3ed33c6527331d82107ba&id=48131a516d) - [Swift Developments Newsletter #113](https://andybargh.com/swiftdevelopments-113/) - [iOS Goodies #204](http://ios-goodies.com/post/167557280951/week-204) - [Swift Weekly #96](http://digest.swiftweekly.com/issues/swift-weekly-issue-96-81759) - [CocoaControls](https://www.cocoacontrols.com/controls/skeletonview) - [Awesome iOS Newsletter #74](https://ios.libhunt.com/newsletter/74) - [Swift News #36](https://www.youtube.com/watch?v=mAGpsQiy6so) ## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ๊ฐœ๋ฐœ์ž [1.1]: http://i.imgur.com/tXSoThF.png [1]: http://www.twitter.com/JuanpeCatalan * Juanpe Catalรกn [![alt text][1.1]][1] Buy me a coffee ## ๐Ÿ‘ฎ๐Ÿป ๋ผ์ด์„ผ์Šค ``` MIT License Copyright (c) 2017 Juanpe Catalรกn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ```