2017-04-20 24 views
18

Ứng dụng Nhạc trong iOS 10 thông qua giao diện giống như thẻ mới: Hiện đang phát màn hình trượt lên, trong khi chế độ xem bên dưới trong hệ thống phân cấp thu nhỏ, nhô ra một chút ở đầu màn hình.Có API công khai cho giao diện người dùng xem thẻ có thể xem trên iOS 10 không?

music app card interface

Dưới đây là ví dụ từ Mail soạn cửa sổ:

mail compose card interface

ẩn dụ này cũng có thể được nhìn thấy trong U ám, người chơi podcast phổ biến:

overcast card interface

Có chức năng nào trong UIKit để đạt được diện mạo giống như thẻ này không?

+1

Tính đến thời điểm này (iOS 10.3.1) không có bất kỳ điều gì được nướng trước vào UIKIt cho chức năng này. – Jeremy1026

Trả lời

18

Bạn có thể tạo phân đoạn trong trình tạo giao diện. Chọn phân đoạn phương thức từ ViewController đến CardViewController.

Đối với bạn CardViewController:

import UIKit 

class CardViewController: UIViewController { 

    required init?(coder aDecoder: NSCoder) { 
    super.init(coder: aDecoder) 
    self.commonInit() 
    } 

    override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: Bundle!) { 
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 

    self.commonInit() 
    } 

    func commonInit() { 
    self.modalPresentationStyle = .custom 
    self.transitioningDelegate = self 
    } 

    override func viewDidLoad() { 
    super.viewDidLoad() 

    roundViews() 
    } 

    func roundViews() { 
    view.layer.cornerRadius = 8 
    view.clipsToBounds = true 
    } 

} 

sau đó thêm phần mở rộng này:

extension CardViewController: UIViewControllerTransitioningDelegate { 

    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { 
    if presented == self { 
     return CardPresentationController(presentedViewController: presented, presenting: presenting) 
    } 
    return nil 
    } 

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 
    if presented == self { 
     return CardAnimationController(isPresenting: true) 
    } else { 
     return nil 
    } 
    } 

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 
    if dismissed == self { 
     return CardAnimationController(isPresenting: false) 
    } else { 
     return nil 
    } 
    } 

} 

Cuối cùng, bạn sẽ cần thêm 2 lớp:

import UIKit 

class CardPresentationController: UIPresentationController { 

    lazy var dimmingView :UIView = { 
    let view = UIView(frame: self.containerView!.bounds) 
    view.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.3) 
    view.layer.cornerRadius = 8 
    view.clipsToBounds = true 
    return view 
    }() 

    override func presentationTransitionWillBegin() { 

    guard 
     let containerView = containerView, 
     let presentedView = presentedView 
     else { 
     return 
    } 

    // Add the dimming view and the presented view to the heirarchy 
    dimmingView.frame = containerView.bounds 
    containerView.addSubview(dimmingView) 
    containerView.addSubview(presentedView) 

    // Fade in the dimming view alongside the transition 
    if let transitionCoordinator = self.presentingViewController.transitionCoordinator { 
     transitionCoordinator.animate(alongsideTransition: {(context: UIViewControllerTransitionCoordinatorContext!) -> Void in 
     self.dimmingView.alpha = 1.0 
     }, completion:nil) 
    } 
    } 

    override func presentationTransitionDidEnd(_ completed: Bool) { 
    // If the presentation didn't complete, remove the dimming view 
    if !completed { 
     self.dimmingView.removeFromSuperview() 
    } 
    } 

    override func dismissalTransitionWillBegin() { 
    // Fade out the dimming view alongside the transition 
    if let transitionCoordinator = self.presentingViewController.transitionCoordinator { 
     transitionCoordinator.animate(alongsideTransition: {(context: UIViewControllerTransitionCoordinatorContext!) -> Void in 
     self.dimmingView.alpha = 0.0 
     }, completion:nil) 
    } 
    } 

    override func dismissalTransitionDidEnd(_ completed: Bool) { 
    // If the dismissal completed, remove the dimming view 
    if completed { 
     self.dimmingView.removeFromSuperview() 
    } 
    } 

    override var frameOfPresentedViewInContainerView : CGRect { 

    // We don't want the presented view to fill the whole container view, so inset it's frame 
    let frame = self.containerView!.bounds; 
    var presentedViewFrame = CGRect.zero 
    presentedViewFrame.size = CGSize(width: frame.size.width, height: frame.size.height - 40) 
    presentedViewFrame.origin = CGPoint(x: 0, y: 40) 

    return presentedViewFrame 
    } 

    override func viewWillTransition(to size: CGSize, with transitionCoordinator: UIViewControllerTransitionCoordinator) { 
    super.viewWillTransition(to: size, with: transitionCoordinator) 

    guard 
     let containerView = containerView 
     else { 
     return 
    } 

    transitionCoordinator.animate(alongsideTransition: {(context: UIViewControllerTransitionCoordinatorContext!) -> Void in 
     self.dimmingView.frame = containerView.bounds 
    }, completion:nil) 
    } 

} 

