지도 앱에서 바텀 시트를 어떻게 모방 할 수 있습니까?


202

누구나 iOS 10의 새로운지도 앱에서 하단 시트를 모방하는 방법을 말해 줄 수 있습니까?

안드로이드에서는 BottomSheet이 동작을 모방 한 것을 사용할 수 있지만 iOS에서는 이와 같은 것을 찾을 수 없습니다.

검색 막대가 맨 아래에 있도록 내용이 삽입 된 간단한 스크롤보기입니까?

나는 iOS 프로그래밍에 익숙하지 않기 때문에 누군가이 레이아웃을 만들 수 있다면 매우 높이 평가 될 것입니다.

이것이 "바닥 시트"의 의미입니다.

지도에서 접힌 바텀 시트의 스크린 샷

지도에서 확장 된 하단 시트의 스크린 샷


1
당신은 내가 그것을 두려워 스스로 두려워해야합니다. 배경이 흐리게 보이고 일부 동작 인식기가있는보기입니다.
Khanh Nguyen

@KhanhNguyen 네, 제가 직접 만들어야한다는 것을 알고 있습니다.하지만 궁금한 점은이 효과를 보관하는 데 어떤 뷰가 사용되는지 그리고 어떻게 주문되는지 등입니다.
qwertz

흐림 효과가있는 시각 효과보기입니다. 이 SO 질문보기
Khanh Nguyen

1
팬 제스처 인식기를 하단보기에 추가하고 그에 따라보기를 이동할 수 있다고 생각합니다 (제스처 인식기의 이벤트 사이의 y 좌표를 저장하고 차이를 계산하여 변경 사항을 관찰함으로써).
Khanh Nguyen

2
나는 당신의 질문을 좋아합니다. 이런 식으로 구현할 계획입니다. 누군가가 예제를 쓸 수 있다면 좋을 것입니다.
wviana 2016 년

답변:


329

새지도 앱의 맨 아래 시트가 사용자 상호 작용에 정확히 어떻게 반응하는지 모르겠습니다. 그러나 스크린 샷에서 보이는 것과 같은 사용자 정의보기를 만들어 기본보기에 추가 할 수 있습니다.

나는 당신이 방법을 알고 있다고 가정합니다 :

1- 스토리 보드 또는 xib 파일을 사용하여 뷰 컨트롤러를 만듭니다.

2- googleMaps 또는 Apple의 MapKit을 사용하십시오.

1- ViewView 컨트롤러 2 개 (예 : MapViewControllerBottomSheetViewController)를 만듭니다 . 첫 번째 컨트롤러는 맵을 호스팅하고 두 번째 컨트롤러는 바텀 시트 자체입니다.

MapViewController 구성

바텀 시트보기를 추가하는 방법을 작성하십시오.

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

그리고 viewDidAppear 메소드에서 호출하십시오.

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

BottomSheetViewController 구성

1) 배경 준비

흐림 효과 및 생동감 효과를 추가하는 방법 만들기

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

뷰에서이 메소드를 호출하십시오.

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

컨트롤러의보기 배경색이 clearColor인지 확인하십시오.

2) 애니메이션 바닥 시트 모양

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) 원하는대로 xib를 수정하십시오.

4) 팬 제스처 인식기를 뷰에 추가하십시오.

viewDidLoad 메소드에서 UIPanGestureRecognizer를 추가하십시오.

override func viewDidLoad() {
    super.viewDidLoad()

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

}

제스처 동작을 구현하십시오.

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

스크롤 가능한 바텀 시트 :

사용자 정의보기가 스크롤보기 또는 상속 된 다른보기 인 경우 두 가지 옵션이 있습니다.

먼저:

헤더 뷰를 사용하여 뷰를 디자인하고 panGesture를 헤더에 추가하십시오. (나쁜 사용자 경험) .

둘째:

1-팬 시트를 하단 시트보기에 추가합니다.

2- UIGestureRecognizerDelegate를 구현하고 panGesture 델리게이트를 컨트롤러로 설정하십시오.

