lundi 12 mars 2012

Vues personnalisées alerte

Il ya quelque temps, j'ai posté un moyen de accepter une entrée de texte en utilisant une vue d'alerte. Le problème avec cette technique est qu'elle s'appuie sur l'une des API privées d'Apple. J'ai eu beaucoup de gens me demandent comment utiliser cette technique sans se faire rejeter dans le processus d'examen. La réponse courte est: vous ne pouvez pas.

Qu'est-ce que vous pouvez faire, cependant, est de créer votre propre classe qui simule le comportement d'UIAlertView. Vous pouvez trouver un exemple d'implémentation de la technique discutée à ce poste par le téléchargement de ce projet. Dans cet exemple simple, nous allons créer une vue d'alerte qui permet à l'utilisateur d'entrer une valeur dans un champ de texte unique. La même technique peut être utilisée pour présenter toute opinion que vous pouvez construire dans Interface Builder. Du point de vue programmation, vous ne serez pas rejeté pour l'aide d'API privée si vous utilisez cette technique, mais sachez que vous pouvez toujours obtenir rejetée pour des violations HIG, alors assurez-vous familiariser avec les HIG.

Il ya plusieurs façons d'imiter le comportement de l'UIAlertView. Une des façons les plus courantes que j'ai vu est tout simplement de la conception d'une vue d'alerte dans le bec du contrôleur estime que les besoins de le présenter. Cela fonctionne, mais c'est un peu salissant et peu près complètement non-réutilisables.

Une meilleure approche est de concevoir l'affichage des alertes que son contrôleur propre point de vue et une paire plume. On peut même le modèle après UIAlertView du feu et oublier l'approche de telle sorte que le code appelant peut ressembler exactement à code pour créer et afficher une UIAlertView. Avant de commencer, nous avons besoin d'un peu de ressources.

Vous avez probablement remarqué que lorsque vous utilisez une vue d'alerte, les gris actifs vue un peu. Je ne sais pas vraiment comment Apple a réalisé cela, mais nous pouvons simuler le comportement en utilisant une image PNG avec un dégradé circulaire qui va de 60% de noir opaque à 40% de noir opaque:

behind_alert_view.png

Nous avons également besoin d'une image de fond pour l'affichage des alertes. Voici celle que j'ai piraté dans Pixelmatoralert_background.png

Pour vos propres alertes, vous pourriez avoir besoin de redimensionner ou de transformer ceci en une image extensible. Par souci de simplicité, j'ai juste fait la taille je le veux. Nous avons également besoin de boutons. Encore une fois, dans un exemple réel, vous pouvez les transformer en images étirable, mais j'ai juste gardé les choses simples en faisant l'image de la taille que je le voulais:

alert_button.png

La prochaine chose que nous avons besoin est un code d'animation. Nous pourrions, bien sûr, créer les instances CAAnimation droit dans notre classe, mais je suis un grand fan de la réutilisation du code à la fois et Objective-C les catégories, ainsi au lieu de faire cela, j'ai créé une catégorie sur UIView qui va gérer l' deux animations nous avons besoin. Une animation «POP dans" une vue et sera utilisé pour montrer l'affichage des alertes, les fondus d'autres dans une vue et sera utilisé pour l'image de fond gris. Les deux animations se produisent dans le même temps de sorte que lorsque l'alerte a parfaitement sauté dans vue, la vue arrière est grisée.

Les timings image-clé du "pop" code d'animation ne sont pas tout à fait un match à 100% pour l'animation d'Apple, donc si quelqu'un veut modifier les valeurs keyframe de faire une meilleure adéquation, je serais heureux de mettre à jour le code avec le bon " «valeurs. Voici la catégorie I créée pour détenir les animations:

UIView-AlertAnimations.h
#import <Foundation/Foundation.h>


@interface UIView(AlertAnimations)
- (void)doPopInAnimation;
- (void)doPopInAnimationWithDelegate:(id)animationDelegate;
- (void)doFadeInAnimation;
- (void)doFadeInAnimationWithDelegate:(id)animationDelegate;
@end