và:

import UIKit 


class CardAnimationController: NSObject { 

    let isPresenting :Bool 
    let duration :TimeInterval = 0.5 

    init(isPresenting: Bool) { 
    self.isPresenting = isPresenting 

    super.init() 
    } 
} 

// MARK: - UIViewControllerAnimatedTransitioning 

extension CardAnimationController: UIViewControllerAnimatedTransitioning { 

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 
    return self.duration 
    } 

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 
    let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) 
    let fromView = fromVC?.view 
    let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) 
    let toView = toVC?.view 

    let containerView = transitionContext.containerView 

    if isPresenting { 
     containerView.addSubview(toView!) 
    } 

    let bottomVC = isPresenting ? fromVC : toVC 
    let bottomPresentingView = bottomVC?.view 

    let topVC = isPresenting ? toVC : fromVC 
    let topPresentedView = topVC?.view 
    var topPresentedFrame = transitionContext.finalFrame(for: topVC!) 
    let topDismissedFrame = topPresentedFrame 
    topPresentedFrame.origin.y -= topDismissedFrame.size.height 
    let topInitialFrame = topDismissedFrame 
    let topFinalFrame = isPresenting ? topPresentedFrame : topDismissedFrame 
    topPresentedView?.frame = topInitialFrame 

    UIView.animate(withDuration: self.transitionDuration(using: transitionContext), 
        delay: 0, 
        usingSpringWithDamping: 300.0, 
        initialSpringVelocity: 5.0, 
        options: [.allowUserInteraction, .beginFromCurrentState], //[.Alert, .Badge] 
     animations: { 
     topPresentedView?.frame = topFinalFrame 
     let scalingFactor : CGFloat = self.isPresenting ? 0.92 : 1.0 
     bottomPresentingView?.transform = CGAffineTransform.identity.scaledBy(x: scalingFactor, y: scalingFactor) 

    }, completion: { 
     (value: Bool) in 
     if !self.isPresenting { 
     fromView?.removeFromSuperview() 
     } 
    }) 


    if isPresenting { 
     animatePresentationWithTransitionContext(transitionContext) 
    } else { 
     animateDismissalWithTransitionContext(transitionContext) 
    } 
    } 

    func animatePresentationWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) { 

    let containerView = transitionContext.containerView 
    guard 
     let presentedController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to), 
     let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.to) 
     else { 
     return 
    } 

    // Position the presented view off the top of the container view 
    presentedControllerView.frame = transitionContext.finalFrame(for: presentedController) 
    presentedControllerView.center.y += containerView.bounds.size.height 

    containerView.addSubview(presentedControllerView) 

    // Animate the presented view to it's final position 
    UIView.animate(withDuration: self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .allowUserInteraction, animations: { 
     presentedControllerView.center.y -= containerView.bounds.size.height 
    }, completion: {(completed: Bool) -> Void in 
     transitionContext.completeTransition(completed) 
    }) 
    } 

    func animateDismissalWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) { 

    let containerView = transitionContext.containerView 
    guard 
     let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.from) 
     else { 
     return 
    } 

    // Animate the presented view off the bottom of the view 
    UIView.animate(withDuration: self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .allowUserInteraction, animations: { 
     presentedControllerView.center.y += containerView.bounds.size.height 
    }, completion: {(completed: Bool) -> Void in 
     transitionContext.completeTransition(completed) 
    }) 
    } 
} 

