UIRefreshControl sem UITableViewController

308

Apenas curioso, pois não parece imediatamente possível, mas existe uma maneira sorrateira de aproveitar a nova UIRefreshControlclasse iOS 6 sem usar uma UITableViewControllersubclasse?

Costumo usar a UIViewControllercom uma UITableViewsubvisão e me conformar UITableViewDataSourcee UITableViewDelegateao invés de usar uma UITableViewControllerobjetiva direta.

Keller
fonte
6
@DaveDeLong: Não, Dave, o que ele deve fazer? Mais adiante nesta página, você diz que a solução dele não é suportada, então qual é a solução certa?
Matt
3
@matt ele deve usar um UITableViewControllere registrar um bug solicitando a API para usar um UIRefreshControlcom um UITableViewdiretamente.
Dave DeLong
31
O UITableViewController teve (e continua a ter) muitos bugs e problemas obscuros e de nicho com hierarquias de visão não triviais ... que desaparecem magicamente quando você passa a usar um VC padrão com uma subvisão TableView. "Use UITVC" é um começo ruim para qualquer solução, IMHO.
Adam
@Adam Esses erros surgem ao usar o 'UITableViewController' como um controlador de exibição filho (dando acesso à personalização da visualização sem mexer na hierarquia tableViewControllers)? Nunca encontrei problemas ao usá-lo dessa maneira.
Memórias

Respostas:

388

Em um palpite, e com base na inspiração do DrummerB, tentei simplesmente adicionar uma UIRefreshControlinstância como uma subvisão à minha UITableView. E magicamente funciona!

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

Isso adiciona uma UIRefreshControlexibição acima da sua tabela e funciona conforme o esperado, sem a necessidade de usar um UITableViewController:)


EDIT: Isso acima ainda funciona, mas como alguns já apontaram, há uma leve "gagueira" ao adicionar o UIRefreshControl dessa maneira. Uma solução para isso é instanciar um UITableViewController e, em seguida, definir UIRefreshControl e UITableView para isso, ou seja:

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
fonte
48
Para sua informação, este é um comportamento não suportado e pode mudar no futuro. A única garantia que a Apple faz é que o uso de acordo com a API fornecida (neste caso -[UITableViewController setRefreshControl:]) continuará funcionando.
Dave DeLong
18
Com esta implementação, parece que, pouco antes de disparar, handleRefresh:há uma mudança inesperada no valor da visualização de rolagem contentInsetspor uma fração de segundo. Alguém mais experimentou isso ou tem uma solução? (Sim, eu sei que isso não é suportado, em primeiro lugar!) #
1155 Tim Tim
6
Você realmente deve simplesmente usar uma exibição de contêiner para isso. Basta definir a classe do controlador de visualização para sua subclasse personalizada UITableViewControllere você estará "fazendo o certo".
319 Eugene
3
No iOS7 (desconhecido para iOS6), a versão editada funciona bem, exceto quando você fecha e reabre o aplicativo. A animação não é reproduzida até a segunda vez que você atualiza. A primeira atualização após a reabertura do aplicativo, é apenas um círculo completo, em vez do círculo incremental, com base na distância em que você o puxa para baixo. Existe uma correção?
david2391
30
Para evitar que o "slutter" como mencionado acima, enquanto ainda ignorando o UITableViewController, basta adicionar o controle de atualização abaixo da vista de tabela assim: [_tableView insertSubview:_refreshControl atIndex:0];. Testado com iOS 7 e 8;)
ptitvinou
95

Para eliminar a gagueira causada pela resposta aceita, você pode atribuir UITableViewa a 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:

Uma maneira de adicionar um UIRefreshControlsem no UITableViewControllercom gagueira e manter a boa animação depois de atualizar os dados na visualização da tabela.

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

Mais tarde, ao manipular os dados atualizados ...

