Compare commits

...

1 Commits

Author SHA1 Message Date
Nikita Semenov 4ccfedfa3c fix: code review notes 2023-03-09 12:46:12 +03:00
9 changed files with 118 additions and 38 deletions

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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))
}

View File

@ -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)
}
/*:
### Отступы

View File

@ -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_ для лейбла: