Ocultar / Mostrar UIView com animação

154

Meu objetivo simples é desvanecer animadas funções de ocultação e exibição.

Button.hidden = YES;

Simples o suficiente. No entanto, é possível que ele desapareça em vez de apenas desaparecer? Parece pouco profissional dessa maneira.

JTApps
fonte

Respostas:

259

No iOS 4 e posterior, há uma maneira de fazer isso usando o método de transição UIView sem precisar importar o QuartzCore. Você pode apenas dizer:

Objetivo C

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

Solução anterior

A solução de Michail funcionará, mas na verdade não é a melhor abordagem.

O problema do desbotamento alfa é que, às vezes, as diferentes camadas de exibição sobrepostas parecem estranhas à medida que desaparecem. Existem outras alternativas usando o Core Animation. Primeiro, inclua a estrutura QuartzCore no seu aplicativo e adicione#import <QuartzCore/QuartzCore.h> ao seu cabeçalho. Agora você pode executar um dos seguintes procedimentos:

1) conjunto button.layer.shouldRasterize = YES; e use o código de animação alfa que Michail forneceu em sua resposta. Isso impedirá que as camadas se misturem estranhamente, mas tem uma pequena penalidade de desempenho e pode fazer com que o botão fique embaçado se não estiver alinhado exatamente no limite de um pixel.

Alternativamente:

2) Use o seguinte código para animar o desbotamento:

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

button.hidden = YES;

O ponto positivo dessa abordagem é que você pode fazer o crossfade de qualquer propriedade do botão, mesmo que eles não sejam animáveis ​​(por exemplo, o texto ou a imagem do botão), basta configurar a transição e definir suas propriedades imediatamente depois.

Nick Lockwood
fonte
5
@robmathers, acabei de testar seu código, acima de dois códigos funcionam quando button.hidden = NO, para desaparecer na situação; não tem efeito de animação para desaparecer quando button.hidden = YES;
Jason
Parece estar quebrado no iOS 12.0
user3532505
5
Você deve usar a super visão geral do que você está animando como transitionWithViewparâmetro para garantir o sucesso do desvanecimento.
AllenH
159

As propriedades animadas do UIView são:

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

Descrever em: Animações

isHidden não é um deles, então a meu ver a melhor maneira é:

Swift 4:

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

Objetivo C:

- (void)setView:(UIView*)view hidden:(BOOL)hidden {
    [UIView transitionWithView:view duration:0.5 options:UIViewAnimationOptionTransitionCrossDissolve animations:^(void){
        [view setHidden:hidden];
    } completion:nil];
}
evya
fonte
8
Na verdade, esta é a simples e melhor resposta
Irshad Mohamed
3
Enquanto isso anima corretamente, o UISearchBar que estou tentando exibir aparece no local errado até a animação ser concluída e, em seguida, salta instantaneamente para a posição correta. Qualquer ideia? Estou usando Storyboards com o Construtor de interfaces e restrições.
Greg Hilston
5
Este código não funciona ... é alterar diretamente o estado sem animação
Mihir Mehta
2
@evya Só trabalho para fade in quando escondida = Não, não é trabalho para fade out, escondido = YES
Jason
Impressionante. 10x muito
Vyacheslav
125

Para desaparecer:

Objetivo-C

[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 desaparecer:

Objetivo-C

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
fonte
usando o fade in / out em conjunto com o estado escondido resolveu o meu problema
Aclima
Por alguma razão, animar para oculto = SIM funcionou bem para mim, mas animar para oculto = NÃO não fez nada; portanto, essa combinação de animar o alfa e definir a propriedade oculta foi útil.
Arlomedia
Eu só escrever uma demo, mas apenas escondido = NO, desvanece-se em obras, estranho
Jason
9

Eu uso esta pequena extensão 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
fonte
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
fonte
7

swift 4.2

com extensão:

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

método simples:

func setView(view: UIView, hidden: Bool) {
    UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
        view.isHidden = hidden
    })
}
Mohsen mokhtari
fonte
Como posso adicionar atraso para este?
Cvogan 30/03/19
7

Use esta solução para obter efeitos suaves de fadeOut e 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)
    }
}

uso é como

uielement.fadeIn()
uielement.fadeOut()

obrigado

Dhanu K
fonte
fadeOutfunciona no iOS 13 somente se eu remover as linhas definidas self.isHidden.
Mike Taverne
6

Eu criei categoria UIViewpara esta finalidade e implementou um pouco especial mordeu conceito diferente: visibility. A principal diferença da minha solução é que você pode ligar [view setVisible:NO animated:YES]e logo depois verificar [view visible]e obter o resultado correto. Isso é bastante simples, mas extremamente útil.