3- delegateRecognizeSimultaneously with delegate 기능을 구현 하고 두 가지 경우에 scrollView isScrollEnabled 속성을 비활성화 하십시오 .

  • 보기가 부분적으로 보입니다.
  • 보기가 완전히 표시되고 scrollView contentOffset 속성이 0이며 사용자가보기를 아래쪽으로 끌고 있습니다.

그렇지 않으면 스크롤을 활성화하십시오.

  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
  }

노트

혹시 설정 .allowUserInteraction를 , 샘플 프로젝트처럼, 애니메이션 옵션으로 사용하면 사용자가 스크롤되는 경우 애니메이션 완료 폐쇄에 스크롤을 활성화해야하므로.

샘플 프로젝트

리포지토리 에 더 많은 옵션 이 포함 된 샘플 프로젝트를 만들었습니다 . 그러면 흐름을 사용자 지정하는 방법에 대한 더 나은 통찰력을 얻을 수 있습니다.

데모에서 addBottomSheetView () 함수는 바텀 시트로 사용할 뷰를 제어합니다.

샘플 프로젝트 스크린 샷

-부분보기

여기에 이미지 설명을 입력하십시오

-FullView

여기에 이미지 설명을 입력하십시오

-스크롤 가능보기

여기에 이미지 설명을 입력하십시오


3
이것은 실제로 childViews 및 subViews에 대한 좋은 자습서입니다. 감사합니다 :)
Edison

@ AhmedElassuty 어떻게 xib에서 tableview를 추가하고 하단 시트로로드 할 수 있습니까? 도움이 될 수 있다면 미리 감사드립니다!
nikhil nangia

3
@ nikhilnangia 방금 tableView가 포함 된 다른 viewController "ScrollableBottomSheetViewController.swift"로 repo를 업데이트했습니다. 최대한 빨리 답변을 업데이트하겠습니다. 이 역시 확인 github.com/AhmedElassuty/IOS-BottomSheet/issues/1
아마드 Elassuty

12
Apple Maps 하단 시트가 한 번의 부드러운 동작으로 시트 드래그 제스처에서 스크롤 뷰 스크롤 제스처로 전환하는 것을 발견했습니다. 즉, 사용자가 뷰 스크롤을 시작하기 위해 하나의 제스처를 중지하고 새로운 제스처를 시작할 필요가 없습니다. 귀하의 예는 이것을하지 않습니다. 그것이 어떻게 달성 될 수 있을지에 대한 생각이 있습니까? 감사합니다. 리포지토리에 예제를 게시 해 주셔서 감사합니다.
Stoph

2
@ RafaelaLourenço 내 답변이 도움이되었다 니 정말 기쁩니다! 감사합니다!
Ahmad Elassuty

55

아래 답변에 따라 도서관을 출시했습니다 .

바로 가기 응용 프로그램 오버레이를 모방합니다. 자세한 내용은 이 기사 를 참조하십시오.

라이브러리의 주요 구성 요소는 OverlayContainerViewController입니다. 뷰 컨트롤러를 위아래로 드래그하여 그 아래의 내용을 숨기거나 표시 할 수있는 영역을 정의합니다.

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

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

window?.rootViewController = containerController

OverlayContainerViewControllerDelegate원하는 노치 수를 지정하도록 구현 하십시오.

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

이전 답변

제안 된 솔루션에서 다루지 않는 중요한 점이 있다고 생각합니다. 스크롤과 번역 사이의 전환.

스크롤과 번역 사이의지도 전환

지도에서 알 수 있듯이 tableView에 도달 contentOffset.y == 0하면 맨 아래 시트가 위로 미끄러지거나 내려갑니다.

팬 제스처가 번역을 시작할 때 단순히 스크롤을 활성화 / 비활성화 할 수 없기 때문에 요점은 까다 롭습니다. 새로운 터치가 시작될 때까지 스크롤을 멈출 것입니다. 여기에 제안 된 대부분의 솔루션이 여기에 해당합니다.

이 모션을 구현하려는 시도는 다음과 같습니다.

출발지 :지도 앱

조사를 시작하려면,하자가지도의 뷰 계층 구조를 시각화 (시뮬레이터에지도를 시작하고 선택 Debug> Attach to process by PID or Name> Maps엑스 코드 9).

