Jak mogę naśladować dolny arkusz z aplikacji Mapy?

202

Czy ktoś może mi powiedzieć, jak mogę naśladować dolny arkusz w nowej aplikacji Mapy w iOS 10?

W Androidzie możesz użyć narzędzia, BottomSheetktóre naśladuje to zachowanie, ale nie mogłem znaleźć czegoś takiego na iOS.

Czy to prosty widok przewijania ze wstawką zawartości, aby pasek wyszukiwania znajdował się na dole?

Jestem całkiem nowy w programowaniu na iOS, więc jeśli ktoś mógłby mi pomóc w tworzeniu tego układu, byłoby to bardzo mile widziane.

Mam na myśli „dolny arkusz”:

zrzut ekranu zwiniętego dolnego arkusza w Mapach

zrzut ekranu rozwiniętego dolnego arkusza w Mapach

qwertz
źródło
1
Obawiam się, że musisz to zbudować sam. Jest to widok z rozmytym widokiem tła i niektórymi rozpoznawaczami gestów.
Khanh Nguyen
@KhanhNguyen tak, wiem, że muszę go zbudować sam, ale zastanawiam się, które widoki są używane do archiwizacji tego efektu oraz jak są uporządkowane i tak dalej
qwertz
To widok efektu wizualnego z efektem rozmycia. Zobacz to SO pytanie
Khanh Nguyen
1
Myślę, że możesz dodać panoramiczny gest rozpoznawania do dolnego widoku i odpowiednio przesunąć widok (obserwując zmiany, przechowując współrzędne y między zdarzeniami rozpoznającego gest i obliczyć różnicę).
Khanh Nguyen
2
Lubię twoje pytania. Planuję wdrożyć coś takiego. Gdyby ktoś mógł napisać jakiś przykład, byłoby miło.
wviana

Odpowiedzi:

329

Nie wiem, jak dokładnie dolny arkusz nowej aplikacji Mapy reaguje na interakcje użytkowników. Ale możesz utworzyć niestandardowy widok, który wygląda jak na zrzutach ekranu i dodać go do widoku głównego.

Zakładam, że wiesz, jak:

1- twórz kontrolery widoku za pomocą scenorysów lub plików Xib.

2 - użyj googleMaps lub Apple's MapKit.

Przykład

1- Utwórz 2 kontrolery widoku, np. MapViewController i BottomSheetViewController . Pierwszy kontroler będzie obsługiwał mapę, a drugi to sam dolny arkusz.

Skonfiguruj MapViewController

Utwórz metodę dodawania widoku dolnego arkusza.

func addBottomSheetView() {
    // 1- Init bottomSheetVC
    let bottomSheetVC = BottomSheetViewController()

    // 2- Add bottomSheetVC as a child view 
    self.addChildViewController(bottomSheetVC)
    self.view.addSubview(bottomSheetVC.view)
    bottomSheetVC.didMoveToParentViewController(self)

    // 3- Adjust bottomSheet frame and initial position.
    let height = view.frame.height
    let width  = view.frame.width
    bottomSheetVC.view.frame = CGRectMake(0, self.view.frame.maxY, width, height)
}

I nazwij to metodą viewDidAppear:

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    addBottomSheetView()
}

Skonfiguruj BottomSheetViewController

1) Przygotuj tło

Utwórz metodę dodawania efektów rozmycia i jaskrawości

func prepareBackgroundView(){
    let blurEffect = UIBlurEffect.init(style: .Dark)
    let visualEffect = UIVisualEffectView.init(effect: blurEffect)
    let bluredView = UIVisualEffectView.init(effect: blurEffect)
    bluredView.contentView.addSubview(visualEffect)

    visualEffect.frame = UIScreen.mainScreen().bounds
    bluredView.frame = UIScreen.mainScreen().bounds

    view.insertSubview(bluredView, atIndex: 0)
}

wywołaj tę metodę w swoim widokuWillAppear

override func viewWillAppear(animated: Bool) {
   super.viewWillAppear(animated)
   prepareBackgroundView()
}

Upewnij się, że kolor tła widoku kontrolera jest przezroczysty.

2) Animuj wygląd dołu arkusza

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    UIView.animateWithDuration(0.3) { [weak self] in
        let frame = self?.view.frame
        let yComponent = UIScreen.mainScreen().bounds.height - 200
        self?.view.frame = CGRectMake(0, yComponent, frame!.width, frame!.height)
    }
}

3) Zmodyfikuj xib, jak chcesz.