Além disso, é permitido evitar o uso de "lógica booleana negativa" (consulte Código completo, página 269, Use nomes de variáveis ​​booleanas positivas para obter mais informações).

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
    }

}

Objetivo-C

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
fonte
@valentin shergin A versão Swift está chegando?
Juan Boero 15/03
Certo! Eu adicionei a versão Swift.
Valentin Shergin
5

o código de @Umair Afzal funcionando bem no swift 5 após algumas alterações

 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 uso

yourView.fadeOut()
yourView.fadeIn()
Sanjay Mishra
fonte
dando algum efeito dura enquanto fadeout, acrescentou alguma solução melhor aqui
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)
    }
}
}

E para usá-lo, chame essas funções como:

yourView.fadeOut() // this will hide your view with animation
yourView.fadeIn() /// this will show your view with animation
Umair Afzal
fonte
Você acabou de copiar a resposta de @ MarkMckelvie #
Ashley Mills
Há uma diferença, ele não estava escondendo a vista. E eu precisava esconder a vista também. Então fez isso e compartilhá-lo.
Umair Afzal
3
Por que não comentar apenas a outra resposta em vez de copiá-la e passar como sua?
21818 Ashley Mills
2

isHidden é um valor imediato e você não pode afetar uma animação, em vez disso, use Alpha para ocultar sua visualização

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

E para mostrar:

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

Você pode fazer isso MUITO facilmente usando a biblioteca do Animatics :

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

//to show button
AlphaAnimator(1) ~> button
Nikita Arkhipov
fonte
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
fonte
1

Você pode tentar isso.

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

E passe seu nome de exibição

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

Se sua visualização estiver oculta por padrão ou se você alterar o estado Oculto, o que eu acho que deveria em muitos casos, nenhuma das abordagens nesta página fornecerá a animação FadeIn / FadeOut, mas sim apenas um desses estados, o motivo é que você está definindo o estado Oculto como false antes de chamar o método UIView.animate , que causará uma visibilidade repentina e, se você animar apenas o alfa, o espaço do objeto ainda está lá, mas não é visível, o que resultará em alguns problemas da interface do usuário.

Portanto, a melhor abordagem é verificar primeiro se a visualização está oculta e, em seguida, defina o alfa como 0,0, assim, quando você definir o estado Oculto como falso, não verá uma visibilidade 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
fonte
Isso resolve o problema que outros perguntaram sobre onde os fadeins não estavam funcionando. Abordagem inteligente.
BooTooMany
1

A função UIView.transition (with :) é agradável e arrumada.

Muitos o publicaram, mas ninguém notou que existe uma falha que será exibida apenas quando você o executar.

É possível fazer a transição perfeita da propriedade oculta para true, enquanto que quando você tenta fazer a transição para false, a visualização desaparece subitamente sem nenhuma animação.

Isso ocorre porque essa API funciona apenas dentro de uma visualização, o que significa que quando você faz a transição de uma visualização para mostrar, na verdade ela mostra imediatamente, apenas o conteúdo animado gradualmente.

Quando você tenta ocultar essa exibição, ela se oculta imediatamente, torna a animação do seu conteúdo sem sentido.

Para resolver isso, ao ocultar uma visualização, o destino da transição deve ser a visualização pai, em vez da visualização que você deseja 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
fonte
0

Minha solução para o Swift 3 . Então, eu criei a função que oculta / mostra a exibição na ordem correta (ao ocultar - defina alpha como 0 e depois é Hidden para true; unhiding - revele a exibição e defina alpha como 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
fonte
Por que no completionbloco há outra animação quando hideé falsa?
Giorgio
0

Transição Swift 4

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

Se você usar a abordagem para versões rápidas mais antigas, receberá um erro:

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

Referência útil .

nanospeck
fonte
isso funciona com autolayout? código semelhante não está animado. o isHiddenvalor é renderizado instantaneamente (ou seja, ocultando / exibindo instantaneamente).
Crashalot #
0

Esse código fornece uma animação como empurrar o viewController no controlador de navegação ...

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

 _viewAccountName.hidden = true;

Usado para animação 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
fonte
0

Tentei algumas das respostas apresentadas, algumas funcionam apenas para uma situação, algumas precisam adicionar duas funções.

Opção 1

Nada a ver com isso 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
        })
    }
}

Então passe isFadeIn( trueou false)

view.animate(fadeIn: isFadeIn) 

opção 2

Não passe nenhum parâmetro. Ele desaparece ou desaparece de acordo com isUserInteractionEnabled. Isso também se adapta muito bem à situação animada.

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

Então você liga

yourView.animateFadeInOut()

Por que self.isUserInteractionEnabled?

Tentou substituir self.isUserInteractionEnabledpor self.isHidden, sem sorte.

É isso aí. Custou-me um dia, espero que ajude alguém.

William Hu
fonte