Compare commits

...

22 Commits

Author SHA1 Message Date
Juanpe Catalán 9a106d1af2 Merge pull request #254 from Juanpe/develop
Merge changes for version 1.8.4
2020-02-12 01:16:35 +01:00
Juanpe Catalán 5544acc63e bump version 1.8.4 2020-02-12 01:14:17 +01:00
Juanpe Catalán 65299378c4 fix: problem with text views that only have one line 2020-02-12 01:13:21 +01:00
Juanpe Catalán d8f1b4c53b Merge pull request #195 from esme-putt/Customise-Single-Line
Customise Single Line
2020-02-12 00:13:39 +01:00
Juanpe Catalán d2d8c1f5db Merge branch 'develop' into Customise-Single-Line 2020-02-12 00:11:28 +01:00
Juanpe Catalán ab385e5afb Merge pull request #234 from AnatoliBenke-Helsana/master
Update SkeletonView.swift
2020-02-12 00:05:32 +01:00
Juanpe Catalán 5d8cd9432e Merge branch 'master' into develop 2020-02-11 14:53:59 +01:00
Juanpe Catalán fbaf2e7b4b Update greetings.yml 2020-02-11 11:38:45 +01:00
Juanpe Catalán 97d83c7038 Create greetings.yml 2020-02-11 11:36:24 +01:00
Juanpe Catalán ce83713240 update staling issues
just stale issues with state equal to awaiting user input
2020-02-11 11:13:32 +01:00
Juanpe Catalán 56d3156f8e Merge branch 'master' into develop 2020-02-11 10:52:04 +01:00
Juanpe Catalán aeb9dcf2c7 Create auto-comment.yml 2020-02-11 10:43:30 +01:00
Juanpe Catalán 9914be0f5b Merge pull request #240 from damien-danglard/master
[fix] Use UIFont.lineHeight for calculate the number of CALayer used in …
2020-02-11 10:33:28 +01:00
Juanpe Catalán 7d0609098c update stale.yml to not stale assigned issues 2020-02-10 00:29:24 +01:00
Damien Danglard a7ae5f0f9f [fix] resolve issues from codebeat 2020-01-27 17:11:23 +01:00
Damien Danglard 85cce0cd7c [fix] Use font lineHeight for calculate the number of CALayer used in multiline Skeleton UILabel
close #239
2020-01-24 15:35:29 +01:00
AnatoliBenke-Helsana 6f1db7e303 Update SkeletonView.swift
- Fixes for Issue #202 UIView.layoutSubviews swizzle is messing with standard controls
- Due to Swizzling, LayoutSubviews is not called. This fixes the issue.
2020-01-07 13:39:32 +01:00
Esme Putt 6d4c7d76c3 Added flipper for custom options 2019-10-04 13:28:21 +13:00
Esme Putt f580fdaac2 Changed update function 2019-10-04 13:11:45 +13:00
Esme Putt 4be93db383 Added spacing 2019-10-04 11:52:38 +13:00
Esme Putt ae4ecfa760 Added comments 2019-10-04 11:28:17 +13:00
Esme Putt f60e5cd7ae Add-custom-options-for-single-line 2019-10-04 11:06:30 +13:00
13 changed files with 152 additions and 73 deletions
+9
View File
@@ -0,0 +1,9 @@
issuesOpened: >
Thank you for raising an issue. We will try and get back to you as soon as possible.
Please make sure you have given us as much context as possible.
pullRequestOpened: >
Thank you for raising your pull request.
Please make sure you have followed our contributing guidelines. We will review it as soon as possible.
+2 -7
View File
@@ -1,15 +1,10 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- work in progress
onlyLabels:
- awaiting user input
staleLabel: given up
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
+14
View File
@@ -0,0 +1,14 @@
name: Greetings
on: [pull_request, issues]
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Hi '
pr-message: 'Hi! Message that will be displayed on users'
+6 -6
View File
@@ -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>
@@ -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>
@@ -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">
+1 -1
View File
@@ -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.
@@ -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 {
@@ -30,5 +31,7 @@ class SkeletonViewAppearance: Appearance {
var multilineLastLineFillPercent: Int = 70
var multilineCornerRadius: Int = 0
var renderSingleLineAsView: Bool = false
}
// codebeat:enable[TOO_MANY_IVARS]
@@ -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
@@ -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
@@ -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
+54 -31
View File
@@ -28,6 +28,16 @@ extension CAGradientLayer {
}
}
struct SkeletonMultilinesLayerConfig {
var lines: Int
var lineHeight: CGFloat? = nil
var type: SkeletonType
var lastLineFillPercent: Int
var multilineCornerRadius: Int
var multilineSpacing: CGFloat
var paddingInsets: UIEdgeInsets
}
// MARK: Skeleton sublayers
extension CALayer {
@@ -36,18 +46,23 @@ extension CALayer {
var skeletonSublayers: [CALayer] {
return sublayers?.filter { $0.name == CALayer.skeletonSubLayersName } ?? [CALayer]()
}
func addMultilinesLayers(lines: Int, type: SkeletonType, lastLineFillPercent: Int, multilineCornerRadius: Int, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets) {
let numberOfSublayers = calculateNumLines(maxLines: lines, multilineSpacing: multilineSpacing, paddingInsets: paddingInsets)
func addMultilinesLayers(for config: SkeletonMultilinesLayerConfig) {
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(type)
.setCornerRadius(multilineCornerRadius)
.setMultilineSpacing(multilineSpacing)
.setPadding(paddingInsets)
.setSkeletonType(config.type)
.setCornerRadius(config.multilineCornerRadius)
.setMultilineSpacing(config.multilineSpacing)
.setPadding(config.paddingInsets)
.setHeight(height)
(0..<numberOfSublayers).forEach { index in
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: lastLineFillPercent, paddingInsets: paddingInsets)
let width = calculatedWidthForLine(at: index, totalLines: numberOfSublayers, lastLineFillPercent: config.lastLineFillPercent, paddingInsets: config.paddingInsets)
if let layer = layerBuilder
.setIndex(index)
.setWidth(width)
@@ -57,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)
}
}
@@ -74,15 +97,15 @@ 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(maxLines: Int, multilineSpacing: CGFloat, paddingInsets: UIEdgeInsets) -> Int {
let requiredSpaceForEachLine = SkeletonAppearance.default.multilineHeight + multilineSpacing
var numberOfSublayers = Int(round(CGFloat(bounds.height - paddingInsets.top - paddingInsets.bottom)/CGFloat(requiredSpaceForEachLine)))
if maxLines != 0, maxLines <= numberOfSublayers { numberOfSublayers = maxLines }
private func calculateNumLines(for config: SkeletonMultilinesLayerConfig) -> Int {
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
}
}
@@ -100,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() }
@@ -130,7 +153,7 @@ public extension CALayer {
$0.playAnimation(anim, key: key, completion: completion)
}
}
func stopAnimation(forKey key: String) {
skeletonSublayers.recursiveSearch(leafBlock: {
removeAnimation(forKey: key)
@@ -141,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() }
}
}
}
@@ -10,6 +10,7 @@ enum MultilineAssociatedKeys {
}
protocol ContainsMultilineText {
var multilineTextFont: UIFont? { get }
var numLines: Int { get }
var lastLineFillingPercent: Int { get }
var multilineCornerRadius: Int { get }
@@ -26,6 +26,10 @@ public extension UILabel {
}
extension UILabel: ContainsMultilineText {
var multilineTextFont: UIFont? {
return font
}
var numLines: Int {
return numberOfLines
}
@@ -29,6 +29,10 @@ public extension UITextView {
}
extension UITextView: ContainsMultilineText {
var multilineTextFont: UIFont? {
return font
}
var lastLineFillingPercent: Int {
get {
let defaultValue = SkeletonAppearance.default.multilineLastLineFillPercent
+45 -27
View File
@@ -13,7 +13,7 @@ public typealias SkeletonLayerAnimation = (CALayer) -> CAAnimation
public enum SkeletonType {
case solid
case gradient
var layer: CALayer {
switch self {
case .solid:
@@ -22,7 +22,7 @@ public enum SkeletonType {
return CAGradientLayer()
}
}
var layerAnimation: SkeletonLayerAnimation {
switch self {
case .solid:
@@ -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)
@@ -63,38 +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)
func addMultilinesIfNeeded() {
guard let multiLineView = multiLineViewHolder else { return }
maskLayer.addMultilinesLayers(lines: multiLineView.numLines, type: type, lastLineFillPercent: multiLineView.lastLineFillingPercent, multilineCornerRadius: multiLineView.multilineCornerRadius, multilineSpacing: multiLineView.multilineSpacing, paddingInsets: multiLineView.paddingInsets)
maskLayer.addMultilinesLayers(for: config)
}
func updateMultilinesIfNeeded() {
guard let multiLineView = multiLineViewHolder else { return }
maskLayer.updateMultilinesLayers(lastLineFillPercent: multiLineView.lastLineFillingPercent, multilineSpacing: multiLineView.multilineSpacing, paddingInsets: multiLineView.paddingInsets)
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)
}
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
}
}
+1
View File
@@ -94,6 +94,7 @@ public extension UIView {
extension UIView {
@objc func skeletonLayoutSubviews() {
skeletonLayoutSubviews()
guard isSkeletonActive else { return }
layoutSkeletonIfNeeded()
}