UIView-AlertAnimations.m
#import "UIView-AlertAnimations.h"
#import <QuartzCore/QuartzCore.h>

#define kAnimationDuration 0.2555

@implementation UIView(AlertAnimations)
- (void)doPopInAnimation
{
[self doPopInAnimationWithDelegate:nil];
}

- (void)doPopInAnimationWithDelegate:(id)animationDelegate
{
CALayer *viewLayer = self.layer;
CAKeyframeAnimation* popInAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];

popInAnimation.duration = kAnimationDuration;
popInAnimation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.6],
[NSNumber numberWithFloat:1.1],
[NSNumber numberWithFloat:.9],
[NSNumber numberWithFloat:1],
nil
]
;
popInAnimation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.6],
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:1.0],
nil
]
;
popInAnimation.delegate = animationDelegate;

[viewLayer addAnimation:popInAnimation forKey:@"transform.scale"];
}

- (void)doFadeInAnimation
{
[self doFadeInAnimationWithDelegate:nil];
}

- (void)doFadeInAnimationWithDelegate:(id)animationDelegate
{
CALayer *viewLayer = self.layer;
CABasicAnimation *fadeInAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeInAnimation.fromValue = [NSNumber numberWithFloat:0.0];
fadeInAnimation.toValue = [NSNumber numberWithFloat:1.0];
fadeInAnimation.duration = kAnimationDuration;
fadeInAnimation.delegate = animationDelegate;
[viewLayer addAnimation:fadeInAnimation forKey:@"opacity"];
}

@end


Comme vous pouvez le voir, j'ai fourni deux versions de chaque animation, celui qui accepte un délégué d'animation et un qui ne fonctionne pas. Cela permet au code appelant à mettre un délégué animation ainsi il peut être avisé de choses comme lorsque l'animation se termine. Nous allons utiliser un délégué pour l'un de nos animations, mais nous pourrions aussi bien avoir l'option avec les deux.

La prochaine chose que nous devons faire est de créer l'en-tête contrôleur de vue, la mise en œuvre, et les fichiers nib. Dans mon exemple de code, j'ai nommé le CustomAlertView classe. Il peut sembler étrange que je donne une classe contrôleur de vue d'un nom qui fait sonner comme une vue au lieu de quelque chose comme CustomAlertViewController. Vous pouvez vous sentir libre à la vôtre nom comme vous le souhaitez, mais cette classe contrôleur sera effectivement imiter le comportement de UIAlertView et je voulais le nom pour refléter cela. J'ai débattu avec moi-même sur le nom pour un peu, mais a finalement décidé que CustomAlertView sentait mieux puisque le nom donnerait un indice quant à la façon de l'utiliser. Si elle se sent sale à vous de «mensonge» dans le nom de la classe, puis par tous les moyens, la vôtre nom différemment.

Nous allons avoir besoin de certaines méthodes d'action et de certains points de vente afin que notre contrôleur et fichier nib peuvent interagir. Une sortie sera pour l'image de fond, un autre pour l'affichage des alertes lui-même, et une pour le champ de texte. Cette dernière est nécessaire afin que nous puissions dire au champ de texte à accepter et démissionner statut de Première répondeur. Nous allons utiliser une méthode d'action unique pour les deux boutons sur le qui-vive, et nous allons utiliser la valeur tag du bouton à la différence entre les deux boutons.

Parce que nous mettons en œuvre le feu et oublier, nous avons aussi besoin de définir un protocole contenant les méthodes du délégué peut mettre en œuvre. Depuis l'alerte est conçu pour accepter l'entrée, j'ai fait la méthode du délégué utilisé pour recevoir l'entrée @ nécessaire, mais l'autre méthode (appelée uniquement lorsque annulé) @ optionnel. L'énumération est ici juste pour rendre le code plus lisible quand il s'agit de traiter avec les valeurs balise button.

CustomAlertView.h
#import <UIKit/UIKit.h>

enum
{
CustomAlertViewButtonTagOk = 1000,
CustomAlertViewButtonTagCancel
}
;

