Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace RefreshControl with native #1352

Merged
merged 3 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 9 additions & 13 deletions Demo/Sources/Components/RefreshControl/RefreshControlDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class RefreshControlDemoView: UIView, Demoable {
}()

private lazy var refreshControl: UIRefreshControl = {
let refreshControl = RefreshControl(frame: .zero)
refreshControl.delegate = self
let refreshControl = UIRefreshControl(frame: .zero)
refreshControl.addTarget(self, action: #selector(handleRefreshBegan), for: .valueChanged)
return refreshControl
}()

Expand Down Expand Up @@ -47,6 +47,13 @@ class RefreshControlDemoView: UIView, Demoable {
tableView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}

@objc private func handleRefreshBegan() {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { [weak self] in
self?.refreshControl.endRefreshing()
self?.tableView.reloadData()
}
}
}

// MARK: - UITableViewDataSource
Expand All @@ -66,14 +73,3 @@ extension RefreshControlDemoView: UITableViewDataSource {
return cell
}
}

// MARK: - RefreshControlDelegate

extension RefreshControlDemoView: RefreshControlDelegate {
func refreshControlDidBeginRefreshing(_ refreshControl: RefreshControl) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { [weak self] in
self?.refreshControl.endRefreshing()
self?.tableView.reloadData()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,62 +1,15 @@
import UIKit

/// Branded replacement for UIActivityIndicatorView with possibility to use delayed start.
public class LoadingIndicatorView: UIView {
/// Currently not in use. Replaced with Native UIActivityIndicatorView. Still here in case we decide to go back
public class LoadingIndicatorView: UIActivityIndicatorView {
public enum State {
case delayedStart
case started
case stopped
}
private let backgroundLayer = CAShapeLayer()
private let animatedLayer = CAShapeLayer()
private let duration: CGFloat = 2.5
private let lineWidth: CGFloat = 4
private let startAngle: CGFloat = 3 * .pi / 2

private var endAngle: CGFloat {
return startAngle + 2 * .pi
}

public private(set) var state = State.stopped

public var progress: CGFloat {
get { return animatedLayer.strokeEnd }
set {
CATransaction.begin()
CATransaction.setDisableActions(true)
animatedLayer.strokeEnd = newValue
backgroundLayer.opacity = Float(newValue)
CATransaction.commit()
}
}

public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}

public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}

public override func layoutSubviews() {
super.layoutSubviews()

let center = CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
let radius = min(bounds.size.width, bounds.size.height) / 2.0 - animatedLayer.lineWidth / 2.0

let bezierPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)

backgroundLayer.path = bezierPath.cgPath
animatedLayer.path = bezierPath.cgPath
backgroundLayer.frame = bounds
animatedLayer.frame = bounds

layer.addSublayer(backgroundLayer)
layer.addSublayer(animatedLayer)
}

/// Starts the animation of the loading indicator after a short delay, unless it has already been stopped.
/// - Parameters:
/// - after: Seconds to wait until starting animation (approximately)
Expand All @@ -70,91 +23,42 @@ public class LoadingIndicatorView: UIView {
}
}

/// Starts the animation of the loading indicator.
public func startAnimating() {
animateGroup()
isHidden = false
state = .started
}

/// Stops the animation of the loading indicator.
public func stopAnimating() {
backgroundLayer.removeAllAnimations()
animatedLayer.removeAllAnimations()
isHidden = true
state = .stopped
public init() {
super.init(frame: .zero)
setup()
}
}

extension LoadingIndicatorView {
private func setup() {
backgroundColor = .clear

backgroundLayer.fillColor = UIColor.clear.cgColor
backgroundLayer.strokeColor = UIColor.loadingIndicatorBackground.cgColor
backgroundLayer.strokeStart = 0
backgroundLayer.strokeEnd = 1
backgroundLayer.lineWidth = lineWidth
backgroundLayer.lineCap = .round

animatedLayer.fillColor = UIColor.clear.cgColor
animatedLayer.strokeColor = UIColor.loadingIndicator.cgColor
animatedLayer.strokeStart = 0
animatedLayer.strokeEnd = 1
animatedLayer.lineWidth = lineWidth
animatedLayer.lineCap = .round

isHidden = true
public override init(style: UIActivityIndicatorView.Style = .large) {
super.init(frame: .zero)
setup(style: style)
}

private func animateStrokeEnd() -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.beginTime = 0
animation.duration = CFTimeInterval(duration / 2.0)
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)

return animation
public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}

private func animateStrokeStart() -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: "strokeStart")
animation.beginTime = CFTimeInterval(duration / 2.0)
animation.duration = CFTimeInterval(duration / 2.0)
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)

return animation
public required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}

private func animateRotation() -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.fromValue = 0
animation.toValue = .pi * 2.0
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.repeatCount = .infinity

return animation
/// Starts the animation of the loading indicator.
public override func startAnimating() {
super.startAnimating()
isHidden = false
state = .started
}