4) Dodaj Pan Gesture Recognizer do swojego widoku.

W metodzie viewDidLoad dodaj UIPanGestureRecognizer.

override func viewDidLoad() {
    super.viewDidLoad()

    let gesture = UIPanGestureRecognizer.init(target: self, action: #selector(BottomSheetViewController.panGesture))
    view.addGestureRecognizer(gesture)

}

I zaimplementuj zachowanie gestów:

func panGesture(recognizer: UIPanGestureRecognizer) {
    let translation = recognizer.translationInView(self.view)
    let y = self.view.frame.minY
    self.view.frame = CGRectMake(0, y + translation.y, view.frame.width, view.frame.height)
     recognizer.setTranslation(CGPointZero, inView: self.view)
}

Przewijany arkusz dolny:

Jeśli Twój widok niestandardowy jest widokiem przewijania lub innym widokiem, który dziedziczy, masz dwie opcje:

Pierwszy:

Zaprojektuj widok z widokiem nagłówka i dodaj panGesture do nagłówka. (złe wrażenia użytkownika) .

Druga:

1 - Dodaj panoramę do widoku dolnego arkusza.

2 - Zaimplementuj UIGestureRecognizerDelegate i ustaw delegata panGesture na kontroler.

3- Wdrożenie shouldRecognizeSimultaneouslyWith funkcji zarządcę oraz wyłączyć z Scrollview isScrollEnabled obiektu dwóch przypadków:

  • Widok jest częściowo widoczny.
  • Widok jest całkowicie widoczny, właściwość scrollView contentOffset ma wartość 0, a użytkownik przeciąga widok w dół.

W przeciwnym razie włącz przewijanie.

  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
      let gesture = (gestureRecognizer as! UIPanGestureRecognizer)
      let direction = gesture.velocity(in: view).y

      let y = view.frame.minY
      if (y == fullView && tableView.contentOffset.y == 0 && direction > 0) || (y == partialView) {
          tableView.isScrollEnabled = false
      } else {
        tableView.isScrollEnabled = true
      }

      return false
  }

UWAGA

Jeśli ustawisz .allowUserInteraction jako opcję animacji, tak jak w przykładowym projekcie, musisz włączyć przewijanie po zakończeniu animacji, jeśli użytkownik przewija w górę.

Przykładowy projekt

I stworzyliśmy przykładowy projekt z większą ilością opcji na ten repozytorium, które mogą dać ci lepszy wgląd w sposób dostosowywania przepływu.

W wersji demo funkcja addBottomSheetView () kontroluje, który widok powinien zostać użyty jako dolny arkusz.

Przykładowe zrzuty ekranu z projektu

- Częściowy widok

wprowadź opis zdjęcia tutaj

- Pełny widok

wprowadź opis zdjęcia tutaj

- Przewijalny widok

wprowadź opis zdjęcia tutaj

Ahmad Elassuty
źródło
3
To jest właściwie dobry samouczek dla childViews i subViews. Dziękuję :)
Edison
@AhmedElassuty jak mogę dodać widok tabeli w XIB i załadować jako dolny arkusz? Jeśli możesz pomóc, byłoby świetnie Dzięki z góry!
nikhil nangia
3
@nikhilnangia Właśnie zaktualizowałem repozytorium o inny viewController „ScrollableBottomSheetViewController.swift”, który zawiera tableView. Zaktualizuję odpowiedź tak szybko, jak to możliwe. Sprawdź to też github.com/AhmedElassuty/IOS-BottomSheet/issues/1
Ahmad Elassuty
12
Zauważyłem, że dolny arkusz mapy Apple obsługuje przechodzenie z gestu przeciągania arkusza do gestu przewijania widoku przewijania jednym płynnym ruchem, tj. Użytkownik nie musi zatrzymywać jednego gestu i rozpocząć nowego, aby rozpocząć przewijanie widoku. Twój przykład tego nie robi. Czy masz jakieś przemyślenia na temat tego, jak można to osiągnąć? Dzięki i dziękuję za opublikowanie przykładu w repozytorium!
Stoph
2
@ RafaelaLourenço Cieszę się, że moja odpowiedź była pomocna! Dziękuję Ci!
Ahmad Elassuty
55

Wydałem bibliotekę na podstawie mojej odpowiedzi poniżej.

Naśladuje nakładkę aplikacji Skróty. Zobacz ten artykuł .

Głównym składnikiem biblioteki jest OverlayContainerViewController. Definiuje obszar, w którym kontroler widoku można przeciągać w górę iw dół, ukrywając lub ujawniając zawartość pod nim.