@class CustomAlertView;

@protocol CustomAlertViewDelegate
@required
- (void) CustomAlertView:(CustomAlertView *)alert wasDismissedWithValue:(NSString *)value;

@optional
- (void) customAlertViewWasCancelled:(CustomAlertView *)alert;
@end



@interface CustomAlertView : UIViewController <UITextFieldDelegate>
{
UIView *alertView;
UIView *backgroundView;
UITextField *inputField;

id<NSObject, CustomAlertViewDelegate> delegate;
}

@property (nonatomic, retain) IBOutlet UIView *alertView;
@property (nonatomic, retain) IBOutlet UIView *backgroundView;
@property (nonatomic, retain) IBOutlet UITextField *inputField;

@property (nonatomic, assign) IBOutlet id<CustomAlertViewDelegate, NSObject> delegate;
- (IBAction)show;
- (IBAction)dismiss:(id)sender;
@end


Une fois le fichier d'entête est terminé et enregistré, on peut sauter d'Interface Builder et créer notre interface. Je ne vais pas vous guider à travers le processus de construction de l'interface, mais je vais souligner les choses importantes. Voici ce que la vue sera ressemble dans Interface Builder:

Screen shot 2010-05-17 at 12.42.51 PM.png

Certaines choses importantes:
  • Le point de vue contenu doit être pas opaque et sa couleur de fond devrait être fixé à un blanc avec une opacité de 0%. L'alpha de la vue devrait être de 1,0. Alpha est héritée par les sous-vues, la couleur de fond n'est pas, donc en le rendant transparent en utilisant la couleur de fond, nous serons en mesure de voir tous les sous-vues. Si nous nous étions fixés alpha à 0.0 au lieu, nous ne serions pas capable de voir l'affichage des alertes;
  • La vue arrière est un UIImageView qui est la même taille que l'affichage du contenu. Ses attributs autosize sont définies de sorte qu'il redimensionne avec la vue du contenu et il est connecté à la prise backgroundView. Il doit pas être opaque et son alpha doit être réglé à 1,0. Même si nous sera d'animer l'alpha, nous voulons la plume afin de refléter la valeur finale;
  • Tous les éléments d'interface qui composent l'alerte à proprement parler sont tous sous-vues d'une seule instance de UIView, dont la couleur de fond est également mise à blanc avec une opacité de 0% et n'est pas opaque afin qu'il soit invisible. Cette vue est utilisée uniquement pour regrouper les éléments de sorte qu'ils peuvent être animés ensemble et est ce que la prise alertView pointera vers;
  • La vue d'alerte n'est pas centré. Dans ce cas, nous voulons qu'il reste centré dans l'espace restant au-dessus du clavier;
  • The OK bouton a son tag mis à 1 000 dans l'inspecteur de l'attribut. L' Cancel bouton a son ensemble de balises pour 1001. Ces chiffres correspondent aux valeurs de l'énumération que nous avons créé dans le fichier d'entête
  • Fichier de propriétaire est le délégué du champ de texte. Cela permet au contrôleur d'être averti lorsque l'utilisateur appuie sur la touche de retour sur le clavier


Une fois l'interface est créé, tout ce qui reste à faire est d'implémenter la classe contrôleur. Voici la mise en œuvre; je vais vous expliquer ce qui se passe en un instant:

CustomAlertView.m
#import "CustomAlertView.h"
#import "UIView-AlertAnimations.h"
#import <QuartzCore/QuartzCore.h>

@interface CustomAlertView()
- (void)alertDidFadeOut;
@end


@implementation CustomAlertView
@synthesize alertView;
@synthesize backgroundView;
@synthesize inputField;
@synthesize delegate;
#pragma mark -
#pragma mark IBActions
- (IBAction)show
{
// Retaining self is odd, but we do it to make this "fire and forget"
[self retain];

// We need to add it to the window, which we can get from the delegate
id appDelegate = [[UIApplication sharedApplication] delegate];
UIWindow *window = [appDelegate window];
[window addSubview:self.view];

// Make sure the alert covers the whole window
self.view.frame = window.frame;
self.view.center = window.center;

// "Pop in" animation for alert
[alertView doPopInAnimationWithDelegate:self];

// "Fade in" animation for background
[backgroundView doFadeInAnimation];
}

