半透明模态视图控制器 - 如何处理旋转

5
我想以 modal 的方式展示一个 UIViewController,并希望在呈现它的视图上看到一个模糊的版本。基于这样一些类似的问题,例如:iOS 7 Translucent Modal View Controller,我已经为我的控制器的视图添加了一个基于被呈现控制器捕获的背景。我面临的问题是,我的应用程序支持多个方向,当模态视图被呈现并旋转时,底层的背景图像不再匹配。我尝试在 modal 视图控制器的 didRotateFromInterfaceOrientation: 中抓取呈现 viewController 的新快照,但似乎呈现 viewController 的 UI 没有更新,因此生成的图像仍然是错误的方向。是否有任何方法可以强制重绘被模态覆盖的视图?
1个回答

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:方法来选择适当的图片。

注意:我已尽可能地减少对图像和图形上下文的处理,但仍存在轻微延迟,当生成背景时(在旧款iPad 2上,每次composeForeground:withBackground:循环大约需30-90毫秒,具体取决于内容)。如果您知道进一步优化或简化以上解决方案的方法,请分享!


1
整个过程对于结果来说确实有点过于费力了——它需要太多的 CPU 资源。我最好的折中方案是禁用旋转,这样整个镜像和旋转就不需要发生了,但经过更多的思考,我们决定响应性更为重要,因此用静态背景替换了它。 - SaltyNuts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接