Como desativar o gesto de furto do UIPageViewController?

125

No meu caso, pai UIViewControllercontém o UIPageViewControllerque contém o UINavigationControllerque contém UIViewController. Preciso adicionar um gesto de furto ao último controlador de exibição, mas os furtos são tratados como se pertencessem ao controlador de exibição de página. Tentei fazer isso programaticamente e via xib, mas sem resultado.

Então, pelo que entendi, não posso alcançar meu objetivo até UIPageViewControllerlidar com seus gestos. Como resolver este problema?

user2159978
fonte
7
UsepageViewControllerObject.view.userInteractionEnabled = NO;
Srikanth
6
incorreto, porque também bloqueia todo o seu conteúdo. Mas eu preciso bloquear seus gestos única
user2159978
Por que você está fazendo isso? Qual comportamento você deseja do aplicativo? Parece que você está complicando demais a arquitetura. Você pode explicar qual comportamento você está buscando?
Fogmeister
Uma das páginas em UIPageViewControllertem UINavigationController. O cliente quer pop controlador de navegação no gesto de furto (se é possível), mas pega controlador de visualização da página furtos em vez
user2159978
@ Obrigado Krikh, é exatamente o que eu precisava! Minha opinião página foi falhando quando eu desencadeou uma mudança de página programaticamente e alguém interrompeu-o com passando, então eu tive que desativar temporariamente swiping cada vez que a mudança de página é acionado no código ...
Kuba Suder

Respostas:

262

A maneira documentada de impedir a UIPageViewControllerrolagem é não atribuir a dataSourcepropriedade. Se você atribuir a fonte de dados, ela passará para o modo de navegação 'baseado em gestos', que é o que você está tentando impedir.

Sem uma fonte de dados, você fornece manualmente os controladores de exibição quando quiser com o setViewControllers:direction:animated:completionmétodo e ele se moverá entre os controladores de exibição sob demanda.

O acima pode ser deduzido da documentação do UIPageViewController da Apple (visão geral, segundo parágrafo):

Para oferecer suporte à navegação baseada em gestos, você deve fornecer seus controladores de exibição usando um objeto de fonte de dados.

