codebeat badge SkeletonView Playground

特性指南安装使用其他贡献

🌎 README 提供其他语言版本:🇪🇸 . 🇨🇳 . 🇧🇷 . 🇰🇷 . 🇫🇷 . 🇩🇪

如今,几乎所有应用程序都有异步进程,例如 API 请求、长时间运行的进程等。在进程工作时,开发人员通常会放置一个加载视图,以向用户表明正在进行某些操作。

SkeletonView 的构思是为了满足此需求,它提供了一种优雅的方式来向用户展示正在发生某些事情,并让他们为等待的内容做好准备。

请尽情享用!🙂

🌟 特性

🎬 指南

SkeletonView 指南 - 入门 如何在 Swift 5.2 中使用 Skeleton View 创建加载视图 作者:iKh4ever Studio 在 App 中创建 Skeleton 加载视图 (Swift 5) - Xcode 11, 2020 作者:iOS Academy 如何在 iOS 中创建数据加载动画 作者:MoureDev

📲 安装

pod 'SkeletonView'
github "Juanpe/SkeletonView"
dependencies: [
  .package(url: "https://github.com/Juanpe/SkeletonView.git", from: "1.7.0")
]

📣 重要!

自 1.30.0 版本起,SkeletonView 支持 XCFrameworks,因此如果要将其作为 XCFramework 安装,请使用 此仓库

🐒 使用

只需 3 个步骤即可使用 SkeletonView

1️⃣ 在适当的位置导入 SkeletonView。

import SkeletonView

2️⃣ 现在,设置哪些视图将是 skeletonables。您可以通过两种方式实现此目的

使用代码

avatarImageView.isSkeletonable = true

使用 IB/Storyboards

3️⃣ 设置好视图后,您可以显示 skeleton。为此,您有 4 个选择

(1) view.showSkeleton()                 // Solid
(2) view.showGradientSkeleton()         // Gradient
(3) view.showAnimatedSkeleton()         // Solid animated
(4) view.showAnimatedGradientSkeleton() // Gradient animated

预览

纯色 渐变 纯色动画 渐变动画

📣 重要!

SkeletonView 是递归的,因此如果您想在所有可骨架化视图中显示骨架,您只需在主容器视图中调用 show 方法。例如,使用 UIViewControllers

🌿 集合

SkeletonViewUITableViewUICollectionView 兼容。

UITableView

如果您想在 UITableView 中显示骨架,则需要遵循 SkeletonTableViewDataSource 协议。

public protocol SkeletonTableViewDataSource: UITableViewDataSource {
    func numSections(in collectionSkeletonView: UITableView) -> Int // Default: 1
    func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
    func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier
    func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? // Default: nil
    func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath)
}

如您所见,此协议继承自 UITableViewDataSource,因此您可以将此协议替换为骨架协议。

此协议对于某些方法具有默认实现。例如,每个部分中的行数是在运行时计算的

func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int
// Default:
// It calculates how many cells need to populate whole tableview

📣 重要!

如果您在上述方法中返回 UITableView.automaticNumberOfSkeletonRows,它的行为类似于默认行为(即,它会计算需要多少个单元格来填充整个 tableview)。

您只需要实现一个方法即可让 Skeleton 知道单元格标识符。此方法没有默认实现

func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
   return "CellIdentifier"
}

默认情况下,该库从每个 indexPath 中对单元格进行出列,但如果您想在骨架出现之前进行一些更改,您也可以这样做

func collectionSkeletonView(_ skeletonView: UITableView, skeletonCellForRowAt indexPath: IndexPath) -> UITableViewCell? {
    let cell = skeletonView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as? Cell
    cell?.textField.isHidden = indexPath.row == 0
    return cell
}

如果您希望将出列部分留给库,您可以使用此方法配置单元格

func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) {
    let cell = cell as? Cell
    cell?.textField.isHidden = indexPath.row == 0
}

此外,您可以骨架化 header 和 footer。您需要遵循 SkeletonTableViewDelegate 协议。

public protocol SkeletonTableViewDelegate: UITableViewDelegate {
    func collectionSkeletonView(_ skeletonView: UITableView, identifierForHeaderInSection section: Int) -> ReusableHeaderFooterIdentifier? // default: nil
    func collectionSkeletonView(_ skeletonView: UITableView, identifierForFooterInSection section: Int) -> ReusableHeaderFooterIdentifier? // default: nil
}

📣 重要!

1️⃣ 如果您正在使用可调整大小的单元格(tableView.rowHeight = UITableViewAutomaticDimension),则必须定义 estimatedRowHeight

2️⃣ 当您在 UITableViewCell 中添加元素时,您应该将其添加到 contentView 而不是直接添加到单元格。

self.contentView.addSubview(titleLabel)          
self.addSubview(titleLabel) 

UICollectionView

