Translucent Modal ViewController - 如何处理旋转

Posted

技术标签:

【中文标题】Translucent Modal ViewController - 如何处理旋转【英文标题】:Translucent Modal ViewController - how to handle rotation 【发布时间】:2014-03-14 19:02:24 【问题描述】:

我想以模态方式显示UIViewController,并能够看到呈现它的视图的模糊版本。

遵循一些类似的问题,例如:

ios 7 Translucent Modal View Controller

我在控制器视图中添加了一个背景,该背景基于呈现控制器的捕获视图。我面临的问题是我的应用程序支持多个方向,当模态视图呈现和旋转时,底层背景图像不再匹配。

我尝试在模态 viewController 的 didRotateFromInterfaceOrientation: 中获取呈现 viewController 的新快照,但呈现 viewController 的 UI 似乎没有更新,并且生成的图像仍然是错误的方向。有没有办法强制重绘被模态视图隐藏的视图?

【问题讨论】:

【参考方案1】:

经过长时间的考虑,我想出了一个可以处理的方法。它的效果如何取决于您在演示viewController 中拥有的内容类型。

一般的想法是在呈现新的viewController 之前不要截取一张,而是截取两张截图 - 一张用于纵向,一张用于横向。这是通过更改顶部viewController 和导航栏(如果适用)的框架以模拟不同的方向,截取结果并将其更改回来来实现的。用户永远不会在设备上看到这种变化,但屏幕抓取仍会显示新的方向。

确切的代码将取决于您从哪里调用它,但主要逻辑是相同的。我的实现从AppDelegate 运行,因为它被UIViewController 的几个子类重用。

以下是获取相应屏幕截图的代码。

// get references to the views you need a screenshot of
// this may very depending on your app hierarchy
UIView *container = [self.window.subviews lastObject]; // UILayoutContainerView
UIView *subview = container.subviews[0]; //  UINavigationTransitionView
UIView *navbar = container.subviews[1]; // UINavigationBar

CGSize originalSubviewSize = subview.frame.size;
CGSize originalNavbarSize = navbar.frame.size;

// compose the current view of the navbar and subview
UIImage *currentComposed = [self composeForeground:navbar withBackground:subview];

// rotate the navbar and subview
subview.frame = CGRectMake(subview.frame.origin.x, subview.frame.origin.y, originalSubviewSize.height, originalSubviewSize.width);
// the navbar has to match the width of the subview, height remains the same
navbar.frame = CGRectMake(navbar.frame.origin.x, navbar.frame.origin.y, originalSubviewSize.height, originalNavbarSize.height);

// compose the rotated view
UIImage *rotatedComposed = [self composeForeground:navbar withBackground:subview];

// change the frames back to normal
subview.frame = CGRectMake(subview.frame.origin.x, subview.frame.origin.y, originalSubviewSize.width, originalSubviewSize.height);
navbar.frame = CGRectMake(navbar.frame.origin.x, navbar.frame.origin.y, originalNavbarSize.width, originalNavbarSize.height);


// assign the variables depending on actual orientations
UIImage *landscape; UIImage *portrait;
if (originalSubviewSize.height > originalSubviewSize.width) 
    // current orientation is portrait
    portrait = currentComposed;
    landscape = rotatedComposed;
 else 
    // current orientation is landscape
    portrait = rotatedComposed;
    landscape = currentComposed;

CustomTranslucentViewController *vc = [CustomTranslucentViewController new];
vc.backgroundSnap = portrait;
vc.backgroundSnapLandscape = landscape;
[rooVC presentViewController:vc animated:YES completion:nil];

composeForeground:withBackground: 方法是一种方便的方法,可以根据两个输入视图(导航栏 + 视图控制器)生成适当的背景图像。除了将两个视图组合在一起之外,它还可以在旋转呈现的viewController 时使结果看起来更自然。具体来说,它将屏幕截图扩展到 1024x1024 正方形,并用合成图像的镜像副本填充额外的空间。在许多情况下,一旦模糊,这看起来就足够了,因为视图重新绘制以改变方向的动画不可用。

- (UIImage *)composeForeground:(UIView *)frontView withBackground:(UIView *)backView 

    UIGraphicsBeginImageContextWithOptions(backView.frame.size, 0, 0);
    [backView.layer renderInContext:UIGraphicsGetCurrentContext()];

    // translation is necessary to account for the extra 20 taken up by the status bar
    CGContextTranslateCTM(UIGraphicsGetCurrentContext(), frontView.frame.origin.x, frontView.frame.origin.y);
    [frontView.layer renderInContext:UIGraphicsGetCurrentContext()];
    CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -frontView.frame.origin.x, -frontView.frame.origin.y);

    // this is the core image, would have left it at this if we did not need to use fancy mirrored tiling
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // add mirrored sections
    CGFloat addition = 256; // 1024 - 768
    if (newImage.size.height > newImage.size.width) 
        // portrait, add a mirrored image on the right
        UIImage *horizMirror = [[UIImage alloc] initWithCGImage:newImage.CGImage scale:newImage.scale orientation:UIImageOrientationUpMirrored];
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(newImage.size.width+addition, newImage.size.height), 0, 0);
        [horizMirror drawAtPoint:CGPointMake(newImage.size.width, 0)];
     else 
        // landscape, add a mirrored image at the bottom
        UIImage *vertMirror = [[UIImage alloc] initWithCGImage:newImage.CGImage scale:newImage.scale orientation:UIImageOrientationDownMirrored];
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(newImage.size.width, newImage.size.height+addition), 0, 0);
        [vertMirror drawAtPoint:CGPointMake(0, newImage.size.height)];
    

    // combine the mirrored extension with the original image
    [newImage drawAtPoint:CGPointZero];
    newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // for ios 6, crop off the top 20px
    if (SYSTEM_VERSION_LESS_THAN(@"7")) 
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(newImage.size.width, newImage.size.height-20), NO, 0);
        [newImage drawAtPoint:CGPointMake(0, -20)];
        newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    

    return newImage;

生成的横向和纵向图像可以根据需要进行模糊和着色,并设置为呈现的viewController 的背景。使用此viewControllerwillRotateToInterfaceOrientation:duration: 方法选择合适的图像。

注意:我已尝试尽可能减少在图像和图形上下文上完成的工作量,但在生成背景时仍有轻微延迟(每次大约 30-90 毫秒) composeForeground:withBackground: 迭代,取决于内容,在老式慢速 iPad 2 上)。如果您知道进一步优化或简化上述解决方案的方法,请分享!

【讨论】:

这整个事情真的是为了结果而付出了太多努力——这太占用 CPU 了。我最好的中间解决方案是禁用旋转,这样就不需要发生整个镜像和旋转,但经过深思熟虑后,决定以响应性为主,并将其替换为静态背景。

以上是关于Translucent Modal ViewController - 如何处理旋转的主要内容,如果未能解决你的问题,请参考以下文章

几种在场景之间传递数据的方式

iOS中的translucent和automaticallyAdjustsScrollViewInsets用法

present的时候是可以直接回到第一个viewcon的

错误:Theme.Translucent & FLAG_ACTIVITY_REORDER_TO_FRONT

当 UINavigation 的 Translucent 设置为 false 时,视图约束发生变化

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ViewCon