Cuối cùng, để animate CardViewController đóng cửa, treo nút đóng cửa của bạn để FirstResponder chọn dismiss và thêm phương pháp này để ViewController:

func dismiss(_ segue: UIStoryboardSegue) { 
    self.dismiss(animated: true, completion: nil) 
} 
+0

Tôi biết tôi đang yêu cầu rất nhiều ở đây, nhưng bạn có thể tải lên một dự án Xcode tối thiểu để Github hoặc Dropbox thực hiện việc này không? – Boletrone

+5

Ở đây bạn đi: https://github.com/epeschard/CardsUI – pesch

+0

Tuyệt vời! Cảm ơn bạn rất nhiều! – Boletrone

2

Ok, tôi sẽ cố gắng cung cấp cho bạn một giải pháp nhỏ gọn với một tối thiểu của mã.

Giải pháp nhanh. Bạn cần trình bày một bộ điều khiển một cách bình thường với modalPresentationStyle - property được đặt thành .overCurrentContext. Bạn có thể đặt giá trị trước preset(controller:...) -method được gọi hoặc trong prepare(for:...) -one nếu đó là chuyển đổi segue. Để trượt sử dụng modalTransitionStyle đặt thành .coverVertical.

Để chế độ xem nguồn "thu nhỏ", chỉ cập nhật giới hạn ở chế độ xem viewWill(Diss)appear -methods. Trong hầu hết các trường hợp, điều này sẽ hoạt động.

Đừng quên đặt chế độ xem nền của bộ điều khiển phương thức trong suốt để chế độ xem cơ bản vẫn hiển thị.

Trượt lên/xuống trơn tru. Bạn cần thiết lập transition giữa các bộ điều khiển theo cách phù hợp. Nếu bạn nhìn gần hơn với ứng dụng âm nhạc của Apple, bạn sẽ thấy một cách để ẩn bộ điều khiển trên cùng với thao tác trượt xuống. Bạn cũng có thể tùy chỉnh giao diện của bạn (dis). Hãy xem this article. Nó sử dụng chỉ UIKit -methods. Thật không may theo cách này đòi hỏi rất nhiều mã, nhưng bạn có thể sử dụng thư viện của bên thứ 3 để thiết lập quá trình chuyển đổi. Giống như this one.

5

Cập nhật: Phần tương tác của bản trình diễn này không hoạt động trên iOS 11 vì một số lý do. Táo cho thấy một kỹ thuật khác nhau trong WWDC 2017 Session 230: Advanced Animations with UIKit nơi họ sử dụng UIViewPropertyAnimator

Tôi đã có một bản demo cơ bản của kỹ thuật này ở đây: https://github.com/peteog/CardUI


Đây là loại giao diện người dùng có thể được xây dựng sử dụng Custom UIViewController Transitions, UIPresentationController, và UIViewPropertyAnimator.

ứng dụng mẫu: https://github.com/peteog/CardUIExample

Đầu tiên tạo một lớp con UIPresentationController. Điều này sẽ:

  • Thêm một cái nhìn mờ
  • Biến đổi bộ điều khiển xem trình bày để inset nó từ thanh trạng thái
  • Đặt khung về quan điểm được trình bày để làm cho hiệu lực thi hành thẻ

