控制iOS 7多任务切换器中的屏幕截图

100

我一直在尝试寻找关于iOS 7中新的多任务切换器以及操作系统在应用程序进入休眠状态时所拍摄的截屏的信息。

输入图片说明

有没有完全关闭这个功能或屏幕截图的方法?或者是否可以完全从切换器中隐藏该应用程序?该应用程序需要在后台运行,但我们不想显示任何来自该应用的截图。

截屏可能存在安全风险,考虑银行应用程序等情况,任何人双击设备的主按钮都可以查看您的卡号或账户汇总。

有了解的人可以提供帮助吗?谢谢。


1
我的当前解决方案是在应用程序进入休眠状态时加载带有应用程序标志的空视图,并在应用程序返回时将其删除,但这种解决方案很难管理,看起来更像是一种黑客而不是优雅的解决方案。此外,这种解决方案有时会因设备等原因而失败。因此,它并不是一个可用的解决方案。 - Tommie
8个回答

103
在苹果的“准备将您的UI运行在后台”中,提到:

为应用快照准备您的UI

在您的应用程序进入后台并且委托方法返回后的某个时刻,UIKit会对您应用程序当前的用户界面进行快照。系统会在应用切换器中显示生成的图像。当把您的应用程序带回前台时,它也会暂时显示图像。

您的应用程序UI不能包含任何敏感用户信息,例如密码或信用卡号码。如果您的界面包含此类信息,请在进入后台时从视图中删除它。此外,当出现遮挡应用程序内容的警报、临时界面和系统视图控制器时,也要将其关闭。该快照代表您的应用程序界面,并且应该能被用户识别。当您的应用程序返回前台时,可以根据需要恢复数据和视图。

请参阅技术Q&A QA1838:防止敏感信息出现在任务切换器中

除了遮盖/替换敏感信息外,您可能还想告诉iOS 7不要通过ignoreSnapshotOnNextApplicationLaunch来获取屏幕快照,其文档如下:

如果您认为快照无法正确反映应用程序在重新启动时的用户界面,则可以调用ignoreSnapshotOnNextApplicationLaunch来防止生成该快照图像。

话虽如此,似乎仍会获取屏幕快照,因此我已提交了一个错误报告。但是您应进一步测试并查看是否使用此设置有所帮助。

如果这是企业应用程序,则还应查看“限制有效负载”部分中概述的allowScreenShot适当设置。


以下是实现我的所需的方法。您可以呈现自己的UIImageView,或者可以使用委托协议模式来遮盖机密信息:

//  SecureDelegate.h

#import <Foundation/Foundation.h>

@protocol SecureDelegate <NSObject>

- (void)hide:(id)object;
- (void)show:(id)object;

@end

然后,我为我的应用程序代理(app delegate)添加了一个属性:

@property (weak, nonatomic) id<SecureDelegate> secureDelegate;

我的视图控制器设置它:

- (void)viewDidLoad
{
    [super viewDidLoad];

    AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
    delegate.secureDelegate = self;
}

视图控制器显然实现了该协议:
- (void)hide:(id)object
{
    self.passwordLabel.alpha = 0.0;
}

- (void)show:(id)object
{
    self.passwordLabel.alpha = 1.0;
}

最后,我的应用委托利用了这个协议和属性:

- (void)applicationWillResignActive:(UIApplication *)application
{
    [application ignoreSnapshotOnNextApplicationLaunch];  // this doesn't appear to work, whether called here or `didFinishLaunchingWithOptions`, but seems prudent to include it

    [self.secureDelegate hide:@"applicationWillResignActive:"];  // you don't need to pass the "object", but it was useful during my testing...
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [self.secureDelegate show:@"applicationDidBecomeActive:"];
}

注意,我正在使用applicationWillResignActive而不是建议的applicationDidEnterBackground,因为正如其他人指出的那样,在应用程序运行时双击主屏幕按钮时,后者不会被调用。

我希望能够使用通知来处理所有这些事情,而不是使用委托协议模式,但在我的有限测试中,通知不能及时地处理,而上述模式很好地工作。


6
请注意,正如在这里提到的https://dev59.com/U3fZa4cB1Zd3GeqPNh2h,在模拟器上的行为与在实际设备上的行为不总是相同的! - Michael Forrest
6
就这个问题而言,如果在状态恢复期间未调用ignoreSnapshotOnNextApplicationLaunch方法,则该方法将被忽略。有关详细信息,请参阅此处 - Charles A.
3
你不需要在 applicationWillResignActive 中执行此操作,因为截屏直到 applicationDidEnterBackground 才会被执行。当你双击主屏幕按钮时,不会拍摄屏幕截图;你看到的是实时画面。添加动画到你的屏幕上进行验证,例如循环背景颜色。如果你选择了另一个应用程序,你的 applicationDidEnterBackground 将在实际截屏之前被调用。 - honus
4
顺便说一下,我得到了一位苹果工程师的通知,关于ignoreSnapshotOnNextApplicationLaunch的文档是不正确的。正如我们所观察到的那样,实际上确实会拍摄快照,但在下一次启动时将不会使用它。 - Rob
2
ignoreSnapshotOnNextApplicationLaunch 看起来就是它的字面意思:在应用程序启动期间防止屏幕截图被显示。它不会影响多任务 UI 屏幕截图,也不会影响当您的应用程序被恢复而不是启动时。 - Glenn Maynard
显示剩余7条评论