지도 디버그 뷰 계층

모션이 어떻게 작동하는지 알려주지는 않지만 모션의 논리를 이해하는 데 도움이되었습니다. lldb 및 뷰 계층 디버거를 사용할 수 있습니다.

뷰 컨트롤러 스택

Maps ViewController 아키텍처의 기본 버전을 만들어 봅시다.

우리는 BackgroundViewController(지도보기)로 시작합니다.

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

tableView를 전용으로 넣습니다 UIViewController.

class OverlayViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    lazy var tableView = UITableView()

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

    [...]
}

이제 오버레이를 포함하고 번역을 관리하려면 VC가 필요합니다. 문제를 단순화하기 위해 오버레이를 한 정적 지점 OverlayPosition.maximum에서 다른 정적 지점 으로 변환 할 수 있다고 생각합니다.OverlayPosition.minimum .

현재 위치 변경에 애니메이션을 적용하는 공개 방법은 하나 뿐이며 투명하게 볼 수 있습니다.

enum OverlayPosition {
    case maximum, minimum
}

class OverlayContainerViewController: UIViewController {

    let overlayViewController: OverlayViewController
    var translatedViewHeightContraint = ...

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

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

마지막으로 모든 것을 포함하기 위해 ViewController가 필요합니다.

class StackViewController: UIViewController {

    private var viewControllers: [UIViewController]

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

AppDelegate에서 시작 순서는 다음과 같습니다.

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

오버레이 번역의 어려움

이제 오버레이를 번역하는 방법은 무엇입니까?

제안 된 솔루션의 대부분은 전용 팬 제스처 인식기를 사용하지만 실제로 테이블 뷰의 팬 제스처가 이미 있습니다. 또한 스크롤과 번역을 동기화하고UIScrollViewDelegate 필요한 모든 이벤트를 유지해야합니다!

순진한 구현은 두 번째 팬 제스처를 사용 contentOffset하고 변환이 발생할 때 테이블 뷰의 재설정을 시도 합니다.

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

그러나 작동하지 않습니다. tableView contentOffset는 자체 팬 제스처 인식기 동작이 트리거되거나 displayLink 콜백이 호출 될 때 해당 테이블을 업데이트합니다 . 바로 그 후 우리의 인식 트리거가 성공적를 오버라이드 (override) 할 수 있다는 기회가 없습니다 contentOffset. 우리의 유일한 기회는 레이아웃 단계에 참여하거나 ( layoutSubviews스크롤보기의 각 프레임에서 스크롤보기 호출을 재정 의 함) 또는 수정 didScroll될 때마다 호출되는 대리자 의 메서드에 응답하는 것입니다 contentOffset. 이것을 시도해 봅시다.

번역 구현

OverlayVC스크롤 뷰의 이벤트를 번역 핸들러 인 디스패치 핸들러에 전달 하기 위해 델리게이트를 추가 합니다 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)
    }
}

컨테이너에서 열거 형을 사용하여 번역을 추적합니다.

enum OverlayInFlightPosition {
    case minimum
    case maximum
    case progressing
}

현재 위치 계산은 다음과 같습니다.

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

번역을 처리하려면 3 가지 방법이 필요합니다.

첫 번째는 번역을 시작해야하는지 알려줍니다.

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

두 번째는 번역을 수행합니다. translation(in:)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)
    )
}

세 번째는 사용자가 손가락을 떼면 번역의 끝을 애니메이션으로 만듭니다. 뷰의 속도와 현재 위치를 사용하여 위치를 계산합니다.

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

오버레이의 델리게이트 구현은 다음과 같습니다.

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()
    }
}

최종 문제 : 오버레이 컨테이너의 터치 디스패치

번역은 이제 매우 효율적입니다. 그러나 여전히 마지막 문제가 있습니다. 터치가 백그라운드 뷰에 전달되지 않습니다. 그것들은 모두 오버레이 컨테이너의 뷰에 의해 가로 챈다. 테이블 뷰에서 상호 작용을 비활성화 isUserInteractionEnabled하기 false때문에 설정할 수 없습니다 . 이 솔루션은지도 앱에서 대량으로 사용되는 솔루션입니다 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
    }
}

