PVView 1.0.4

PVView 1.0.4

Toan Nguyen 维护。



PVView 1.0.4

  • Toan Nguyen

PVView

PVView 是一个小型库,它可以帮助你制作出令人惊叹的视差视图。

安装

使用 CocoaPods

编辑 Podfile 并指定依赖项

pod 'PVView', '~> 1.0.4'

要求

  • iOS 9.0+
  • Swift 5

概念

  • PVItemType 代表一个视差项
  • PVActionType 代表项的动作
  • PVView 是分页的
  • 每个页面包含一组 PVItemType,而 PVView 只更新当前页面上项的动作
  • 在一个页面上,每个 PVItemType 都有一组 PVActionType
  • 所有 PVActionType 都通过 进度 更新。页面中的 进度 始终在 [0...1] 范围内
  • 一个动作可能有 startOffsetstopOffset。这些参数描述了动作开始和结束的页面偏移量。(0 <= startOffset < stopOffset <= 1)

如何使用

1. 在合适的位置导入 PVView

import PVView

2. 通过实现 PVItemType 协议创建视差项。

enum YourItem: String, PVItemType {
    case item1
    case item2

    var identifier: String {
        return self.rawValue
    }
}

3. 设置 PVView 的代理并实现 PVViewDelegate 协议

必须的代理方法

PVViewDelegate 有 4 个必须实现的方法

  • 通过实现来指定 PVView 中的页面数量
func numberOfPages(in parallaxView: PVView) -> Int {
    return 2
}
  • 通过实现来指定页面上的项
func parallaxView(_ parallaxView: PVView, itemsOnPage pageIndex: Int) -> [PVItemType] {
    return [YourItem.item1, .item2]
}
  • 然后 PVView 会通过以下方式询问您要附加到项的视图
func parallaxview(_ parallaxView: PVView, viewForItem item: PVItemType) -> UIView

注意:此方法返回的视图将被缓存在项中,并且当缓存中没有此项的视图时才会调用此方法

  • 最后,通过实现来指定将在项上运行的动作
func parallaxView(_ parallaxView: PVView, actionsOfItem item: PVItemType, onPage pageIndex: Int) -> [PVActionType] {
        if pageIndex == 0 {
            return [PVActionMove(fromOrigin: PVPoint(x: 50, y: 200), toOrigin: PVPoint(x: 250, y: 200))]
        }
        return []
    }

注意:在这些方法中,您可以使用 PVViewcurrentPageIndex 属性来在切换前获取页面的索引。

可选的代理方法

  • PVView 支持水平和垂直滚动。通过实现以下方法来指定 PVView 的滚动方向(默认为水平)