let contentController = MapsViewController()
let overlayController = SearchViewController()

let containerController = OverlayContainerViewController()
containerController.delegate = self
containerController.viewControllers = [
    contentController,
    overlayController
]

window?.rootViewController = containerController

Zaimplementuj, OverlayContainerViewControllerDelegateaby określić liczbę żądanych wycięć:

enum OverlayNotch: Int, CaseIterable {
    case minimum, medium, maximum
}

func numberOfNotches(in containerViewController: OverlayContainerViewController) -> Int {
    return OverlayNotch.allCases.count
}

func overlayContainerViewController(_ containerViewController: OverlayContainerViewController,
                                    heightForNotchAt index: Int,
                                    availableSpace: CGFloat) -> CGFloat {
    switch OverlayNotch.allCases[index] {
        case .maximum:
            return availableSpace * 3 / 4
        case .medium:
            return availableSpace / 2
        case .minimum:
            return availableSpace * 1 / 4
    }
}

Poprzednia odpowiedź

Myślę, że w sugerowanych rozwiązaniach istnieje znacząca kwestia: przejście między zwojem a tłumaczeniem.

Przejście map między przewijaniem a tłumaczeniem

W Mapach, jak być może zauważyłeś, kiedy tableView sięga contentOffset.y == 0 , dolny arkusz przesuwa się w górę lub spada.

Chodzi o to, ponieważ nie możemy po prostu włączyć / wyłączyć przewijania, gdy nasz gest przesuwania rozpoczyna tłumaczenie. Zatrzymuje przewijanie do momentu rozpoczęcia nowego dotknięcia. Tak jest w przypadku większości proponowanych tutaj rozwiązań.

Oto moja próba realizacji tego ruchu.

Punkt początkowy: aplikacja Maps

Aby rozpocząć dochodzenie, niech wizualizacji hierarchii wyświetl Maps (Mapy rozpocząć na symulatorze, a następnie wybierz Debug> Attach to process by PID or Name> Mapsw Xcode 9).

Mapy hierarchii widoków debugowania

Nie mówi, jak działa ruch, ale pomogło mi zrozumieć jego logikę. Możesz grać z lldb i debugerem hierarchii widoków.

Nasz stos kontrolerów widoków

Stwórzmy podstawową wersję architektury Maps ViewController.

Zaczynamy od BackgroundViewController(naszego widoku mapy):

class BackgroundViewController: UIViewController {
    override func loadView() {
        view = MKMapView()
    }
}

Umieszczamy tableView w dedykowanym UIViewController:

class OverlayViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    lazy var tableView = UITableView()

    override func loadView() {
        view = tableView
        tableView.dataSource = self
        tableView.delegate = self
    }

    [...]
}

Teraz potrzebujemy VC do osadzenia nakładki i zarządzania jej tłumaczeniem. Aby uprościć problem, uważamy, że może on przełożyć nakładkę z jednego punktu statycznego OverlayPosition.maximumna innyOverlayPosition.minimum .

Na razie ma tylko jedną publiczną metodę animacji zmiany pozycji i ma przejrzysty widok:

enum OverlayPosition {
    case maximum, minimum
}

class OverlayContainerViewController: UIViewController {

    let overlayViewController: OverlayViewController
    var translatedViewHeightContraint = ...

    override func loadView() {
        view = UIView()
    }

    func moveOverlay(to position: OverlayPosition) {
        [...]
    }
}

Wreszcie potrzebujemy ViewController do osadzenia wszystkich:

class StackViewController: UIViewController {

    private var viewControllers: [UIViewController]

    override func viewDidLoad() {
        super.viewDidLoad()
        viewControllers.forEach { gz_addChild($0, in: view) }
    }
}

W naszej AppDelegate nasza sekwencja uruchamiania wygląda następująco:

let overlay = OverlayViewController()
let containerViewController = OverlayContainerViewController(overlayViewController: overlay)
let backgroundViewController = BackgroundViewController()
window?.rootViewController = StackViewController(viewControllers: [backgroundViewController, containerViewController])

Trudność związana z tłumaczeniem nakładki

Jak przetłumaczyć naszą nakładkę?

Większość proponowanych rozwiązań wykorzystuje dedykowany moduł rozpoznawania gestów panoramy, ale tak naprawdę już go mamy: gest panoramy widoku tabeli. Ponadto musimy synchronizować zwój i tłumaczenie oraz UIScrollViewDelegatemieć wszystkie potrzebne zdarzenia!