응답자 체인에서 자신을 제거합니다.

에서 OverlayContainerViewController:

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

결과

결과는 다음과 같습니다.

결과

코드는 여기에서 찾을 수 있습니다 .

버그가 있으면 알려주십시오! 물론 오버레이에 헤더를 추가하는 경우 구현시 두 번째 팬 제스처를 사용할 수 있습니다.

23/08/18 업데이트

우리는 대체 할 수 scrollViewDidEndDragging와 함께 willEndScrollingWithVelocity보다는 enabling/ disabling스크롤 사용자의 끝을 드래그 할 때 :

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

스프링 애니메이션을 사용하고 모션 흐름을 개선하기 위해 애니메이션을 적용하면서 사용자 상호 작용을 허용 할 수 있습니다.

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

이 방법은 애니메이션 단계에서는 대화식이 아닙니다. 예를 들어지도에서 손가락이 늘어 나면 시트를 잡을 수 있습니다. 그런 다음 위 또는 아래로 이동하여 애니메이션을 스크러빙 할 수 있습니다. 가장 가까운 곳으로 돌아갑니다. 나는 이것이 UIViewPropertyAnimator(일시 정지 할 수있는 기능을 사용하여) 해결할 수 있다고 생각 하고 fractionComplete속성을 사용하여 스크럽을 수행합니다.
aust

1
당신은 바로 @aust입니다. 이전 변경 사항을 푸시하는 것을 잊었습니다. 지금은 좋을 것입니다. 감사.
GaétanZ

이것은 좋은 생각이지만 즉시 한 가지 문제를 볼 수 있습니다. 이 translateView(following:)방법 에서는 변환을 기반으로 높이를 계산하지만 0.0과 다른 값에서 시작할 수 있습니다 (예 : 테이블보기가 약간 스크롤되고 드래그를 시작할 때 오버레이가 최대화 됨) . 초기 점프로 이어질 것입니다. 테이블 뷰 scrollViewWillBeginDragging의 초기 값 contentOffset을 기억하고 translateView(following:)의 계산 에 사용 하는 콜백 으로이 문제를 해결할 수 있습니다.
Jakub Truhlář

1
@ GaétanZ 시뮬레이터에서 Maps.app에 디버거를 연결하는 데 관심이 있었지만 "pid에 연결할 수 없습니다 :"9276 ""과 "지도"가 이미 실행 중이 아닌지 확인하고 <username>에 대한 권한이 있습니다. 디버깅하십시오. " -Maps.app에 첨부 할 수 있도록 Xcode를 어떻게 속였습니까?
matm

1
@matm @ GaétanZ는 Xcode 10.1 / Mojave에서 나를 위해 일했습니다-> 시스템 무결성 보호 (SIP)를 비활성화하려면 비활성화해야합니다. 1) Mac을 재시동하십시오. 2) cmd + R을 누르고 있으십시오. 3) 메뉴에서 유틸리티를 선택한 다음 터미널을 선택하십시오. 4) 터미널 창에서 다음을 입력하십시오 csrutil disable && reboot. 그런 다음 Apple 프로세스에 연결할 수있는 것을 포함하여 루트가 일반적으로 제공하는 모든 기능을 사용할 수 있습니다.
valdyr

18

도르래를 보십시오 :

풀리는 iOS 10의지도 앱에서 서랍을 모방하기위한 사용하기 쉬운 서랍 라이브러리입니다. UIViewController 서브 클래스를 드로어 컨텐츠 또는 기본 컨텐츠로 사용할 수있는 간단한 API를 제공합니다.

풀리 미리보기

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


1
기본 목록에서 스크롤을 지원합니까?
Richard Lindhout

@RichardLindhout-데모를 실행 한 후에는 스크롤을 지원하는 것처럼 보이지만 서랍 이동에서 목록 스크롤로의 부드러운 전환은 아닙니다.
Stoph

이 예는 왼쪽 하단에 Apples 법적 링크를 숨기는 것으로 보입니다.
Gerd Castan