Jessedc
fonte
1
Eu prefiro essa solução também. Você pode salvar a fonte de dados antiga em uma variável, definir a fonte de dados como nula e, em seguida, restaurar a fonte de dados antiga para reativar a rolagem. Não tão limpo assim, mas muito melhor do que atravessar a hierarquia de vista subjacente para encontrar a vista de rolagem.
Matt R
3
Desativar a fonte de dados causa problemas ao fazê-lo em resposta a notificações do teclado para subvisões de campo de texto. A repetição das subvisões da visualização para encontrar o UIScrollView parece ser mais confiável.
AR Younce
9
Isso não remove os pontos na parte inferior da tela? E se você não tem swiping nem pontos, não há muito sentido em usar um PageViewController. Suponho que o autor da pergunta quer reter os pontos.
Leo Flaherty
Isso ajudou a resolver um problema que eu estava enfrentando quando as páginas são carregadas de forma assíncrona e o usuário faz um gesto de furto. Bravo :)
Ja͢ck
1
A. R Younce está correto. Se você estiver desativando a fonte de dados do PageViewController em resposta a uma notificação do teclado (por exemplo: você não deseja que o usuário role horizontalmente quando o teclado estiver ativado), o teclado desaparecerá imediatamente quando você anular a fonte de dados do PageViewController.
micnguyen
82
for (UIScrollView *view in self.pageViewController.view.subviews) {

    if ([view isKindOfClass:[UIScrollView class]]) {

        view.scrollEnabled = NO;
    }
}
user2159978
fonte
5
Este mantém o controle da página, o que é legal.
Marcus Adams
1
Essa é uma ótima abordagem, pois mantém o controle da página (pequenos pontos). Você deseja atualizá-los ao fazer a página mudar manualmente. Eu usei esta resposta para este stackoverflow.com/a/28356383/1071899
Simon Bøgh
1
Por que nãofor (UIView *view in self.pageViewController.view.subviews) {
DengApro
Observe que os usuários ainda poderão navegar entre as páginas tocando no controle de página na metade esquerda e direita.
MasterCarl
57

Traduzo resposta do usuário2159978 para Swift 5.1

func removeSwipeGesture(){
    for view in self.pageViewController!.view.subviews {
        if let subView = view as? UIScrollView {
            subView.isScrollEnabled = false
        }
    }
}
lee
fonte
30

Implementando a solução @ lee (@ user2159978) como uma extensão:

extension UIPageViewController {
    var isPagingEnabled: Bool {
        get {
            var isEnabled: Bool = true
            for view in view.subviews {
                if let subView = view as? UIScrollView {
                    isEnabled = subView.isScrollEnabled
                }
            }
            return isEnabled
        }
        set {
            for view in view.subviews {
                if let subView = view as? UIScrollView {
                    subView.isScrollEnabled = newValue
                }
            }
        }
    }
}

Uso: (in UIPageViewController)

self.isPagingEnabled = false
LinusGeffarth
fonte
Para ficar mais elegante, você pode adicionar: var scrollView: UIScrollView? { for view in view.subviews { if let subView = view as? UIScrollView { return subView } } return nil }
Wagner Sales
O getter aqui retorna apenas o status ativado da última subvisão.
Andrew Duncan
8

Estou lutando contra isso há um tempo e achei que deveria postar minha solução, seguindo a resposta de Jessedc; removendo a fonte de dados do PageViewController.

Adicionei isso à minha classe PgeViewController (vinculada ao meu controlador de exibição de página no storyboard, herda tanto UIPageViewControllere UIPageViewControllerDataSource):

static func enable(enable: Bool){
    let appDelegate  = UIApplication.sharedApplication().delegate as! AppDelegate
    let pageViewController = appDelegate.window!.rootViewController as! PgeViewController
    if (enable){
        pageViewController.dataSource = pageViewController
    }else{
        pageViewController.dataSource = nil
    }
}

Isso pode ser chamado quando cada sub-visualização aparecer (neste caso, para desativá-la);

override func viewDidAppear(animated: Bool) {
    PgeViewController.enable(false)
}

Espero que isso ajude alguém, não é tão limpo quanto eu gostaria, mas não parece muito hacky etc.

EDIT: Se alguém quiser traduzir isso em Objective-C, por favor :)

Jamie Robinson
fonte
8

Editar : esta resposta funciona apenas para o estilo de ondulação da página. A resposta de Jessedc é muito melhor: funciona independentemente do estilo e depende de comportamento documentado .

UIPageViewController expõe sua variedade de reconhecedores de gestos, que você pode usar para desativá-los:

// myPageViewController is your UIPageViewController instance
for (UIGestureRecognizer *recognizer in myPageViewController.gestureRecognizers) {
    recognizer.enabled = NO;
}
Austin
fonte
8
Você já testou seu código? myPageViewController.gestureRecognizers está sempre vazio
user2159978 3/14
11
parece que sua solução funciona para página estilo onda, mas em meu aplicativo eu uso scroll vista
user2159978
8
Não está funcionando para rolagem horizontal. O valor retornado é sempre uma matriz vazia.
Khanh Nguyen
vocês descobriram isso no estilo de rolagem?
Esqarrouth
4

Se você deseja UIPageViewControllermanter sua capacidade de deslizar, enquanto permite que seus controles de conteúdo usem seus recursos (deslize para excluir etc.), basta desativar canCancelContentToucheso UIPageViewController.

Coloque isso no seu UIPageViewController's viewDidLoadfunc. (Rápido)

if let myView = view?.subviews.first as? UIScrollView {
    myView.canCancelContentTouches = false
}

O UIPageViewControllerpossui uma subvisão gerada automaticamente que lida com os gestos. Podemos impedir que essas subvisões cancelem gestos de conteúdo.

De...

Deslize para excluir em um tableView que esteja dentro de um pageViewController

Carter Medlin
fonte
Obrigado Carter. Eu tentei sua solução. No entanto, ainda existem vezes que o pageViewController substitui a ação de furto horizontal da exibição da tabela filho. Como garantir que, quando o usuário desliza o dedo na exibição da tabela filho, a exibição da tabela sempre é priorizada para obter o gesto primeiro?
David Liu
3

