UIView Hide / Show con animación

154

Mi objetivo simple es desvanecer las funciones de ocultar y mostrar animadas.

Button.hidden = YES;

Suficientemente simple. Sin embargo, ¿es posible que desaparezca en lugar de desaparecer? Parece bastante poco profesional de esa manera.

JTApps
fuente

Respuestas:

259

En iOS 4 y versiones posteriores, hay una manera de hacer esto simplemente usando el método de transición UIView sin necesidad de importar QuartzCore. Solo puedes decir:

C objetivo

[UIView transitionWithView:button
                  duration:0.4
                   options:UIViewAnimationOptionTransitionCrossDissolve
                animations:^{
                     button.hidden = YES;
                }
                completion:NULL];

Rápido

UIView.transition(with: button, duration: 0.4, 
                  options: .transitionCrossDissolve, 
                  animations: {
                 button.hidden = false
              })

Solución anterior

La solución de Michail funcionará, pero en realidad no es el mejor enfoque.

El problema con el desvanecimiento alfa es que a veces las diferentes capas de vista superpuestas se ven extrañas a medida que se desvanecen. Hay algunas otras alternativas que usan Core Animation. Primero incluya el marco QuartzCore en su aplicación y agréguelo #import <QuartzCore/QuartzCore.h>a su encabezado. Ahora puede hacer uno de los siguientes:

1) establezca button.layer.shouldRasterize = YES;y luego use el código de animación alfa que Michail proporcionó en su respuesta. Esto evitará que las capas se mezclen extrañamente, pero tiene una ligera penalización de rendimiento y puede hacer que el botón se vea borroso si no está alineado exactamente en un límite de píxeles.

Alternativamente:

2) Use el siguiente código para animar el desvanecimiento en su lugar:

CATransition *animation = [CATransition animation];
animation.type = kCATransitionFade;
animation.duration = 0.4;
[button.layer addAnimation:animation forKey:nil];

button.hidden = YES;

Lo bueno de este enfoque es que puede hacer un fundido cruzado de cualquier propiedad del botón, incluso si no son animables (por ejemplo, el texto o la imagen del botón), simplemente configure la transición y luego configure sus propiedades inmediatamente después.

Nick Lockwood
fuente
55
@robmathers, acabo de probar su código, por encima de dos códigos solo funcionan cuando button.hidden = NO, para desvanecerse en la situación; no tiene efecto de animación para desaparecer cuando button.hidden = YES;
Jason
Parece estar roto en iOS 12.0
user3532505
55
Debe usar la vista general de lo que está animando como transitionWithViewparámetro para garantizar un fundido de entrada y salida exitoso.
Allen
159

Las propiedades animadas de UIView son:

- frame
- bounds
- center
- transform
- alpha
- backgroundColor
- contentStretch

Describa en: animaciones

isHidden no es uno de ellos, así que, según veo, la mejor manera es:

Swift 4:

func setView(view: UIView, hidden: Bool) {
    UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
        view.isHidden = hidden
    })
}

C objetivo:

- (void)setView:(UIView*)view hidden:(BOOL)hidden {
    [UIView transitionWithView:view duration:0.5 options:UIViewAnimationOptionTransitionCrossDissolve animations:^(void){
        [view setHidden:hidden];
    } completion:nil];
}
evya
fuente
8
En realidad, esta es la mejor y más simple respuesta
Irshad Mohamed
3
Mientras esto se anima correctamente, la UISearchBar que estoy tratando de mostrar aparece en la ubicación incorrecta hasta que se completa la animación y luego salta instantáneamente a la posición correcta. ¿Alguna idea? Estoy usando Storyboards con Interface Builder y Restricciones.
Greg Hilston
55
Este código no funciona ... cambia directamente el estado sin animación
Mihir Mehta
2
@evya Solo funciona para desvanecerse cuando está oculto = NO , No funciona para desvanecerse, oculto = SÍ
Jason
Increíble. 10 veces más
Vyacheslav
125