Mã số:

import UIKit 

class PresentationController: UIPresentationController { 
    private let dimmingView: UIView = { 
     let dimmingView = UIView() 
     dimmingView.backgroundColor = UIColor(white: 0, alpha: 0.5) 
     dimmingView.alpha = 0 
     return dimmingView 
    }() 

    // MARK: UIPresentationController 

    override func presentationTransitionWillBegin() { 
     guard let containerView = containerView, 
      let presentedView = presentedView else { return } 

     dimmingView.frame = containerView.bounds 
     containerView.addSubview(dimmingView) 
     containerView.addSubview(presentedView) 

     guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return } 

     transitionCoordinator.animateAlongsideTransition(in: presentingViewController.view, animation: { _ in 
      self.presentingViewController.view.transform = CGAffineTransform(scaleX: 0.94, y: 0.94) 
      if !transitionCoordinator.isInteractive { 
       (self.presentingViewController as? ViewController)?.statusBarStyle = .lightContent 
      } 
     }) 

     transitionCoordinator.animate(alongsideTransition: { _ in 
      self.dimmingView.alpha = 1.0 
     }) 
    } 

    override func presentationTransitionDidEnd(_ completed: Bool) { 
     if !completed { 
      dimmingView.removeFromSuperview() 
     } 

     if completed { 
      (presentingViewController as? ViewController)?.statusBarStyle = .lightContent 
     } 
    } 

    override func dismissalTransitionWillBegin() { 
     guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return } 
     transitionCoordinator.animate(alongsideTransition: { _ in 
      self.dimmingView.alpha = 0 
     }) 

     transitionCoordinator.animateAlongsideTransition(in: presentingViewController.view, animation: { _ in 
      self.presentingViewController.view.transform = CGAffineTransform.identity 
      if !transitionCoordinator.isInteractive { 
       (self.presentingViewController as? ViewController)?.statusBarStyle = .default 
      } 
     }) 
    } 

    override func dismissalTransitionDidEnd(_ completed: Bool) { 
     guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return } 
     if transitionCoordinator.isCancelled { 
      return 
     } 

     if completed { 
      dimmingView.removeFromSuperview() 
      (presentingViewController as? ViewController)?.statusBarStyle = .default 
     } 
    } 

    override var frameOfPresentedViewInContainerView: CGRect { 
     guard let containerView = containerView else { return .zero } 
     var frame = containerView.bounds 
     frame.size.height -= 40 
     frame.origin.y += 40 
     return frame 
    } 

    // MARK: UIViewController 

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 
     super.viewWillTransition(to: size, with: coordinator) 

     guard let containerView = containerView else { return } 
     coordinator.animate(alongsideTransition: { _ in 
      self.dimmingView.frame = containerView.bounds 
     }) 
    } 
} 

Tiếp theo chúng ta cần một đối tượng sẽ làm hoạt ảnh hai màn hình:

import UIKit 

class AnimationController: NSObject, UIViewControllerAnimatedTransitioning { 
    enum Direction { 
     case present 
     case dismiss 
    } 

    private let direction: Direction 

