ios iphone swift uiscrollview uipangesturerecognizer

ios - Transferir el gesto de desplazamiento del contenedor principal al objeto anidado UICollectionView



iphone swift (3)

No puede "transferir" un gesto, porque el reconocedor de gestos sigue siendo el mismo objeto y su view varía: es la vista a la que está conectado el reconocedor de gestos.

Sin embargo, nada le impide decirle a otra vista qué hacer en respuesta a un gesto. La vista de colección es una vista de desplazamiento, para que sepa cómo se está desplazando en cada instante y puede hacer otra cosa en paralelo.

Intento construir un controlador de contenedor de vista dividida complejo que facilite dos contenedores de altura variable, cada uno con su propio controlador de vista anidado. Hay un gesto pan global en el controlador principal que permite al usuario arrastrar a cualquier lugar en el contenedor de vista y desliza el "divisor" entre las vistas hacia arriba y hacia abajo. También tiene una lógica de detección de umbral de posición inteligente que expandirá cualquiera de las vistas (o restablecerá la posición del divisor):

Esto funciona bien También hay un montón de código para construir esto, que me complace compartir, pero no creo que sea relevante, así que lo omitiré por el momento.

Ahora intento complicar las cosas agregando una vista de colección a la vista inferior:

Pude resolverlo para poder desplazar la vista dividida hacia arriba con un gesto de panorámica decisivo, y desplazar la vista de colección con un rápido movimiento del dedo (un gesto de deslizar, supongo que es así), pero esto es una experiencia realmente inferior: no puede desplazarse por la vista y desplazarse por la vista de colección al mismo tiempo, y esperar que un usuario repita consistentemente gestos similares pero diferentes para controlar la vista es una interacción demasiado difícil.

Para intentar resolver esto, he intentado varias soluciones de delegado / protocolo en las que detecto la posición del divisor en la vista dividida y canCancelTouchesInView / deshabilito canCancelTouchesInView y / o isUserInteractionEnable en la vista de colección en función de si la vista inferior está completamente expandida . Esto funciona hasta cierto punto, pero no en los siguientes dos escenarios:

  1. Cuando el divisor de vista dividida está en su posición predeterminada, si el usuario realiza un barrido hacia arriba donde la vista inferior está completamente expandida, y luego continúa haciendo un barrido ascendente, la vista de colección debería comenzar a desplazarse hasta que finalice el gesto.
  2. Cuando el divisor de vista dividida está en la parte superior (la vista de contenedor inferior está completamente expandida) y la vista de colección no está en la parte superior, si el usuario lo hace, la vista de colección debe desplazarse en lugar del divisor de vista dividida en movimiento, hasta la vista de colección alcanza su posición superior, en cuyo punto la vista dividida debería volver a su posición predeterminada.

Aquí hay una animación que ilustra este comportamiento:

Dado esto, estoy empezando a pensar que la única forma de resolver el problema es creando un método de delegado en la vista dividida que indique a la vista de recopilación cuando la vista inferior esté a la altura máxima, lo que puede interceptar el gesto panorámico o reenviar del padre la pantalla toca la vista de colección en su lugar? Pero, no estoy seguro de cómo hacer eso. Si estoy en el camino correcto con una solución, mi pregunta es sencilla: ¿cómo puedo reenviar o transferir un gesto de panorámica a una vista de colección y hacer que la vista de colección interactúe de la misma manera que si los toques hubieran sido capturados por en primer lugar? ¿Puedo hacer algo con pointInside o touches____ methods?

Si no puedo hacerlo de esta manera, ¿de qué otra forma puedo solucionar este problema?

Actualización para cazarrecompensas: he tenido un poco de suerte fragmentada al crear un método de delegado en la vista de colección, y al llamarlo al contenedor de vista dividida para establecer una propiedad shouldScroll , mediante el cual uso información de dirección y posicionamiento para determinar si la vista de desplazamiento debería desplazarse. A continuación, devuelvo este valor en gestureRecognizer:shouldReceive touch: delegar método:

// protocol delegate protocol GalleryCollectionViewDelegate { var shouldScroll: Bool? { get } } // shouldScroll property private var _shouldScroll: Bool? = nil var shouldScroll: Bool { get { // Will attempt to retrieve delegate value, or self set value, or return false return self.galleryDelegate?.shouldScroll ?? self._shouldScroll ?? false } set { self._shouldScroll = newValue } } // UIGestureRecognizerDelegate method func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { return shouldScroll } // ---------------- // Delegate property/getter called on the split view controller and the logic: var shouldScroll: Bool? { get { return panTarget != self } } var panTarget: UIViewController! { get { // Use intelligent position detection to determine whether the pan should be // captured by the containing splitview or the gallery''s collectionview switch (viewState.currentPosition, viewState.pan?.directionTravelled, galleryScene.galleryCollectionView.isScrolled) { case (.top, .up?, _), (.top, .down?, true): return galleryScene default: return self } } }

