114 lines
4.7 KiB
Swift
Executable File
114 lines
4.7 KiB
Swift
Executable File
//
|
|
// SkeletonLayer.swift
|
|
// SkeletonView-iOS
|
|
//
|
|
// Created by Juanpe Catalán on 02/11/2017.
|
|
// Copyright © 2017 SkeletonView. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
struct SkeletonLayer {
|
|
|
|
private var maskLayer: CALayer
|
|
private weak var holder: UIView?
|
|
|
|
var type: SkeletonType {
|
|
return maskLayer is CAGradientLayer ? .gradient : .solid
|
|
}
|
|
|
|
var contentLayer: CALayer {
|
|
return maskLayer
|
|
}
|
|
|
|
init(type: SkeletonType, colors: [UIColor], skeletonHolder holder: UIView) {
|
|
self.holder = holder
|
|
self.maskLayer = type.layer
|
|
self.maskLayer.anchorPoint = .zero
|
|
self.maskLayer.bounds = holder.definedMaxBounds
|
|
self.maskLayer.cornerRadius = CGFloat(holder.skeletonCornerRadius)
|
|
addTextLinesIfNeeded()
|
|
self.maskLayer.tint(withColors: colors, traitCollection: holder.traitCollection)
|
|
}
|
|
|
|
func update(usingColors colors: [UIColor]) {
|
|
layoutIfNeeded()
|
|
maskLayer.tint(withColors: colors, traitCollection: holder?.traitCollection)
|
|
}
|
|
|
|
func layoutIfNeeded() {
|
|
if let bounds = holder?.definedMaxBounds {
|
|
maskLayer.bounds = bounds
|
|
}
|
|
updateLinesIfNeeded()
|
|
}
|
|
|
|
func removeLayer(transition: SkeletonTransitionStyle, completion: (() -> Void)? = nil) {
|
|
switch transition {
|
|
case .none:
|
|
maskLayer.removeFromSuperlayer()
|
|
completion?()
|
|
case .crossDissolve(let duration):
|
|
maskLayer.setOpacity(from: 1, to: 0, duration: duration) {
|
|
self.maskLayer.removeFromSuperlayer()
|
|
completion?()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// If there is more than one line, or custom preferences have been set for a single line, draw custom layers
|
|
func addTextLinesIfNeeded() {
|
|
guard let textView = holderAsTextView else { return }
|
|
let config = SkeletonMultilinesLayerConfig(lines: textView.estimatedNumberOfLines,
|
|
lineHeight: textView.estimatedLineHeight,
|
|
type: type,
|
|
lastLineFillPercent: textView.lastLineFillingPercent,
|
|
multilineCornerRadius: textView.multilineCornerRadius,
|
|
multilineSpacing: textView.multilineSpacing,
|
|
paddingInsets: textView.paddingInsets,
|
|
alignment: textView.textAlignment,
|
|
isRTL: holder?.isRTL ?? false,
|
|
shouldCenterVertically: textView.shouldCenterTextVertically)
|
|
|
|
maskLayer.addMultilinesLayers(for: config)
|
|
}
|
|
|
|
func updateLinesIfNeeded() {
|
|
guard let textView = holderAsTextView else { return }
|
|
let config = SkeletonMultilinesLayerConfig(lines: textView.estimatedNumberOfLines,
|
|
lineHeight: textView.estimatedLineHeight,
|
|
type: type,
|
|
lastLineFillPercent: textView.lastLineFillingPercent,
|
|
multilineCornerRadius: textView.multilineCornerRadius,
|
|
multilineSpacing: textView.multilineSpacing,
|
|
paddingInsets: textView.paddingInsets,
|
|
alignment: textView.textAlignment,
|
|
isRTL: holder?.isRTL ?? false,
|
|
shouldCenterVertically: textView.shouldCenterTextVertically)
|
|
|
|
maskLayer.updateMultilinesLayers(for: config)
|
|
}
|
|
|
|
var holderAsTextView: SkeletonTextNode? {
|
|
guard let textView = holder as? SkeletonTextNode,
|
|
(textView.estimatedNumberOfLines == -1 || textView.estimatedNumberOfLines == 0 || textView.estimatedNumberOfLines > 1 || textView.estimatedNumberOfLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
|
|
return nil
|
|
}
|
|
return textView
|
|
}
|
|
|
|
}
|
|
|
|
extension SkeletonLayer {
|
|
|
|
func start(_ anim: SkeletonLayerAnimation? = nil, completion: (() -> Void)? = nil) {
|
|
let animation = anim ?? type.defaultLayerAnimation(isRTL: holder?.isRTL ?? false)
|
|
contentLayer.playAnimation(animation, key: "skeletonAnimation", completion: completion)
|
|
}
|
|
|
|
func stopAnimation() {
|
|
contentLayer.stopAnimation(forKey: "skeletonAnimation")
|
|
}
|
|
|
|
}
|