Qual é a maneira mais robusta de forçar um UIView a redesenhar?

Eu tenho um UITableView com uma lista de itens. A seleção de um item envia um viewController que, em seguida, faz o seguinte. from method viewDidLoad Disparo um URLRequest para dados que são requeridos por uma das minhas subvisualizações – uma subclass de UIView com drawRect sobrescrita. Quando os dados chegam da nuvem, começo a construir minha hierarquia de visualizações. a subclass em questão recebe os dados e seu método drawRect agora tem tudo que precisa para renderizar.

Mas.

Porque eu não chamo drawRect explicitamente – Cocoa-Touch lida com isso – eu não tenho como informar Cocoa-Touch que eu realmente quero que essa subclass de UIView seja renderizada. Quando? Agora seria bom!

Eu tentei [myView setNeedsDisplay]. Esse tipo de trabalho às vezes. Muito irregular.

Eu estou lutando com isso por horas e horas. Alguém que poderia me fornecer uma abordagem sólida e garantida para forçar uma nova renderização do UIView?

Aqui está o trecho de código que alimenta os dados para a exibição:

// Create the subview self.chromosomeBlockView = [[[ChromosomeBlockView alloc] initWithFrame:frame] autorelease]; // Set some properties self.chromosomeBlockView.sequenceString = self.sequenceString; self.chromosomeBlockView.nucleotideBases = self.nucleotideLettersDictionary; // Insert the view in the view hierarchy [self.containerView addSubview:self.chromosomeBlockView]; [self.containerView bringSubviewToFront:self.chromosomeBlockView]; // A vain attempt to convince Cocoa-Touch that this view is worthy of being displayed ;-) [self.chromosomeBlockView setNeedsDisplay]; 

Felicidades, Doug

A maneira sólida e garantida de forçar um UIView a renderizar novamente é [myView setNeedsDisplay] . Se você está tendo problemas com isso, provavelmente está se deparando com um desses problemas:

  • Você está chamando isso antes de realmente ter os dados, ou seu -drawRect: está com excesso de caching.

  • Você está esperando a exibição para desenhar no momento em que você chama esse método. Intencionalmente, não há como exigir “desenhar agora mesmo neste segundo” usando o sistema de desenho Cocoa. Isso interromperia todo o sistema de composição da visualização, o desempenho da lixeira e provavelmente criaria todos os tipos de artefatos. Existem apenas maneiras de dizer “isso precisa ser desenhado no próximo ciclo de sorteio”.

Se o que você precisa é de “alguma lógica, desenhar, um pouco mais de lógica”, então você precisa colocar “um pouco mais de lógica” em um método separado e invocá-lo usando -performSelector:withObject:afterDelay: com um atraso de 0. coloque “um pouco mais de lógica” depois do próximo ciclo de sorteio. Veja esta questão para um exemplo desse tipo de código e um caso em que pode ser necessário (embora geralmente seja melhor procurar outras soluções, se possível, uma vez que isso complica o código).

Se você não acha que as coisas estão sendo desenhadas, coloque um ponto de interrupção em -drawRect: e veja quando você está sendo chamado. Se você está chamando -setNeedsDisplay , mas -drawRect: não está sendo chamado no próximo evento loop, em seguida, cavar em sua hierarquia de visão e verifique se você não está tentando enganar está em algum lugar. Excesso de inteligência é a causa número 1 de maus desenhos em minha experiência. Quando você acha que sabe como enganar o sistema para fazer o que você quer, você normalmente faz com que ele faça exatamente o que você não quer.

Eu tive um problema com um grande atraso entre chamar setNeedsDisplay e drawRect: (5 segundos). Acontece que eu chamei setNeedsDisplay em um thread diferente do thread principal. Depois de mover esta chamada para o thread principal, o atraso foi embora.

Espero que isso ajude.

A maneira garantida de devolver dinheiro, reforçada em concreto para forçar uma visão a ser desenhada de forma síncrona (antes de retornar ao código de chamada) é configurar as CALayer da CALayer com sua subclass UIView .