Naiwna implementacja użyłaby drugiego gestu panoramy i spróbowała zresetować contentOffsetwidok tabeli, gdy nastąpi tłumaczenie:

func panGestureAction(_ recognizer: UIPanGestureRecognizer) {
    if isTranslating {
        tableView.contentOffset = .zero
    }
}

Ale to nie działa. TableView aktualizuje go, contentOffsetgdy wyzwala własne działanie rozpoznawania gestów panoramy lub gdy wywoływane jest wywołanie zwrotne displayLink. Nie ma szans, że nasz program rozpoznający uruchomi się zaraz po udanym zastąpieniu contentOffset. Naszą jedyną szansą jest albo wzięcie udziału w fazie układu (poprzez przesłonięcie layoutSubviewswywołań widoku przewijania w każdej ramce widoku przewijania) lub odpowiedź na didScrollmetodę uczestnika wywoływaną przy każdej contentOffsetmodyfikacji. Spróbujmy tego.

Implementacja tłumaczenia

Dodajemy do naszego delegata, aby OverlayVCwysłać zdarzenia scrollview do naszego modułu obsługi tłumaczeń OverlayContainerViewController:

protocol OverlayViewControllerDelegate: class {
    func scrollViewDidScroll(_ scrollView: UIScrollView)
    func scrollViewDidStopScrolling(_ scrollView: UIScrollView)
}

class OverlayViewController: UIViewController {

    [...]

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        delegate?.scrollViewDidScroll(scrollView)
    }

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        delegate?.scrollViewDidStopScrolling(scrollView)
    }
}

W naszym kontenerze śledzimy tłumaczenie za pomocą wyliczenia:

enum OverlayInFlightPosition {
    case minimum
    case maximum
    case progressing
}

Obecne obliczenie pozycji wygląda następująco:

private var overlayInFlightPosition: OverlayInFlightPosition {
    let height = translatedViewHeightContraint.constant
    if height == maximumHeight {
        return .maximum
    } else if height == minimumHeight {
        return .minimum
    } else {
        return .progressing
    }
}

Potrzebujemy 3 metod do obsługi tłumaczenia:

Pierwszy mówi nam, czy musimy rozpocząć tłumaczenie.

private func shouldTranslateView(following scrollView: UIScrollView) -> Bool {
    guard scrollView.isTracking else { return false }
    let offset = scrollView.contentOffset.y
    switch overlayInFlightPosition {
    case .maximum:
        return offset < 0
    case .minimum:
        return offset > 0
    case .progressing:
        return true
    }
}

Drugi wykonuje tłumaczenie. Wykorzystuje translation(in:)metodę gestu przewijania scrollView.

private func translateView(following scrollView: UIScrollView) {
    scrollView.contentOffset = .zero
    let translation = translatedViewTargetHeight - scrollView.panGestureRecognizer.translation(in: view).y
    translatedViewHeightContraint.constant = max(
        Constant.minimumHeight,
        min(translation, Constant.maximumHeight)
    )
}

Trzeci animuje koniec tłumaczenia, gdy użytkownik puści palec. Pozycję obliczamy na podstawie prędkości i aktualnej pozycji widoku.

private func animateTranslationEnd() {
    let position: OverlayPosition =  // ... calculation based on the current overlay position & velocity
    moveOverlay(to: position)
}

Implementacja naszej delegacji po prostu wygląda następująco:

class OverlayContainerViewController: UIViewController {

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard shouldTranslateView(following: scrollView) else { return }
        translateView(following: scrollView)
    }

    func scrollViewDidStopScrolling(_ scrollView: UIScrollView) {
        // prevent scroll animation when the translation animation ends
        scrollView.isEnabled = false
        scrollView.isEnabled = true
        animateTranslationEnd()
    }
}

Ostatni problem: wywołanie zmian w kontakcie nakładki

Tłumaczenie jest teraz całkiem wydajne. Ale wciąż jest ostatni problem: poprawki nie są dostarczane do naszego widoku tła. Wszystkie są przechwytywane przez widok pojemnika nakładki. Nie możemy ustawić isUserInteractionEnabledsię falsebo to również wyłączyć interakcje w naszym widoku tabeli. Rozwiązanie to jest szeroko stosowane w aplikacji Mapy PassThroughView:

class PassThroughView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if view == self {
            return nil
        }
        return view
    }
}

Usuwa się z łańcucha odpowiadającego.

W OverlayContainerViewController:

