2017-09-15 93 views
5

Próbuję zbudować złożony kontroler kontenera podzielonego widoku, który ułatwia dwa pojemniki o zmiennej wysokości, każdy z własnym zagnieżdżonym kontrolerem widoku. Na kontrolerze nadrzędnym istnieje globalny gest przesunięcia, który pozwala użytkownikowi przeciągnąć dowolne miejsce w kontenerze widoku i przesunąć "dzielnik" między widokami w górę iw dół. Ma też trochę inteligentną logikę wykrywania progowej pozycja, która będzie rozszerzać albo pogląd (lub wyzerowanie pozycji dzielnik):Przekazuj gest panoramowania kontenera nadrzędnego do zagnieżdżonego UICollectionView

         

to działa prawidłowo. Jest też sporo kodu do napisania, który chętnie udostępnię, ale nie sądzę, że jest to istotne, więc na razie pominę to.

mam teraz stara się komplikować dodając Zobacz kolekcję do dolnej widzenia:

udało mi się dogadać tak, że mogę przewinąć podział zobaczyć się z zdecydowany gest panoramy i przewijanie widoku kolekcji szybkim ruchem palca (gestem machnięcia, jak sądzę?), ale jest to naprawdę podporządkowane doświadczenie: nie można przesuwać widoku i przewijać kolekcji widok w tym samym czasie i oczekiwanie, że użytkownik konsekwentnie replikuje podobne, ale różne gesty, aby kontrolować widok, jest zbyt trudne do interakcji.

Aby rozwiązać ten problem, wypróbowałem kilka rozwiązań delegatów/protokołów, w których wykryłem pozycję dzielnika w widoku podziału i włącz/wyłącz canCancelTouchesInView i/lub isUserInteractionEnable w widoku kolekcji na podstawie tego, czy dno widok jest w pełni rozwinięty. Działa to do pewnego stopnia, ale nie w dwóch następujących scenariuszy:

  1. Gdy widok podzielonego rozdzielacz jest w pozycji domyślnej, jeśli patelnie użytkowników aż do miejsca, gdzie widok z dołu jest w pełni rozwinięty, a następnie utrzymuje się na przesuwanie się , widok kolekcji powinien zacząć się przewijać aż do zakończenia gestu.
  2. Jeśli dzielnik widoku dzielonego znajduje się u góry (widok dolnego kontenera jest w pełni rozwinięty), a widok kolekcji jest na górze , a nie, jeśli użytkownik się opuści, widok kolekcji powinien przewijać zamiast podziału dzielonego , dopóki widok kolekcji nie osiągnie najwyższej pozycji, wtedy widok podziału powinien powrócić do domyślnej pozycji.

Oto animacja ilustruje to zachowanie:

enter image description here

Biorąc pod uwagę to, zaczynam myśleć, że jedynym sposobem na rozwiązanie tego problemu jest stworzenie metody delegata na widoku podzielonego który mówi widok kolekcji, gdy widok z dołu jest na maksymalnej wysokości, który może następnie przechwycić gest panoramy rodzica lub przesunąć ekran do widoku kolekcji zamiast tego? Ale nie jestem pewien, jak to zrobić. Jeśli jestem na dobrej drodze z rozwiązaniem, moje pytanie brzmi po prostu: Jak mogę przekazać lub odrzucić gest panoramy do widoku kolekcji i czy widok kolekcji oddziałuje w taki sam sposób, jak w przypadku przechwycenia dotknięć przez to w pierwszej kolejności? Czy mogę zrobić coś metodami pointInside lub touches____?

Jeśli nie mogę tego zrobić w ten sposób, jak inaczej mogę rozwiązać ten problem?


Aktualizacja dla łowców nagród: Miałem pewne fragmentaryczne szczęścia tworząc metodę delegata na widoku zbiórki i wzywającą go na pojemniku widoku podzielonego ustawić właściwość shouldScroll, przez który używam trochę pan kierunek i informacje o pozycji, aby określić, czy powinien przewijać się widok przewijania. Potem powrót tej wartości w UIGestureRecognizerDelegate „s metody gestureRecognizer:shouldReceive touch: Delegat:

// 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 
     } 
    } 
} 

Działa OK gdy zaczniesz przesuwać, ale nie ma dobrze wykonywać raz przewijanie jest włączony na widok kolekcji, ponieważ prawie zawsze gest przewijania zastępuje gest panoramy. Zastanawiam się, czy mogę coś podłączyć do gestureRecognizer:shouldRecognizeSimultaneouslyWith:, ale jeszcze mnie tam nie ma.

+0

Czy możesz udostępnić swój kod, aby przetestować to zachowanie? W jednym z dwóch przypadków, o których wspomniałeś, wygląda na to, że collectionView powinien spożywać zdarzenia dotykowe, dopóki jego poziome przesunięcie nie przekroczy granic, co wydaje się dość proste. – Lukas