Em sua subclass UIView, crie um método - display que diga à camada que “ sim ele precisa ser exibido ”, então “ para fazer isso ”:

 /// Redraws the view's contents immediately. /// Serves the same purpose as the display method in GLKView. /// Not to be confused with CALayer's `- display`; naming's really up to you. - (void)display { CALayer *layer = self.layer; [layer setNeedsDisplay]; [layer displayIfNeeded]; } 

Também implemente o método a - drawLayer:inContext: que chamará seu método de desenho privado / interno (que funciona já que todo UIView é um CALayerDelegate) :

 /// Called by our CALayer when it wants us to draw /// (in compliance with the CALayerDelegate protocol). - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context { UIGraphicsPushContext(context); [self internalDrawWithRect:self.bounds]; UIGraphicsPopContext(); } 

E crie seu método customizado - internalDrawWithRect: :, juntamente com fail-safe - drawRect: ::

 /// Internal drawing method; naming's up to you. - (void)internalDrawWithRect:(CGRect)rect { // @FILLIN: Custom drawing code goes here. // (Use `UIGraphicsGetCurrentContext()` where necessary.) } /// For compatibility, if something besides our display method asks for draw. - (void)drawRect:(CGRect)rect { [self internalDrawWithRect:rect]; } 

E agora é só ligar para [myView display] sempre que você realmente precisar desenhar. - display irá dizer ao CALayer para displayIfNeeded , que chamará sincronicamente de volta para o nosso - drawLayer:inContext: e fará o desenho em - internalDrawWithRect: - drawLayer:inContext: atualizando o visual com o que é desenhado no contexto antes de prosseguir.


Esta abordagem é semelhante ao @ RobNapier acima, mas tem a vantagem de chamar - displayIfNeeded além de - setNeedsDisplay , o que o torna síncrono.

Isso é possível porque o CALayer expõe mais funcionalidades de desenho do que as do UIView as camadas são de nível inferior às vistas e projetadas explicitamente com o propósito de desenho altamente configurável no layout e (como muitas coisas no Cocoa) são projetadas para serem usadas flexivelmente (como uma class pai, ou como um delegante, ou como uma ponte para outros sistemas de desenho, ou apenas por conta própria).

Mais informações sobre a configurabilidade de CALayer podem ser encontradas na seção Configurando Objetos de Camada do Core Animation Programming Guide .

Eu tive o mesmo problema, e todas as soluções da SO ou do Google não funcionaram para mim. Normalmente, o setNeedsDisplay funciona, mas quando não …
Eu tentei chamar setNeedsDisplay da exibição apenas de todas as formas possíveis a partir de todos os tópicos possíveis e outras coisas – ainda sem sucesso. Sabemos, como Rob disse, que

“isso precisa ser desenhado no próximo ciclo de sorteio.”

Mas, por algum motivo, não seria atraído dessa vez. E a única solução que encontrei é chamá-lo manualmente depois de algum tempo, para deixar passar qualquer coisa que bloqueia o sorteio, assim:

 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.005 * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { [viewToRefresh setNeedsDisplay]; }); 

É uma boa solução se você não precisar da visualização para redesenhar com muita frequência. Caso contrário, se você estiver fazendo algum movimento (ação), geralmente não há problemas em apenas chamar setNeedsDisplay .

Espero que ajude alguém que está perdido lá, como eu estava.

Bem, eu sei que isso pode ser uma grande mudança ou mesmo não ser adequado para o seu projeto, mas você considerou não executar o push até que você já tenha os dados ? Dessa forma, você só precisará desenhar a visualização uma vez e a experiência do usuário também será melhor – o push se moverá já carregado.

A maneira de fazer isso é no UITableView didSelectRowAtIndexPath você solicita de forma assíncrona os dados. Depois de receber a resposta, você executa manualmente o segue e passa os dados para o viewController em prepareForSegue . Enquanto isso, você pode querer mostrar algum indicador de atividade, para verificar o indicador de carregamento simples https://github.com/jdg/MBProgressHUD