Contrôle de la capture d'écran dans le sélecteur multitâche iOS 7

98

J'ai essayé de trouver des informations concernant le nouveau sélecteur multitâche dans iOS 7 et en particulier la capture d'écran que le système d'exploitation prend lorsque l'application est en veille prolongée.

entrez la description de l'image ici

Existe-t-il un moyen de désactiver complètement cette fonctionnalité ou cette capture d'écran? Ou puis-je masquer complètement l'application du mélangeur? L'application doit s'exécuter en arrière-plan, mais nous ne souhaitons afficher aucune capture d'écran de l'application.

La capture d'écran est potentiellement un risque pour la sécurité, pensez aux applications bancaires où votre numéro de carte ou le résumé de votre compte sera disponible pour quiconque double-clique sur le bouton d'accueil de l'appareil.

Quelqu'un avec un aperçu de cela? Merci.

Tommie
la source
1
Ma solution actuelle consiste à charger une vue vide avec le logo de l'application une fois que l'application entre en veille prolongée et à la supprimer lorsque l'application revient, mais cette solution est difficile à gérer et ressemble à un hack plutôt qu'à une solution élégante. De plus, cette solution échoue de temps en temps en fonction de l'appareil, etc. Ce n'est donc pas vraiment une solution utilisable.
Tommie

Réponses:

101

En préparant votre interface utilisateur pour qu'elle s'exécute en arrière-plan , Apple déclare:

Préparez votre interface utilisateur pour l'instantané de l'application

À un moment donné, une fois que votre application est entrée en arrière-plan et que votre méthode de délégué est retournée, UIKit prend un instantané de l'interface utilisateur actuelle de votre application. Le système affiche l'image résultante dans le sélecteur d'application. Il affiche également temporairement l'image lorsque vous ramenez votre application au premier plan.

L'interface utilisateur de votre application ne doit contenir aucune information utilisateur sensible, telle que des mots de passe ou des numéros de carte de crédit. Si votre interface contient de telles informations, supprimez-les de vos vues lorsque vous entrez en arrière-plan. En outre, ignorez les alertes, les interfaces temporaires et les contrôleurs de vue système qui masquent le contenu de votre application. L'instantané représente l'interface de votre application et doit être reconnaissable par les utilisateurs. Lorsque votre application revient au premier plan, vous pouvez restaurer les données et les vues selon vos besoins.

Voir Questions et réponses techniques QA1838: Empêcher les informations sensibles d'apparaître dans le sélecteur de tâches

En plus de masquer / remplacer les informations sensibles, vous pouvez également demander à iOS 7 de ne pas prendre l'instantané d'écran via ignoreSnapshotOnNextApplicationLaunch, dont la documentation dit:

Si vous pensez que l'instantané ne peut pas refléter correctement l'interface utilisateur de votre application lorsque celle-ci est relancée, vous pouvez appeler ignoreSnapshotOnNextApplicationLaunchpour empêcher la prise de cette image instantanée.

Cela dit, il semble que la capture d'écran soit toujours prise et j'ai donc déposé un rapport de bogue. Mais vous devriez tester plus loin et voir si l'utilisation de ce paramètre aide.

S'il s'agissait d'une application d'entreprise, vous souhaiterez peut-être également examiner le paramètre approprié de allowScreenShotdécrit dans la section Charge utile des restrictions de la référence du profil de configuration.


Voici une mise en œuvre qui réalise ce dont j'avais besoin. Vous pouvez présenter le vôtre UIImageViewou utiliser un modèle de protocole de délégué pour masquer les informations confidentielles:

//  SecureDelegate.h

#import <Foundation/Foundation.h>

@protocol SecureDelegate <NSObject>

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

@end

J'ai ensuite donné à mon délégué d'application une propriété pour cela:

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

Mon contrôleur de vue le définit:

- (void)viewDidLoad
{
    [super viewDidLoad];

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

Le contrôleur de vue implémente évidemment ce protocole:

- (void)hide:(id)object
{
    self.passwordLabel.alpha = 0.0;
}

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

Et, enfin, mon délégué d'application utilise ce protocole et cette propriété:

- (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:"];
}

Notez que j'utilise applicationWillResignActiveplutôt que le conseillé applicationDidEnterBackground, car, comme d'autres l'ont souligné, ce dernier n'est pas appelé lorsque vous appuyez deux fois sur le bouton d'accueil pendant que l'application est en cours d'exécution.

J'aimerais pouvoir utiliser les notifications pour gérer tout cela, plutôt que le modèle de protocole de délégué, mais dans mes tests limités, les notifications ne sont pas gérées assez rapidement, mais le modèle ci-dessus fonctionne bien.

Rob
la source
6
Notez que, comme mentionné ici stackoverflow.com/questions/18937313/ ... le comportement n'est pas toujours le même dans le simulateur que sur l'appareil!
Michael Forrest
5
Pour ce qu'elle vaut, la ignoreSnapshotOnNextApplicationLaunchméthode est ignorée si elle n'est pas invoquée lors de la restauration de l'état, comme décrit ici .
Charles A.
3
Vous n'avez pas besoin de le faire dans applicationWillResignActive car la capture d'écran n'est pas prise avant applicationDidEnterBackground. Lorsque vous appuyez deux fois sur le bouton d'accueil, aucune capture d'écran n'est prise; l'écran que vous voyez est en direct. Ajoutez une animation à votre écran pour vérifier, par exemple les couleurs de fond de cycle. Si vous choisissez ensuite une autre application, votre applicationDidEnterBackground sera appelée avant que la capture d'écran réelle ne soit prise.
honus le
3
BTW, j'ai été informé par un ingénieur Apple que la documentation ignoreSnapshotOnNextApplicationLaunchest incorrecte, que, comme nous l'avons tous observé, un instantané est réellement pris, mais ne sera tout simplement pas utilisé au prochain lancement.
Rob
2
ignoreSnapshotOnNextApplicationLaunch semble faire ce que cela ressemble: empêcher une capture d'écran de s'afficher lors du lancement de l' application . Cela n'affecte pas la capture d'écran de l'interface utilisateur multitâche, ou si votre application est reprise plutôt que lancée.
Glenn Maynard
32

Voici la solution avec laquelle j'ai travaillé pour mon application:

Comme l'a dit Tommy: vous pouvez utiliser l'applicationWillResignActive. Ce que j'ai fait, c'est de créer une UIImageView avec mon SplashImage et de l'ajouter en tant que sous-vue à ma fenêtre principale.

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

J'ai utilisé cette méthode au lieu de applicationDidEnterBackground car applicationDidEnterBackground ne sera pas déclenché si vous appuyez deux fois sur le bouton d'accueil, et applicationWillResignActive le sera. J'ai entendu des gens dire que cela pouvait être déclenché dans d'autres cas également, donc je suis toujours en train de tester pour voir si cela pose problème, mais aucun pour l'instant! ;)

Ici pour supprimer l'imageview:

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

J'espère que cela t'aides!

Note de bas de page: J'ai testé cela à la fois sur le simulateur et sur un appareil réel: il ne s'affichera pas sur le simulateur, mais c'est le cas sur un vrai appareil!

Laura
la source
2
Il y a un inconvénient: applicationWillResignActive est appelé, entre autres cas, lorsque l'utilisateur tire vers le bas le dock des notifications. Vous faites donc glisser votre doigt de haut en bas et voyez cette image, alors que vous vous attendez à voir le contenu de l'application.
Kirill Gamazkov
4
Il est préférable de brouiller les informations sensibles sur l'applicationWillResignActive et de définir imageView sur aplicationDidEnterBackground
Kirill Gamazkov
Cela fonctionne, mais il y a un bug de rotation lors du déploiement sur iOS7, construit avec xCode6: stackoverflow.com/questions/26147068/…
Alex Stone
Si j'écris le code que vous avez mentionné dans applicationWillResignActive , que verrai-je lorsque je ramènerai mon application à l'état de premier plan? Vais-je voir l'image "Portrait (768x1024) .png" ou l'écran réel?
NSPratik
2
@Pratik: Vous verrez l'image, sauf si vous la supprimez à nouveau dans la méthode applicationDidBecomeActive (voir ci-dessus).
Laura
14

Cette méthode rapide et facile donnera un instantané noir au-dessus de l'icône de votre application dans le sélecteur d'application iOS7 ou version ultérieure.

Tout d'abord, prenez la fenêtre clé de votre application (généralement configurée dans AppDelegate.m dans l'application: didFinishLaunchingWithOptions) et cachez-la lorsque votre application est sur le point de passer en arrière-plan:

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

Ensuite, désactivez la fenêtre principale de votre application lorsque celle-ci redevient active:

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

À ce stade, consultez le sélecteur d'application et vérifiez que vous voyez un instantané noir au-dessus de l'icône de votre application. J'ai remarqué que si vous lancez le sélecteur d'application immédiatement après avoir déplacé votre application en arrière-plan, il peut y avoir un délai d'environ 5 secondes pendant lequel vous verrez un instantané de votre application (celle que vous souhaitez masquer!), Après qu'il transforme en un instantané entièrement noir. Je ne sais pas ce qui se passe avec le retard; si quelqu'un a des suggestions, merci de le signaler.

