UIRefreshControl sin UITableViewController

308

Solo es curioso, ya que no parece posible de inmediato, pero ¿hay una manera astuta de aprovechar la nueva UIRefreshControlclase iOS 6 sin usar una UITableViewControllersubclase?

A menudo uso un UIViewControllercon una UITableViewsubvista y me conforma con UITableViewDataSourcey en UITableViewDelegatelugar de usar un UITableViewControllerabsoluto.

Keller
fuente
66
@DaveDeLong: No, Dave, ¿qué debería hacer? Más adelante en esta página usted dice que su solución no es compatible, entonces, ¿cuál es la solución correcta?
mate
3
@matt, debería usar un UITableViewControllerarchivo y solicitar una API de solicitud de error para usar un UIRefreshControlcon un UITableViewdirectamente.
Dave DeLong
31
UITableViewController ha tenido (y sigue teniendo) demasiados errores y problemas oscuros y de nicho con jerarquías de vista no triviales ... que desaparecen mágicamente cuando cambia a usar un VC estándar con una subvista TableView. "Usar UITVC" es un mal comienzo para cualquier solución, en mi humilde opinión.
Adam
@ Adam ¿Aparecen estos errores cuando se usa 'UITableViewController' como un controlador de vista secundario (dando así acceso a la personalización de la vista sin perder el tiempo en la jerarquía tableViewControllers)? Nunca he encontrado problemas al usarlo de esta manera.
memmons

Respuestas:

388

En una corazonada, y basado en la inspiración de DrummerB, intenté simplemente agregar una UIRefreshControlinstancia como subvista a mi UITableView. ¡Y mágicamente simplemente funciona!

UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
[self.myTableView addSubview:refreshControl];

Esto agrega una UIRefreshControlvista sobre su tabla y funciona como se espera sin tener que usar un UITableViewController:)


EDITAR: Esto anterior todavía funciona, pero como algunos han señalado, hay un ligero "tartamudeo" al agregar el UIRefreshControl de esta manera. Una solución para eso es crear una instancia de UITableViewController y luego configurar UIRefreshControl y UITableView para eso, es decir:

UITableViewController *tableViewController = [[UITableViewController alloc] init];
tableViewController.tableView = self.myTableView;

self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(getConnections) forControlEvents:UIControlEventValueChanged];
tableViewController.refreshControl = self.refreshControl;
Keller
fuente
48
Para su información, este es un comportamiento no compatible y puede cambiar en el futuro. La única garantía que Apple hace es que usarlo de acuerdo con la API proporcionada (en este caso -[UITableViewController setRefreshControl:]) continuará funcionando.
Dave DeLong
18
Con esta implementación, parece que justo antes de activarse handleRefresh:hay un cambio inesperado en el valor de la vista de desplazamiento contentInsetsdurante una fracción de segundo. ¿Alguien más experimenta esto o tiene una solución? (¡Sí, sé que esto no es compatible en primer lugar!)
Tim
66
Realmente debería simplemente usar una Vista de contenedor para esto. Simplemente configure la clase de su controlador de vista en su subclase personalizada de UITableViewControllery estará "haciéndolo bien".
Eugene
3
En iOS7 (desconocido para iOS6) la versión editada funciona bien, excepto cuando cierra y vuelve a abrir la aplicación. La animación no se reproduce hasta la segunda vez que actualiza. La primera vez que se actualiza después de volver a abrir la aplicación, es solo un círculo completo en lugar del círculo de incremento en función de cuán lejos lo baje. ¿Hay alguna solución?
david2391
30
Para evitar que el "Slutter" como se mencionó anteriormente, mientras que sigue sin pasar por el UITableViewController, sólo tiene que añadir el control de refresco por debajo de la vista de tabla, así: [_tableView insertSubview:_refreshControl atIndex:0];. Probado con iOS 7 y 8;)
ptitvinou
95

Para eliminar el tartamudeo causado por la respuesta aceptada, puede asignar su UITableViewa UITableViewController.

_tableViewController = [[UITableViewController alloc]initWithStyle:UITableViewStylePlain];
[self addChildViewController:_tableViewController];