    init(direction: Direction) { 
     self.direction = direction 
     super.init() 
    } 

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 
     return 0.3 
    } 

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 
     let animator = interruptibleAnimator(using: transitionContext) 
     animator.startAnimation() 
    } 

    func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { 
     let duration = transitionDuration(using: transitionContext) 
     let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) 
     let containerView = transitionContext.containerView 
     let containerFrame = containerView.frame 

     switch direction { 
     case .present: 
      guard let toViewController = transitionContext.viewController(forKey: .to), 
       let toView = transitionContext.view(forKey: .to) 
       else { fatalError() } 

      var toViewStartFrame = transitionContext.initialFrame(for: toViewController) 
      let toViewFinalFrame = transitionContext.finalFrame(for: toViewController) 
      toViewStartFrame = toViewFinalFrame 
      toViewStartFrame.origin.y = containerFrame.size.height - 44 
      toView.frame = toViewStartFrame 
      animator.addAnimations { 
       toView.frame = toViewFinalFrame 
      } 
     case .dismiss: 
      guard let fromViewController = transitionContext.viewController(forKey: .from), 
       let fromView = transitionContext.view(forKey: .from) 
       else { fatalError() } 

      var fromViewFinalFrame = transitionContext.finalFrame(for: fromViewController) 
      fromViewFinalFrame.origin.y = containerFrame.size.height - 44 
      animator.addAnimations { 
       fromView.frame = fromViewFinalFrame 
      } 
     } 

     animator.addCompletion { finish in 
      if finish == .end { 
       transitionContext.finishInteractiveTransition() 
       transitionContext.completeTransition(true) 
      } else { 
       transitionContext.cancelInteractiveTransition() 
       transitionContext.completeTransition(false) 
      } 
     } 

     return animator 
    } 
} 

Cuối cùng móc tất cả lại với nhau trong bộ điều khiển xem, thêm công cụ nhận dạng cử chỉ để kiểm soát quá trình chuyển đổi tương tác.

import UIKit 

class ViewController: UIViewController, UIViewControllerTransitioningDelegate { 
    var statusBarStyle: UIStatusBarStyle = .default { 
     didSet { 
      setNeedsStatusBarAppearanceUpdate() 
     } 
    } 

    override var preferredStatusBarStyle: UIStatusBarStyle { 
     return statusBarStyle 
    } 

    private var interactionController: UIPercentDrivenInteractiveTransition? 

