Skip to content

Commit

Permalink
Merge pull request #254 from Juanpe/develop
Browse files Browse the repository at this point in the history
Merge changes for version 1.8.4
  • Loading branch information
Juanpe committed Feb 12, 2020
2 parents fbaf2e7 + 5544acc commit 9a106d1
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 65 deletions.
12 changes: 6 additions & 6 deletions Example/TableView/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Va7-1y-Tel">
<device id="retina5_9" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15510"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
Expand Down Expand Up @@ -87,12 +87,12 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="isSkeletonable" value="YES"/>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhU-1t-AaI" userLabel="Label">
<rect key="frame" x="118" y="29" width="237" height="20.333333333333329"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhU-1t-AaI" userLabel="Label">
<rect key="frame" x="118" y="29" width="237" height="18"/>
<constraints>
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="71" id="HRL-cI-ieC"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
Expand Down Expand Up @@ -261,7 +261,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-2682" y="340"/>
<point key="canvasLocation" x="-2682.4000000000001" y="339.90147783251234"/>
</scene>
<!--Item-->
<scene sceneID="Cfc-AT-AS1">
Expand Down
2 changes: 1 addition & 1 deletion SkeletonView.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "SkeletonView"
s.version = "1.8.3"
s.version = "1.8.4"
s.summary = "An elegant way to show users that something is happening and also prepare them to which contents he is waiting"
s.description = <<-DESC
Today almost all apps have async processes, as API requests, long runing processes, etc. And while the processes are working, usually developers place a loading view to show users that something is going on.
Expand Down
3 changes: 3 additions & 0 deletions Sources/Appearance/SkeletonAppearance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public protocol Appearance {
var multilineSpacing: CGFloat { get set }
var multilineLastLineFillPercent: Int { get set }
var multilineCornerRadius: Int { get set }
var renderSingleLineAsView: Bool { get set }
}