32

这是我用于我的应用程序的解决方案:

就像Tommy所说:您可以使用applicationWillResignActive。 我所做的是制作一个带有我的SplashImage的UIImageView,并将其添加为我的主窗口的子视图-

(void)applicationWillResignActive:(UIApplication *)application
{
    imageView = [[UIImageView alloc]initWithFrame:[self.window frame]];
    [imageView setImage:[UIImage imageNamed:@"Portrait(768x1024).png"]];
    [self.window addSubview:imageView];
}

我使用了这种方法,而不是applicationDidEnterBackground,因为如果你双击home按钮,applicationDidEnterBackground不会被触发,而applicationWillResignActive则会被触发。虽然有人说它也可以在其他情况下触发,但我仍在测试中,看看它是否会出现问题,但目前还没有出现!;)

这里是移除图片视图的代码:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    if(imageView != nil) {
        [imageView removeFromSuperview];
        imageView = nil;
    }
}

希望这有所帮助!

另外说明一下:我在模拟器和真机上都进行了测试,它在模拟器上不会显示,但在真机上会显示!


2
有一个缺点:applicationWillResignActive被调用时,除了其他情况,当用户下拉通知栏时也会被调用。因此,当您从顶部向下滑动手指并看到该图像时,您本来期望看到应用程序内容。 - Kirill Gamazkov
4
在 applicationWillResignActive 上模糊敏感信息并在 applicationDidEnterBackground 上设置 imageView 会更好。 - Kirill Gamazkov
这个代码能够工作,但是在iOS7系统下使用xCode6编译时会出现旋转错误:http://stackoverflow.com/questions/26147068/xcode6-incorrect-view-controller-orientation-when-deployed-to-ios7-device - Alex Stone
如果我在_applicationWillResignActive_中编写你提到的代码,那么当我将我的应用程序切换回前台状态时,我会看到什么?我会看到"Portrait(768x1024).png"图片还是实际屏幕? - NSPratik
2
@Pratik:除非你在applicationDidBecomeActive方法中再次删除它(参见上文),否则你将看到这个图片。 - Laura
有一个缺点,当应用程序注册推送通知或接收到位置警报时,应用程序也会进入非活动状态,直到警报关闭,因此图像视图将被显示。 - megha

15

使用这种快捷简便的方法,您可以在iOS7或更高版本的应用切换器中获得应用程序图标上方的黑色快照。

首先,在您的应用程序即将进入后台时,隐藏应用程序的主窗口(通常在AppDelegate.m的application:didFinishLaunchingWithOptions中设置):

- (void)applicationWillResignActive:(UIApplication *)application
{
    if(isIOS7Or8)
    {
        self.window.hidden = YES;
    }
}

然后,在您的应用程序再次变为活动状态时取消隐藏您的应用程序的关键窗口:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    if(isIOS7Or8)
    {
        self.window.hidden = NO;
    }
}

此时,请查看应用程序切换器,并验证您是否在应用程序图标上方看到了一个黑色快照。我注意到,如果您在将应用程序移到后台后立即启动应用程序切换器,则可能会延迟约5秒钟,您将看到应用程序的快照(您想要隐藏的那个!),然后它将过渡到全黑的快照。我不确定延迟是什么原因;如果有人有任何建议,请发言。

如果您希望在切换器中使用其他颜色,可以通过添加具有任何背景颜色的子视图来实现:

- (void)applicationWillResignActive:(UIApplication *)application
{
    if(isIOS7Or8)
    {
        UIView *colorView = [[[UIView alloc] initWithFrame:self.window.frame] autorelease];
        colorView.tag = 9999;
        colorView.backgroundColor = [UIColor purpleColor];
        [self.window addSubview:colorView];
        [self.window bringSubviewToFront:colorView];
    }
}

然后,在您的应用程序再次变为活动状态时,请删除此颜色子视图:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    if(isIOS7Or8)
    {
        UIView *colorView = [self.window viewWithTag:9999];
        [colorView removeFromSuperview];
    }
}