Para desvanecerse:

C objetivo

[UIView animateWithDuration:0.3 animations:^{
    button.alpha = 0;
} completion: ^(BOOL finished) {//creates a variable (BOOL) called "finished" that is set to *YES* when animation IS completed.
    button.hidden = finished;//if animation is finished ("finished" == *YES*), then hidden = "finished" ... (aka hidden = *YES*)
}];

Swift 2

UIView.animateWithDuration(0.3, animations: {
    button.alpha = 0
}) { (finished) in
    button.hidden = finished
}

Swift 3, 4, 5

UIView.animate(withDuration: 0.3, animations: {
    button.alpha = 0
}) { (finished) in
    button.isHidden = finished
}

Para desvanecerse en:

C objetivo

button.alpha = 0;
button.hidden = NO;
[UIView animateWithDuration:0.3 animations:^{
    button.alpha = 1;
}];

Swift 2

button.alpha = 0
button.hidden = false
UIView.animateWithDuration(0.3) {
    button.alpha = 1
}

Swift 3, 4, 5

button.alpha = 0
button.isHidden = false
UIView.animate(withDuration: 0.3) {
    button.alpha = 1
}
Mikhail Grebionkin
fuente
usando el fundido de entrada / salida en conjunto con el estado oculto resuelto mi problema
Aclima
Por alguna razón, animar a hidden = YES funcionó bien para mí, pero animar a hidden = NO no hizo nada, por lo que esta combinación de animar el alfa y configurar la propiedad oculta fue útil.
arlomedia
Solo escribo una demostración, pero solo oculto = NO, desvanecimiento en las obras, extraño
Jason
9

Yo uso esta pequeña extensión de Swift 3 :

extension UIView {

  func fadeIn(duration: TimeInterval = 0.5,
              delay: TimeInterval = 0.0,
              completion: @escaping ((Bool) -> Void) = {(finished: Bool) -> Void in }) {
    UIView.animate(withDuration: duration,
                   delay: delay,
                   options: UIViewAnimationOptions.curveEaseIn,
                   animations: {
      self.alpha = 1.0
    }, completion: completion)
  }

  func fadeOut(duration: TimeInterval = 0.5,
               delay: TimeInterval = 0.0,
               completion: @escaping (Bool) -> Void = {(finished: Bool) -> Void in }) {
    UIView.animate(withDuration: duration,
                   delay: delay,
                   options: UIViewAnimationOptions.curveEaseIn,
                   animations: {
      self.alpha = 0.0
    }, completion: completion)
  }
}
Mark Mckelvie
fuente
7

Swift 3

func appearView() {
     self.myView.alpha = 0
     self.myView.isHidden = false

     UIView.animate(withDuration: 0.9, animations: {
         self.myView.alpha = 1
     }, completion: {
         finished in
         self.myView.isHidden = false
     })
}
Scaraux
fuente
7

swift 4.2

con extensión:

extension UIView {
func hideWithAnimation(hidden: Bool) {
        UIView.transition(with: self, duration: 0.5, options: .transitionCrossDissolve, animations: {
            self.isHidden = hidden
        })
    }
}

método simple:

func setView(view: UIView, hidden: Bool) {
    UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
        view.isHidden = hidden
    })
}
Mohsen mokhtari
fuente
¿Cómo puedo agregar retraso para este?
cvdogan
7

Use esta solución para obtener efectos suaves de fadeOut y fadeIn

extension UIView {
    func fadeIn(duration: TimeInterval = 0.5, delay: TimeInterval = 0.0, completion: @escaping ((Bool) -> Void) = {(finished: Bool) -> Void in }) {
        self.alpha = 0.0

        UIView.animate(withDuration: duration, delay: delay, options: UIView.AnimationOptions.curveEaseIn, animations: {
            self.isHidden = false
            self.alpha = 1.0
        }, completion: completion)
    }

