tutorial que framework ios iphone swift cocoa-touch uikit

framework - ¿Existe una API pública para la interfaz de usuario de visualización de tarjeta que se puede ver en iOS 10?



que es uikit ios (3)

Ok, intentaré darle una solución compacta con un mínimo de código.

Solución rápida. modalPresentationStyle presentar un controlador de manera modal con modalPresentationStyle - property establecida en .overCurrentContext . Puede establecer el valor antes de preset(controller:...) -method get call o en prepare(for:...) -one si se trata de una transición segue. Para deslizar hacia arriba use modalTransitionStyle establecido en .coverVertical .

Para "alejar" la vista de origen, simplemente actualice sus límites en la viewWill(Diss)appear métodos viewWill(Diss)appear . En la mayoría de los casos esto funcionará.

No olvide configurar la vista de fondo de su controlador modal de forma transparente para que la vista subyacente siga siendo visible.

Deslizándose hacia arriba / abajo suavemente. Es necesario configurar una transition entre los controladores de una manera adecuada. Si te fijas más en la aplicación de música de Apple, verás una forma de ocultar el controlador superior con un gesto deslizante hacia abajo. Puede personalizar su vista (des) apariencia también. Echa un vistazo a este artículo . Utiliza UIKit métodos UIKit . Desafortunadamente, esta forma requiere mucho código, pero puede usar bibliotecas de terceros para configurar las transiciones. Como este

La aplicación de música en iOS 10 adopta una nueva apariencia de tarjeta: la pantalla Reproduciendo ahora se desliza hacia arriba, mientras que la vista de abajo en la jerarquía se aleja, sobresaliendo ligeramente en la parte superior de la pantalla.

Aquí está el ejemplo de la ventana de redacción de Mail:

Esta metáfora también se puede ver en Overcast, el popular reproductor de podcast:

¿Hay una función en UIKit para lograr esta apariencia de tarjeta?


Actualización: la parte interactiva de esta demostración no funciona en iOS 11 por alguna razón. Apple muestra una técnica diferente en WWDC 2017 Sesión 230: Animaciones avanzadas con UIKit donde utilizan UIViewPropertyAnimator

Tengo una demostración básica de esta técnica aquí: https://github.com/peteog/CardUI

Este tipo de interfaz de usuario se puede crear utilizando transiciones UIViewController personalizadas , UIPresentationController y UIViewPropertyAnimator .

Aplicación de ejemplo: https://github.com/peteog/CardUIExample

Primero crea una subclase UIPresentationController . Esta voluntad:

  • Añadir una vista de atenuación
  • Transforme el controlador de vista de presentación para insertarlo desde la barra de estado
  • Establece el marco de la vista presentada para hacer el efecto de la carta.

Código:

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

A continuación necesitamos un objeto que hará la animación entre las dos pantallas:

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

Finalmente, enganche todo junto en el controlador de vista, agregando reconocedores de gestos para controlar la transición interactiva.

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


Puedes construir el segue en el constructor de interfaces. Seleccionando el modo modal de ViewController a CardViewController .

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

a continuación, agregue esta extensión:

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

Finalmente, necesitarás 2 clases más:

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

y:

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

Finalmente, para animar el cierre de CardViewController , enganche su botón de cierre a FirstResponder seleccione dismiss y agregue este método a ViewController :

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