Compare commits
1 Commits
master
...
feature/sk
| Author | SHA1 | Date |
|---|---|---|
|
|
4ccfedfa3c |
|
|
@ -33,7 +33,7 @@ open class BaseViewSkeletonsConfiguration {
|
|||
public var padding: UIEdgeInsets
|
||||
public var shape: Shape
|
||||
|
||||
public init(padding: UIEdgeInsets = .zero, shape: Shape = .rectangle(cornerRadius: .zero)) {
|
||||
public init(padding: UIEdgeInsets = .edges(5), shape: Shape = .rectangle(cornerRadius: .zero)) {
|
||||
self.shape = shape
|
||||
self.padding = padding
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class ContainerViewSkeletonsConfiguration: BaseViewSkeletonsConfiguration {
|
||||
|
||||
public var borderWidth: CGFloat
|
||||
|
||||
public init(borderWidth: CGFloat = .zero,
|
||||
padding: UIEdgeInsets = .zero,
|
||||
shape: Shape = .rectangle(cornerRadius: .zero)) {
|
||||
|
||||
self.borderWidth = borderWidth
|
||||
|
||||
super.init(padding: padding, shape: shape)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,43 +26,43 @@ import UIKit
|
|||
open class SkeletonsConfiguration {
|
||||
|
||||
public var viewConfiguration: BaseViewSkeletonsConfiguration
|
||||
public var containerViewConfiguration: ContainerViewSkeletonsConfiguration
|
||||
public var labelConfiguration: TextSkeletonsConfiguration
|
||||
public var imageViewConfiguration: BaseViewSkeletonsConfiguration
|
||||
public var animation: Closure<SkeletonLayer, CAAnimationGroup>?
|
||||
|
||||
public var skeletonsBackgroundColor: CGColor
|
||||
public var skeletonsMovingColor: CGColor
|
||||
public var borderWidth: CGFloat
|
||||
|
||||
public weak var configurationDelegate: SkeletonsConfigurationDelegate?
|
||||
|
||||
open var isContainersHidden: Bool {
|
||||
borderWidth == .zero
|
||||
containerViewConfiguration.borderWidth == .zero
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(viewConfiguration: BaseViewSkeletonsConfiguration = .init(),
|
||||
containerViewConfiguration: ContainerViewSkeletonsConfiguration = .init(),
|
||||
labelConfiguration: TextSkeletonsConfiguration = .init(),
|
||||
imageViewConfiguration: BaseViewSkeletonsConfiguration = .init(shape: .circle),
|
||||
animation: Closure<SkeletonLayer, CAAnimationGroup>? = nil,
|
||||
skeletonsBackgroundColor: UIColor = .lightGray.withAlphaComponent(0.7),
|
||||
borderWidth: CGFloat = .zero,
|
||||
configurationDelegate: SkeletonsConfigurationDelegate? = nil) {
|
||||
|
||||
self.viewConfiguration = viewConfiguration
|
||||
self.containerViewConfiguration = containerViewConfiguration
|
||||
self.labelConfiguration = labelConfiguration
|
||||
self.imageViewConfiguration = imageViewConfiguration
|
||||
self.animation = animation
|
||||
self.skeletonsBackgroundColor = skeletonsBackgroundColor.cgColor
|
||||
self.skeletonsMovingColor = skeletonsBackgroundColor.withAlphaComponent(0.2).cgColor
|
||||
self.borderWidth = borderWidth
|
||||
self.configurationDelegate = configurationDelegate
|
||||
}
|
||||
|
||||
// MARK: - Open methods
|
||||
|
||||
open func createSkeletonLayer(for baseView: UIView?) -> SkeletonLayer {
|
||||
open func createSkeletonLayer(for baseView: UIView) -> SkeletonLayer {
|
||||
SkeletonLayer(config: self, baseView: baseView)
|
||||
}
|
||||
|
||||
|
|
@ -75,9 +75,9 @@ open class SkeletonsConfiguration {
|
|||
|
||||
if !isContainersHidden {
|
||||
layer.borderColor = skeletonsBackgroundColor
|
||||
layer.borderWidth = borderWidth
|
||||
layer.borderWidth = containerViewConfiguration.borderWidth
|
||||
|
||||
if case let .rectangle(cornerRadius: radius) = viewConfiguration.shape {
|
||||
if case let .rectangle(cornerRadius: radius) = containerViewConfiguration.shape {
|
||||
layer.cornerRadius = radius
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ open class TextSkeletonsConfiguration: BaseViewSkeletonsConfiguration {
|
|||
public init(numberOfLines: Int? = nil,
|
||||
lineHeight: Closure<UIFont?, CGFloat>? = nil,
|
||||
lineSpacing: Closure<UIFont?, CGFloat>? = nil,
|
||||
padding: UIEdgeInsets = .zero,
|
||||
padding: UIEdgeInsets = .edges(5),
|
||||
shape: Shape = .rectangle(cornerRadius: .zero)) {
|
||||
|
||||
self.numberOfLines = numberOfLines
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
import UIKit
|
||||
|
||||
public protocol SkeletonsPresenter {
|
||||
var skeletonsHolder: UIView { get }
|
||||
var skeletonsConfiguration: SkeletonsConfiguration { get }
|
||||
var isSkeletonsHidden: Bool { get }
|
||||
var viewsToSkeletone: [UIView] { get }
|
||||
|
|
@ -40,38 +41,37 @@ extension SkeletonsPresenter {
|
|||
public var skeletonsConfiguration: SkeletonsConfiguration {
|
||||
SkeletonsConfiguration()
|
||||
}
|
||||
|
||||
public var isSkeletonsHidden: Bool {
|
||||
isSkeletonsHidden(forView: skeletonsHolder)
|
||||
}
|
||||
|
||||
public var viewsToSkeletone: [UIView] {
|
||||
skeletonsHolder.skeletonableViews
|
||||
}
|
||||
|
||||
public func showSkeletons() {
|
||||
skeletonsHolder.showSkeletons(viewsToSkeletone: viewsToSkeletone,
|
||||
skeletonsConfiguration)
|
||||
}
|
||||
|
||||
func isSkeletonsHidden(forView view: UIView) -> Bool {
|
||||
(view.layer.sublayers ?? []).first { $0 is SkeletonLayer } == nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIView + SkeletonsPresenter
|
||||
|
||||
extension SkeletonsPresenter where Self: UIView {
|
||||
public var isSkeletonsHidden: Bool {
|
||||
(layer.sublayers ?? []).first { $0 is SkeletonLayer } == nil
|
||||
}
|
||||
|
||||
public var viewsToSkeletone: [UIView] {
|
||||
skeletonableViews
|
||||
}
|
||||
|
||||
public func showSkeletons() {
|
||||
showSkeletons(viewsToSkeletone: viewsToSkeletone,
|
||||
skeletonsConfiguration)
|
||||
public var skeletonsHolder: UIView {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIViewController + SkeletonsPresenter
|
||||
|
||||
extension SkeletonsPresenter where Self: UIViewController {
|
||||
public var isSkeletonsHidden: Bool {
|
||||
(view.layer.sublayers ?? []).first { $0 is SkeletonLayer } == nil
|
||||
}
|
||||
|
||||
public var viewsToSkeletone: [UIView] {
|
||||
view.skeletonableViews
|
||||
}
|
||||
|
||||
public func showSkeletons() {
|
||||
showSkeletons(viewsToSkeletone: viewsToSkeletone,
|
||||
skeletonsConfiguration)
|
||||
public var skeletonsHolder: UIView {
|
||||
view
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@ open class SkeletonLayer: CAShapeLayer {
|
|||
public var configuration: SkeletonsConfiguration
|
||||
public weak var baseView: UIView?
|
||||
|
||||
public var isAnimating: Bool {
|
||||
animationLayer.animation(forKey: Constants.animationKeyPath) != nil
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
// For debug purposes in Lookin or other programs for view hierarchy inspections
|
||||
|
|
@ -72,7 +76,7 @@ open class SkeletonLayer: CAShapeLayer {
|
|||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
public init(config: SkeletonsConfiguration, baseView: UIView?) {
|
||||
public init(config: SkeletonsConfiguration, baseView: UIView) {
|
||||
self.configuration = config
|
||||
self.baseView = baseView
|
||||
|
||||
|
|
@ -85,6 +89,10 @@ open class SkeletonLayer: CAShapeLayer {
|
|||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
// MARK: - Open methods
|
||||
|
||||
open func bind(to viewType: ViewType) {
|
||||
|
|
@ -95,6 +103,13 @@ open class SkeletonLayer: CAShapeLayer {
|
|||
self?.updateGeometry(viewType: view.viewType)
|
||||
}
|
||||
|
||||
if let _ = configuration.animation?(self) {
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(SkeletonLayer.restartAnimationIfNeeded),
|
||||
name: UIApplication.willEnterForegroundNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
configuration.configurationDelegate?.layerDidConfigured(forViewType: viewType, layer: self)
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +120,7 @@ open class SkeletonLayer: CAShapeLayer {
|
|||
}
|
||||
|
||||
open func startAnimation() {
|
||||
guard let animation = configuration.animation?(self) else {
|
||||
guard !isAnimating, let animation = configuration.animation?(self) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -148,11 +163,20 @@ open class SkeletonLayer: CAShapeLayer {
|
|||
path = configuration.imageViewConfiguration.drawPath(rect: viewType.view.bounds)
|
||||
frame = configuration.imageViewConfiguration.applyPadding(viewFrame: rect)
|
||||
|
||||
default:
|
||||
case .container(_):
|
||||
path = configuration.containerViewConfiguration.drawPath(rect: viewType.view.bounds)
|
||||
frame = configuration.containerViewConfiguration.applyPadding(viewFrame: rect)
|
||||
|
||||
case .generic(_):
|
||||
path = configuration.viewConfiguration.drawPath(rect: viewType.view.bounds)
|
||||
frame = configuration.viewConfiguration.applyPadding(viewFrame: rect)
|
||||
}
|
||||
|
||||
animationLayer.frame = bounds
|
||||
}
|
||||
|
||||
@objc private func restartAnimationIfNeeded() {
|
||||
stopAnimation()
|
||||
startAnimation()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ extension UIView {
|
|||
}
|
||||
}
|
||||
|
||||
subviews.forEach { $0.isHidden = false}
|
||||
subviews.forEach { $0.isHidden = false }
|
||||
}
|
||||
|
||||
public func startAnimation() {
|
||||
|
|
@ -84,7 +84,7 @@ extension UIView {
|
|||
var subviewSkeletonLayers = [SkeletonLayer]()
|
||||
|
||||
if view.isSkeletonsContainer {
|
||||
if conf.borderWidth != .zero {
|
||||
if !conf.isContainersHidden {
|
||||
skeletonLayer.bind(to: .container(view))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ class CustomConfigurableSkeletonableView: UIView, SkeletonsPresenter {
|
|||
- `UITextView`
|
||||
- `UIImageView`
|
||||
|
||||
> Для контейнеров доступна только настройка `borderWidth`, а `borderColor` используется тот же, что и для других скелетонов
|
||||
> Для контейнеров в качестве `borderColor` используется тот же цвет, что и для других скелетонов
|
||||
|
||||
### Анимация
|
||||
|
||||
|
|
@ -220,6 +220,14 @@ var confWithLabelSettings: SkeletonsConfiguration {
|
|||
return .init(labelConfiguration: labelConf)
|
||||
}
|
||||
|
||||
//: Для контейнеров можно настроить `borderWidth`. Стандартно он равняется 0, а значит контейнеры не будут показываться без дополнительной настройки ширины.
|
||||
var skeletonsConfiguration: SkeletonsConfiguration {
|
||||
let containerConf = ContainerViewSkeletonsConfiguration(borderWidth: 4,
|
||||
shape: .rectangle(cornerRadius: 10))
|
||||
|
||||
return .init(containerViewConfiguration: containerConf)
|
||||
}
|
||||
|
||||
/*:
|
||||
### Отступы
|
||||
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ class CustomConfigurableSkeletonableView: UIView, SkeletonsPresenter {
|
|||
- `UITextView`
|
||||
- `UIImageView`
|
||||
|
||||
> Для контейнеров доступна только настройка `borderWidth`, а `borderColor` используется тот же, что и для других скелетонов
|
||||
> Для контейнеров в качестве `borderColor` используется тот же цвет, что и для других скелетонов
|
||||
|
||||
### Анимация
|
||||
|
||||
|
|
@ -233,6 +233,17 @@ var confWithLabelSettings: SkeletonsConfiguration {
|
|||
}
|
||||
```
|
||||
|
||||
Для контейнеров можно настроить `borderWidth`. Стандартно он равняется 0, а значит контейнеры не будут показываться без дополнительной настройки ширины.
|
||||
|
||||
```swift
|
||||
var skeletonsConfiguration: SkeletonsConfiguration {
|
||||
let containerConf = ContainerViewSkeletonsConfiguration(borderWidth: 4,
|
||||
shape: .rectangle(cornerRadius: 10))
|
||||
|
||||
return .init(containerViewConfiguration: containerConf)
|
||||
}
|
||||
```
|
||||
|
||||
### Отступы
|
||||
|
||||
Отступы можно настроить отдельно для `UILabel`, `UITextView`, `UIImageView` и остальных вью. Например, для предыдущего примера можно добавить горизонтальный _padding_ для лейбла:
|
||||
|
|
|
|||
Loading…
Reference in New Issue