    func fadeOut(duration: TimeInterval = 0.5, delay: TimeInterval = 0.0, completion: @escaping (Bool) -> Void = {(finished: Bool) -> Void in }) {
        self.alpha = 1.0

        UIView.animate(withDuration: duration, delay: delay, options: UIView.AnimationOptions.curveEaseOut, animations: {
            self.isHidden = true
            self.alpha = 0.0
        }, completion: completion)
    }
}

el uso es como

uielement.fadeIn()
uielement.fadeOut()

Gracias

Dhanu K
fuente
fadeOutfunciona en iOS 13 solo si elimino las líneas que establecen self.isHidden.
Mike Taverne
6

He creado una categoría para UIVieweste fin y puso en práctica un poco especial poco diferente concepto: visibility. La principal diferencia de mi solución es que puede llamar [view setVisible:NO animated:YES]y justo después de eso verificar sincrónicamente [view visible]y obtener el resultado correcto. Esto es bastante simple pero extremadamente útil.

Además, se permite evitar el uso de "lógica booleana negativa" (consulte Código completo, página 269, Usar nombres de variable booleana positiva para obtener más información).

Rápido

UIView+Visibility.swift

import UIKit


private let UIViewVisibilityShowAnimationKey = "UIViewVisibilityShowAnimationKey"
private let UIViewVisibilityHideAnimationKey = "UIViewVisibilityHideAnimationKey"


private class UIViewAnimationDelegate: NSObject {
    weak var view: UIView?

    dynamic override func animationDidStop(animation: CAAnimation, finished: Bool) {
        guard let view = self.view where finished else {
            return
        }

        view.hidden = !view.visible
        view.removeVisibilityAnimations()
    }
}


extension UIView {

    private func removeVisibilityAnimations() {
        self.layer.removeAnimationForKey(UIViewVisibilityShowAnimationKey)
        self.layer.removeAnimationForKey(UIViewVisibilityHideAnimationKey)
    }

    var visible: Bool {
        get {
            return !self.hidden && self.layer.animationForKey(UIViewVisibilityHideAnimationKey) == nil
        }

        set {
            let visible = newValue

            guard self.visible != visible else {
                return
            }

            let animated = UIView.areAnimationsEnabled()

            self.removeVisibilityAnimations()

            guard animated else {
                self.hidden = !visible
                return
            }

            self.hidden = false

            let delegate = UIViewAnimationDelegate()
            delegate.view = self

            let animation = CABasicAnimation(keyPath: "opacity")
            animation.fromValue = visible ? 0.0 : 1.0
            animation.toValue = visible ? 1.0 : 0.0
            animation.fillMode = kCAFillModeForwards
            animation.removedOnCompletion = false
            animation.delegate = delegate

            self.layer.addAnimation(animation, forKey: visible ? UIViewVisibilityShowAnimationKey : UIViewVisibilityHideAnimationKey)
        }
    }

    func setVisible(visible: Bool, animated: Bool) {
        let wereAnimationsEnabled = UIView.areAnimationsEnabled()

        if wereAnimationsEnabled != animated {
            UIView.setAnimationsEnabled(animated)
            defer { UIView.setAnimationsEnabled(!animated) }
        }

        self.visible = visible
    }

}

C objetivo

UIView+Visibility.h

#import <UIKit/UIKit.h>

@interface UIView (Visibility)

- (BOOL)visible;
- (void)setVisible:(BOOL)visible;
- (void)setVisible:(BOOL)visible animated:(BOOL)animated;

@end

UIView+Visibility.m

#import "UIView+Visibility.h"

NSString *const UIViewVisibilityAnimationKeyShow = @"UIViewVisibilityAnimationKeyShow";
NSString *const UIViewVisibilityAnimationKeyHide = @"UIViewVisibilityAnimationKeyHide";

@implementation UIView (Visibility)

- (BOOL)visible
{
    if (self.hidden || [self.layer animationForKey:UIViewVisibilityAnimationKeyHide]) {
        return NO;
    }

    return YES;
}

- (void)setVisible:(BOOL)visible
{
    [self setVisible:visible animated:NO];
}