4
我使用了以下解决方案: 当应用程序即将退出时,我获取应用窗口的快照作为视图,并对其进行模糊处理。然后将此视图添加到应用窗口中。
如何实现:
  1. in appDelegate just before implementation add line:

    static const int kNVSBlurViewTag = 198490;//or wherever number you like
    
  2. add this methods:

    - (void)nvs_blurPresentedView
        {
            if ([self.window viewWithTag:kNVSBlurViewTag]){
                return;
            }
            [self.window addSubview:[self p_blurView]];
        }
    
        - (void)nvs_unblurPresentedView
        {
            [[self.window viewWithTag:kNVSBlurViewTag] removeFromSuperview];
        }
    
        #pragma mark - Private
    
        - (UIView *)p_blurView
        {
            UIView *snapshot = [self.window snapshotViewAfterScreenUpdates:NO];
    
            UIView *blurView = nil;
            if ([UIVisualEffectView class]){
                UIVisualEffectView *aView = [[UIVisualEffectView alloc]initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
                blurView        = aView;
                blurView.frame  = snapshot.bounds;
                [snapshot addSubview:aView];
            }
            else {
                UIToolbar *toolBar  = [[UIToolbar alloc] initWithFrame:snapshot.bounds];
                toolBar.barStyle    = UIBarStyleBlackTranslucent;
                [snapshot addSubview:toolBar];
            }
            snapshot.tag = kNVSBlurViewTag;
            return snapshot;
        }
    
  3. make your appDelegate implementation be the as follows:

    - (void)applicationWillResignActive:(UIApplication *)application {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
        //...
        //your code
        //...
    
        [self nvs_blurPresentedView];
    }
    
        - (void)applicationWillEnterForeground:(UIApplication *)application {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
        //...
        //your code
        //...
    
        [self nvs_unblurPresentedView];
    }
    
我将翻译以下内容:

我创建了SwiftObjective C的示例项目。 这两个项目在以下情况下执行以下操作:

-application:didResignActive - 创建快照,模糊并添加到应用程序窗口

-application:willBecomeActive - 模糊视图将从窗口中移除。

如何使用:

Objective C

  1. Add AppDelegate+NVSBlurAppScreen .h and .m files to your project

  2. in your -applicationWillResignActive: method add the following line:

    [self nvs_blurPresentedView];
    
  3. in your -applicationDidEnterBackground: method add the following line:

    [self nvs_unblurPresentedView];
    

Swift

  1. add AppDelegateExtention.swift file to your project

  2. in your applicationWillResignActive function add the following line:

    blurPresentedView()
    
  3. in your applicationDidBecomeActive function add the following line:

    unblurPresentedView()
    

2

如果只在applicationWillResignActive函数中使用[self.window addSubview:imageView];,这个imageView不会覆盖UIAlertView、UIActionSheet或MFMailComposeViewController...

最好的解决办法是

- (void)applicationWillResignActive:(UIApplication *)application
{
    UIWindow *mainWindow = [[[UIApplication sharedApplication] windows] lastObject];
    [mainWindow addSubview:imageView];
}

如果您在Webview中全屏播放视频,则这是一个非常好的解决方案! - Tommie
太棒了!即使我们在进入多任务切换器之前打开了键盘,它也能正常工作!谢谢。 - Prabhu.Somasundaram

0

您可以使用激活器来配置双击主页按钮以启动多任务处理,并禁用默认的双击主页按钮和启动多任务处理窗口。此方法可用于将屏幕截图更改为应用程序的默认图像。这适用于具有默认密码保护功能的应用程序。


0

虽然我的解决方案非常不可靠,但我会提供自己的“答案”。有时我会得到一个黑屏截图,有时是XIB,有时是应用程序本身的截图。这取决于设备和/或是否在模拟器中运行。

请注意,我无法为此解决方案提供任何代码,因为其中包含了很多特定于应用程序的细节。但这应该可以解释我的解决方案的基本要点。

在AppDelegate.m文件的applicationWillResignActive下,我检查我们是否正在运行iOS7,如果是,我加载一个新视图,其中心是空的,并带有应用程序标志。一旦调用了applicationDidBecomeActive,我重新启动我的旧视图,这将被重置 - 但对于我正在开发的应用程序类型来说,这是有效的。


1
如果你看到相机应用程序的操作 - 当进入后台时,它会将图像转换为模糊状态(当图像缩小到图标时,可以看到过渡效果)。 - Anya Shenanigans
@Petesh 是的,那是一个不错的实现,但问题是他们是如何做到的。我担心这是一个私有API。 - Rob
@Tommie 你说“取决于设备和/或是否在模拟器中运行”。对我来说,它似乎在设备上可以工作(尽管我没有进行详尽的测试),但在模拟器上不行。你发现它在设备上无法工作吗? - Rob

-2

Xamarin.iOS

参考自https://dev59.com/02Mk5IYBdhLWcg3wyw7H#20040270

我不仅想显示一种颜色,而是要显示我的启动屏幕。

 public override void DidEnterBackground(UIApplication application)
 {
    //to add the background image in place of 'active' image
    var backgroundImage = new UIImageView();
    backgroundImage.Tag = 1234;
    backgroundImage.Image = UIImage.FromBundle("Background");
    backgroundImage.Frame = this.window.Frame;
    this.window.AddSubview(backgroundImage);
    this.window.BringSubviewToFront(backgroundImage);
}

public override void WillEnterForeground(UIApplication application)
{
    //remove 'background' image
    var backgroundView = this.window.ViewWithTag(1234);
    if(null != backgroundView)
        backgroundView.RemoveFromSuperview();
}

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