- (IBAction)dismiss:(id)sender
{
[inputField resignFirstResponder];
[UIView beginAnimations:nil context:nil];
self.view.alpha = 0.0;
[UIView commitAnimations];

[self performSelector:@selector(alertDidFadeOut) withObject:nil afterDelay:0.5];



if (sender == self || [sender tag] == CustomAlertViewButtonTagOk)
[delegate CustomAlertView:self wasDismissedWithValue:inputField.text];
else
{
if ([delegate respondsToSelector:@selector(customAlertViewWasCancelled:)])
[delegate customAlertViewWasCancelled:self];
}

}

#pragma mark -
- (void)viewDidUnload
{
[super viewDidUnload];
self.alertView = nil;
self.backgroundView = nil;
self.inputField = nil;
}

- (void)dealloc
{
[alertView release];
[backgroundView release];
[inputField release];
[super dealloc];
}

#pragma mark -
#pragma mark Private Methods
- (void)alertDidFadeOut
{
[self.view removeFromSuperview];
[self autorelease];
}

#pragma mark -
#pragma mark CAAnimation Delegate Methods
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.inputField becomeFirstResponder];
}

#pragma mark -
#pragma mark Text Field Delegate Methods
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self dismiss:self];
return YES;
}

@end



Alors, qu'est-ce qu'on fait ici? Tout d'abord, nous commençons par l'importation de la catégorie avec nos animations affichage des alertes et aussi QuartzCore.h ce qui nous donne accès à tous les types de données utilisés dans les Core Animation.

#import "CustomAlertView.h"
#import "UIView-AlertAnimations.h"
#import <QuartzCore/QuartzCore.h>

Ensuite, nous déclarons une extension de classe avec une méthode unique. En mettant cette méthode ici, nous pouvons l'utiliser n'importe où dans notre classe sans avoir les avertissements du compilateur et pourtant nous ne le diffuse pas l'existence de cette méthode pour le monde. C'est, essentiellement, une méthode privée. Dans un langage dynamique comme Objective-C, il n'existe aucune méthode vraiment privé, mais puisque la méthode n'est pas déclarée dans le fichier en-tête, c'est notre façon de dire «ceci est la nôtre, ne pas toucher». Cette méthode, que vous verrez dans un instant, sera appelé après l'alerte a été rejetée pour le retirer de son SuperView. Nous ne voulons pas de l'enlever qu'après l'animation fade-out est terminé, c'est pourquoi nous avons déclaré une méthode distincte.

@interface CustomAlertView()
- (void)alertDidFadeOut;
@end


Après nous synthétisons nos propriétés, la première méthode que nous écrivons est spectacle. C'est la méthode qui est appelée à bien ... afficher l'alerte. J'ai adapté le nom de méthode utilisée dans UIAlertView et fait également un IBAction afin qu'il puisse être déclenchée directement à l'intérieur d'un fichier nib.

Le plus étrange partie de cette méthode est qu'elle conserve réellement soi-même. C'est quelque chose que vous êtes généralement ne vont pas vouloir le faire. Depuis que nous avons mis en œuvre notre vision d'alerte comme un contrôleur de vue au lieu d'une sous-classe UIView comme UIAlertView, nous avons besoin de tricher un peu, car un contrôleur de vue n'est pas retenu par quelque chose par la vertu de son point de vue être dans la hiérarchie d'affichage. Ce n'est pas faux - que nous allons garder notre bookend avec un communiqué (ainsi, en réalité, une autorelease) donc pas de fuite de mémoire, mais il est inhabituel et non quelque chose que vous allez avoir à utiliser dans des endroits très nombreuses . Quand vous retenez l'auto, vous avez besoin de prendre un long regard dur à votre code et vous assurer d'avoir une sacrée bonne raison pour le faire. Dans ce cas, nous le faisons.