Uma extensão útil UIPageViewControllerpara ativar e desativar o furto.

extension UIPageViewController {

    func enableSwipeGesture() {
        for view in self.view.subviews {
            if let subView = view as? UIScrollView {
                subView.isScrollEnabled = true
            }
        }
    }

    func disableSwipeGesture() {
        for view in self.view.subviews {
            if let subView = view as? UIScrollView {
                subView.isScrollEnabled = false
            }
        }
    }
}
jbustamante
fonte
3

Eu resolvi assim (Swift 4.1)

if let scrollView = self.view.subviews.filter({$0.isKind(of: UIScrollView.self)}).first as? UIScrollView {
             scrollView.isScrollEnabled = false
}
Darko
fonte
3

Maneira rápida para a resposta @lee

extension UIPageViewController {
    var isPagingEnabled: Bool {
        get {
            return scrollView?.isScrollEnabled ?? false
        }
        set {
            scrollView?.isScrollEnabled = newValue
        }
    }

    var scrollView: UIScrollView? {
        return view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView
    }
}
Szymon W
fonte
0

Similar to @ user3568340 resposta

Swift 4

private var _enabled = true
    public var enabled:Bool {
        set {
            if _enabled != newValue {
                _enabled = newValue
                if _enabled {
                    dataSource = self
                }
                else{
                    dataSource = nil
                }
            }
        }
        get {
            return _enabled
        }
    }
Emmanouil Nicolas
fonte
0

Graças à resposta de @ user2159978.

Eu faço isso um pouco mais compreensível.

- (void)disableScroll{
    for (UIView *view in self.pageViewController.view.subviews) {
        if ([view isKindOfClass:[UIScrollView class]]) {
            UIScrollView * aView = (UIScrollView *)view;
            aView.scrollEnabled = NO;
        }
    }
}
dengApro
fonte
0

As respostas que encontrei me parecem muito confusas ou incompletas, então aqui está uma solução completa e configurável:

Passo 1:

Dê a cada um dos seus elementos de PVC a responsabilidade de saber se a rolagem esquerda e direita está ativada ou não.

protocol PageViewControllerElement: class {
    var isLeftScrollEnabled: Bool { get }
    var isRightScrollEnabled: Bool { get }
}
extension PageViewControllerElement {
    // scroll is enabled in both directions by default
    var isLeftScrollEnabled: Bool {
        get {
            return true
        }
    }

    var isRightScrollEnabled: Bool {
        get {
            return true
        }
    }
}

Cada um dos seus controladores de exibição de PVC deve implementar o protocolo acima.

Passo 2:

Nos controladores de PVC, desative a rolagem, se necessário:

extension SomeViewController: PageViewControllerElement {
    var isRightScrollEnabled: Bool {
        get {
            return false
        }
    }
}

class SomeViewController: UIViewController {
    // ...    
}

Etapa 3:

Adicione os métodos eficazes de bloqueio de rolagem ao seu PVC:

class PVC: UIPageViewController, UIPageViewDelegate {
    private var isLeftScrollEnabled = true
    private var isRightScrollEnabled = true
    // ...

    override func viewDidLoad() {
        super.viewDidLoad()
        // ...
        self.delegate = self
        self.scrollView?.delegate = self
    }
} 

extension PVC: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        print("contentOffset = \(scrollView.contentOffset.x)")

        if !self.isLeftScrollEnabled {
            disableLeftScroll(scrollView)
        }
        if !self.isRightScrollEnabled {
            disableRightScroll(scrollView)
        }
    }

    private func disableLeftScroll(_ scrollView: UIScrollView) {
        let screenWidth = UIScreen.main.bounds.width
        if scrollView.contentOffset.x < screenWidth {
            scrollView.setContentOffset(CGPoint(x: screenWidth, y: 0), animated: false)
        }
    }

    private func disableRightScroll(_ scrollView: UIScrollView) {
        let screenWidth = UIScreen.main.bounds.width
        if scrollView.contentOffset.x > screenWidth {
            scrollView.setContentOffset(CGPoint(x: screenWidth, y: 0), animated: false)
        }
    }
}