- (void)setVisible:(BOOL)visible animated:(BOOL)animated
{
    if (self.visible == visible) {
        return;
    }

    [self.layer removeAnimationForKey:UIViewVisibilityAnimationKeyShow];
    [self.layer removeAnimationForKey:UIViewVisibilityAnimationKeyHide];

    if (!animated) {
        self.alpha = 1.f;
        self.hidden = !visible;
        return;
    }

    self.hidden = NO;

    CGFloat fromAlpha = visible ? 0.f : 1.f;
    CGFloat toAlpha = visible ? 1.f : 0.f;
    NSString *animationKey = visible ? UIViewVisibilityAnimationKeyShow : UIViewVisibilityAnimationKeyHide;

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.duration = 0.25;
    animation.fromValue = @(fromAlpha);
    animation.toValue = @(toAlpha);
    animation.delegate = self;
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    [self.layer addAnimation:animation forKey:animationKey];
}

#pragma mark - CAAnimationDelegate

- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
{
    if ([[self.layer animationForKey:UIViewVisibilityAnimationKeyHide] isEqual:animation]) {
        self.hidden = YES;
    }
}

@end
Valentin Shergin
fuente
@valentin shergin ¿Viene la versión Swift?
Juan Boero
¡Por supuesto! He agregado la versión Swift.
Valentin Shergin
5

El código de @Umair Afzal funciona bien en Swift 5 después de algunos cambios

 extension UIView {

func fadeIn(duration: TimeInterval = 0.5, delay: TimeInterval = 0.0, completion: @escaping ((Bool) -> Void) = {(finished: Bool) -> Void in }) {
    self.alpha = 0.0

    UIView.animate(withDuration: duration, delay: delay, options: UIView.AnimationOptions.curveEaseIn, animations: {
        self.isHidden = false
        self.alpha = 1.0
    }, completion: completion)
}

func fadeOut(duration: TimeInterval = 0.5, delay: TimeInterval = 0.0, completion: @escaping (Bool) -> Void = {(finished: Bool) -> Void in }) {
    self.alpha = 1.0

    UIView.animate(withDuration: duration, delay: delay, options: UIView.AnimationOptions.curveEaseIn, animations: {
        self.alpha = 0.0
    }) { (completed) in
        self.isHidden = true
        completion(true)
    }
  }
}

para usar

yourView.fadeOut()
yourView.fadeIn()
Sanjay Mishra
fuente
dando un efecto duro mientras se desvanece, agregó una solución mejor aquí
Dhanu K
4

Swift 4

extension UIView {

func fadeIn(duration: TimeInterval = 0.5, delay: TimeInterval = 0.0, completion: @escaping ((Bool) -> Void) = {(finished: Bool) -> Void in }) {
    self.alpha = 0.0

    UIView.animate(withDuration: duration, delay: delay, options: UIViewAnimationOptions.curveEaseIn, animations: {
        self.isHidden = false
        self.alpha = 1.0
    }, completion: completion)
}

func fadeOut(duration: TimeInterval = 0.5, delay: TimeInterval = 0.0, completion: @escaping (Bool) -> Void = {(finished: Bool) -> Void in }) {
    self.alpha = 1.0

    UIView.animate(withDuration: duration, delay: delay, options: UIViewAnimationOptions.curveEaseIn, animations: {
        self.alpha = 0.0
    }) { (completed) in
        self.isHidden = true
        completion(true)
    }
}
}

Y para usarlo, simplemente llame a estas funciones como:

yourView.fadeOut() // this will hide your view with animation
yourView.fadeIn() /// this will show your view with animation
Umair Afzal
fuente
Acaba de copiar la respuesta de @ MarkMckelvie
Ashley Mills
Hay una diferencia, él no estaba ocultando la vista. Y también necesitaba ocultar la vista. Así lo hizo y compartirlo.
Umair Afzal
3
¿Por qué no comentar la otra respuesta en lugar de copiarla y hacerla pasar por la suya?
Ashley Mills
2

