Как реализовать Pull-to-Refresh (UIRefreshControl) в Swift для iOS приложения?
Полное решение: UIRefreshControl для UITableView
Вы на верном пути с UIRefreshControl. Это стандартный компонент UIKit для реализации pull-to-refresh. Давайте разберем пошаговое решение с правильной настройкой и симуляцией загрузки данных.
1. Базовая настройка (iOS 10+)
Начиная с iOS 10, UIRefreshControl можно добавить напрямую через свойство refreshControl у UIScrollView (родительский класс UITableView):
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView!
var items = ["Item 1", "Item 2", "Item 3"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
// Создаем и настраиваем UIRefreshControl
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshData(_:)), for: .valueChanged)
// Настройка внешнего вида (опционально)
refreshControl.tintColor = .systemBlue
refreshControl.attributedTitle = NSAttributedString(string: "Обновление данных...")
// Добавляем к таблице
tableView.refreshControl = refreshControl
}
@objc func refreshData(_ sender: UIRefreshControl) {
// Здесь будет логика обновления
}
// ... остальные методы tableView
}
2. Симуляция загрузки с задержкой
Для тестирования добавим искусственную задержку с помощью DispatchQueue или asyncAfter:
@objc func refreshData(_ sender: UIRefreshControl) {
// Симулируем загрузку данных (например, из сети)
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
guard let self = self else { return }
// Пример: добавляем новый элемент
self.items.insert("New item \(Date())", at: 0)
// Обновляем таблицу
self.tableView.reloadData()
// Завершаем анимацию обновления
sender.endRefreshing()
}
}
3. Полный рабочий пример
Вот готовый класс с правильной обработкой:
class ViewController: UIViewController, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView!
var items = ["Item 1", "Item 2", "Item 3"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
setupRefreshControl()
}
private func setupRefreshControl() {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshData), for: .valueChanged)
refreshControl.tintColor = UIColor.systemBlue
refreshControl.attributedTitle = NSAttributedString(
string: "Обновление...",
attributes: [.foregroundColor: UIColor.systemGray]
)
tableView.refreshControl = refreshControl
}
@objc private func refreshData() {
print("Pull-to-refresh активирован")
// Имитация сетевого запроса
DispatchQueue.global().asyncAfter(deadline: .now() + 1.5) { [weak self] in
guard let self = self else { return }
// Создаем новые данные
let newItems = (1...3).map { "Loaded item \($0) at \(Date())" }
DispatchQueue.main.async {
self.items = newItems + self.items
self.tableView.reloadData()
self.tableView.refreshControl?.endRefreshing()
}
}
}
// MARK: - UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
}
4. Важные моменты
- Не забудьте про weak self — при использовании замыканий внутри
asyncAfterобязательно используйте[weak self], чтобы избежать утечек памяти. - endRefreshing() обязателен — без вызова этого метода анимация обновления не прекратится, и пользователь не сможет сделать pull-to-refresh снова.
- Работа с main thread — обновление UI (reloadData, endRefreshing) должно выполняться на главном потоке.
- Совместимость — код работает на iOS 10 и новее, что покрывает практически все современные устройства.
5. Альтернативный способ (для iOS 9 и ниже)
Если по каким-то причинам нужно поддерживать iOS 9, используйте старый метод:
// Вместо tableView.refreshControl = refreshControl
tableView.addSubview(refreshControl)
Но для современных проектов рекомендуется первый вариант.
Теперь ваш pull-to-refresh будет работать корректно. Попробуйте запустить приложение, потяните таблицу вниз — увидите индикатор загрузки, а через 1.5 секунды появятся новые элементы.
<<Дополнительный подход: использование Combine (iOS 13+)
Если вы используете SwiftUI или Combine в своем проекте, можно реализовать pull-to-refresh более декларативно:
import UIKit
import Combine
class ViewController: UIViewController, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView!
@Published var items = ["Item 1", "Item 2", "Item 3"]
private var cancellables = Set()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
setupRefreshControl()
}
private func setupRefreshControl() {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshData), for: .valueChanged)
tableView.refreshControl = refreshControl
}
@objc private func refreshData() {
// Используем Combine для асинхронной загрузки
Future<[String], Never> { promise in
DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
let newData = ["Loaded \(Date())", "Item 4", "Item 5"]
promise(.success(newData))
}
}
.receive(on: DispatchQueue.main)
.sink { [weak self] newItems in
self?.items = newItems + (self?.items ?? [])
self?.tableView.reloadData()
self?.tableView.refreshControl?.endRefreshing()
}
.store(in: &cancellables)
}
// UITableViewDataSource методы
}
Преимущества:
- Чистый и декларативный код без вложенных замыканий
- Легко комбинировать с другими Combine операторами
- Автоматическое управление потоками через
receive(on:)
Этот подход особенно полезен, если вы уже используете Combine в проекте для сетевых запросов или обработки данных.
Ответов пока нет.
Войдите чтобы оставить ответ