override func loadView() {
    view = PassThroughView()
}

Wynik

Oto wynik:

Wynik

Możesz znaleźć kod tutaj .

Jeśli zauważysz jakieś błędy, daj mi znać! Pamiętaj, że twoja implementacja może oczywiście użyć drugiego gestu przesuwania, szczególnie jeśli dodasz nagłówek w nakładce.

Zaktualizuj 23/08/18

Możemy wymienić scrollViewDidEndDraggingze willEndScrollingWithVelocityzamiast enabling/ disablingzwoju kiedy użytkownik przeciągając końce:

func scrollView(_ scrollView: UIScrollView,
                willEndScrollingWithVelocity velocity: CGPoint,
                targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    switch overlayInFlightPosition {
    case .maximum:
        break
    case .minimum, .progressing:
        targetContentOffset.pointee = .zero
    }
    animateTranslationEnd(following: scrollView)
}

Możemy użyć animacji wiosennej i umożliwić interakcję użytkownika podczas animacji, aby usprawnić przepływ ruchu:

func moveOverlay(to position: OverlayPosition,
                 duration: TimeInterval,
                 velocity: CGPoint) {
    overlayPosition = position
    translatedViewHeightContraint.constant = translatedViewTargetHeight
    UIView.animate(
        withDuration: duration,
        delay: 0,
        usingSpringWithDamping: velocity.y == 0 ? 1 : 0.6,
        initialSpringVelocity: abs(velocity.y),
        options: [.allowUserInteraction],
        animations: {
            self.view.layoutIfNeeded()
    }, completion: nil)
}
GaétanZ
źródło
To podejście nie jest interaktywne podczas etapów animacji. Na przykład w Mapach mogę złapać arkusz palcem, gdy się rozwija. Następnie mogę wyszorować animację, przesuwając w górę lub w dół. Powróci do najbliższego miejsca. Wierzę, że można to rozwiązać za pomocą UIViewPropertyAnimator(który ma możliwość wstrzymania), a następnie użyć fractionCompletewłaściwości do wykonania szorowania.
aust
1
Masz rację @ust. Zapomniałem wprowadzić poprzednią zmianę. Teraz powinno być dobrze. Dzięki.
GaétanZ
To świetny pomysł, jednak od razu widzę jeden problem. W translateView(following:)metodzie obliczasz wysokość na podstawie translacji, ale może ona zaczynać się od innej wartości niż 0,0 (np. Widok tabeli jest nieco przewijany, a nakładka jest zmaksymalizowana po rozpoczęciu przeciągania) . Spowodowałoby to początkowy skok. Możesz to rozwiązać za pomocą scrollViewWillBeginDraggingwywołania zwrotnego, w którym zapamiętasz początkowy contentOffsetwidok tabeli i użyjesz go w translateView(following:)obliczeniach.
Jakub Truhlář
1
@ GaétanZ Byłem zainteresowany dołączeniem debugera do Maps.app na symulatorze, ale pojawia się błąd „Nie można dołączyć do pid:„ 9276 ””, a następnie „Upewnij się, że„ Mapy ”jeszcze nie działa, a <nazwa użytkownika> ma uprawnienia do debuguj ”. - Jak oszukałeś Xcode, aby umożliwić dołączanie do Maps.app?
matm
1
@matm @ GaétanZ pracował dla mnie na Xcode 10.1 / Mojave -> wystarczy wyłączyć, aby wyłączyć System Integrity Protection (SIP). Musisz: 1) Uruchom ponownie komputer Mac; 2) Przytrzymaj cmd + R; 3) Z menu wybierz Narzędzia, a następnie Terminal; 4) W oknie terminala typu: csrutil disable && reboot. Będziesz wtedy mieć wszystkie uprawnienia, które root powinien normalnie dać ci, w tym możliwość przyłączenia się do procesu Apple.
valdyr
18

Wypróbuj koło pasowe :

Pulley to łatwa w użyciu biblioteka szuflad przeznaczona do naśladowania szuflady w aplikacji Mapy iOS 10. Udostępnia prosty interfejs API, który pozwala używać dowolnej podklasy UIViewController jako zawartości szuflady lub treści podstawowej.

Podgląd koła pasowego

https://github.com/52inc/Pulley