isHidden es un valor inmediato y no puede afectar una animación en él, en lugar de esto puede usar Alpha para ocultar su vista

UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
        view.alpha = 0
    })

Y por mostrar:

UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
      view.alpha = 1
})
Mohsen
fuente
1

Puedes hacerlo MUY fácilmente usando la biblioteca Animatics :

//To hide button:
AlphaAnimator(0) ~> button

//to show button
AlphaAnimator(1) ~> button
Nikita Arkhipov
fuente
1
func flipViews(fromView: UIView, toView: UIView) {

    toView.frame.origin.y = 0

    self.view.isUserInteractionEnabled = false

    UIView.transition(from: fromView, to: toView, duration: 0.5, options: .transitionFlipFromLeft, completion: { finished in            

        fromView.frame.origin.y = -900

        self.view.isUserInteractionEnabled = true

    })


}
Vimal Saifudin
fuente
1

Puedes probar esto.

 func showView(objView:UIView){

    objView.alpha = 0.0
    UIView.animate(withDuration: 0.5, animations: {
        objView.alpha = 0.0
    }, completion: { (completeFadein: Bool) -> Void in
        objView.alpha = 1.0
        let transition = CATransition()
        transition.duration = 0.5
        transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        transition.type = kCATransitionFade
        objView.layer.add(transition, forKey: nil)
    })
}

func HideView(objView:UIView){

    UIView.animate(withDuration: 0.5, animations: {
        objView.alpha = 1.0
    }, completion: { (completeFadein: Bool) -> Void in
        objView.alpha = 0.0
        let transition = CATransition()
        transition.duration = 0.5
        transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        transition.type = kCATransitionFade
        objView.layer.add(transition, forKey: nil)
    })
}

Y pasa tu nombre de vista

        showView(objView: self.viewSaveCard)
        HideView(objView: self.viewSaveCard)
kalpesh
fuente
1

Si su vista está configurada como oculta de manera predeterminada o si cambia el estado Oculto, lo que creo que debería hacer en muchos casos, ninguno de los enfoques en esta página le proporcionará animación FadeIn / FadeOut, solo animará uno de estos estados, la razón es que está configurando el estado Oculto en falso antes de llamar al método UIView.animate , lo que causará una visibilidad repentina y si solo anima el alfa, el espacio del objeto todavía está allí, pero no es visible, lo que provocará algunos problemas de IU.

Entonces, el mejor enfoque es verificar primero si la vista está oculta y luego establecer el alfa en 0.0, de esta manera cuando establece el estado Oculto en falso, no verá una visibilidad repentina.

func hideViewWithFade(_ view: UIView) {
    if view.isHidden {
        view.alpha = 0.0
    }

    view.isHidden = false

    UIView.animate(withDuration: 0.3, delay: 0.0, options: .transitionCrossDissolve, animations: {
        view.alpha = view.alpha == 1.0 ? 0.0 : 1.0
    }, completion: { _ in
        view.isHidden = !Bool(truncating: view.alpha as NSNumber)
    })
}
Shahriar
fuente
Esto resuelve el problema que otros han preguntado sobre dónde no estaban funcionando los fadeins. Enfoque inteligente.
BooTooMany
1

UIView.transition (con :) la función es agradable y ordenada.

Muchos lo han publicado, pero ninguno se ha dado cuenta de que existe una falla que se mostrará solo cuando lo ejecute.

Puede pasar la propiedad oculta a verdadero perfectamente, mientras que cuando intente hacer la transición a falso, la vista simplemente desaparecerá repentinamente sin ninguna animación.

Esto se debe a que esta API solo funciona dentro de una vista, lo que significa que cuando hace la transición de una vista para mostrar, de hecho, se muestra de inmediato, solo su contenido se anima gradualmente.

Cuando intenta ocultar esta vista, se oculta de inmediato, hace que la animación de su contenido no tenga sentido.

Para resolver esto, al ocultar una vista, el objetivo de transición debe ser su vista principal en lugar de la vista que desea ocultar.

