Crear una vista superpuesta borrosa

Resuelto kondratyevdev asked hace 54 años • 26 respuestas

En la aplicación Música del nuevo iOS podemos ver la portada de un álbum detrás de una vista que la difumina.

¿Cómo se puede lograr algo así? Leí la documentación, pero no encontré nada allí.

kondratyevdev avatar Jan 01 '70 08:01 kondratyevdev
Aceptado

Puedes utilizar UIVisualEffectViewpara lograr este efecto. Esta es una API nativa que ha sido ajustada para brindar rendimiento y una excelente duración de la batería, además es fácil de implementar.

Rápido:

//only apply the blur if the user hasn't disabled transparency effects
if !UIAccessibility.isReduceTransparencyEnabled {
    view.backgroundColor = .clear

    let blurEffect = UIBlurEffect(style: .dark)
    let blurEffectView = UIVisualEffectView(effect: blurEffect)
    //always fill the view
    blurEffectView.frame = self.view.bounds
    blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

    view.addSubview(blurEffectView) //if you have more UIViews, use an insertSubview API to place it where needed
} else {
    view.backgroundColor = .black
}

C objetivo:

//only apply the blur if the user hasn't disabled transparency effects
if (!UIAccessibilityIsReduceTransparencyEnabled()) {
    self.view.backgroundColor = [UIColor clearColor];

    UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
    UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
    //always fill the view
    blurEffectView.frame = self.view.bounds;
    blurEffectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [self.view addSubview:blurEffectView]; //if you have more UIViews, use an insertSubview API to place it where needed
} else {
    self.view.backgroundColor = [UIColor blackColor];
}

Si está presentando este controlador de vista de manera modal para desenfocar el contenido subyacente, deberá configurar el estilo de presentación modal en Sobre contexto actual y establecer el color de fondo en claro para garantizar que el controlador de vista subyacente permanezca visible una vez que se presente por encima.

Jordan H avatar Sep 07 '2014 01:09 Jordan H

Imagen central

Dado que la imagen en la captura de pantalla es estática, puedes usarla CIGaussianBlurdesde Core Image (requiere iOS 6). Aquí hay un ejemplo: https://github.com/evanwdavis/Fun-with-Masks/blob/master/Fun%20with%20Masks/EWDBlurExampleVC.m

Eso sí, esto es más lento que las otras opciones de esta página.

#import <QuartzCore/QuartzCore.h>

- (UIImage*) blur:(UIImage*)theImage
{   
    // ***********If you need re-orienting (e.g. trying to blur a photo taken from the device camera front facing camera in portrait mode)
    // theImage = [self reOrientIfNeeded:theImage];

    // create our blurred image
    CIContext *context = [CIContext contextWithOptions:nil];
    CIImage *inputImage = [CIImage imageWithCGImage:theImage.CGImage];

    // setting up Gaussian Blur (we could use one of many filters offered by Core Image)
    CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
    [filter setValue:inputImage forKey:kCIInputImageKey];
    [filter setValue:[NSNumber numberWithFloat:15.0f] forKey:@"inputRadius"];
    CIImage *result = [filter valueForKey:kCIOutputImageKey];

    // CIGaussianBlur has a tendency to shrink the image a little, 
    // this ensures it matches up exactly to the bounds of our original image
    CGImageRef cgImage = [context createCGImage:result fromRect:[inputImage extent]];

    UIImage *returnImage = [UIImage imageWithCGImage:cgImage];//create a UIImage for this function to "return" so that ARC can manage the memory of the blur... ARC can't manage CGImageRefs so we need to release it before this function "returns" and ends.
    CGImageRelease(cgImage);//release CGImageRef because ARC doesn't manage this on its own.

    return returnImage;

    // *************** if you need scaling
    // return [[self class] scaleIfNeeded:cgImage];
}

+(UIImage*) scaleIfNeeded:(CGImageRef)cgimg {
    bool isRetina = [[[UIDevice currentDevice] systemVersion] intValue] >= 4 && [[UIScreen mainScreen] scale] == 2.0;
    if (isRetina) {
        return [UIImage imageWithCGImage:cgimg scale:2.0 orientation:UIImageOrientationUp];
    } else {
        return [UIImage imageWithCGImage:cgimg];
    }
}