Rajee Jones
źródło
1
Czy obsługuje przewijanie na liście bazowej?
Richard Lindhout
@RichardLindhout - Po uruchomieniu demo wygląda na to, że obsługuje przewijanie, ale nie płynne przejście od przesunięcia szuflady do przewijania listy.
Stoph
Wygląda na to, że przykład ukrył prawny link do jabłka w lewym dolnym rogu.
Gerd Castan
1
Super proste pytanie, skąd wziął się wskaźnik na górze dolnej kartki?
Israfil
12

Możesz spróbować mojej odpowiedzi https://github.com/SCENEE/FloatingPanel . Zapewnia kontroler widoku kontenera do wyświetlania interfejsu „dolnego arkusza”.

Jest łatwy w użyciu i nie przeszkadza Ci żadna obsługa rozpoznawania gestów! W razie potrzeby możesz również śledzić widok przewijania (lub widok rodzeństwa) w dolnym arkuszu.

To jest prosty przykład. Pamiętaj, że musisz przygotować kontroler widoku, aby wyświetlać zawartość w dolnym arkuszu.

import UIKit
import FloatingPanel

class ViewController: UIViewController {
    var fpc: FloatingPanelController!

    override func viewDidLoad() {
        super.viewDidLoad()

        fpc = FloatingPanelController()

        // Add "bottom sheet" in self.view.
        fpc.add(toParent: self)

        // Add a view controller to display your contents in "bottom sheet".
        let contentVC = ContentViewController()
        fpc.set(contentViewController: contentVC)

        // Track a scroll view in "bottom sheet" content if needed.
        fpc.track(scrollView: contentVC.tableView)    
    }
    ...
}

Oto kolejny przykładowy kod wyświetlający dolny arkusz do wyszukiwania lokalizacji takiej jak Apple Maps.

Przykładowa aplikacja, taka jak Apple Maps

SCENA
źródło
fpc.set (contentViewController: newsVC), brak metody w bibliotece
Faris Muhammed
1
Super proste pytanie, skąd wziął się wskaźnik na górze dolnej kartki?
Israfil
1
@Assrafil Myślę, że to tylko UIView
ShadeToD
Myślę, że to naprawdę miłe rozwiązanie, ale w iOS 13 występuje problem z prezentacją kontrolera widoku modalnego z tym ruchomym panelem.
ShadeToD
11

Napisałem własną bibliotekę, aby osiągnąć zamierzone zachowanie w aplikacji ios Maps. Jest to rozwiązanie zorientowane na protokół. Więc nie musisz dziedziczyć żadnej klasy bazowej, zamiast tego utwórz kontroler arkuszy i skonfiguruj, jak chcesz. Obsługuje również wewnętrzną nawigację / prezentację z lub bez UINavigationController.

Zobacz poniższy link, aby uzyskać więcej informacji.

https://github.com/OfTheWolf/UBottomSheet

arkusz ubottom

ugur
źródło
TO powinna być wybrana odpowiedź, ponieważ nadal możesz kliknąć VC za oknem wyskakującym.
Jay
Super proste pytanie, skąd wziął się wskaźnik na górze dolnej kartki?
Israfil
1

Niedawno stworzyłem komponent o nazwie SwipeableViewpodklasa UIViewnapisany w Swift 5.1. Obsługuje wszystkie 4 kierunki, ma kilka opcji dostosowywania i może animować i interpolować różne atrybuty i elementy (takie jak ograniczenia układu, kolor tła / odcienia, transformacja afiniczna, kanał alfa i centrum widoku, wszystkie z nich pokazane z odpowiednią gablotą). Obsługuje również koordynację przesuwania z wewnętrznym widokiem przewijania, jeśli jest ustawiony lub automatycznie wykryty. Powinien być dość łatwy w użyciu (mam nadzieję 🙂)

Link na https://github.com/LucaIaco/SwipeableView

dowód koncepcji:

wprowadź opis zdjęcia tutaj

Mam nadzieję, że to pomoże

Luca Iaco
źródło
0

Może spróbujesz mojej odpowiedzi https://github.com/AnYuan/AYPannel , zainspirowanej Pulleyem. Płynne przejście z przesuwania szuflady do przewijania listy. Dodałem gest panoramowania w widoku przewijania kontenera i ustawiłem returnRecognizeSimultentlyWithGestureRecognizer, aby zwrócił TAK. Więcej szczegółów w moim linku github powyżej. Chciałbym pomóc.

dzień
źródło
6
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest dołączyć tutaj istotne części odpowiedzi i podać link w celach informacyjnych. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie. Chociaż ten link może odpowiedzieć na pytanie, lepiej jest dołączyć tutaj istotne części odpowiedzi i podać link w celach informacyjnych. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie.
pirho