public enum SkeletonAppearance {
Expand All @@ -30,5 +31,7 @@ class SkeletonViewAppearance: Appearance {
var multilineLastLineFillPercent: Int = 70

var multilineCornerRadius: Int = 0

var renderSingleLineAsView: Bool = false
}
// codebeat:enable[TOO_MANY_IVARS]
9 changes: 8 additions & 1 deletion Sources/Builders/SkeletonMultilineLayerBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import UIKit
class SkeletonMultilineLayerBuilder {
var skeletonType: SkeletonType?
var index: Int?
var height: CGFloat?
var width: CGFloat?
var cornerRadius: Int?
var multilineSpacing: CGFloat = SkeletonAppearance.default.multilineSpacing
Expand All @@ -22,6 +23,11 @@ class SkeletonMultilineLayerBuilder {
return self
}

func setHeight(_ height: CGFloat) -> SkeletonMultilineLayerBuilder {
self.height = height
return self
}

func setWidth(_ width: CGFloat) -> SkeletonMultilineLayerBuilder {
self.width = width
return self
Expand All @@ -46,13 +52,14 @@ class SkeletonMultilineLayerBuilder {
guard let type = skeletonType,
let index = index,
let width = width,
let height = height,
let radius = cornerRadius
else { return nil }

let layer = type.layer
layer.anchorPoint = .zero
layer.name = CALayer.skeletonSubLayersName
layer.updateLayerFrame(for: index, width: width, multilineSpacing: self.multilineSpacing, paddingInsets: paddingInsets)
layer.updateLayerFrame(for: index, size: CGSize(width: width, height: height), multilineSpacing: self.multilineSpacing, paddingInsets: paddingInsets)

layer.cornerRadius = CGFloat(radius)
layer.masksToBounds = true
Expand Down
58 changes: 35 additions & 23 deletions Sources/Extensions/CALayer+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,19 @@ extension CALayer {
}

func addMultilinesLayers(for config: SkeletonMultilinesLayerConfig) {
let numberOfSublayers = calculateNumLines(for: config)
let numberOfSublayers = config.lines == 1 ? 1 : calculateNumLines(for: config)
var height = config.lineHeight ?? SkeletonAppearance.default.multilineHeight
if numberOfSublayers == 1 {
height = bounds.height
}

let layerBuilder = SkeletonMultilineLayerBuilder()
.setSkeletonType(config.type)
.setCornerRadius(config.multilineCornerRadius)
.setMultilineSpacing(config.multilineSpacing)
.setPadding(config.paddingInsets)

.setPadding(config.paddingInsets)
.setHeight(height)

(0..<numberOfSublayers).forEach { index in
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: config.lastLineFillPercent, paddingInsets: config.paddingInsets)
if let layer = layerBuilder
Expand All @@ -67,12 +72,20 @@ extension CALayer {
}
}

func updateMultilinesLayers(lastLineFillPercent: Int, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets) {
func updateMultilinesLayers(for config: SkeletonMultilinesLayerConfig) {
let currentSkeletonSublayers = skeletonSublayers
let numberOfSublayers = currentSkeletonSublayers.count
let lastLineFillPercent = config.lastLineFillPercent
let paddingInsets = config.paddingInsets
let multilineSpacing = config.multilineSpacing
var height = config.lineHeight ?? SkeletonAppearance.default.multilineHeight
if numberOfSublayers == 1 {
height = bounds.height
}

for (index, layer) in currentSkeletonSublayers.enumerated() {
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: lastLineFillPercent, paddingInsets: paddingInsets)
layer.updateLayerFrame(for: index, width: width, multilineSpacing: multilineSpacing, paddingInsets: paddingInsets)
layer.updateLayerFrame(for: index, size: CGSize(width: width, height: height), multilineSpacing: multilineSpacing, paddingInsets: paddingInsets)
}
}

Expand All @@ -84,14 +97,13 @@ extension CALayer {
return width
}

func updateLayerFrame(for index: Int, width: CGFloat, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets) {
func updateLayerFrame(for index: Int, size: CGSize, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets) {
let spaceRequiredForEachLine = SkeletonAppearance.default.multilineHeight + multilineSpacing
frame = CGRect(x: paddingInsets.left, y: CGFloat(index) * spaceRequiredForEachLine + paddingInsets.top, width: width, height: SkeletonAppearance.default.multilineHeight)
frame = CGRect(x: paddingInsets.left, y: CGFloat(index) * spaceRequiredForEachLine + paddingInsets.top, width: size.width, height: size.height)
}

private func calculateNumLines(for config: SkeletonMultilinesLayerConfig) -> Int {
let requiredSpaceForEachLine = config.lineHeight ?? (SkeletonAppearance.default.multilineHeight
+ config.multilineSpacing)
let requiredSpaceForEachLine = (config.lineHeight ?? SkeletonAppearance.default.multilineHeight) + config.multilineSpacing
var numberOfSublayers = Int(round(CGFloat(bounds.height - config.paddingInsets.top - config.paddingInsets.bottom)/CGFloat(requiredSpaceForEachLine)))
if config.lines != 0, config.lines <= numberOfSublayers { numberOfSublayers = config.lines }
return numberOfSublayers
Expand All @@ -111,26 +123,26 @@ public extension CALayer {
pulseAnimation.isRemovedOnCompletion = false
return pulseAnimation
}

var sliding: CAAnimation {
let startPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.startPoint))
startPointAnim.fromValue = CGPoint(x: -1, y: 0.5)
startPointAnim.toValue = CGPoint(x:1, y: 0.5)

let endPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.endPoint))
endPointAnim.fromValue = CGPoint(x: 0, y: 0.5)
endPointAnim.toValue = CGPoint(x:2, y: 0.5)

let animGroup = CAAnimationGroup()
animGroup.animations = [startPointAnim, endPointAnim]
animGroup.duration = 1.5
animGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
animGroup.repeatCount = .infinity
animGroup.isRemovedOnCompletion = false

return animGroup
}

func playAnimation(_ anim: SkeletonLayerAnimation, key: String, completion: (() -> Void)? = nil) {
skeletonSublayers.recursiveSearch(leafBlock: {
DispatchQueue.main.async { CATransaction.begin() }
Expand All @@ -141,7 +153,7 @@ public extension CALayer {
$0.playAnimation(anim, key: key, completion: completion)
}
}

func stopAnimation(forKey key: String) {
skeletonSublayers.recursiveSearch(leafBlock: {
removeAnimation(forKey: key)
Expand All @@ -152,15 +164,15 @@ public extension CALayer {
}

extension CALayer {
func setOpacity(from: Int, to: Int, duration: TimeInterval, completion: (() -> Void)?) {
func setOpacity(from: Int, to: Int, duration: TimeInterval, completion: (() -> Void)?) {
DispatchQueue.main.async { CATransaction.begin() }
let animation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
animation.fromValue = from
animation.toValue = to
animation.duration = duration
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
let animation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
animation.fromValue = from
animation.toValue = to
animation.duration = duration
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
DispatchQueue.main.async { CATransaction.setCompletionBlock(completion) }
add(animation, forKey: "setOpacityAnimation")
add(animation, forKey: "setOpacityAnimation")
DispatchQueue.main.async { CATransaction.commit() }
}
}
}
79 changes: 45 additions & 34 deletions Sources/SkeletonLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation
public enum SkeletonType {
case solid
case gradient

var layer: CALayer {
switch self {
case .solid:
Expand All @@ -22,7 +22,7 @@ public enum SkeletonType {
return CAGradientLayer()
}
}

var layerAnimation: SkeletonLayerAnimation {
switch self {
case .solid:
Expand All @@ -36,24 +36,24 @@ public enum SkeletonType {
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.maxBoundsEstimated
addMultilinesIfNeeded()
addTextLinesIfNeeded()
self.maskLayer.tint(withColors: colors)
}

func update(usingColors colors: [UIColor]) {
layoutIfNeeded()
maskLayer.tint(withColors: colors)
Expand All @@ -63,45 +63,56 @@ struct SkeletonLayer {
if let bounds = holder?.maxBoundsEstimated {
maskLayer.bounds = bounds
}
updateMultilinesIfNeeded()
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?()
}
maskLayer.setOpacity(from: 1, to: 0, duration: duration) {
self.maskLayer.removeFromSuperlayer()
completion?()
}
}
}

/// Returns a multiLineViewHolder only if the current holder implements the `ContainsMultilineText` protocol,
/// and actually displays multiple lines of text.
private var multiLineViewHolder: ContainsMultilineText? {
guard let multiLineView = holder as? ContainsMultilineText,
multiLineView.numLines != 1 else { return nil }
return multiLineView
/// 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.numLines,
lineHeight: textView.multilineTextFont?.lineHeight,
type: type,
lastLineFillPercent: textView.lastLineFillingPercent,
multilineCornerRadius: textView.multilineCornerRadius,
multilineSpacing: textView.multilineSpacing,
paddingInsets: textView.paddingInsets)

maskLayer.addMultilinesLayers(for: config)
}

func addMultilinesIfNeeded() {
guard let multiLineView = multiLineViewHolder else { return }

let config = SkeletonMultilinesLayerConfig(lines: multiLineView.numLines,
lineHeight: multiLineView.multilineTextFont?.lineHeight,
type: type, lastLineFillPercent: multiLineView.lastLineFillingPercent,
multilineCornerRadius: multiLineView.multilineCornerRadius,
multilineSpacing: multiLineView.multilineSpacing,
paddingInsets: multiLineView.paddingInsets)
maskLayer.addMultilinesLayers(for: config)

func updateLinesIfNeeded() {
guard let textView = holderAsTextView else { return }
let config = SkeletonMultilinesLayerConfig(lines: textView.numLines,
lineHeight: textView.multilineTextFont?.lineHeight,
type: type,
lastLineFillPercent: textView.lastLineFillingPercent,
multilineCornerRadius: textView.multilineCornerRadius,
multilineSpacing: textView.multilineSpacing,
paddingInsets: textView.paddingInsets)

maskLayer.updateMultilinesLayers(for: config)
}

func updateMultilinesIfNeeded() {
guard let multiLineView = multiLineViewHolder else { return }
maskLayer.updateMultilinesLayers(lastLineFillPercent: multiLineView.lastLineFillingPercent, multilineSpacing: multiLineView.multilineSpacing, paddingInsets: multiLineView.paddingInsets)

var holderAsTextView: ContainsMultilineText? {
guard let textView = holder as? ContainsMultilineText,
(textView.numLines == 0 || textView.numLines > 1 || textView.numLines == 1 && !SkeletonAppearance.default.renderSingleLineAsView) else {
return nil
}
return textView
}
}

Expand Down
1 change: 1 addition & 0 deletions Sources/SkeletonView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public extension UIView {

extension UIView {
@objc func skeletonLayoutSubviews() {
skeletonLayoutSubviews()
guard isSkeletonActive else { return }
layoutSkeletonIfNeeded()
}
Expand Down

0 comments on commit 9a106d1

Please sign in to comment.