Como posso forçar um UIScrollView
em que a paginação e a rolagem estão ativadas para se mover apenas vertical ou horizontalmente em um determinado momento?
Meu entendimento é que a directionalLockEnabled
propriedade deve atingir isso, mas um deslizar diagonal ainda faz a visualização rolar diagonalmente em vez de restringir o movimento a um único eixo.
Editar: para ser mais claro, gostaria de permitir ao usuário rolar horizontalmente OU verticalmente, mas não ambos simultaneamente.
Respostas:
Você está em uma situação muito difícil, devo dizer.
Observe que você precisa usar um UIScrollView com
pagingEnabled=YES
para alternar entre as páginas, mas precisapagingEnabled=NO
rolar verticalmente.Existem 2 estratégias possíveis. Não sei qual vai funcionar / é mais fácil de implementar, então tente os dois.
Primeiro: UIScrollViews aninhados. Francamente, ainda estou para ver uma pessoa que faça isso funcionar. No entanto, eu não tentei o suficiente pessoalmente, e minha prática mostra que quando você se esforça o suficiente, você pode fazer o UIScrollView fazer o que quiser .
Portanto, a estratégia é permitir que a exibição de rolagem externa trate apenas a rolagem horizontal e as exibições de rolagem interna apenas a rolagem vertical. Para fazer isso, você deve saber como o UIScrollView funciona internamente. Ele substitui o
hitTest
método e sempre retorna a si mesmo, de forma que todos os eventos de toque vão para UIScrollView. Em seguidatouchesBegan
, dentro ,touchesMoved
etc , ele verifica se ele está interessado no evento e trata ou passa para os componentes internos.Para decidir se o toque deve ser tratado ou encaminhado, o UIScrollView inicia um cronômetro quando você o toca pela primeira vez:
Se você não moveu seu dedo significativamente em 150 ms, ele passa o evento para a visualização interna.
Se você moveu seu dedo significativamente em 150 ms, ele começa a rolar (e nunca passa o evento para a visualização interna).
Observe como quando você toca em uma tabela (que é uma subclasse da visualização de rolagem) e começa a rolar imediatamente, a linha que você tocou nunca é destacada.
Se você não moveu seu dedo significativamente em 150 ms e o UIScrollView começou a passar os eventos para a visualização interna, mas então você moveu o dedo o suficiente para a rolagem começar, o UIScrollView chama
touchesCancelled
na visualização interna e começa a rolagem.Observe como, quando você toca em uma mesa, mantém o dedo um pouco pressionado e começa a rolar, a linha que você tocou é destacada primeiro, mas desmarcada depois.
Essa sequência de eventos pode ser alterada pela configuração do UIScrollView:
delaysContentTouches
for NÃO, então nenhum cronômetro é usado - os eventos vão imediatamente para o controle interno (mas são cancelados se você mover o dedo longe o suficiente)cancelsTouches
for NÃO, uma vez que os eventos forem enviados para um controle, a rolagem nunca acontecerá.Note que é UIScrollView que recebe tudo
touchesBegin
,touchesMoved
,touchesEnded
etouchesCanceled
eventos de CocoaTouch (porque a suahitTest
diz-lhe para fazê-lo). Em seguida, ele os encaminha para a visão interna se quiser, pelo tempo que quiser.Agora que você sabe tudo sobre o UIScrollView, pode alterar seu comportamento. Posso apostar que você deseja dar preferência à rolagem vertical, de modo que, assim que o usuário tocar na visualização e começar a mover o dedo (mesmo que ligeiramente), a visualização comece a se deslocar na direção vertical; mas quando o usuário move o dedo na direção horizontal longe o suficiente, você deseja cancelar a rolagem vertical e iniciar a rolagem horizontal.
Você deseja criar uma subclasse de seu UIScrollView externo (digamos, você nomeia sua classe como RemorsefulScrollView), de modo que, em vez do comportamento padrão, ele encaminhe imediatamente todos os eventos para a visualização interna e somente quando um movimento horizontal significativo for detectado ele role.
Como fazer RemorsefulScrollView se comportar dessa maneira?
Parece que está desativando a rolagem vertical e a configuração
delaysContentTouches
como NÃO deve fazer com que UIScrollViews aninhados funcionem. Infelizmente, não; UIScrollView parece fazer alguma filtragem adicional para movimentos rápidos (que não podem ser desabilitados), de forma que mesmo se UIScrollView só puder ser rolado horizontalmente, ele sempre consumirá (e ignorará) movimentos verticais rápidos o suficiente.O efeito é tão grave que a rolagem vertical dentro de uma visualização de rolagem aninhada é inutilizável. (Parece que você tem exatamente esta configuração, então tente: segure um dedo por 150 ms e, em seguida, mova-o na direção vertical - o UIScrollView aninhado funciona conforme o esperado então!)
Isso significa que você não pode usar o código do UIScrollView para manipulação de eventos; você tem que substituir todos os quatro métodos de manipulação de toque em RemorsefulScrollView e fazer seu próprio processamento primeiro, apenas encaminhando o evento para
super
(UIScrollView) se você decidiu usar a rolagem horizontal.No entanto, você deve passar
touchesBegan
para UIScrollView, porque você deseja que ele se lembre de uma coordenada de base para rolagem horizontal futura (se mais tarde você decidir que é uma rolagem horizontal). Você não poderá enviartouchesBegan
para UIScrollView mais tarde, porque você não pode armazenar otouches
argumento: ele contém objetos que sofrerão mutação antes do próximotouchesMoved
evento e você não pode reproduzir o estado antigo.Portanto, você deve passar
touchesBegan
para o UIScrollView imediatamente, mas ocultará quaisquertouchesMoved
eventos posteriores até decidir rolar horizontalmente. NãotouchesMoved
significa que não há rolagem, então esta inicialtouchesBegan
não fará mal. Mas definadelaysContentTouches
como NÃO, de modo que nenhum temporizador surpresa adicional interfira.(Offtopic - ao contrário de você, UIScrollView pode armazenar toques corretamente e pode reproduzir e encaminhar o
touchesBegan
evento original posteriormente. Ele tem uma vantagem injusta de usar APIs não publicadas, então pode clonar objetos de toque antes que eles sofram mutação.)Como você sempre encaminha
touchesBegan
, você também deve encaminhartouchesCancelled
etouchesEnded
. Você tem que virartouchesEnded
emtouchesCancelled
, no entanto, porque UIScrollView iria interpretartouchesBegan
,touchesEnded
seqüência como um toque de cliques, e iria enviá-lo para o ponto de vista interno. Você já está encaminhando os eventos apropriados, então nunca deseja que o UIScrollView encaminhe nada.Basicamente, aqui está um pseudocódigo para o que você precisa fazer. Para simplificar, nunca permito a rolagem horizontal após a ocorrência do evento multitoque.
Eu não tentei executar ou mesmo compilar isso (e digitei a classe inteira em um editor de texto simples), mas você pode começar com o descrito acima e, com sorte, fazê-lo funcionar.
O único problema oculto que vejo é que, se você adicionar visualizações filho não UIScrollView ao RemorsefulScrollView, os eventos de toque que você encaminha para um filho podem chegar de volta a você via cadeia de resposta, se o filho nem sempre lida com toques como o UIScrollView faz. Uma implementação RemorsefulScrollView à prova de balas protegeria contra
touchesXxx
reentrada.Segunda estratégia: se por algum motivo UIScrollViews aninhados não funcionarem ou se mostrarem muito difíceis de acertar, você pode tentar se dar bem com apenas um UIScrollView, trocando sua
pagingEnabled
propriedade em tempo real de seuscrollViewDidScroll
método delegado.Para evitar a rolagem diagonal, você deve primeiro tentar lembrar o contentOffset in
scrollViewWillBeginDragging
e verificar e redefinir o contentOffset internoscrollViewDidScroll
se detectar um movimento diagonal. Outra estratégia a ser tentada é redefinir contentSize para permitir a rolagem apenas em uma direção, depois de decidir para qual direção o dedo do usuário está indo. (UIScrollView parece bastante tolerante quanto a mexer com contentSize e contentOffset em seus métodos delegados.)Se isso também não funcionar ou resultar em visuais desleixados, você deve substituir
touchesBegan
,touchesMoved
etc. , e não encaminhar eventos de movimento diagonal para UIScrollView. (A experiência do usuário será subótima neste caso, no entanto, porque você terá que ignorar os movimentos diagonais em vez de forçá-los em uma única direção. Se você estiver se sentindo realmente aventureiro, pode escrever seu próprio UITouch parecido com algo como RevengeTouch. Objetivo -C é o C simples e antigo, e não há nada mais complicado no mundo do que C; contanto que ninguém verifique a classe real dos objetos, o que eu acredito que ninguém faz, você pode fazer qualquer classe se parecer com qualquer outra. Isso abre a possibilidade de sintetizar qualquer toque que você quiser, com as coordenadas que você quiser.)Estratégia de backup: há TTScrollView, uma reimplementação sã do UIScrollView na biblioteca Three20 . Infelizmente, parece muito pouco natural e não-iphonish para o usuário. Mas se todas as tentativas de usar UIScrollView falharem, você pode voltar para uma visualização de rolagem com código personalizado. Eu não recomendo, se possível; o uso do UIScrollView garante que você obtenha a aparência e o comportamento nativos, não importa como ele evolua nas futuras versões do iPhone OS.
Ok, este pequeno ensaio ficou um pouco longo demais. Ainda estou interessado nos jogos UIScrollView depois de trabalhar no ScrollingMadness há vários dias.
PS: Se você conseguir algum desses funcionando e quiser compartilhar, envie-me um e-mail com o código relevante para [email protected]. Eu o incluiria em meu pacote de truques do ScrollingMadness .
PPS Adicionando este pequeno ensaio ao README de ScrollingMadness.
fonte
Isso funciona para mim sempre ...
fonte
Para as pessoas preguiçosas como eu:
Definir
scrollview.directionalLockEnabled
paraYES
fonte
Usei o seguinte método para resolver este problema, espero que ajude você.
Primeiro defina as seguintes variáveis no arquivo de cabeçalho de seus controladores.
startPos manterá o valor contentOffset quando seu delegado receber a mensagem scrollViewWillBeginDragging . Portanto, neste método, fazemos isso;
em seguida, usamos esses valores para determinar a direção de rolagem pretendida pelos usuários na mensagem scrollViewDidScroll .
finalmente, temos que parar todas as operações de atualização quando o usuário termina de arrastar;
fonte
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (decelerate) { scrollDirection=3; } }
for alterado para- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (!decelerate) { scrollDirection=0; } }
e- (void)scrollViewDidEndDecelerating{ scrollDirection=0; }
Posso estar gravedigging aqui, mas me deparei com este post hoje enquanto tentava resolver o mesmo problema. Aqui está minha solução, parece estar funcionando muito bem.
Desejo exibir uma tela cheia de conteúdo, mas permitir que o usuário role para uma das outras quatro telas, para cima, para baixo, à esquerda e à direita. Eu tenho um único UIScrollView com tamanho 320x480 e contentSize de 960x1440. O deslocamento de conteúdo começa em 320.480. Em scrollViewDidEndDecelerating :, eu redesenho as 5 visualizações (visualizações centrais e as 4 ao redor delas) e redefino o deslocamento de conteúdo para 320.480.
Aqui está a parte principal da minha solução, o método scrollViewDidScroll :.
fonte
Pessoal, é tão simples quanto fazer a altura da subvisualização igual à altura da visualização do scroll e do tamanho do conteúdo. Em seguida, a rolagem vertical é desativada.
fonte
Aqui está minha implementação de um paginado
UIScrollView
que só permite rolagens ortogonais. Certifique-se de definirpagingEnabled
comoYES
.PagedOrthoScrollView.h
PagedOrthoScrollView.m
fonte
Eu também tive que resolver esse problema e, embora a resposta de Andrey Tarantsov definitivamente tenha muitas informações úteis para entender como
UIScrollViews
funciona, acho que a solução é um pouco complicada demais. Minha solução, que não envolve nenhuma subclasse ou encaminhamento de toque, é a seguinte:UIPanGestureRecognizers
, novamente um para cada direção.UIScrollViews
propriedades 'panGestureRecognizer e o manequimUIPanGestureRecognizer
correspondente à direção perpendicular à direção de rolagem desejada de seu UIScrollView usando oUIGestureRecognizer's
-requireGestureRecognizerToFail:
seletor.-gestureRecognizerShouldBegin:
retornos de chamada para seus UIPanGestureRecognizers fictícios, calcule a direção inicial da panorâmica usando o seletor -translationInView: do gesto (seuUIPanGestureRecognizers
não chamará este retorno de chamada delegado até que seu toque tenha traduzido o suficiente para registrar como um pan). Permitir ou proibir que o reconhecedor de gestos comece, dependendo da direção calculada, que por sua vez deve controlar se o reconhecedor de gestos panorâmicos UIScrollView perpendicular terá permissão para iniciar ou não.-gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
, não permita que seu manequimUIPanGestureRecognizers
seja executado junto com a rolagem (a menos que você tenha algum comportamento extra que queira adicionar).fonte
O que fiz foi criar um UIScrollView de paginação com um quadro do tamanho da tela de visualização e definir o tamanho do conteúdo para a largura necessária para todo o meu conteúdo e uma altura de 1 (obrigado Tonetel). Então criei as páginas do menu UIScrollView com frames definidos para cada página, o tamanho da tela de visualização e defina o tamanho do conteúdo de cada uma para a largura da tela e a altura necessária para cada uma. Em seguida, simplesmente adicionei cada uma das páginas do menu à exibição de paginação. Certifique-se de que a paginação esteja habilitada na visualização de rolagem de paginação, mas desabilitada nas visualizações de menu (deve ser desabilitada por padrão) e você deve estar pronto para continuar.
A visualização de rolagem de paginação agora rola horizontalmente, mas não verticalmente por causa da altura do conteúdo. Cada página rola verticalmente, mas não horizontalmente por causa de suas restrições de tamanho de conteúdo também.
Aqui está o código se essa explicação deixou algo a desejar:
fonte
Obrigado Tonetel. Modifiquei ligeiramente sua abordagem, mas era exatamente o que eu precisava para evitar a rolagem horizontal.
fonte
Esta é uma solução melhorada de icompot, que era bastante instável para mim. Este funciona bem e é fácil de implementar:
fonte
Para referência futura, aproveitei a resposta de Andrey Tanasov e encontrei a seguinte solução que funciona bem.
Primeiro defina uma variável para armazenar o contentOffset e configure-a para coordenadas 0,0
Agora implemente o seguinte no delegado scrollView:
Espero que isto ajude.
fonte
Dos documentos:
"o valor padrão é NÃO, o que significa que a rolagem é permitida nas direções horizontal e vertical. Se o valor for SIM e o usuário começar a arrastar em uma direção geral (horizontal ou vertical), a visualização de rolagem desativa a rolagem na outra direção. "
Acho que a parte importante é "se ... o usuário começar arrastar em uma direção geral". Portanto, se eles começarem a arrastar diagonalmente, isso não funcionará. Não que esses documentos sejam sempre confiáveis quando se trata de interpretação - mas isso parece se adequar ao que você está vendo.
Isso realmente parece sensato. Eu tenho que perguntar - por que você quer restringir apenas horizontal ou vertical a qualquer momento? Talvez o UIScrollView não seja a ferramenta para você?
fonte
Minha visualização consiste em 10 visualizações horizontais, e algumas dessas visualizações consistem em múltiplas visualizações verticais, então você obterá algo assim:
Com paginação habilitada nos eixos horizontal e vertical
A visualização também possui os seguintes atributos:
Agora, para evitar a rolagem diagonal, faça o seguinte:
Funciona como um encanto para mim!
fonte
É necessário redefinir
_isHorizontalScroll
como NÃO emtouchesEnded
etouchesCancelled
.fonte
Se você está tendo uma visualização de rolagem grande ... e deseja restringir a rolagem diagonal http://chandanshetty01.blogspot.in/2012/07/restricting-diagonal-scrolling-in.html
fonte
Se você ainda não tentou fazer isso no 3.0 beta, recomendo que experimente. No momento, estou usando uma série de visualizações de tabela dentro de uma visualização de rolagem e funciona exatamente como esperado. (Bem, a rolagem sim, de qualquer maneira. Essa pergunta foi encontrada ao procurar uma solução para um problema totalmente diferente ...)
fonte
Para aqueles que procuram em Swift a segunda solução @AndreyTarantsov, é assim no código:
o que estamos fazendo aqui é manter a posição atual antes de a visualização de rolagem ser arrastada e, em seguida, verificar se x aumentou ou diminuiu em comparação com a posição atual de x. Se sim, defina pagingEnabled como true, se não (y aumentou / diminuiu), defina pagingEnabled como false
Espero que seja útil para os recém-chegados!
fonte
Consegui resolver isso implementando scrollViewDidBeginDragging (_ :) e observando a velocidade no UIPanGestureRecognizer subjacente.
NB: Com esta solução, cada panela que teria sido diagonal será ignorada pelo scrollView.
fonte
Se você estiver usando o construtor de interface para projetar sua interface - isso pode ser feito facilmente.
No construtor de interface, basta clicar no UIScrollView e selecionar a opção (no inspetor) que o limita a apenas uma direção de rolagem.
fonte