对于 UICollectionView,您需要遵循 SkeletonCollectionViewDataSource 协议。

public protocol SkeletonCollectionViewDataSource: UICollectionViewDataSource {
    func numSections(in collectionSkeletonView: UICollectionView) -> Int  // default: 1
    func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier
    func collectionSkeletonView(_ skeletonView: UICollectionView, supplementaryViewIdentifierOfKind: String, at indexPath: IndexPath) -> ReusableCellIdentifier? // default: nil
    func collectionSkeletonView(_ skeletonView: UICollectionView, skeletonCellForItemAt indexPath: IndexPath) -> UICollectionViewCell?  // default: nil
    func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath)
    func collectionSkeletonView(_ skeletonView: UICollectionView, prepareViewForSkeleton view: UICollectionReusableView, at indexPath: IndexPath)
}

其余过程与 UITableView 相同

🔠 文本

当使用带有文本的元素时,SkeletonView 会绘制线条来模拟文本。

您可以为多行元素设置一些属性。

属性 类型 默认值 预览
lastLineFillPercent CGFloat 70
linesCornerRadius Int 0
skeletonLineSpacing CGFloat 10
skeletonPaddingInsets UIEdgeInsets .zero
skeletonTextLineHeight SkeletonTextLineHeight .fixed(15)
skeletonTextNumberOfLines SkeletonTextNumberOfLines .inherited

使用代码修改百分比或半径,请设置属性

descriptionTextView.lastLineFillPercent = 50
descriptionTextView.linesCornerRadius = 5

或者,如果您更喜欢使用 IB/Storyboard


如何定义行数?

默认情况下,行数与 numberOfLines 属性的值相同。并且,如果设置为 ,它将计算需要多少行来填充整个骨架并绘制它。

但是,如果您想设置特定数量的骨架线条,您可以通过设置 skeletonTextNumberOfLines 属性来完成。此属性有两个可能的值,inherited 返回 numberOfLines 值,而 custom(Int) 返回指定为关联值的特定行数。

例如

label.skeletonTextNumberOfLines = 3   // .custom(3)

⚠️已弃用!

useFontLineHeight 已弃用。您可以使用 skeletonTextLineHeight 代替

descriptionTextView.skeletonTextLineHeight = .relativeToFont

📣 重要!

请注意,对于没有多行的视图,单行将被视为最后一行。

🦋 外观

骨架具有默认外观。因此,当您未指定颜色、渐变或多行属性时,SkeletonView 会使用默认值。

默认值

要获取这些默认值,您可以使用 SkeletonAppearance.default。使用此属性,您也可以设置这些值

SkeletonAppearance.default.multilineHeight = 20
SkeletonAppearance.default.tintColor = .green

⚠️已弃用!

useFontLineHeight 已弃用。您可以使用 textLineHeight 代替

SkeletonAppearance.default.textLineHeight = .relativeToFont

🎨 自定义颜色

您可以决定骨架的着色颜色。您只需要将您想要的颜色或渐变作为参数传递即可。

使用纯色

view.showSkeleton(usingColor: UIColor.gray) // Solid
// or
view.showSkeleton(usingColor: UIColor(red: 25.0, green: 30.0, blue: 255.0, alpha: 1.0))

使用渐变

let gradient = SkeletonGradient(baseColor: UIColor.midnightBlue)
view.showGradientSkeleton(usingGradient: gradient) // Gradient

此外,SkeletonView 具有 20 种平面颜色 🤙🏼

UIColor.turquoise, UIColor.greenSea, UIColor.sunFlower, UIColor.flatOrange ...

图片捕获自网站 https://flatuicolors.com

🏃‍♀️ 动画

SkeletonView 有两个内置动画,脉冲 用于纯色骨架,滑动 用于渐变。

此外,如果您想自己进行骨架动画,这非常容易。

Skeleton 提供了 showAnimatedSkeleton 函数,该函数具有一个 SkeletonLayerAnimation 闭包,您可以在其中定义您的自定义动画。

public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation

您可以这样调用该函数

view.showAnimatedSkeleton { (layer) -> CAAnimation in
  let animation = CAAnimation()
  // Customize here your animation

  return animation
}

可以使用 SkeletonAnimationBuilder。它是一个用于创建 SkeletonLayerAnimation 的构建器。

现在,您可以为渐变创建 滑动动画,决定 方向 并设置动画的 持续时间(默认 = 1.5s)。

// func makeSlidingAnimation(withDirection direction: GradientDirection, duration: CFTimeInterval = 1.5) -> SkeletonLayerAnimation

let animation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftToRight)
view.showAnimatedGradientSkeleton(usingGradient: gradient, animation: animation)

GradientDirection 是一个枚举,具有以下情况

方向 预览
.leftRight
.rightLeft
.topBottom
.bottomTop
.topLeftBottomRight
.bottomRightTopLeft