- (void)handleRefresh:(UIRefreshControl *)refreshControl {
    [self.theTableView reloadData];
    [self.theTableView layoutIfNeeded];
    [refreshControl endRefreshing];
}
Piotr Tomasik
fonte
7
Você nem precisa criar um novo UITableView. Apenas diga initWithStyle: existingTableView.style - e depois execute newTableViewController.tableView = existingTableView e atribua refreshControl. Resume isso em três linhas.
Trenskow 27/02
Não se esqueça de ligar [_tablewViewController didMoveToParentViewController:self];depois de adicionar a subvisão.
Qix 11/07
4
self.tableView = _tableViewController.tableView;deveria ser_tableViewController.tableView = self.tableView
Ali
@ Linus, por que isso é necessário? Adicionei _tableViewController.view como um subView no método viewDidLoad do meu viewController personalizado e isso parecia fazer o truque. Eu nem sequer precisa definir _tableViewController.tableView
Taber
O motivo pelo qual não posso usar um UITableViewController e estou usando um UIViewController com uma exibição de tabela dentro. stackoverflow.com/questions/18900428/…
lostintranslation
21

O que você tentaria é usar a exibição de contêiner dentro do ViewController que você está usando. você pode definir a subclasse UITableViewController limpa com tableview dedicado e colocá-la no ViewController.

Tomohisa Takaoka
fonte
2
Precisamente, a maneira mais limpa parece adicionar o controlador de exibição filho do tipo UITableViewController.
Zdenek
Você pode pegar o UITableViewController usando self.childViewControllers.firstObject.
precisa saber é o seguinte
^ você também pode considerar usar prepareForSeguepara obter uma referência ao objeto UITableViewController em uma exibição de contêiner, pois isso é imediatamente acionado como um evento segue na instanciação do storyboard.
crarho
18

Bem, o UIRefreshControl é uma subclasse UIView, para que você possa usá-lo por conta própria. Eu não tenho certeza, no entanto, como ele se processa. A renderização pode simplesmente depender do quadro, mas também pode depender de um UIScrollView ou UITableViewController.

De qualquer forma, será mais um hack do que uma solução elegante. Eu recomendo que você procure um dos clones de terceiros disponíveis ou escreva o seu.

ODRefreshControl

insira a descrição da imagem aqui

SlimeRefresh

insira a descrição da imagem aqui

DrummerB
fonte
1
Obrigado pela sua resposta, mas não exatamente o que eu estava procurando. A primeira frase do seu post me inspirou a experimentar o que acabou sendo a solução!
21712 Keller
7
ODRefreshControl é incrível - obrigado pela dica! Muito simples e faz o trabalho. E, claro, muito mais flexível que o da Apple. Eu nunca entendi por que alguém usaria UITableViewController, IMO a pior classe em toda a API. Não faz (quase) nada, mas torna suas visualizações inflexíveis.
n13 17/04
Na verdade, depende do seu projeto o que você usa lá .. usando uma revisão uitableview no meu caso estava aumentando a complexidade do projeto e agora mudei para uitableviewcontroller que meio que dividiu meu código em dois segmentos, estou feliz por ter 2 arquivos menores que podem ser usado para depuração em vez de um arquivo enorme ..
Kush
@ n13 Olá, um bom motivo para usar o UITableViewController é poder projetar com células estáticas. Infelizmente indisponível em um tableView residente em um UIViewController.
Jean Le Moignan
As ferramentas e bibliotecas do iOS estão mudando rapidamente, de modo que um comentário meu com 3 anos de idade não é mais válido. Eu mudei para criar todas as interfaces de usuário em código com o SnapKit e, honestamente, é extremamente mais eficiente do que clicar 10 mil vezes no criador de interfaces. Também é fácil alterar um UITableViewController para um UIViewController e leva apenas um segundo, enquanto no IB é uma grande dor.
n13 23/08/16
7

Tente atrasar a chamada para o -endRefreshmétodo refreshControl por uma fração de segundo após o tableView recarregar seu conteúdo, usando NSObject -performSelector:withObject:afterDelay:ou GCD dispatch_after.

Eu criei uma categoria no UIRefreshControl para isso:

@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

Eu testei e isso também funciona em vistas de coleção. Percebi que um atraso tão pequeno quanto 0,01 segundos é suficiente:

// My data refresh process here while the refresh control 'isRefreshing'
[self.tableView reloadData];
[self.refreshControl endRefreshingAfterDelay:.01];
boliva
fonte
4
Atrasos e esperas são muito ruins.
orkoden
2
@orkoden Eu concordo. Esta é uma solução alternativa para um uso já não suportado de UIRefreshControl.
Boliv
Você pode chamar um método com um atraso muito mais fácil com. [self performSelector:@selector(endRefreshing) withObject:nil afterDelay:0.01]
precisa saber é
2
O efeito final é exatamente o mesmo e não torna o 'hack' mais / menos elegante. Usar -performSelector:withObject:afterDelay:ou GCD para isso depende de uma questão de gosto pessoal.
boliva
7