Après avoir retenu, on récupère une référence à la fenêtre par l'intermédiaire du délégué de l'application et d'ajouter notre point de vue à la fenêtre, correspondant de son cadre. Ensuite, nous appelons les deux méthodes d'animation nous avons créé précédemment à s'estomper dans l'image avec le dégradé circulaire et "pop" dans la vue d'alerte:

- (IBAction)show
{
// Retaining self is odd, but we do it to make this "fire and forget"
[self retain];

// We need to add it to the window, which we can get from the delegate
id appDelegate = [[UIApplication sharedApplication] delegate];
UIWindow *window = [appDelegate window];
[window addSubview:self.view];

// Make sure the alert covers the whole window
self.view.frame = window.frame;
self.view.center = window.center;

// "Pop in" animation for alert
[alertView doPopInAnimationWithDelegate:self];

// "Fade in" animation for background
[backgroundView doFadeInAnimation];
}

La méthode la prochaine action que nous écrivons est celle qui est appelée par les deux boutons sur le qui-vive. Peu importe quel bouton a été poussé, nous voulons que le champ de texte à démissionner statut de Première répondeur afin que le clavier disparaît, et nous voulons que l'alerte à s'estomper. Nous allons utiliser des animations implicitement cette fois et ensuite utiliser performSelector withObject: afterDelay: pour déclencher notre méthode privée qui va enlever la vue de son SuperView. Après cela, nous vérifions valeur de la balise de l'expéditeur pour voir quelle méthode déléguée à notifier.

- (IBAction)dismiss:(id)sender
{
[inputField resignFirstResponder];
[UIView beginAnimations:nil context:nil];
self.view.alpha = 0.0;
[UIView commitAnimations];

[self performSelector:@selector(alertDidFadeOut) withObject:nil afterDelay:0.5];

if (sender == self || [sender tag] == CustomAlertViewButtonTagOk)
[delegate CustomAlertView:self wasDismissedWithValue:inputField.text];
else
{
if ([delegate respondsToSelector:@selector(customAlertViewWasCancelled:)])
[delegate customAlertViewWasCancelled:self];
}

}

Le viewDidUnload et dealloc sont standard tourbière, donc il ne sert à rien d'en discuter. La méthode suivante après celles-ci est notre "privée" méthode. Il ne fait que supprimer l'affichage maintenant invisible alerte de la hiérarchie d'affichage et de autoreleases auto pour que le contrôleur de vue sera publié à la fin de l'itération de la boucle d'exécution actuel. Nous ne voulons pas d'utiliser la libération parce que nous ne voulez vraiment pas l'objet disparaît alors un de ses méthode s'exécute:

- (void)alertDidFadeOut
{
[self.view removeFromSuperview];
[self autorelease];
}

Auparavant, lorsque nous avons créé le "pop" l'animation, nous avons spécifié l'auto en tant que délégué. La méthode suivante nous mettons en œuvre est appelée lorsque le "pop" animation complète en vertu de ce fait. Tout ce que nous faisons ici est de s'assurer que le clavier est montré à l'utilisateur et associée à notre champ de texte. Nous faisons tout cela simplement en faisant le champ de texte du premier intervenant:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.inputField becomeFirstResponder];
}

Enfin, nous mettons en œuvre l'une des méthodes texte du délégué sur le terrain de sorte que lorsque l'utilisateur appuie sur la touche de retour sur le clavier, il rejette le dialogue.

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self dismiss:self];
return YES;
}

À ce stade, nous avons terminé. Nous pouvons maintenant utiliser cette vue alerte personnalisée exactement de la même façon que nous utilisons UIAlertView:

    CustomAlertView *alert = [[CustomAlertView alloc]init];
alert.delegate = self;
[alert show];
[alert release];


Comme je l'ai indiqué précédemment, vous pouvez utiliser cette même technique pour présenter tout ce que vous pouvez construire dans Interface Builder, et le résultat sera un objet hautement réutilisables alerte.

Aucun commentaire: