Как использовать pull для обновления в Swift?
Я создаю программу для чтения RSS с использованием swift и мне нужно реализовать функцию pull для перезагрузки.
Вот как я пытаюсь это сделать.
class FirstViewController: UIViewController,
UITableViewDelegate, UITableViewDataSource {
@IBOutlet var refresh: UIScreenEdgePanGestureRecognizer
@IBOutlet var newsCollect: UITableView
var activityIndicator:UIActivityIndicatorView? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.newsCollect.scrollEnabled = true
// Do any additional setup after loading the view, typically from a nib.
if nCollect.news.count <= 2{
self.collectNews()
}
else{
self.removeActivityIndicator()
}
view.addGestureRecognizer(refresh)
}
@IBAction func reload(sender: UIScreenEdgePanGestureRecognizer) {
nCollect.news = News[]()
return newsCollect.reloadData()
}
Я получаю:
Свойство self.refresh не инициализируется при вызове super.init
Пожалуйста, помогите мне понять поведение распознавателей жестов. Рабочий пример кода будет отличной помощью.
Благодарю.
19 ответов
Pull для обновления встроен в iOS. Вы можете сделать это быстро, как
var refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(refresh(_:)), for: UIControl.Event.valueChanged)
tableView.addSubview(refreshControl) // not required when using UITableViewController
}
@objc func refresh(sender:AnyObject) {
// Code to refresh table view
}
В какой-то момент вы можете закончить освежиться.
refreshControl.endRefreshing()
Решение с раскадровкой и быстрой...
1.) Откройте файл.storyboard, выберите TableViewController в вашей раскадровке и "Включите" контроллер табличного представления - функция обновления в утилитах.
2.) Откройте связанный UITableViewController-Class и добавьте следующую строку в viewDidLoad-Method.
self.refreshControl?.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
ИЛИ в Swift 2.2:
self.refreshControl?.addTarget(self, action: #selector(TestTableViewController.refresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
3.) Добавьте следующий метод выше метода viewDidLoad
func refresh(sender:AnyObject)
{
// Updating your data here...
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}
iOS 10 - Swift 3 (также применимо для iOS 11 - Swift 4)
Я хотел бы упомянуть функцию PRETTY COOL, которая была включена с iOS 10, а именно:
На данный момент UIRefreshControl напрямую поддерживается в каждом из UICollectionView
, UITableView
а также UIScrollView
!
Каждое из этих представлений имеет свойство экземпляра refreshControl, что означает, что больше нет необходимости добавлять его в качестве подпредставления в представлении прокрутки, все, что вам нужно сделать, это:
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(doSomething), for: .valueChanged)
// this is the replacement of implementing: "collectionView.addSubview(refreshControl)"
collectionView.refreshControl = refreshControl
}
func doSomething(refreshControl: UIRefreshControl) {
print("Hello World!")
// somewhere in your code you might need to call:
refreshControl.endRefreshing()
}
Лично я считаю более естественным рассматривать его как свойство для представления прокрутки, а не добавлять его как подпредставление, особенно потому, что единственное подходящее представление в качестве суперпредставления для UIRefreshControl - это представление прокрутки, то есть функциональность использования UIRefreshControl полезно при работе с прокруткой; Вот почему этот подход должен быть более очевидным для настройки представления управления обновлением.
Тем не менее, у вас все еще есть возможность использования addSubview
на основе версии iOS:
if #available(iOS 10.0, *) {
collectionView.refreshControl = refreshControl
} else {
collectionView.addSubview(refreshControl)
}
Swift 4
var refreshControl: UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
tableView.addSubview(refreshControl)
}
@objc func refresh(_ sender: Any) {
// your code to refresh tableView
}
И вы можете перестать освежаться с:
refreshControl.endRefreshing()
Swift 5
private var pullControl = UIRefreshControl()
pullControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
pullControl.addTarget(self, action: #selector(refreshListData(_:)), for: .valueChanged)
if #available(iOS 10.0, *) {
tableView.refreshControl = pullControl
} else {
tableView.addSubview(pullControl)
}
// Actions
@objc private func refreshListData(_ sender: Any) {
self.pullControl.endRefreshing() // You can stop after API Call
// Call API
}
В Swift используйте это,
Если вы хотите, чтобы тянуть, чтобы обновить в WebView,
Так что попробуйте этот код:
override func viewDidLoad() {
super.viewDidLoad()
addPullToRefreshToWebView()
}
func addPullToRefreshToWebView(){
var refreshController:UIRefreshControl = UIRefreshControl()
refreshController.bounds = CGRectMake(0, 50, refreshController.bounds.size.width, refreshController.bounds.size.height) // Change position of refresh view
refreshController.addTarget(self, action: Selector("refreshWebView:"), forControlEvents: UIControlEvents.ValueChanged)
refreshController.attributedTitle = NSAttributedString(string: "Pull down to refresh...")
YourWebView.scrollView.addSubview(refreshController)
}
func refreshWebView(refresh:UIRefreshControl){
YourWebView.reload()
refresh.endRefreshing()
}
подробности
- Версия Xcode 10.3 (10G8), Swift 5
Особенности
- Возможность сделать "тянуть, чтобы обновить" программно
- Защита от нескольких событий "тянуть, чтобы обновить"
- Возможность продолжить анимацию индикатора активности при переключении контроллера вида (например, в случае TabController)
Решение
import UIKit
class RefreshControl: UIRefreshControl {
private weak var actionTarget: AnyObject?
private var actionSelector: Selector?
override init() { super.init() }
convenience init(actionTarget: AnyObject?, actionSelector: Selector) {
self.init()
self.actionTarget = actionTarget
self.actionSelector = actionSelector
addTarget()
}
private func addTarget() {
guard let actionTarget = actionTarget, let actionSelector = actionSelector else { return }
addTarget(actionTarget, action: actionSelector, for: .valueChanged)
}
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
func endRefreshing(deadline: DispatchTime? = nil) {
guard let deadline = deadline else { endRefreshing(); return }
DispatchQueue.global(qos: .default).asyncAfter(deadline: deadline) { [weak self] in
DispatchQueue.main.async { self?.endRefreshing() }
}
}
func refreshActivityIndicatorView() {
guard let selector = actionSelector else { return }
let _isRefreshing = isRefreshing
removeTarget(actionTarget, action: selector, for: .valueChanged)
endRefreshing()
if _isRefreshing { beginRefreshing() }
addTarget()
}
func generateRefreshEvent() {
beginRefreshing()
sendActions(for: .valueChanged)
}
}
public extension UIScrollView {
private var _refreshControl: RefreshControl? { return refreshControl as? RefreshControl }
func addRefreshControll(actionTarget: AnyObject?, action: Selector, replaceIfExist: Bool = false) {
if !replaceIfExist && refreshControl != nil { return }
refreshControl = RefreshControl(actionTarget: actionTarget, actionSelector: action)
}
func scrollToTopAndShowRunningRefreshControl(changeContentOffsetWithAnimation: Bool = false) {
_refreshControl?.refreshActivityIndicatorView()
guard let refreshControl = refreshControl,
contentOffset.y != -refreshControl.frame.height else { return }
setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.height), animated: changeContentOffsetWithAnimation)
}
private var canStartRefreshing: Bool {
guard let refreshControl = refreshControl, !refreshControl.isRefreshing else { return false }
return true
}
func startRefreshing() {
guard canStartRefreshing else { return }
_refreshControl?.generateRefreshEvent()
}
func pullAndRefresh() {
guard canStartRefreshing else { return }
scrollToTopAndShowRunningRefreshControl(changeContentOffsetWithAnimation: true)
_refreshControl?.generateRefreshEvent()
}
func endRefreshing(deadline: DispatchTime? = nil) { _refreshControl?.endRefreshing(deadline: deadline) }
}
использование
// Add refresh control to UICollectionView / UITableView / UIScrollView
private func setupTableView() {
let tableView = UITableView()
// ...
tableView.addRefreshControll(actionTarget: self, action: #selector(refreshData))
}
@objc func refreshData(_ refreshControl: UIRefreshControl) {
tableView?.endRefreshing(deadline: .now() + .seconds(3))
}
// Stop refreshing in UICollectionView / UITableView / UIScrollView
tableView.endRefreshing()
// Simulate pull to refresh in UICollectionView / UITableView / UIScrollView
tableView.pullAndRefresh()
Полный образец
Не забудьте добавить код решения здесь
import UIKit
class ViewController: UIViewController {
private weak var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
private func setupTableView() {
let tableView = UITableView()
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.dataSource = self
tableView.delegate = self
tableView.addRefreshControll(actionTarget: self, action: #selector(refreshData))
self.tableView = tableView
}
}
extension ViewController {
@objc func refreshData(_ refreshControl: UIRefreshControl) {
print("refreshing")
tableView?.endRefreshing(deadline: .now() + .seconds(3))
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int { return 1 }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "\(indexPath)"
return cell
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.pullAndRefresh()
}
}
Ответ Анхила мне очень помог.
Однако после дальнейших экспериментов я заметил, что предложенное решение иногда вызывает не очень приятный сбой пользовательского интерфейса.
Вместо этого переход на этот подход* сделал свое дело для меня.
* Swift 2.1
//Create an instance of a UITableViewController. This will host your UITableView.
private let tableViewController = UITableViewController()
//Add tableViewController as a childViewController and set its tableView property to your UITableView.
self.addChildViewController(self.tableViewController)
self.tableViewController.tableView = self.tableView
self.refreshControl.addTarget(self, action: "refreshData:", forControlEvents: .ValueChanged)
self.tableViewController.refreshControl = self.refreshControl
На 2023 год этот простой
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
your data source = latest ...
table.reloadData()
table.refreshControl = UIRefreshControl()
table.refreshControl?.addTarget(self,
action: #selector(pulldown), for: .valueChanged)
table.refreshControl?.tintColor = .clear
}
@objc func pulldown() {
your data source = latest ...
table.reloadData()
DispatchQueue.main.async { self.table.refreshControl?.endRefreshing() }
}
Очень часто вам не нужен "спиннер". Эта строка — самый простой способ скрыть счетчик:
table.refreshControl?.tintColor = .clear
Вот и все.
Если (по какой-то неясной причине) вы действительно хотите создать подклассUIRefreshControl
, здесь есть отличный недавний ответ , который показывает, как это сделать.
Что ошибка говорит вам, это то, что refresh
не инициализирован. Обратите внимание, что вы решили сделать refresh
не обязательно, что в Swift означает, что оно должно иметь значение перед вызовом super.init
(или это неявно называется, что, кажется, ваш случай). Либо сделать refresh
необязательно (вероятно, что вы хотите) или инициализировать его каким-либо образом.
Я бы посоветовал снова прочитать вводную документацию Swift, в которой подробно об этом говорится.
И последнее, не часть ответа, как отмечает @Anil, в iOS есть встроенный элемент управления обновлением, который называется UIRefresControl
, что может быть что-то стоит посмотреть.
Я создал приложение для RSS-канала, в котором у меня есть функция обновления для Pull To, в которой изначально были некоторые из перечисленных выше проблем.
Но чтобы добавить к ответам пользователей выше, я искал повсюду свой вариант использования и не мог его найти. Я загружал данные из Интернета (RSSFeed), и я хотел, чтобы раскрыть свой список историй для обновления.
То, что упомянуто выше, покрывает правильные области, но с некоторыми из проблем, с которыми сталкиваются люди, вот что я сделал, и это работает удовольствие:
Я воспользовался подходом @Blankarsch и перешел на свою main.storyboard и выбрал табличное представление для использования обновления, тогда не упомянуто создание IBOutlet и IBAction для эффективного использования обновления.
//Created from main.storyboard cntrl+drag refresh from left scene to assistant editor
@IBOutlet weak var refreshButton: UIRefreshControl
override func viewDidLoad() {
......
......
//Include your code
......
......
//Is the function called below, make sure to put this in your viewDidLoad
//method or not data will be visible when running the app
getFeedData()
}
//Function the gets my data/parse my data from the web (if you havnt already put this in a similar function)
//remembering it returns nothing, hence return type is "-> Void"
func getFeedData() -> Void{
.....
.....
}
//From main.storyboard cntrl+drag to assistant editor and this time create an action instead of outlet and
//make sure arguments are set to none and note sender
@IBAction func refresh() {
//getting our data by calling the function which gets our data/parse our data
getFeedData()
//note: refreshControl doesnt need to be declared it is already initailized. Got to love xcode
refreshControl?.endRefreshing()
}
Надеюсь, что это помогает любому в той же ситуации, что и я
func pullToRefresh(){
let refresh = UIRefreshControl()
refresh.addTarget(self, action: #selector(handleTopRefresh(_:)), for: .valueChanged )
refresh.tintColor = UIColor.appBlack
self.tblAddressBook.addSubview(refresh)
}
@objc func handleTopRefresh(_ sender:UIRefreshControl){
self.callAddressBookListApi(isLoaderRequired: false)
sender.endRefreshing()
}
Вы можете добиться этого, используя несколько строк кода. Итак, почему вы собираетесь застрять в сторонней библиотеке или пользовательском интерфейсе. Оттягивание для обновления встроено в iOS. Вы можете сделать это быстро, как
var pullControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
pullControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
pullControl.addTarget(self, action: #selector(pulledRefreshControl(_:)), for: UIControl.Event.valueChanged)
tableView.addSubview(pullControl) // not required when using UITableViewController
}
@objc func pulledRefreshControl(sender:AnyObject) {
// Code to refresh table view
}
Для тяги, чтобы обновить, я использую
DGElasticPullToRefresh
https://github.com/gontovnik/DGElasticPullToRefresh
Монтаж
под 'DGElasticPullToRefresh'
import DGElasticPullToRefresh
и поместите эту функцию в ваш файл swift и вызовите эту функцию из вашего
переопределить функцию viewWillAppear(_ animated: Bool)
func Refresher() {
let loadingView = DGElasticPullToRefreshLoadingViewCircle()
loadingView.tintColor = UIColor(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 1.0)
self.table.dg_addPullToRefreshWithActionHandler({ [weak self] () -> Void in
//Completion block you can perfrom your code here.
print("Stack Overflow")
self?.table.dg_stopLoading()
}, loadingView: loadingView)
self.table.dg_setPullToRefreshFillColor(UIColor(red: 255.0/255.0, green: 57.0/255.0, blue: 66.0/255.0, alpha: 1))
self.table.dg_setPullToRefreshBackgroundColor(self.table.backgroundColor!)
}
И не забудьте удалить ссылку, пока вид исчезнет
чтобы удалить тягу, чтобы обновить, вставьте этот код в ваш
переопределить функцию viewDidDisappear(_ animated: Bool)
override func viewDidDisappear(_ animated: Bool) {
table.dg_removePullToRefresh()
}
И это будет выглядеть
Удачного кодирования:)
Я предлагаю сделать расширение pull To Refresh для использования в каждом классе.
1) Создайте пустой файл Swift: Файл - Новый - Файл - Файл Swift.
2) Добавить следующее
// AppExtensions.swift
import Foundation
import UIKit
var tableRefreshControl:UIRefreshControl = UIRefreshControl()
//MARK:- VIEWCONTROLLER EXTENSION METHODS
public extension UIViewController
{
func makePullToRefreshToTableView(tableName: UITableView,triggerToMethodName: String){
tableRefreshControl.attributedTitle = NSAttributedString(string: "TEST: Pull to refresh")
tableRefreshControl.backgroundColor = UIColor.whiteColor()
tableRefreshControl.addTarget(self, action: Selector(triggerToMethodName), forControlEvents: UIControlEvents.ValueChanged)
tableName.addSubview(tableRefreshControl)
}
func makePullToRefreshEndRefreshing (tableName: String)
{
tableRefreshControl.endRefreshing()
//additional codes
}
}
3) В вашем View Controller вызовите эти методы как:
override func viewWillAppear(animated: Bool) {
self.makePullToRefreshToTableView(bidderListTable, triggerToMethodName: "pullToRefreshBidderTable")
}
4) В какой-то момент вы хотели закончить обновление:
func pullToRefreshBidderTable() {
self.makePullToRefreshEndRefreshing("bidderListTable")
//Code What to do here.
}
OR
self.makePullToRefreshEndRefreshing("bidderListTable")
Вот как я заставил это работать, используя Xcode 7.2, который я считаю серьезной ошибкой. Я использую это в моем UITableViewController
внутри моего viewWillAppear
refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: "configureMessages", forControlEvents: .ValueChanged)
refreshControl!.beginRefreshing()
configureMessages()
func configureMessages() {
// configuring messages logic here
self.refreshControl!.endRefreshing()
}
Как видите, я буквально должен позвонить configureMessage()
метод после настройки моего UIRefreshControl
после этого последующие обновления будут работать нормально.
Вы можете использовать этот подкласс tableView:
import UIKit
protocol PullToRefreshTableViewDelegate : class {
func tableViewDidStartRefreshing(tableView: PullToRefreshTableView)
}
class PullToRefreshTableView: UITableView {
@IBOutlet weak var pullToRefreshDelegate: AnyObject?
private var refreshControl: UIRefreshControl!
private var isFirstLoad = true
override func willMoveToSuperview(newSuperview: UIView?) {
super.willMoveToSuperview(newSuperview)
if (isFirstLoad) {
addRefreshControl()
isFirstLoad = false
}
}
private func addRefreshControl() {
refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: "refresh", forControlEvents: .ValueChanged)
self.addSubview(refreshControl)
}
@objc private func refresh() {
(pullToRefreshDelegate as? PullToRefreshTableViewDelegate)?.tableViewDidStartRefreshing(self)
}
func endRefreshing() {
refreshControl.endRefreshing()
}
}
1 - в конструкторе интерфейса измените класс вашего tableView на PullToRefreshTableView
или создать PullToRefreshTableView
программно
2 - реализовать PullToRefreshTableViewDelegate
по вашему мнению контроллер
3 - tableViewDidStartRefreshing(tableView: PullToRefreshTableView)
будет вызван в вашем контроллере представления, когда представление таблицы начнет обновляться
4 - звонок yourTableView.endRefreshing()
закончить освежающий
Другие ответы верны, но для более подробной информации проверьте это сообщение Потяните, чтобы обновить
Включить обновление в раскадровке
Когда вы работаете с UITableViewController, решение довольно простое: во-первых, выберите контроллер табличного представления в вашей раскадровке, откройте инспектор атрибутов и включите обновление:
UITableViewController поставляется со ссылкой на UIRefreshControl из коробки. Вам просто нужно подключить несколько вещей, чтобы начать и завершить обновление, когда пользователь отключается.
Переопределить viewDidLoad()
В переопределении viewDidLoad() добавьте цель для обработки обновления следующим образом:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.refreshControl?.addTarget(self, action: "handleRefresh:", forControlEvents: UIControlEvents.ValueChanged)
}
- Поскольку я указал "handleRefresh:" (обратите внимание на двоеточие!) В качестве аргумента действия, мне нужно определить функцию в этом классе UITableViewController с тем же именем. Кроме того, функция должна принимать один аргумент
- Мы бы хотели, чтобы это действие вызывалось для UIControlEvent с именем ValueChanged.
- Не забудьте позвонить
refreshControl.endRefreshing()
Для получения дополнительной информации, пожалуйста, перейдите по ссылке Ссылка и все кредиты идут на этот пост
Из-за меньшей настраиваемости, дублирования кода и ошибок, связанных с управлением pull to refresh, я создал библиотеку PullToRefreshDSL, которая использует
DSL
узор так же, как
SnapKit
// You only have to add the callback, rest is taken care of
tableView.ptr.headerCallback = { [weak self] in // weakify self to avoid strong reference
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) { // your network call
self?.tableView.ptr.isLoadingHeader = false // setting false will hide the view
}
}
Вам нужно только добавить волшебное ключевое слово
ptr
после любого подкласса UIScrollView, то есть UITableView / UICollectionView
Вам не нужно загружать библиотеку, вы можете исследовать и изменять исходный код, я просто указываю на возможную реализацию pull to refresh для iOS.