_tableViewController.refreshControl = [UIRefreshControl new];
[_tableViewController.refreshControl addTarget:self action:@selector(loadStream) forControlEvents:UIControlEventValueChanged];

_theTableView = _tableViewController.tableView;

EDITAR:

Una manera de añadir un UIRefreshControlsin UITableViewControllersin tartamudeo y retener la animación agradable después de actualizar los datos en el tableview.

UIRefreshControl *refreshControl = [UIRefreshControl new];
[refreshControl addTarget:self action:@selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
[self.theTableView addSubview:refreshControl];
[self.theTableView sendSubviewToBack:refreshControl];

Más tarde, cuando maneje los datos actualizados ...

- (void)handleRefresh:(UIRefreshControl *)refreshControl {
    [self.theTableView reloadData];
    [self.theTableView layoutIfNeeded];
    [refreshControl endRefreshing];
}
Piotr Tomasik
fuente
77
Ni siquiera tiene que crear un nuevo UITableView. Simplemente diga initWithStyle: existenteTableView.style - y luego haga newTableViewController.tableView = existenteTableView y asigne refreshControl. Se reduce a tres líneas.
Trenskow
No olvide llamar [_tablewViewController didMoveToParentViewController:self];después de agregar la subvista.
qix
44
self.tableView = _tableViewController.tableView;debería ser_tableViewController.tableView = self.tableView
Ali
@Linus ¿por qué es necesario? Agregué _tableViewController.view como subView en mi método viewDidLoad de viewController personalizado y eso pareció funcionar. Ni siquiera necesité configurar _tableViewController.tableView
taber
La razón por la que no puedo usar un UITableViewController y estoy usando un UIViewController con una vista de tabla dentro. stackoverflow.com/questions/18900428/…
lostintranslation
21

Lo que probaría es usar la vista de contenedor dentro de ViewController que está usando. puede definir una subclase limpia de UITableViewController con una vista de tabla dedicada y colocarla en ViewController.

Tomohisa Takaoka
fuente
2
Precisamente, la forma más limpia parece ser agregar un controlador de vista secundario del tipo UITableViewController.
Zdenek
Luego puede tomar el UITableViewController usando self.childViewControllers.firstObject.
cbh2000
^ también puede considerar usar prepareForSeguepara obtener una referencia al objeto UITableViewController en una vista de contenedor, ya que esto se desencadena inmediatamente como un evento segue en la creación de instancias desde el guión gráfico.
crarho
18

Bueno, UIRefreshControl es una subclase de UIView, por lo que puede usarlo solo. Sin embargo, no estoy seguro de cómo se representa. La representación podría simplemente depender del marco, pero también podría depender de un UIScrollView o el UITableViewController.

De cualquier manera, será más un truco que una solución elegante. Le recomiendo que busque en uno de los clones de terceros disponibles o escriba el suyo propio.

ODRefreshControl

ingrese la descripción de la imagen aquí

SlimeRefresh

ingrese la descripción de la imagen aquí

BateristaB
fuente
1
Gracias por su respuesta, pero no exactamente lo que estaba buscando. Sin embargo, la primera oración de tu publicación me inspiró a probar lo que terminó siendo la solución.
Keller
77
ODRefreshControl es increíble, ¡gracias por la sugerencia! Muy simple, y hace el trabajo. Y, por supuesto, mucho más flexible que el de Apple. Nunca entendí por qué alguien usaría UITableViewController, IMO, la peor clase en toda la API. No hace (al lado de) nada, pero hace que sus puntos de vista sean inflexibles.
n13
De hecho, depende de su proyecto lo que use allí ... el uso de uitableview en mi caso fue aumentar la complejidad del proyecto y ahora cambié a uitableviewcontroller que dividió mi código en dos segmentos. Estoy feliz de tener 2 archivos más pequeños que pueden se utilizará para depurar en lugar de un archivo enorme ..
Kush
@ n13 Hola, una buena razón para usar UITableViewController es poder diseñar con celdas estáticas. Desafortunadamente, no está disponible en una tabla Vista que reside en un UIViewController.
Jean Le Moignan
@JeanLeMoignan Las herramientas y bibliotecas de iOS están cambiando rápidamente, por lo que un comentario mío de 3 años ya no es válido. He cambiado a crear todas las interfaces de usuario en código con SnapKit y, sinceramente, es mucho más eficiente que hacer clic 10 mil veces en el generador de interfaces. También cambiar un UITableViewController a un UIViewController es fácil y solo toma un segundo, mientras que en IB es un gran dolor.
n13
7

Intente retrasar la llamada al -endRefreshmétodo refreshControl por una fracción de segundo después de que tableView recargue su contenido, ya sea utilizando NSObject -performSelector:withObject:afterDelay:o GCD dispatch_after.

Creé una categoría en UIRefreshControl para esto:

@implementation UIRefreshControl (Delay)

- (void)endRefreshingAfterDelay:(NSTimeInterval)delay {
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        [self endRefreshing];
    });
}