    override func viewDidLoad() { 
     super.viewDidLoad() 
     view.backgroundColor = .white 

     let cardView = UIView(frame: .zero) 
     cardView.translatesAutoresizingMaskIntoConstraints = false 
     cardView.backgroundColor = UIColor(red:0.976, green:0.976, blue:0.976, alpha:1) 
     view.addSubview(cardView) 

     let borderView = UIView(frame: .zero) 
     borderView.translatesAutoresizingMaskIntoConstraints = false 
     borderView.backgroundColor = UIColor(red:0.697, green:0.698, blue:0.697, alpha:1) 
     view.addSubview(borderView) 

     let cardViewTextLabel = UILabel(frame: .zero) 
     cardViewTextLabel.translatesAutoresizingMaskIntoConstraints = false 
     cardViewTextLabel.text = "Tap or drag" 
     cardViewTextLabel.font = UIFont.boldSystemFont(ofSize: 16) 
     view.addSubview(cardViewTextLabel) 

     let cardViewConstraints = [ 
      cardView.heightAnchor.constraint(equalToConstant: 44), 
      cardView.leadingAnchor.constraint(equalTo: view.leadingAnchor), 
      cardView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 
      cardView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 
      borderView.heightAnchor.constraint(equalToConstant: 0.5), 
      borderView.topAnchor.constraint(equalTo: cardView.topAnchor), 
      borderView.leadingAnchor.constraint(equalTo: cardView.leadingAnchor), 
      borderView.trailingAnchor.constraint(equalTo: cardView.trailingAnchor), 
      cardViewTextLabel.centerXAnchor.constraint(equalTo: cardView.centerXAnchor), 
      cardViewTextLabel.centerYAnchor.constraint(equalTo: cardView.centerYAnchor) 
     ] 
     NSLayoutConstraint.activate(cardViewConstraints) 

     let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handlePresentTapGesture(gestureRecognizer:))) 
     cardView.addGestureRecognizer(tapGestureRecognizer) 

     let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePresentPanGesture(gestureRecognizer:))) 
     cardView.addGestureRecognizer(panGestureRecognizer) 
    } 

    // MARK: Actions 

    @objc private func handlePresentTapGesture(gestureRecognizer: UITapGestureRecognizer) { 
     let viewController = createViewController() 
     present(viewController, animated: true, completion: nil) 
    } 

    @objc private func handlePresentPanGesture(gestureRecognizer: UIPanGestureRecognizer) { 
     let translation = gestureRecognizer.translation(in: gestureRecognizer.view?.superview) 
     let height = (gestureRecognizer.view?.superview?.bounds.height)! - 40 
     let percentage = abs(translation.y/height) 
     switch gestureRecognizer.state { 
     case .began: 
      interactionController = UIPercentDrivenInteractiveTransition() 
      let viewController = createViewController() 
      present(viewController, animated: true, completion: nil) 
     case .changed: 
      interactionController?.update(percentage) 
     case .ended: 
      if percentage < 0.5 { 
       interactionController?.cancel() 
      } else { 
       interactionController?.finish() 
      } 
      interactionController = nil 
     default: break 
     } 
    } 

    @objc private func handleDismissTapGesture(gestureRecognizer: UITapGestureRecognizer) { 
     dismiss(animated: true, completion: nil) 
    } 

    @objc private func handleDismissPanGesture(gestureRecognizer: UIPanGestureRecognizer) { 
     let translation = gestureRecognizer.translation(in: gestureRecognizer.view) 
     let height = (gestureRecognizer.view?.bounds.height)! 
     let percentage = (translation.y/height) 
     switch gestureRecognizer.state { 
     case .began: 
      interactionController = UIPercentDrivenInteractiveTransition() 
      dismiss(animated: true, completion: nil) 
     case .changed: 
      interactionController?.update(percentage) 
     case .ended: 
      if percentage < 0.5 { 
       interactionController?.cancel() 
      } else { 
       interactionController?.finish() 
      } 
      interactionController = nil 
     default: break 
     } 
    } 

    // MARK: UIViewControllerTransitioningDelegate 

    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { 
     return PresentationController(presentedViewController: presented, presenting: presenting) 
    } 

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 
     // Get UIKit to animate if it's not an interative animation 
     return interactionController != nil ? AnimationController(direction: .present) : nil 
    } 

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 
     // Get UIKit to animate if it's not an interative animation 
     return interactionController != nil ? AnimationController(direction: .dismiss) : nil 
    } 

    func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 
     return interactionController 
    } 

    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 
     return interactionController 
    } 

    // MARK: Private 

    func createViewController() -> UIViewController { 
     let viewController = UIViewController(nibName: nil, bundle: nil) 
     viewController.title = "Tap or drag" 
     viewController.view.backgroundColor = .white 
     let navigationController = UINavigationController(rootViewController: viewController) 
     navigationController.transitioningDelegate = self 
     navigationController.modalPresentationStyle = .custom 
     UINavigationBar.appearance().titleTextAttributes = [NSFontAttributeName: UIFont.boldSystemFont(ofSize: 16)] 

     let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDismissTapGesture(gestureRecognizer:))) 
     navigationController.view.addGestureRecognizer(tapGestureRecognizer) 

     let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleDismissPanGesture(gestureRecognizer:))) 
     navigationController.view.addGestureRecognizer(panGestureRecognizer) 

     return navigationController 
    } 
} 
+0

Tuyệt đối rực rỡ. Cảm ơn bạn! – Boletrone

+0

@peteog hiệu ứng trình bày thẻ không hoạt ảnh khi thực hiện chuyển đổi tương tác, mặc dù phương thức cập nhật được gọi là thích hợp. Có suy nghĩ gì không? – Stefan

+0

@Stefan điều này dường như bị hỏng trong iOS 11. Apple cho thấy một kỹ thuật khác trong WWDC 2017 Phiên 230: Ảnh động nâng cao với UIKit; Tôi đã có một bản demo nhanh ở đây: https://github.com/peteog/CardUI – peteog

Các vấn đề liên quan