- (UIImage*) reOrientIfNeeded:(UIImage*)theImage{

    if (theImage.imageOrientation != UIImageOrientationUp) {

        CGAffineTransform reOrient = CGAffineTransformIdentity;
        switch (theImage.imageOrientation) {
            case UIImageOrientationDown:
            case UIImageOrientationDownMirrored:
                reOrient = CGAffineTransformTranslate(reOrient, theImage.size.width, theImage.size.height);
                reOrient = CGAffineTransformRotate(reOrient, M_PI);
                break;
            case UIImageOrientationLeft:
            case UIImageOrientationLeftMirrored:
                reOrient = CGAffineTransformTranslate(reOrient, theImage.size.width, 0);
                reOrient = CGAffineTransformRotate(reOrient, M_PI_2);
                break;
            case UIImageOrientationRight:
            case UIImageOrientationRightMirrored:
                reOrient = CGAffineTransformTranslate(reOrient, 0, theImage.size.height);
                reOrient = CGAffineTransformRotate(reOrient, -M_PI_2);
                break;
            case UIImageOrientationUp:
            case UIImageOrientationUpMirrored:
                break;
        }

        switch (theImage.imageOrientation) {
            case UIImageOrientationUpMirrored:
            case UIImageOrientationDownMirrored:
                reOrient = CGAffineTransformTranslate(reOrient, theImage.size.width, 0);
                reOrient = CGAffineTransformScale(reOrient, -1, 1);
                break;
            case UIImageOrientationLeftMirrored:
            case UIImageOrientationRightMirrored:
                reOrient = CGAffineTransformTranslate(reOrient, theImage.size.height, 0);
                reOrient = CGAffineTransformScale(reOrient, -1, 1);
                break;
            case UIImageOrientationUp:
            case UIImageOrientationDown:
            case UIImageOrientationLeft:
            case UIImageOrientationRight:
                break;
        }

        CGContextRef myContext = CGBitmapContextCreate(NULL, theImage.size.width, theImage.size.height, CGImageGetBitsPerComponent(theImage.CGImage), 0, CGImageGetColorSpace(theImage.CGImage), CGImageGetBitmapInfo(theImage.CGImage));

        CGContextConcatCTM(myContext, reOrient);

        switch (theImage.imageOrientation) {
            case UIImageOrientationLeft:
            case UIImageOrientationLeftMirrored:
            case UIImageOrientationRight:
            case UIImageOrientationRightMirrored:
                CGContextDrawImage(myContext, CGRectMake(0,0,theImage.size.height,theImage.size.width), theImage.CGImage);
                break;

            default:
                CGContextDrawImage(myContext, CGRectMake(0,0,theImage.size.width,theImage.size.height), theImage.CGImage);
                break;
        }

        CGImageRef CGImg = CGBitmapContextCreateImage(myContext);
        theImage = [UIImage imageWithCGImage:CGImg];

        CGImageRelease(CGImg);
        CGContextRelease(myContext);
    }

    return theImage;
}

Desenfoque de pila (Caja + Gaussiano)

  • StackBlur Esto implementa una combinación de desenfoque de caja y gaussiano. 7 veces más rápido que el gaussiano no acelerado, pero no tan feo como el desenfoque de cuadro. Vea una demostración aquí ( versión del complemento Java) o aquí (versión JavaScript). Este algoritmo se utiliza en KDE y Camera+ y otros. No utiliza Accelerate Framework pero es rápido.

Acelerar el marco

  • En la sesión "Implementación de una interfaz de usuario atractiva en iOS" de la WWDC 2013 , Apple explica cómo crear un fondo borroso (a las 14:30) y menciona un método applyLightEffectimplementado en el código de muestra usando Accelerate.framework.

  • GPUImage utiliza sombreadores OpenGL para crear desenfoques dinámicos. Tiene varios tipos de desenfoque: GPUImageBoxBlurFilter, GPUImageFastBlurFilter, GaussianSelectiveBlur, GPUImageGaussianBlurFilter. Incluso hay un GPUImageiOSBlurFilter que “debería replicar completamente el efecto de desenfoque proporcionado por el panel de control de iOS 7” ( twitt , artículo ). El artículo es detallado e informativo.

    -(UIImage *)borrosaGPUImage:(UIImage *)imagen conBlurLevel:(NSInteger)desenfoque {
        GPUImageFastBlurFilter *blurFilter = [GPUImageFastBlurFilter nuevo];
        BlurFilter.blurSize = desenfoque;
        UIImage *resultado = [blurFilter imageByFilteringImage:image];
        resultado de devolución;
    }
  • De indieambitions.com: realice un desenfoque usando vImage . El algoritmo también se utiliza en iOS-RealTimeBlur .

  • De Nick Lockwood: https://github.com/nicklockwood/FXBlurView El ejemplo muestra el desenfoque en una vista de desplazamiento. Se difumina con Dispatch_async, luego se sincroniza para llamar a las actualizaciones con UITrackingRunLoopMode para que el desenfoque no se retrase cuando UIKit le da más prioridad al desplazamiento de UIScrollView. Esto se explica en el libro de Nick iOS Core Animation , que por cierto es genial.

  • iOS-blur Esto toma la capa de desenfoque de UIToolbar y la coloca en otro lugar. Apple rechazará tu aplicación si utilizas este método. Consulte https://github.com/mochidev/MDBlurView/issues/4

  • Del blog de Evadne: LiveFrost: convolución rápida y sincrónica de instantáneas de UIView . Gran código y una gran lectura. Algunas ideas de esta publicación:

    • Utilice una cola en serie para acelerar las actualizaciones de CADisplayLink.
    • Reutilice contextos de mapas de bits a menos que cambien los límites.
    • Dibuja imágenes más pequeñas usando -[CALayer renderInContext:] con un factor de escala de 0,5f.