IOS 10 Swift 3.0

é simples

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
    }

}

insira a descrição da imagem aqui

Se você quiser aprender sobre o iOS 10 UIRefreshControl, leia aqui .

Ashok R
fonte
3

Adicionar o controle de atualização como uma subvisão cria um espaço vazio acima dos cabeçalhos das seções.

Em vez disso, incorporei um UITableViewController ao meu UIViewController e alterei minha tableViewpropriedade para apontar para o incorporado, e viola! Alterações mínimas de código. :-)

Passos:

  1. Crie um novo UITableViewController no Storyboard e incorpore-o ao seu UIViewController original
  2. Substitua @IBOutlet weak var tableView: UITableView!por um do UITableViewController recém-incorporado, conforme mostrado abaixo

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
fonte
2

Para Swift 2.2.

Primeiro faça UIRefreshControl ().

var refreshControl : UIRefreshControl!

No seu método viewDidLoad (), adicione:

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

E faça a função de atualização

func refresh(refreshControl: UIRefreshControl) {

    // do something ...

    // reload tableView
    self.tableView.reloadData()

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

Aqui está outra solução que é um pouco diferente.

Eu tive que usá-lo devido a alguns problemas de hierarquia de exibição que eu tinha: eu estava criando algumas funcionalidades que exigiam a passagem de visualizações para lugares diferentes na hierarquia de exibições, que eram quebradas ao usar a visualização de tabela de um UITableViewController porque a tableView é a visualização raiz do UITableViewController ( self.view) e não apenas uma visão regular, ele criou hierarquias inconsistentes de controlador / visão e causou uma falha.

Crie basicamente sua própria subclasse de UITableViewController e substitua o loadView para atribuir self.view a uma exibição diferente e substitua a propriedade tableView para retornar uma visualização de tabela separada.

por exemplo:

@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

Quando combinado com a solução da Keller, isso será mais robusto no sentido de que o tableView agora é uma visualização regular, não uma visualização raiz do VC, e será mais robusto contra alterações nas hierarquias de visualização. Exemplo de uso desta maneira:

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;

Há outro uso possível para isso:

Como a subclassificação dessa maneira separa self.view de self.tableView, agora é possível usar esse UITableViewController como um controlador comum e adicionar outras subviews ao self.view sem as peculiaridades de adicionar subviews ao UITableView, portanto, pode-se considerar fazer suas visualize controladores diretamente uma subclasse de UITableViewController em vez de ter filhos UITableViewController.

Algumas coisas a observar:

Como estamos substituindo a propriedade tableView sem chamar super, pode haver algumas coisas a serem observadas e que devem ser tratadas sempre que necessário. Por exemplo, configurar a tableview no exemplo acima não adicionará a tableview a self.view e não definirá o quadro que você deseja fazer. Além disso, nesta implementação, não é fornecido tableView padrão quando a classe é instanciada, o que também é algo que você pode considerar adicionar. Não o incluo aqui porque é caso a caso, e essa solução realmente se encaixa bem na solução de Keller.

psilencer
fonte
1
A pergunta especificada "sem usar uma subclasse UITableViewController" e esta resposta é para uma subclasse UITableViewController.
Brian
0

Tente isso,

As soluções acima são boas, mas o tableView.refreshControl está disponível apenas para UITableViewController, até o iOS 9.xe vem no UITableView a partir do iOS 10.x em diante.

Escrito em 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
fonte
Melhor solução até agora
Karthik KM
-1

A primeira sugestão de Keller causa um bug estranho no iOS 7, em que a inserção da tabela é aumentada após o controlador de exibição reaparecer. Mudar para a segunda resposta, usando o uitableviewcontroller, corrigiu as coisas para mim.

Mark Bridges
fonte
-6

Acontece que você pode usar o seguinte quando usa um UIViewController com uma subvisão UITableView e está em conformidade com UITableViewDataSource e UITableViewDelegate:

self.refreshControl = [[UIRefreshControl alloc]init];
[self.refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];
Clint Pick
fonte
9
Discordo. self.refreshControlé uma propriedade para a UITableViewController, não a UIViewController.
Rickster 23/12/13
Não funciona, refershControl não está definido para UIViewController
omgpop