展示 1.1.0

展示 1.1.0

测试测试
Lang语言 SwiftSwift
许可证 MIT
发布最后发布2017年10月
SwiftSwift 版本3.2
SPM支持 SPM

h-yuya 维护。



展示 1.1.0

  • h-yuya




基本概述

Showcase是一个基于UIView的视图,可以在框架中显示任何视图,就像在货架上展示物品一样。

功能

  • 基于协议 - 对于视图和模型,类型安全,不需要重写任何特殊视图。
  • 循环 - 首个单元格可以循环到最后一个单元格,最后单元格可以循环到第一个单元格重新使用。
  • 分页 - 可以启用展示单元格大小的分页。
  • 布局 - 定制自己的布局。

如何使用

首先,创建一个你想显示的视图,并且模型需要配置视图,视图必须符合ReusableViewReusableModel协议。

import UIKit
import Showcase

struct ColorModel: ReusableModel {
    let color: UIColor
    
    init(color: UIColor) {
        self.color = color
    }
}

final class ColorView: ReusableView {
    typealias Model = ColorModel
    
    func configure(with model: ColorModel) {
        backgroundColor = model.color
    }
 
    override func awakeFromNib() {
        super.awakeFromNib()
        configureAppearance()
    }
}

private extension ColorView {
    func configureAppearance() {
        layer.cornerRadius = 30
        clipsToBounds = true
    }
}
  • typealias Model

    • Model是一个用于配置视图的模型
    • typealias它到任何符合ReusableModel的模型。
  • func configure(with model: Model)

    • 如其所名,使用Model配置视图

第二步,在ViewController中配置展示。

import UIKit
import Showcase

final class ColorViewController: UIViewController {
    // UIView is the super class of Showcase
    @IBOutlet fileprivate weak var showcase: Showcase!
    fileprivate let colors: [UIColor] = [.red, .green, .blue, .yellow, .gray]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configure()
    }
}

private extension ColorViewController {
    func configure() {
        let models = colors.map(ColorModel.init(color:))
        
        showcase.layout.direction = .horizontal
        showcase.layout.itemSize = .init(width: 300, height: 300)
        
        // registered view is able to be used for resetting showcase.
        showcase.register(byNibName: ColorView.self)
        
        // Type-safely reset the showcase. You can call this method any number of times.
        // views used for resetting must be registered before resetting.
        showcase.reset(ColorView.self, models: models)
        
        // The type is used as the identifier for reuse
    }
}

一切都完成。让我们看看演示。

simple_horizontal
simple_vertical

您想在每个视图大小上执行分页吗?
好的,只需在重置之前添加showcase.isPagingEnabled = true

simple_horizontal_p
simple_vertical_p

布局

Showcase有一个布局属性。您可以轻松设计自己的布局。
这些都是布局拥有的属性。

showcase.layout

  • direction: LayoutDirection -> .horizontal 或 .vertical
  • itemSize: CGSize -> 单元格大小
  • lineSpacing: CGFloat -> 每个单元格之间的间隔
  • path: PathProtocol -> 路径,滚动时视图的轨迹
  • transform: TransformProtocol? -> 通过滚动变换视图的形状

特殊之处在于pathtransform属性。

PathProtocol和TransformProtocol的定义及特性

public protocol PathProtocol {
    func normalizedPath(withCoordinate value: CGFloat) -> CGFloat
}
public protocol TransformProtocol {
    func transform(withScrollingRate rate: CGFloat) -> CATransform3D
}
  • 标准化坐标
  • 可以创建遵守PathProtocol的自定义路径

上面的蓝色绿色区域显示了展示的框架。在pathtransform中标准化为:

  • 展示的上边界 = -1
  • 展示的下边界 = 1
  • 展示的左边界 = -1
  • 展示的右边界 = 1

坐标系与UIKit坐标的方向相同。

示例

Showcase提供了4种类型的路径。

  • DefaultPath - 在x轴或y轴上移动
  • CircularPath - 在圆形轨道上移动
  • WavePath- 存在波浪形移动
  • LinearPath- 线性移动

还有3种类型的变换

  • TransformRotate - 旋转变换
  • TransformScale - 缩放变换
  • TransformScaleRotate - 旋转和缩放变换

默认路径

这个 DefaultPath 是展示视图的默认路径。

public final class DefaultPath: PathProtocol {
    public func normalizedPath(withCoordinate value: CGFloat) -> CGFloat {
        return 0
    }
    
    public init() {}
}

在这里,x 是当 showcase.layout.direction 等于 .horizontal 或 .vertical 时的 x, y 坐标。
normalizedPath 对所有输入返回 0。这表示视图在轴上移动,因为在归一化坐标中,0 在轴上。

圆形路径 & 旋转变换

CircularPath 可以用于圆形轨道。

public final class CircularPath: PathProtocol {
    public var normalizedRadius: CGFloat = 1
    public var normalizedOffset: CGFloat = -1
    public var offsetAngle: CGFloat = 0
    public var angle: CGFloat = .pi / 2
    
    public func normalizedPath(withCoordinate value: CGFloat) -> CGFloat {
        let theta = value * angle
        return normalizedRadius * cos(theta - offsetAngle) + normalizedOffset
    }
    
    public init() {}
}
public class TransformRotate: TransformProtocol {
    public var startRate: CGFloat = 0.2
    public var rotateAngle: CGFloat = 2 * .pi
    