Otras cosas

Andy Matuschak dijo en Twitter: "ya sabes, muchos de los lugares donde parece que lo estamos haciendo en tiempo real, son estáticos con trucos inteligentes".

En doubleencore.com dicen "hemos descubierto que un radio de desenfoque de 10 pt más un aumento de 10 pt en la saturación imita mejor el efecto de desenfoque de iOS 7 en la mayoría de las circunstancias".

Un vistazo a los encabezados privados de SBFProceduralWallpaperView de Apple .

Finalmente, esto no es un desenfoque real, pero recuerda que puedes configurar rasterizationScale para obtener una imagen pixelada: http://www.dimzzy.com/blog/2010/11/blur-effect-for-uiview/

Jano avatar Jun 11 '2013 10:06 Jano

Aquí hay una manera fácil de agregar desenfoque personalizado sin tener que regatear con API privadas usando UIViewPropertyAnimator :

Primero, declara la propiedad de clase:

var blurAnimator: UIViewPropertyAnimator!

Luego configura tu vista borrosa en viewDidLoad():

let blurEffectView = UIVisualEffectView()
blurEffectView.backgroundColor = .clear
blurEffectView.frame = view.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(blurEffectView)

blurAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [blurEffectView] in
    blurEffectView.effect = UIBlurEffect(style: .light)
}

blurAnimator.fractionComplete = 0.15 // set the blur intensity.    

Nota: Esta solución no es adecuada para células UICollectionView/UITableView

Adam Bardon avatar Mar 27 '2019 13:03 Adam Bardon

Decidí publicar una versión escrita de Objective-C a partir de la respuesta aceptada solo para brindar más opciones en esta pregunta.

- (UIView *)applyBlurToView:(UIView *)view withEffectStyle:(UIBlurEffectStyle)style andConstraints:(BOOL)addConstraints
{
  //only apply the blur if the user hasn't disabled transparency effects
  if(!UIAccessibilityIsReduceTransparencyEnabled())
  {
    UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:style];
    UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
    blurEffectView.frame = view.bounds;

    [view addSubview:blurEffectView];

    if(addConstraints)
    {
      //add auto layout constraints so that the blur fills the screen upon rotating device
      [blurEffectView setTranslatesAutoresizingMaskIntoConstraints:NO];

      [view addConstraint:[NSLayoutConstraint constraintWithItem:blurEffectView
                                                       attribute:NSLayoutAttributeTop
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:view
                                                       attribute:NSLayoutAttributeTop
                                                      multiplier:1
                                                        constant:0]];

      [view addConstraint:[NSLayoutConstraint constraintWithItem:blurEffectView
                                                       attribute:NSLayoutAttributeBottom
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:view
                                                       attribute:NSLayoutAttributeBottom
                                                      multiplier:1
                                                        constant:0]];

      [view addConstraint:[NSLayoutConstraint constraintWithItem:blurEffectView
                                                       attribute:NSLayoutAttributeLeading
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:view
                                                       attribute:NSLayoutAttributeLeading
                                                      multiplier:1
                                                        constant:0]];

      [view addConstraint:[NSLayoutConstraint constraintWithItem:blurEffectView
                                                       attribute:NSLayoutAttributeTrailing
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:view
                                                       attribute:NSLayoutAttributeTrailing
                                                      multiplier:1
                                                        constant:0]];
    }
  }
  else
  {
    view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.7];
  }

  return view;
}

Las restricciones se pueden eliminar si lo desea, en caso de que solo admita el modo vertical o simplemente agrego una bandera a esta función para usarlas o no.

valbu17 avatar Feb 23 '2015 04:02 valbu17