+0

@Lukas Mogę, ale nie jestem pewien, czy to naprawdę pomoże rozwiązać problem - zarówno dlatego, że wiele jest zbudowanych w IB z ograniczeniami IB, a także dlatego, że już wiesz, że mam metody wykrywania delegatów, które strzelają dokładnie tak, jak ty "ve opisany:" gdy przesunięcie poziome osiągnie swoją górną granicę, collectionView powinien spożywać zdarzenia dotyku. " Mimo to, jeśli potrzebujesz jakiegoś kodu, mogę dodać trochę '¯ \ _ (ツ) _/¯' – brandonscript

Odpowiedz

2

Co zrobić, aby widok podrzędny dla widoku od dołu rzeczywiście zajmuje cały ekran i ustawić contentInset.top widoku kolekcji na wysokość widoku z góry. A następnie dodaj drugi kontroler widoku podrzędnego nad dolnym widokiem. Następnie jedyną rzeczą, którą musisz zrobić, to uczynić kontroler widoku rodzica delegatem, aby odsłuchał przesunięcie przewijania widoku widoku widoku od dołu i zmienić położenie widoku z góry. Bez skomplikowanego rozpoznawania gestów. Tylko jeden zwój (widok kolekcja)

enter image description here

Aktualizacja: Spróbuj tego !!

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?() 
    } 
} 
+0

Brzmi jak obiecujący pomysł ... jeśli zaatakujesz go w kodzie, może być warta nagroda – brandonscript

3

Nie można "oddać" gestu, ponieważ urządzenie rozpoznające gesty pozostaje ten sam obiekt, a jego niezmienna jest niezmienna - jest to widok, do którego jest dołączony czytnik.

Jednak nic nie stoi na przeszkodzie, aby powiedzieć innym, co robić w odpowiedzi na gest. Widok kolekcji to widok przewijania, dzięki czemu wiesz, jak jest przewijany w każdej chwili i może równolegle wykonywać inne czynności.

+0

To była jedna z moich wcześniejszych prób - wykrycie gestu i użycie odsłonięcia zawartości widoku przewijania (przewijanie) w celu obsłużenia go . Jednak robiąc to, poświęcam zdolność widoku kolekcji do "odbijania", a interakcja wydaje się być bardzo bez życia. – brandonscript

1

Powinieneś być w stanie osiągnąć to, czego szukasz, dzięki jednemu widokowi kolekcji przy użyciu UICollectionViewDelegateFlowLayout. Jeśli potrzebujesz specjalnego zachowania przewijania w widoku z góry, na przykład paralaksy, możesz to osiągnąć w widoku pojedynczego zbioru, implementując niestandardowy obiekt layoutu, który dziedziczy po UICollectionViewLayout.

Stosując podejście UICollectionViewDelegateFlowLayout jest trochę prostsze niż wykonawczych niestandardowy układ, więc jeśli chcesz dać tę szansę, spróbuj wykonać następujące czynności:

  • Tworzenie widoku z góry jako podklasa UICollectionViewCell i zarejestruj go w swoim widoku kolekcji.

  • Utwórz „rozdzielacz” Widok jako podklasa UICollectionViewCell i zarejestrować go z widzenia zbiórki jako widok zapasowego przy użyciu func register(_ viewClass: AnyClass?, forSupplementaryViewOfKind elementKind: String, withReuseIdentifier identifier: String)

  • Czy kontroler widoku kolekcja zgodne UICollectionViewDelegateFlowLayout, utworzyć obiekt układu jako przykład z UICollectionViewFlowLayout przypisz swój kontroler widoku kolekcji jako delegat instancji układu przepływu i uruchom swój widok kolekcji za pomocą układu przepływu.

  • Implementacja optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize zwracająca żądany rozmiar każdego z różnych widoków w kontrolerze widoku kolekcjonera.

+0

W praktyce wygląda to tak, jakby działało całkiem dobrze, ale w mojej sytuacji używam kontenera kontrolera widoku dla kontrolera podziału - góra i dół są niezależnymi kontrolerami widoku. Nie jestem do końca pewny, czy mógłbym podłączyć taki schemat i użyć kontenerów ... – brandonscript

+0

OK, w takim wypadku do kontrolera kontenera dodałabym rozpoznawacz gestów panoramowania. Następnie w programie obsługi wykonaj jedną z dwóch rzeczy: 1) jeśli stan gestu panoramy to '.changed', zaktualizuj każdą ramkę widoku kontrolera widoku dziecka na podstawie jego tłumaczenia w widoku kontenera. 2) Jeśli stan gestu panoramy jest ".wed", pokaż lub ukryj swój widok kolekcji na podstawie kierunku panoramy i minimalnego progu translacji, który możesz ustawić. – nicksweet