Esto funciona bien para cuando comienzas a desplazarte, pero no funciona bien una vez que el desplazamiento está habilitado en la vista de colección, porque el gesto de desplazamiento casi siempre anula el gesto de panorámica. Me pregunto si puedo conectar algo con gestureRecognizer:shouldRecognizeSimultaneouslyWith: pero todavía no estoy allí.


Debería poder lograr lo que está buscando con una sola vista de colección usando UICollectionViewDelegateFlowLayout . Si necesita un comportamiento de desplazamiento especial para su vista superior, como paralaje, puede lograrlo en una única vista de colección implementando un objeto de diseño personalizado que herede de UICollectionViewLayout .

Utilizar el enfoque UICollectionViewDelegateFlowLayout es un poco más sencillo que implementar un diseño personalizado, por lo que si quiere dar una oportunidad, intente lo siguiente:

  • Cree su vista superior como una subclase de UICollectionViewCell y regístrela con su vista de colección.

  • Cree su vista "divisor" como una subclase de UICollectionViewCell y regístrelo con su vista de colección como una vista suplementaria usando func register(_ viewClass: AnyClass?, forSupplementaryViewOfKind elementKind: String, withReuseIdentifier identifier: String)

  • Haga que su controlador de vista de colección se ajuste a UICollectionViewDelegateFlowLayout , cree un objeto de diseño como una instancia de UICollectionViewFlowLayout asigne su controlador de vista de colección como el delegado de su instancia de diseño de flujo e inicie su vista de colección con su diseño de flujo.

  • Implementar optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize devuelve el tamaño deseado de cada una de sus diferentes vistas en su controlador de vista collecton.


¿Qué pasa con hacer que la vista secundaria para la vista inferior realmente ocupe toda la pantalla y establezca el contenido de la vista de colección hasta la altura de la vista superior? Y luego agregue el otro controlador de vista hijo encima de la vista inferior. Entonces, lo único que debe hacer es hacer que el controlador de vista padre sea el delegado para escuchar el desplazamiento de desplazamiento de la vista de colección de la vista inferior y cambiar la posición de la vista superior. Sin cosas complicadas de reconocimiento de gestos. Solo una vista de desplazamiento (vista de colección)

Actualización: ¡Prueba esto!

import Foundation import UIKit let topViewHeight: CGFloat = 250 class SplitViewController: UIViewController, BottomViewControllerScrollDelegate { let topViewController: TopViewController = TopViewController() let bottomViewController: BottomViewController = BottomViewController() override func viewDidLoad() { super.viewDidLoad() automaticallyAdjustsScrollViewInsets = false bottomViewController.delegate = self addViewController(bottomViewController, frame: view.bounds, completion: nil) addViewController(topViewController, frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: topViewHeight), completion: nil) } func bottomViewScrollViewDidScroll(_ scrollView: UIScrollView) { print("/(scrollView.contentOffset.y)") let offset = (scrollView.contentOffset.y + topViewHeight) if offset < 0 { topViewController.view.frame.origin.y = 0 topViewController.view.frame.size.height = topViewHeight - offset } else { topViewController.view.frame.origin.y = -(scrollView.contentOffset.y + topViewHeight) topViewController.view.frame.size.height = topViewHeight } } } class TopViewController: UIViewController { let label = UILabel() override func viewDidLoad() { super.viewDidLoad() automaticallyAdjustsScrollViewInsets = false view.backgroundColor = UIColor.red label.text = "Top View" view.addSubview(label) } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() label.sizeToFit() label.center = view.center } } protocol BottomViewControllerScrollDelegate: class { func bottomViewScrollViewDidScroll(_ scrollView: UIScrollView) } class BottomViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { var collectionView: UICollectionView! weak var delegate: BottomViewControllerScrollDelegate? let cellPadding: CGFloat = 5 override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.yellow automaticallyAdjustsScrollViewInsets = false let layout = UICollectionViewFlowLayout() layout.minimumInteritemSpacing = cellPadding layout.minimumLineSpacing = cellPadding layout.scrollDirection = .vertical layout.sectionInset = UIEdgeInsets(top: cellPadding, left: 0, bottom: cellPadding, right: 0) collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.contentInset.top = topViewHeight collectionView.scrollIndicatorInsets.top = topViewHeight collectionView.alwaysBounceVertical = true collectionView.backgroundColor = .clear collectionView.dataSource = self collectionView.delegate = self collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: String(describing: UICollectionViewCell.self)) view.addSubview(collectionView) } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 30 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: UICollectionViewCell.self), for: indexPath) cell.backgroundColor = UIColor.darkGray return cell } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let width = floor((collectionView.frame.size.width - 2 * cellPadding) / 3) return CGSize(width: width, height: width) } func scrollViewDidScroll(_ scrollView: UIScrollView) { delegate?.bottomViewScrollViewDidScroll(scrollView) } } extension UIViewController { func addViewController(_ viewController: UIViewController, frame: CGRect, completion: (()-> Void)?) { viewController.willMove(toParentViewController: self) viewController.beginAppearanceTransition(true, animated: false) addChildViewController(viewController) viewController.view.frame = frame viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(viewController.view) viewController.didMove(toParentViewController: self) viewController.endAppearanceTransition() completion?() } }