Si vous voulez une couleur autre que le noir dans le sélecteur, vous pouvez faire quelque chose comme ceci en ajoutant une sous-vue avec la couleur d'arrière-plan de votre choix:

- (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];
    }
}

Ensuite, supprimez cette sous-vue de couleur lorsque votre application redevient active:

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

J'ai utilisé la solution suivante: lorsque l'application va démissionner, j'obtiens un instantané appWindow en tant que vue et y ajoute du flou. Ensuite, j'ajoute cette vue à la fenêtre de l'application

comment faire ceci:

  1. dans appDelegate juste avant l'implémentation, ajoutez la ligne:

    static const int kNVSBlurViewTag = 198490;//or wherever number you like
  2. ajoutez ces méthodes:

    - (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. faites en sorte que votre implémentation appDelegate soit la suivante:

    - (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];
    }

J'ai créé des exemples de projets dans Swift et Objective C . Les deux projets réalisent les actions suivantes dans:

-application: didResignActive - l'instantané est créé, flouté et ajouté à la fenêtre de l'application

-application: willBecomeActive blur view est en cours de suppression de la fenêtre.

Comment utiliser:

Objecitf C

  1. Ajoutez des AppDelegate+NVSBlurAppScreenfichiers .h et .m à votre projet

  2. dans votre méthode -applicationWillResignActive: ajoutez la ligne suivante:

    [self nvs_blurPresentedView];
  3. dans votre méthode -applicationDidEnterBackground: ajoutez la ligne suivante:

    [self nvs_unblurPresentedView];

Rapide

  1. ajoutez le fichier AppDelegateExtention.swift à votre projet

  2. dans votre fonction applicationWillResignActive ajoutez la ligne suivante:

    blurPresentedView()
  3. dans votre fonction applicationDidBecomeActive, ajoutez la ligne suivante:

    unblurPresentedView()
Nikolay Shubenkov
la source
2

si seulement utilisé [self.window addSubview:imageView];dans la applicationWillResignActivefonction, cette imageView ne couvrira pas UIAlertView, UIActionSheet ou MFMailComposeViewController ...

La meilleure solution est

- (void)applicationWillResignActive:(UIApplication *)application
{
    UIWindow *mainWindow = [[[UIApplication sharedApplication] windows] lastObject];
    [mainWindow addSubview:imageView];
}
Zorot
la source
Très bonne solution si vous avez une vidéo en cours de lecture en mode plein écran dans une webview!
Tommie
Excellent!. Cela fonctionne même si nous avons un clavier ouvert avant d'entrer dans le mélangeur multitâche! Merci
Prabhu.Somasundaram
0

Fournir ma propre solution en tant que «réponses», bien que cette solution soit très peu fiable. Parfois, j'obtiens un écran noir comme capture d'écran, parfois le XIB et parfois une capture d'écran de l'application elle-même. Selon l'appareil et / ou si je l'exécute dans le simulateur.

Veuillez noter que je ne peux pas fournir de code pour cette solution car il contient beaucoup de détails spécifiques à l'application. Mais cela devrait expliquer l'essentiel de ma solution.

Dans AppDelegate.m sous applicationWillResignActive, je vérifie si nous exécutons iOS7, si nous le faisons, je charge une nouvelle vue qui est vide avec le logo de l'application au milieu. Une fois que applicationDidBecomeActive est appelée, je relance mes anciennes vues, qui seront réinitialisées - mais cela fonctionne pour le type d'application que je développe.

Tommie
la source
1
Si vous voyez ce que fait l'application Appareil photo - elle transforme l'image en une image floue lorsqu'elle passe en arrière-plan (vous pouvez voir la transition lorsqu'elle se réduit à l'icône)
Petesh
@Petesh Oui, c'est une belle implémentation, mais la question est de savoir comment ils le font. J'ai peur d'une API privée.
Rob
@Tommie Vous dites "En fonction de l'appareil et / ou si je l'exécute dans le simulateur". Pour moi, cela semble fonctionner sur l'appareil (même si je n'ai pas fait de tests exhaustifs), mais pas sur le simulateur. Trouvez-vous que cela ne fonctionne pas sur l'appareil?
Rob
0

Vous pouvez utiliser l'activateur pour configurer le double-clic du bouton d'accueil pour lancer le multitâche et désactiver le double-clic par défaut du bouton d'accueil et le lancement de la fenêtre multitâche. Cette méthode peut être utilisée pour remplacer les captures d'écran par l'image par défaut de l'application. Cela s'applique aux applications avec la fonction de protection par mot de passe par défaut.

Fareed.MK
la source
-2

Xamarin.iOS

Adapté de https://stackoverflow.com/a/20040270/7561

Au lieu d'afficher simplement une couleur, je voulais montrer mon écran de lancement.

 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();
}
ben
la source