private func animateGroup() {
let animationGroup = CAAnimationGroup()
animationGroup.animations = [animateStrokeEnd(), animateStrokeStart(), animateRotation()]
animationGroup.duration = CFTimeInterval(duration)
animationGroup.fillMode = .both
animationGroup.isRemovedOnCompletion = false
animationGroup.repeatCount = .infinity

animatedLayer.add(animationGroup, forKey: "loading")
/// Stops the animation of the loading indicator.
public override func stopAnimating() {
super.stopAnimating()
isHidden = true
state = .stopped
}
}

// MARK: - Private extensions

private extension UIColor {
static var loadingIndicatorBackground: UIColor {
return UIColor(r: 221, g: 232, b: 250)
private func setup(style: UIActivityIndicatorView.Style = .large) {
self.style = style
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ public class RefreshControl: UIRefreshControl {
private func handleLoadingProgress() {
let pullDistance = superview.flatMap({ $0.bounds.height / 5 }) ?? defaultPullDistance
let progress = min(max(topOffset, 0.0), pullDistance) / pullDistance
loadingIndicatorView.progress = progress

if topOffset >= pullDistance {
beginRefreshing()
Expand All @@ -105,7 +104,6 @@ public class RefreshControl: UIRefreshControl {

private func startAnimatingLoadingIndicator() {
isAnimating = true
loadingIndicatorView.progress = 1
loadingIndicatorView.startAnimating()
delegate?.refreshControlDidBeginRefreshing(self)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public struct LoadingSwiftUIView: View {
}
}
} else {
SwiftUILoadingIndicator()
ProgressView()
.scaleEffect(loadingIndicatorScale)
.onAppear {
loadingIndicatorScale = initialScale
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ import UIKit
private var loadingIndicatorCenterY: NSLayoutConstraint?

private lazy var loadingIndicator: LoadingIndicatorView = {
let view = LoadingIndicatorView()
view.translatesAutoresizingMaskIntoConstraints = false
let view = LoadingIndicatorView(withAutoLayout: true)
view.transform = loadingIndicatorInitialTransform
return view
}()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ public class AdRecommendationsGridView: UIView {
}()

private lazy var refreshControl: UIRefreshControl = {
let refreshControl = RefreshControl(frame: .zero)
refreshControl.delegate = self
let refreshControl = UIRefreshControl(frame: .zero)
refreshControl.addTarget(self, action: #selector(handleRefreshBegan), for: .valueChanged)
return refreshControl
}()

Expand Down Expand Up @@ -116,6 +116,10 @@ public class AdRecommendationsGridView: UIView {
collectionView.collectionViewLayout.invalidateLayout()
}

@objc private func handleRefreshBegan() {
delegate?.adRecommendationsGridViewDidStartRefreshing(self)
}

// MARK: - Public methods

public func reloadData() {
Expand Down Expand Up @@ -252,11 +256,3 @@ extension AdRecommendationsGridView: AdRecommendationsGridViewLayoutDelegate {
return dataSource?.adRecommendationsGridView(self, heightForItemWithWidth: width, at: indexPath) ?? 0
}
}

// MARK: - RefreshControlDelegate

extension AdRecommendationsGridView: RefreshControlDelegate {
public func refreshControlDidBeginRefreshing(_ refreshControl: RefreshControl) {
delegate?.adRecommendationsGridViewDidStartRefreshing(self)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ public class FavoriteFoldersListView: UIView {
}()

private lazy var refreshControl: UIRefreshControl = {
let refreshControl = RefreshControl(frame: .zero)
refreshControl.delegate = self
let refreshControl = UIRefreshControl(frame: .zero)
refreshControl.addTarget(self, action: #selector(handleRefreshBegan), for: .valueChanged)
return refreshControl
}()

Expand Down Expand Up @@ -366,6 +366,10 @@ public class FavoriteFoldersListView: UIView {
@objc private func handleXmasButtonTap() {
delegate?.favoriteFoldersListViewDidSelectXmasButton(self)
}

@objc private func handleRefreshBegan() {
delegate?.favoriteFoldersListViewDidBeginRefreshing(self)
}
}

// MARK: - UITableViewDataSource
Expand Down Expand Up @@ -460,14 +464,6 @@ extension FavoriteFoldersListView: UITableViewDelegate {
}
}

// MARK: - RefreshControlDelegate

extension FavoriteFoldersListView: RefreshControlDelegate {
public func refreshControlDidBeginRefreshing(_ refreshControl: RefreshControl) {
delegate?.favoriteFoldersListViewDidBeginRefreshing(self)
}
}

// MARK: - FavoriteFoldersFooterViewDelegate

extension FavoriteFoldersListView: FavoriteFoldersFooterViewDelegate {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SwiftUI

/// Currently not in use. Replaced with Native ProgressView. Still here in case we decide to go back
public struct SwiftUILoadingIndicator: View {
private struct ProgressCircle: Shape {
var startAngle: Angle
Expand Down