extension UIPageViewController {
    var scrollView: UIScrollView? {
        return view.subviews.filter { $0 is UIScrollView }.first as? UIScrollView
    }
}

Passo 4:

Atualize os atributos relacionados à rolagem ao acessar uma nova tela (se você passar para alguma tela manualmente, não se esqueça de chamar o método enableScroll):

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    let pageContentViewController = pageViewController.viewControllers![0]
    // ...

    self.enableScroll(for: pageContentViewController)
}

private func enableScroll(for viewController: UIViewController) {
    guard let viewController = viewController as? PageViewControllerElement else {
        self.isLeftScrollEnabled = true
        self.isRightScrollEnabled = true
        return
    }

    self.isLeftScrollEnabled = viewController.isLeftScrollEnabled
    self.isRightScrollEnabled = viewController.isRightScrollEnabled

    if !self.isLeftScrollEnabled {
        print("Left Scroll Disabled")
    }
    if !self.isRightScrollEnabled {
        print("Right Scroll Disabled")
    }
}
michael-martinez
fonte
0

Há uma abordagem muito mais simples do que a maioria das respostas sugeridas aqui, que é retornar nilnos retornos de chamada viewControllerBeforee viewControllerAfterdataSource.

Isso desativa o gesto de rolagem nos dispositivos iOS 11+, mantendo a possibilidade de usar o dataSource(para itens como o presentationIndex/ presentationCountusado para o indicador da página)

Também desativa a navegação via. os pageControl(os pontos na parte inferior)

extension MyPageViewController: UIPageViewControllerDataSource {
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        return nil
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        return nil
    }
}
Claus Jørgensen
fonte
0

pageViewController.view.isUserInteractionEnabled = false

gutte
fonte
-1

Traduzindo a resposta de @ user2159978 para C #:

foreach (var view in pageViewController.View.Subviews){
   var subView = view as UIScrollView;
   if (subView != null){
     subView.ScrollEnabled = enabled;
   }
}
gtrujillos
fonte
-1

(Swift 4) Você pode remover os reconhecedores de gestos da sua páginaViewController:

pageViewController.view.gestureRecognizers?.forEach({ (gesture) in
            pageViewController.view.removeGestureRecognizer(gesture)
        })

Se você preferir em extensão:

extension UIViewController{
    func removeGestureRecognizers(){
        view.gestureRecognizers?.forEach({ (gesture) in
            view.removeGestureRecognizer(gesture)
        })
    }
}

e pageViewController.removeGestureRecognizers

Miguel
fonte
-1

Declare assim:

private var scrollView: UIScrollView? {
    return pageViewController.view.subviews.compactMap { $0 as? UIScrollView }.first
}

Em seguida, use-o assim:

scrollView?.isScrollEnabled = true //false
Bartłomiej Semańczyk
fonte
-1

Enumerar as subvisões para encontrar o scrollView de um UIPageViewControllernão funcionou para mim, pois não consigo encontrar nenhum scrollView na subclasse do controlador de página. Então, o que pensei em fazer é desativar os reconhecedores de gestos, mas com cuidado o suficiente para não desativar os necessários.

Então eu vim com isso:

if let panGesture = self.gestureRecognizers.filter({$0.isKind(of: UIPanGestureRecognizer.self)}).first           
    panGesture.isEnabled = false        
}

Coloque isso dentro do viewDidLoad()e está tudo pronto!

Glenn
fonte
-1
 override func viewDidLayoutSubviews() {
    for View in self.view.subviews{
        if View.isKind(of: UIScrollView.self){
            let ScrollV = View as! UIScrollView
            ScrollV.isScrollEnabled = false
        }

    }
}

Adicione isso na sua classe pageviewcontroller. 100% trabalhando

ap00724
fonte
comente qual problema você está enfrentando com este código que funciona perfeitamente: /
ap00724
-1

basta adicionar esta propriedade de controle à sua subclasse UIPageViewController :

var isScrollEnabled = true {
    didSet {
        for case let scrollView as UIScrollView in view.subviews {
            scrollView.isScrollEnabled = isScrollEnabled
        }
    }
}
Stanislau Baranouski
fonte