func transitionView(_ view: UIView?, show: Bool, completion: BoolFunc? = nil) {
    guard let view = view, view.isHidden == show, let parent = view.superview else { return }

    let target: UIView = show ? view : parent
    UIView.transition(with: target, duration: 0.4, options: [.transitionCrossDissolve], animations: {
        view.isHidden = !show
    }, completion: completion)
}
LiLi Kazine
fuente
0

Mi solución para Swift 3 . Entonces, creé la función, que oculta / muestra la vista en el orden correcto (cuando se oculta, establece alfa en 0 y luego se oculta en verdadero; oculta: primero revela la vista y luego establece su alfa en 1):

func hide(_ hide: Bool) {
    let animations = hide ? { self.alpha = 0 } :
                            { self.isHidden = false }
    let completion: (Bool) -> Void = hide ? { _ in self.isHidden = true } :
                                            { _ in UIView.animate(withDuration: duration, animations: { self.alpha = 1 }) }
    UIView.animate(withDuration: duration, animations: animations, completion: completion)
}
Nazariy Vlizlo
fuente
¿Por qué en el completionbloque hay otra animación cuando hidees falsa?
Giorgio
0

Transición Swift 4

    UIView.transition(with: view, duration: 3, options: .transitionCurlDown,
                      animations: {
                        // Animations
                        view.isHidden = hidden
    },
                      completion: { finished in
                        // Compeleted
    })

Si utiliza el enfoque para versiones rápidas anteriores, obtendrá un error:

Cannot convert value of type '(_) -> ()' to expected argument type '(() -> Void)?'

Útil de referencia .

nanospeck
fuente
funciona esto con autolayout? código similar no está animando. el isHiddenvalor se representa instantáneamente (es decir, ocultando / mostrando instantáneamente la vista).
Crashalot
0

Este código da una animación como presionar viewController en un controlador de navegación ...

CATransition *animation = [CATransition animation];
 animation.type = kCATransitionPush;
 animation.subtype = kCATransitionFromRight;
 animation.duration = 0.3;
 [_viewAccountName.layer addAnimation:animation forKey:nil];

 _viewAccountName.hidden = true;

Lo usé para la animación pop ...

 CATransition *animation = [CATransition animation];
 animation.type = kCATransitionPush;
 animation.subtype = kCATransitionFromLeft;
 animation.duration = 0.3;
 [_viewAccountName.layer addAnimation:animation forKey:nil];

 _viewAccountName.hidden = false;
Prasanna
fuente
0

Intenté algunas de las respuestas salidas, algunas solo funcionan para una situación, algunas de ellas necesitan agregar dos funciones.

Opción 1

Nada que ver con view.isHidden.

extension UIView {
    func animate(fadeIn: Bool, withDuration: TimeInterval = 1.0) {
        UIView.animate(withDuration: withDuration, delay: 0.0, options: .curveEaseInOut, animations: {
            self.alpha = fadeIn ? 1.0 : 0.0
        })
    }
}

Luego pasa isFadeIn( trueo false)

view.animate(fadeIn: isFadeIn) 

opcion 2

No pase ningún parámetro. Se desvanece dentro o fuera de acuerdo con isUserInteractionEnabled. Esto también se adapta muy bien a la situación animada de un lado a otro.

func animateFadeInOut(withDuration: TimeInterval = 1.0) {
    self.isUserInteractionEnabled = !self.isUserInteractionEnabled
    UIView.animate(withDuration: withDuration, delay: 0.0, options: .curveEaseInOut, animations: {
        self.alpha = self.isUserInteractionEnabled ? 1.0 : 0.0
    })
}

Entonces llamas

yourView.animateFadeInOut()

¿Por qué self.isUserInteractionEnabled?

Intentado sustituir self.isUserInteractionEnabledpor self.isHidden, sin suerte en absoluto.

Eso es. Me costó alguna vez, espero que ayude a alguien.

William Hu
fuente