    public func transform(withScrollingRate rate: CGFloat) -> CATransform3D {
        let canRotate = abs(rate) > startRate
        let optimizedRate = (rate + (rate < 0 ? startRate : -startRate))
        
        return CATransform3DMakeRotation(canRotate > rotateAngle * optimizedRate : 0, 0, 0, 1)
    }
    
    public init() {}
}

在这里,范围 [-1, 1] 转换为 [-π/2, π/2]。之后,范围内 cos() 的值显示了圆形坐标。
当你使用这个路径时,创建此路径,并在配置函数中将 layout.path 替换为值。

对于 TransformProtocol,[-1, 1] 是滚动速率的范围。将其应用于你想要通过滚动更改的参数,这里旋转角度就是。
使用时替换 layout.transform

在显示示例之前,我会改变上述配置。

...
    //fileprivate let colors: [UIColor] = [.red, .green, .blue, .yellow, .gray]
    fileprivate let colors: [UIColor] = [.red, .green, .blue, .yellow, .gray, .purple, .orange, .cyan]
...

func configure() {
    let models = colors.map(ColorModel.init(color:))
    let path = CircularPath()
    let transform = TransformRotate()
    transform.startRate = 0.4
    transform.rotateAngle = 8 * .pi
    
    showcase.layout.direction = .vertical
    showcase.itemSize = .init(width: 100, height: 100)
    showcase.layout.path = path
    //showcase.layout.transform = transform
    
    showcase.register(byNibName: ShowView.self)
    showcase.reset(ShowView.self, models: models)
}

circle_vertical
circle_vertical_rotate

波浪路径 & 缩放变换

WavePath 可以用于波浪形轨道。

public final class WavePath: PathProtocol {
    public var normalizedAmplitude: CGFloat = 0.5
    public var normalizedOffset: CGFloat = 0
    public var offsetAngle: CGFloat = 0
    public var angle: CGFloat = .pi / 2
    
    public func normalizedPath(withCoordinate value: CGFloat) -> CGFloat {
        let theta = value * angle
        return normalizedAmplitude * sin(theta - offsetAngle) + normalizedOffset
    }
    
    public init() {}
}
public class TransformScale: TransformProtocol {
    public var startRate: CGFloat = 0.8
    public var normalizedMinimumScale: CGFloat = 0.5

    public func transform(withScrollingRate rate: CGFloat) -> CATransform3D {
        let transformedRate = transformRange(withType: .centerPeak, rate: rate)
        let canScale = transformedRate < startRate
        let optimizedRate = max(transformedRate + (1 - startRate), normalizedMinimumScale)

        return CATransform3DMakeScale(canScale ? optimizedRate : 1, canScale ? optimizedRate : 1, 0)
    }

    public init() {}
}

范围[-1, 1] 转换为 [-π/2, π/2],与 CircularPath 相似。对输入 x 应用 sin() 函数,轨道将呈波浪状移动。
只需像下面那样更改上面的路径和变换。

let path = WavePath()
path.normalizedAmplitude = 0.5
path.angle = .pi

let transform = TransformScale()
transform.normalizedMinimumScale = 0.2

showcase.layout.path = path
showcase.layout.transform = transform

wave_vertical
wave_vertical_scale

线性路径 & 缩放旋转变换

LinearPath 可以用于线性轨道。

public final class LinearPath: PathProtocol {
    public var normalizedGradient: CGFloat = -0.5
    public var normalizedInversePosition: CGFloat?
    
    public func normalizedPath(withCoordinate value: CGFloat) -> CGFloat {
        switch normalizedInversePosition {
        case .some(let inverse):
            return value < inverse ? -normalizedGradient * value : normalizedGradient * (value - 2 * inverse)
            
        case .none:
            return normalizedGradient * value
        }
    }
    
    public init() {}
}
public class TransformScaleRotate: TransformProtocol {
    public var transformRotate: TransformRotate = .init()
    public var transformScale: TransformScale = .init()

    public func transform(withScrollingRate rate: CGFloat) -> CATransform3D {
        return CATransform3DConcat(transformRotate.transform(withScrollingRate: rate), transformScale.transform(withScrollingRate: rate))
    }

    public init() {}
}

LinearPath 线性移动视图。这里,如果你设置了 normalizedInversePosition,则移动将被反转。所以移动会像 V
只需像下面那样更改上面的路径和变换。

let path = LinearPath()
path.normalizedInversePosition = 0.3
path.normalizedGradient = 0.8

let rotate = TransformRotate()
rotate.startRate = 0.4
rotate.rotateAngle = 8 * .pi
let scale = TransformScale()
scale.normalizedMinimumScale = 0.2
let transform = TransformScaleRotate()
transform.transformRotate = rotate
transform.transformScale = scale

showcase.layout.path = path
showcase.layout.transform = transform

linear_vertical
linear_vertical_rotate_scale

就像这里,符合 TransformProtocol 的变换可以与相同的滚动速率连接。

总结

  1. 使用 ReusableViewReusableModelshowcase 中显示视图
  2. 你想使用每个单元格的大小来分页吗?只需设置 showcase.isPagingEnabled = true
  3. 你想以你想象的方式移动视图吗?Showcase 提供了 4 种不同的 path 类型。
  4. 如果你创建自己的路径,请使用 PathProtocol 并进行设计。
  5. 并且 TransformProtocol 是使视图变换。
  6. Showcase 对 directionitemSizelineSpacingisPagingEnabledpathtransform 等进行了很多自定义。