Showcase是一个基于UIView的视图,可以在框架中显示任何视图,就像在货架上展示物品一样。
ReusableView
和ReusableModel
协议。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
配置视图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
}
}
您想在每个视图大小上执行分页吗?
好的,只需在重置之前添加showcase.isPagingEnabled = true
Showcase有一个布局属性。您可以轻松设计自己的布局。
这些都是布局拥有的属性。
showcase.layout
direction
: LayoutDirection -> .horizontal 或 .verticalitemSize
: CGSize -> 单元格大小lineSpacing
: CGFloat -> 每个单元格之间的间隔path
: PathProtocol -> 路径,滚动时视图的轨迹transform
: TransformProtocol? -> 通过滚动变换视图的形状特殊之处在于path
和transform
属性。
public protocol PathProtocol {
func normalizedPath(withCoordinate value: CGFloat) -> CGFloat
}
public protocol TransformProtocol {
func transform(withScrollingRate rate: CGFloat) -> CATransform3D
}
上面的蓝色绿色区域显示了展示的框架。在path
和transform
中标准化为:
坐标系与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)
}
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
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
就像这里,符合 TransformProtocol
的变换可以与相同的滚动速率连接。
ReusableView
和 ReusableModel
在 showcase
中显示视图showcase.isPagingEnabled = true
path
类型。PathProtocol
并进行设计。TransformProtocol
是使视图变换。direction
、itemSize
、lineSpacing
、isPagingEnabled
、path
、transform
等进行了很多自定义。