From 6a16d11ab02cdba550e85ca550e949f074592090 Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+adobels@users.noreply.github.com> Date: Sun, 14 Jan 2024 19:22:22 +0100 Subject: [PATCH] Initial project --- PullToRefreshControl/.gitignore | 8 +++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 +++ PullToRefreshControl/Package.swift | 23 +++++++ .../PullToRefreshControl.swift | 60 +++++++++++++++++++ .../PullToRefreshControlTests.swift | 12 ++++ README.md | 14 ++++- 6 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 PullToRefreshControl/.gitignore create mode 100644 PullToRefreshControl/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 PullToRefreshControl/Package.swift create mode 100644 PullToRefreshControl/Sources/PullToRefreshControl/PullToRefreshControl.swift create mode 100644 PullToRefreshControl/Tests/PullToRefreshControlTests/PullToRefreshControlTests.swift diff --git a/PullToRefreshControl/.gitignore b/PullToRefreshControl/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/PullToRefreshControl/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/PullToRefreshControl/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PullToRefreshControl/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/PullToRefreshControl/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/PullToRefreshControl/Package.swift b/PullToRefreshControl/Package.swift new file mode 100644 index 0000000..75473c7 --- /dev/null +++ b/PullToRefreshControl/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "PullToRefreshControl", + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "PullToRefreshControl", + targets: ["PullToRefreshControl"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "PullToRefreshControl"), + .testTarget( + name: "PullToRefreshControlTests", + dependencies: ["PullToRefreshControl"]), + ] +) diff --git a/PullToRefreshControl/Sources/PullToRefreshControl/PullToRefreshControl.swift b/PullToRefreshControl/Sources/PullToRefreshControl/PullToRefreshControl.swift new file mode 100644 index 0000000..8d71f7d --- /dev/null +++ b/PullToRefreshControl/Sources/PullToRefreshControl/PullToRefreshControl.swift @@ -0,0 +1,60 @@ +// +// PullToRefreshControl.swift +// PullToRefreshControl +// +// Created by Blazej SLEBODA on 14/01/2024. +// + +import UIKit + +public class PullToRefreshControl: UIRefreshControl { + public override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + public required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + private func commonInit() { + NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main) { [weak self] _ in + self?.cancelOrRestartRefreshingIfNeeded() + } + } + public func pullToRefresh() { + guard let superview = superview as? UITableView else { + return + } + superview.contentOffset = .init(x: superview.contentOffset.x, y: -frame.height) + beginRefreshing() + sendActions(for: .valueChanged) + } + public func cancelOrRestartRefreshingIfNeeded() { + cancelIncompletePullToRefreshIfNeeded() + restartBeginRefreshingIfNeeded() + } + private func restartBeginRefreshingIfNeeded() { + guard + isRefreshing, + let superview = superview as? UITableView + else { return } + let oldValueContentOffset = superview.contentOffset + UIView.performWithoutAnimation { + endRefreshing() + } + DispatchQueue.main.async { + superview.contentOffset = oldValueContentOffset + self.beginRefreshing() + } + } + private func cancelIncompletePullToRefreshIfNeeded() { + guard + !isRefreshing, + let superview = superview as? UITableView, + superview.contentOffset != .zero + else { return } + UIView.performWithoutAnimation { [weak self] in + self?.endRefreshing() + } + } +} diff --git a/PullToRefreshControl/Tests/PullToRefreshControlTests/PullToRefreshControlTests.swift b/PullToRefreshControl/Tests/PullToRefreshControlTests/PullToRefreshControlTests.swift new file mode 100644 index 0000000..63ac52d --- /dev/null +++ b/PullToRefreshControl/Tests/PullToRefreshControlTests/PullToRefreshControlTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import PullToRefreshControl + +final class PullToRefreshControlTests: XCTestCase { + func testExample() throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +} diff --git a/README.md b/README.md index ccb7174..955cf21 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # PullToRefreshControl -UIRefreshControl with programatic pull to refresh and fixed paused animations and inclomplete pull to refresh + +"PullToRefreshControl" is a custom subclass of UIRefreshControl, designed to enhance the functionality of the standard pull-to-refresh action in iOS applications. This control is specifically tailored for use with UITableView, offering a programmable approach to initiate the refresh action and addressing a common issue where the refresh animation pauses or fails to resume correctly. + +Key Features + +1. Programmatic Refresh Triggering: +The control introduces a method, pullToRefresh(), allowing developers to initiate the pull-to-refresh action programmatically. This is particularly useful for scenarios where the refresh needs to be triggered without user interaction, such as during the initial loading of the table view. +2. Handling App State Transitions: +It automatically manages the refresh control's state during various app state transitions. This includes scenarios where the app returns from the background, or when the user navigates back to the view controller containing the UITableView. +3. Seamless Animation Continuity: +The cancelOrRestartRefreshingIfNeeded() method ensures that the refresh animation continues seamlessly. It addresses the common issue where the animation gets paused or stuck, especially after the app transitions from the background or when returning from another view controller. +4. Improved User Experience: +By fixing the interrupted animation issue, this control provides a smoother and more consistent user experience. It maintains the visual feedback that users expect when they initiate a refresh action.