func direction(of parallaxView: PVView) -> PVView.PVDirection
  • 默认情况下,PVItemType 的视图将自动添加为 PVView 的子视图。您可以通过实现此方法来指定 PVItemType 视图的容器视图(返回 nil 表示容器视图为 PVView
func parallaxView(_ parallaxView: PVView, containerViewForItem item: PVItemType, onPage pageIndex: Int) -> UIView?
  • 在过渡到新页面之前,PVView 将调用以下方法,这是您在新页面出现之前做什么的机会(例如,在上一项上添加一些动画或隐藏/显示一些视图...)
func parallaxView(_ parallaxView: PVView, willBeginTransitionTo pageIndex: Int)
  • 在完成新页面的过渡后,PVView 将调用以下方法,这是您在新页面出现后做什么的机会(例如,为项的动作设置初始状态...)
func parallaxView(_ parallaxView: PVView, didEndTransitionFrom previousPageIndex: Int?)
  • 最后一个代理方法是
func parallaxView(_ parallaxView: PVView, didUpdate pageProgress: Double, onPage pageIndex: Int)

此方法在滚动过程中持续调用,以报告页面内的进度

4. 启动 PVView

parallaxView.reload()

更多

动作参数

动作的参数由 PVParameters 提供

  • startOffset:动作开始的页面偏移量
  • stopOffset:动作结束的页面偏移量
  • timingFunction:一个根据页面进度计算动作进度的对象

示例

Action offsets

startOffset = 0.2, stopOffset = 0.6. 动作将在进度 = 0.2 时开始,在进度 = 0.6 时停止

时序函数

Timing function

存在一个名为 PVTimingFunction 的内置函数。它与 CAMediaTimingFunction 相同,但提供了 evaluate 方法来计算动作的进度

您可以通过 PVTimingFunctionName 中定义的名称来初始化一个 PVTimingFunction

有关这些函数名称的详细信息,请参阅 缓动函数

动作组

PVActionGroup 表示一组动作。组内动作的参数是组的参数。

由于 PVActionGroupPVActionBasicType,因此可以通过调用组的 reverse 方法轻松反转组中的所有动作

反转动作序列

有时您想在一个序列中反转所有动作。存在一个内置方法可以帮助

public extension Sequence where Element: PVActionBasicType {
    func reversedActions(with newParameters: PVParameters = .default) -> [Element] {
        return self.map { $0.reverse(with: newParameters) }
    }
}

相对点 & 大小

您可以使用 PVPoint 来创建利用相对点的操作,绝对点将基于容器视图的大小进行计算

PVActionMove(fromPosition: PVPoint(x: 2, y: 0.2, isRelative: true)

您可以使用 PVSize 创建利用相对大小的操作,绝对大小将基于容器视图的大小进行计算

PVActionSize(from: PVSize(width: 0.2, height: 0.2, isRelative: true),
             to: PVSize(width: 0.4, height: 0.4, isRelative: true))

自定义

自定义操作

您可以通过实现 PVActionType 来轻松地自定义任何您想要的操作

public protocol PVActionType {
    func update(_ progress: Double, target: UIView)
}

或者实现继承自 PVActionTypePVActionBasicType

public protocol PVActionBasicType: PVActionType {
    var parameters: PVParameters { get }
    func step(_ progress: Double, target: UIView)
    func reverse(with newParameters: PVParameters) -> Self
}

示例:我编写了一个自定义操作名为 LetterSpacingAction,这个操作将改变 UILabel 中字符的间距

struct LetterSpacingAction: PVActionBasicType {
    let parameters: PVParameters
    let fromSpacing: Double
    let toSpacing: Double
    let maxWidth: CGFloat
    init(fromSpacing: Double, toSpacing: Double, maxWidth: CGFloat, parameters: PVParameters = .default) {
        self.fromSpacing = fromSpacing
        self.toSpacing = toSpacing
        self.maxWidth = maxWidth
        self.parameters = parameters
    }
    
    func step(_ progress: Double, target: UIView) {
        guard let label = target as? UILabel else { return }
        let current = fromSpacing + (toSpacing - fromSpacing) * progress
        label.attributedText = NSAttributedString(string: label.text ?? "", attributes: [.kern : current])
        label.frame = CGRect(origin: label.frame.origin, size: label.sizeThatFits(CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)))
    }
    
    func reverse(with newParameters: PVParameters) -> LetterSpacingAction {
        return LetterSpacingAction(fromSpacing: toSpacing,
                                   toSpacing: fromSpacing,
                                   maxWidth: maxWidth,
                                   parameters: newParameters)
    }
}

自定义时间函数

您可以通过实现 PVTimingFunctionType 来创建任何时间函数

public protocol PVTimingFunctionType {
    func evaluate(_ input: Double) -> Double
}

示例:创建一个 EaseOutSine 函数

Ease out sine

struct EaseOutSineFunction: PVTimingFunctionType {
    func evaluate(_ input: Double) -> Double {
        return sin(input * Double.pi / 2)
    }
}

文档

即将推出...😅

贡献力量

这是一个开源项目,我迫不及待地想看到你们许多精彩的想法。😘

许可协议

此代码根据MIT许可协议的条款和条件分发。