1
아주 간단한 질문입니다. 하단 시트 상단에 표시기를 어떻게 얻었습니까?
Israfil

12

당신은 내 대답 https://github.com/SCENEE/FloatingPanel을 시도 할 수 있습니다 . "하단 시트"인터페이스를 표시하는 컨테이너 뷰 컨트롤러를 제공합니다.

사용하기 쉽고 제스처 인식기 처리에 신경 쓰지 않습니다! 또한 필요한 경우 맨 아래 시트에서 스크롤보기 (또는 형제보기)를 추적 할 수 있습니다.

이것은 간단한 예입니다. 하단 시트에 콘텐츠를 표시하려면 뷰 컨트롤러를 준비해야합니다.

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)    
    }
    ...
}

여기 은 Apple Maps와 같은 위치를 검색하기 위해 바텀 시트를 표시하는 또 다른 예제 코드입니다.

Apple Maps와 같은 샘플 앱


fpc.set (contentViewController : newsVC), 라이브러리에서 누락 된 메소드
Faris Muhammed

1
아주 간단한 질문입니다. 하단 시트 상단에 표시기를 어떻게 얻었습니까?
Israfil

1
@AIsrafil 나는 단지 UIView라고 생각합니다
ShadeToD

정말 좋은 해결책이라고 생각하지만 iOS 13에서는이 부동 패널로 모달보기 컨트롤러를 표시하는 데 문제가 있습니다.
ShadeToD

11

ios Maps 앱에서 의도 한 동작을 달성하기 위해 내 라이브러리를 작성했습니다. 프로토콜 지향 솔루션입니다. 따라서 기본 클래스를 상속하지 않아도 시트 컨트롤러를 만들고 원하는대로 구성 할 수 있습니다. 또한 UINavigationController가 있거나없는 내부 탐색 / 프레젠테이션을 지원합니다.

자세한 내용은 아래 링크를 참조하십시오.

https://github.com/OfTheWolf/UBottomSheet

우 보텀 시트


팝업 뒤에있는 VC를 계속 클릭 할 수 있으므로이 답변이 선택되어 있어야합니다.
Jay

아주 간단한 질문입니다. 하단 시트 상단에 표시기를 어떻게 얻었습니까?
Israfil

1

최근 에 Swift 5.1로 작성된 SwipeableView하위 클래스 라는 구성 요소를 만들었습니다 UIView. 4 가지 방향을 모두 지원하고 여러 가지 사용자 정의 옵션이 있으며 다양한 속성 및 항목 (예 : 레이아웃 제약 조건, 배경 / 색조 색상, 아핀 변환, 알파 채널 및 뷰 센터와 같은 각 쇼케이스로 데모)을 애니메이션하고 보간 할 수 있습니다. 또한 설정 또는 자동 감지 된 경우 내부 스크롤보기와 스 와이프 조정을 지원합니다. 매우 쉽고 간단하게 사용해야합니다 (희망 🙂)

링크 https://github.com/LucaIaco/SwipeableView

개념의 증거:

여기에 이미지 설명을 입력하십시오

그것이 도움이되기를 바랍니다.


0

어쩌면 Pulley에서 영감을 얻은 내 대답 https://github.com/AnYuan/AYPannel을 시도해 볼 수 있습니다 . 서랍 이동에서 목록 스크롤로 부드럽게 전환합니다. 컨테이너 스크롤보기에 팬 제스처를 추가하고 shouldRecognizeSimultaneouslyWithGestureRecognizer를 YES로 반환하도록 설정했습니다. 위의 github 링크에서 자세한 내용을 확인하십시오. 도와주세요.


6
이 링크가 질문에 대답 할 수 있지만 여기에 답변의 필수 부분을 포함시키고 참조 할 수있는 링크를 제공하는 것이 좋습니다. 링크 된 페이지가 변경되면 링크 전용 답변이 유효하지 않을 수 있습니다. 이 링크가 질문에 대답 할 수 있지만 여기에 답변의 필수 부분을 포함시키고 참조 할 수있는 링크를 제공하는 것이 좋습니다. 링크 된 페이지가 변경되면 링크 전용 답변이 유효하지 않을 수 있습니다.
pirho
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.