@end

Lo probé y esto también funciona en Vistas de colección. Me di cuenta de que un retraso tan pequeño como 0.01 segundos es suficiente:

// My data refresh process here while the refresh control 'isRefreshing'
[self.tableView reloadData];
[self.refreshControl endRefreshingAfterDelay:.01];
boliva
fuente
44
Los retrasos y las esperas son de muy mal estilo.
orkoden
2
@orkoden estoy de acuerdo. Esta es una solución para un uso ya no compatible de UIRefreshControl.
boliva
Puede llamar a un método con un retraso mucho más fácil con. [self performSelector:@selector(endRefreshing) withObject:nil afterDelay:0.01]
orkoden
2
El efecto final es exactamente el mismo, y no hace que el 'pirateo' sea más / menos elegante. El uso de -performSelector:withObject:afterDelay:GCD o para esto depende de un gusto personal.
boliva
7

IOS 10 Swift 3.0

es sencillo

import UIKit

class ViewControllerA: UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet weak var myTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        myTableView.delegate = self
        myTableView.dataSource = self

        if #available(iOS 10.0, *) {
            let refreshControl = UIRefreshControl()
            let title = NSLocalizedString("PullToRefresh", comment: "Pull to refresh")
            refreshControl.attributedTitle = NSAttributedString(string: title)
            refreshControl.addTarget(self,
                                     action: #selector(refreshOptions(sender:)),
                                     for: .valueChanged)
            myTableView.refreshControl = refreshControl
        }
    }

    @objc private func refreshOptions(sender: UIRefreshControl) {
        // Perform actions to refresh the content
        // ...
        // and then dismiss the control
        sender.endRefreshing()
    }

    // MARK: - Table view data source

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 12
    }


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)

        cell.textLabel?.text = "Cell \(String(indexPath.row))"
        return cell
    }

}

ingrese la descripción de la imagen aquí

Si quieres aprender sobre iOS 10 UIRefreshControl lee aquí .

Ashok R
fuente
3

Agregar el control de actualización como una subvista crea un espacio vacío arriba de los encabezados de sección.

En cambio, incruste un UITableViewController en mi UIViewController, luego cambié mi tableView propiedad para apuntar hacia el incrustado, ¡y viola! Cambios mínimos de código. :-)

Pasos:

  1. Cree un nuevo UITableViewController en Storyboard e insértelo en su UIViewController original
  2. Reemplace @IBOutlet weak var tableView: UITableView!con el del UITableViewController recién incrustado, como se muestra a continuación

class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableViewController = self.childViewControllers.first as! UITableViewController
        tableView = tableViewController.tableView
        tableView.dataSource = self
        tableView.delegate = self

        // Now we can (properly) add the refresh control
        let refreshControl = UIRefreshControl()
        refreshControl.addTarget(self, action: "handleRefresh:", forControlEvents: .ValueChanged)
        tableViewController.refreshControl = refreshControl
    }

    ...
}
cbh2000
fuente
2

Para Swift 2.2.

Primero haga UIRefreshControl ().

var refreshControl : UIRefreshControl!

En su método viewDidLoad () agregue:

refreshControl = UIRefreshControl()
    refreshControl.attributedTitle = NSAttributedString(string: "Refreshing..")
    refreshControl.addTarget(self, action: #selector(YourUIViewController.refresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
    self.tableView.addSubview(refreshControl)

Y hacer la función de actualización

func refresh(refreshControl: UIRefreshControl) {

    // do something ...

    // reload tableView
    self.tableView.reloadData()

    // End refreshing
    refreshControl.endRefreshing()
}
stakahop
fuente
0

Aquí hay otra solución que es un poco diferente.

Tuve que usarlo debido a algunos problemas de la jerarquía de vistas que tenía: estaba creando algunas funcionalidades que requerían pasar vistas a diferentes lugares en la jerarquía de vistas, que se rompieron al usar una vista de tabla de UITableViewController b / c, la vista de tabla es la vista raíz de UITableViewController ( self.view) y no solo una vista normal, creó jerarquías inconsistentes de controlador / vista y causó un bloqueo.

Básicamente, cree su propia subclase de UITableViewController y anule loadView para asignar self.view una vista diferente, y anule la propiedad tableView para devolver una vista de tabla separada.

por ejemplo:

@interface MyTableVC : UITableViewController
@end

@interface MyTableVC ()
@property (nonatomic, strong) UITableView *separateTableView;
@end

@implementation MyTableVC

- (void)loadView {
    self.view = [[UIView alloc] initWithFrame:CGRectZero];
}

- (UITableView *)tableView {
    return self.separateTableView;
}

- (void)setTableView:(UITableView *)tableView {
    self.separateTableView = tableView;
}

@end

Cuando se combina con la solución de Keller, esto será más robusto en el sentido de que tableView ahora es una vista normal, no una vista raíz de VC, y será más robusto frente a cambios en las jerarquías de vista. Ejemplo de usarlo de esta manera:

MyTableVC *tableViewController = [[MyTableVC alloc] init];
tableViewController.tableView = self.myTableView;

self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(getConnections) forControlEvents:UIControlEventValueChanged];
tableViewController.refreshControl = self.refreshControl;

Hay otro uso posible para esto:

Dado que la subclasificación de esta manera separa self.view de self.tableView, ahora es posible usar este UITableViewController como un controlador más regular y agregar otras subvistas a self.view sin las rarezas de agregar subvistas a UITableView, por lo que uno puede considerar hacer su ver los controladores directamente una subclase de UITableViewController en lugar de tener hijos de UITableViewController.

Algunas cosas a tener en cuenta:

Dado que estamos anulando la propiedad tableView sin llamar a super, puede haber algunas cosas a tener en cuenta y que debemos manejar cuando sea necesario. Por ejemplo, establecer la vista de tabla en mi ejemplo anterior no agregará la vista de tabla a self.view y no establecerá el marco que es posible que desee hacer. Además, en esta implementación no se proporciona una vista de tabla predeterminada cuando se instancia la clase, que también es algo que puede considerar agregar. No lo incluyo aquí porque es caso por caso, y esta solución en realidad encaja bien con la solución de Keller.

psilencer
fuente
1
La pregunta especificaba "sin usar una subclase UITableViewController", y esta respuesta es para una subclase UITableViewController.
Brian
0

Prueba esto,

Las soluciones anteriores están bien, pero tableView.refreshControl está disponible solo para UITableViewController, hasta iOS 9.xy viene en UITableView desde iOS 10.x en adelante.

Escrito en Swift 3 -

let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(FeedViewController.loadNewData), for: UIControlEvents.valueChanged)
// Fix for the RefreshControl Not appearing in background
tableView?.addSubview(refreshControl)
tableView.sendSubview(toBack: refreshControl)
Ankit Kumar Gupta
fuente
La mejor solución hasta ahora
Karthik KM
-1

La primera sugerencia de Keller provoca un extraño error en iOS 7, donde el recuadro de la tabla aumenta después de que vuelve a aparecer el controlador de vista. Cambiar a la segunda respuesta, usando el controlador uitableview, me arregló las cosas.

Mark Bridges
fuente
-6

Resulta que puede usar lo siguiente cuando usa un UIViewController con una subvista UITableView y se ajusta a UITableViewDataSource y UITableViewDelegate:

self.refreshControl = [[UIRefreshControl alloc]init];
[self.refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];
Clint Pick
fuente
99
Discrepar. self.refreshControles una propiedad para un UITableViewController, no un UIViewController.
Rickster
NO FUNCIONA, refierehControl no está definido para uiviewcontroller
OMGPOP