😉 技巧!

存在另一种创建滑动动画的方法,只需使用此快捷方式即可

let animation = GradientDirection.leftToRight.slidingAnimation()

🏄 过渡

SkeletonView 具有内置的过渡效果,可以以更平滑的方式 显示隐藏 骨架 🤙

要使用过渡,只需将 transition 参数添加到您的 showSkeleton()hideSkeleton() 函数中,并指定过渡时间,如下所示

view.showSkeleton(transition: .crossDissolve(0.25))     //Show skeleton cross dissolve transition with 0.25 seconds fade time
view.hideSkeleton(transition: .crossDissolve(0.25))     //Hide skeleton cross dissolve transition with 0.25 seconds fade time

默认值为 crossDissolve(0.25)

预览

交叉溶解

✨ 其他

层级结构

由于 SkeletonView 是递归的,并且我们希望骨架非常高效,因此我们希望尽快停止递归。因此,您必须将容器视图设置为 Skeletonable,因为只要视图不是 Skeletonable,Skeleton 就会停止查找 skeletonable 子视图,从而中断递归。

因为一张图片胜过千言万语

在此示例中,我们有一个带有 ContainerViewUITableViewUIViewController。当视图准备就绪时,我们使用此方法显示骨架

view.showSkeleton()

isSkeletonable= ☠️

配置 结果

骨架视图布局

有时,由于父视图的边界已更改,骨架布局可能不适合您的布局。例如,旋转设备。

您可以像这样重新布局骨架视图

override func viewDidLayoutSubviews() {
    view.layoutSkeletonIfNeeded()
}

📣 重要!

您不应该调用此方法。从 1.8.1 版本 开始,您不需要调用此方法,该库会自动执行此操作。因此,您只能在需要手动更新骨架布局的情况下使用此方法。

更新骨架

您可以使用以下方法随时更改骨架配置,例如颜色、动画等

(1) view.updateSkeleton()                 // Solid
(2) view.updateGradientSkeleton()         // Gradient
(3) view.updateAnimatedSkeleton()         // Solid animated
(4) view.updateAnimatedGradientSkeleton() // Gradient animated

在动画开始时隐藏视图

有时你希望在动画开始时隐藏某些视图,可以使用一个快速属性来实现此目的。

view.isHiddenWhenSkeletonIsActive = true  // This works only when isSkeletonable = true

当骨架屏处于活动状态时,不要修改用户交互。

默认情况下,骨架化项目会禁用用户交互。但是,如果你不想在骨架屏处于活动状态时修改用户交互指示器,可以使用 isUserInteractionDisabledWhenSkeletonIsActive 属性。

view.isUserInteractionDisabledWhenSkeletonIsActive = false  // The view will be active when the skeleton will be active.

不要在标签中的骨架线中使用字体行高。

设置为 False 可以禁用骨架屏自动调整 `UILabel` 或 `UITextView` 的字体高度。 默认情况下,骨架线的高度会自动调整为字体高度,以便更准确地反映标签矩形中的文本,而不是使用边界框。

label.useFontLineHeight = false

延迟显示骨架屏

如果视图更新速度很快,您可以延迟骨架屏的显示。

func showSkeleton(usingColor: UIColor,
                  animated: Bool,
                  delay: TimeInterval,
                  transition: SkeletonTransitionStyle)
func showGradientSkeleton(usingGradient: SkeletonGradient,
                          animated: Bool,
                          delay: TimeInterval,
                          transition: SkeletonTransitionStyle)

调试

为了方便调试,当出现问题时,**SkeletonView** 提供了一些新工具。

首先,UIView 有一个属性可以获取其骨架屏信息。

var sk.skeletonTreeDescription: String

此外,您可以激活新的 **debug mode(调试模式)**。只需添加环境变量 SKELETON_DEBUG 并激活它即可。

然后,当骨架屏出现时,您可以在 Xcode 控制台中看到视图层级结构。

{ 
  "type" : "UIView", // UITableView, UILabel...
  "isSkeletonable" : true,
  "reference" : "0x000000014751ce30",
  "children" : [
    {
      "type" : "UIView",
      "isSkeletonable" : true,
      "children" : [ ... ],
      "reference" : "0x000000014751cfa0"
    }
  ]
}

支持的操作系统和 SDK 版本

❤️ 贡献

这是一个开源项目,欢迎大家贡献代码。如何贡献?

查看 所有贡献者

更多信息,请阅读 贡献指南

📢 提及

🏆 赞助商

没有您的帮助,开源项目无法长久生存。如果您觉得 SkeletonView 有用,请考虑通过成为赞助商来支持这个项目。

通过 GitHub Sponsors ❤️ 成为赞助商

👨🏻‍💻 作者

Juanpe Catalán

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.