UIScrollView: Extention du protocole associé au delegate

permalien, lien court

Après le grand ménage sur le blog, plein d’enthousiasme, j’ai entrepris de rédiger un tutoriel détaillé à propos du développement d’un ImageViewer pour iPhone, avec une interface la plus proche possible de l’application Photos. Au cours de l’écriture de ce billet, j’ai constaté 2 choses:

  • Tout d’abord, ça fait un billet sacrément long, à l’écriture comme à la lecture.
  • Ensuite et surtout, j’ai constaté que l’intérêt était assez limité car l’exercice s’est révélé relativement simple.

En revanche, lors du développement de mon ImageViewer, il y a un aspect qui a attiré mon attention: l’extension d’un protocole. Ce point précis peut présenter une certaine difficulté pour peu qu’on n’y ai jamais été confronté et il m’a été assez difficile de trouver des exemples clair sur le web.
J’ai donc décidé de rédiger un billet plus court, qui détaille la façon de dériver la classe UIScrollView tout en étendant le protocole UIScrollViewDelegate associé.

Si vous souhaitez, comme moi, reproduire l’interface de l’application Photos de votre iPhone, vous allez sans doute créer un UIScrollView afin de permettre à l’utilisateur de naviguer d’une image à l’autre. Pour ce faire, le projet “Scrolling“ fourni en exemple dans la documentation d’Apple constitue un bon point de départ. La logique du scroll est contenue dans les méthodes viewDidLoad et layoutScrollImages du fichier MyViewController.m mais il vous faudra l’adapter un peu à votre cas d’utilisation.

En revanche, j’ai constaté que les événements de Touch n’étaient pas transmis par la UIScrollView. Gênant lorsque l’on veut ajouter des interactions en plus du scroll.

Pour capturer ces événements, qui ne sont pas transmis par la UIScrollView, la logique voudrait que l’on créé une classe qui étends la scrollView.

En surchargeant la méthode qui transmet les événements, on obtient ce qui suit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// TapScrollView.h
@interface TapScrollView : UIScrollView {
}
@end

// TapScrollView.m
#import "TapScrollView.h"

@implementation TapScrollView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

[self.delegate tap];
[super touchesBegan:touches withEvent:event];
}
@end

À ce stade, il faut ajouter la méthode tap au protocole qui défini le delegate de notre TapScrollView. On procède en ajoutant la définition d’un nouveau protocole dans le fichier TapScrollView.h.

Ce protocole va étendre UIScrollViewDelegate afin que l’on puisse encore recevoir les événements de touch, comme suit:

1
2
3
4
5
6
7
8
9
10
// TapScrollView.h
@protocol TapScrollViewDelegate <UIScrollViewDelegate>

- (void) tap;

@end

@interface TapScrollView : UIScrollView {
}
@end

Si on en reste là, la propriété delegate reste celle défini par UIScrollView car nous ne l’avons pas surchargée. On peut donc supposer que celle-ci répond au protocole UIScrollViewDelegate, et non pas TapScrollViewDelegate comme on l’aurai souhaité. Dans ce cas, il suffit de surcharger la propriété delegate dans TapScrollView.
Voilà le code obtenu:

1
2
3
4
5
6
7
8
9
10
11
12
// TapScrollView.h
@protocol TapScrollViewDelegate <UIScrollViewDelegate>

- (void) tap;

@end

@interface TapScrollView : UIScrollView {
id<TapScrollViewDelegate> delegate;
}
@property (nonatomic, assign) id<TapScrollViewDelegate> delegate;
@end

Ok, on approche du but. TapScrollView réponds bien au protocole et la méthode tap sera correctement appelée elle-aussi. En revanche, les méthodes du protocole UIScrollViewDelegate ne sont plus appelés…
On tourne en rond.
J’ai mis pas mal de temps à trouver la solution. L’astuce consiste à surcharger aussi les accès à la propriété delegate, de façon à transmettre les appels dans les deux sens (UIScrollView vers TapScrollView et vice-versa).
Voilà le code final:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// TapScrollView.m
#import "TapScrollView.h"

@implementation TapScrollView

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.delegate tap];
[super touchesBegan:touches withEvent:event];
}

- (id<TapScrollViewDelegate>) delegate {
return (id<TapScrollViewDelegate>)super.delegate;
}

- (void) setDelegate :(id<TapScrollViewDelegate>) aDelegate {
super.delegate = aDelegate;
}
@end

Vous voilà donc avec une ScrollView qui, en plus de capturer les événements relatifs au scroll, va transmettre les actions de votre choix. Ici, un simple tap, mais le principe reste valable pour des actions plus complexes.

Quelques liens utilies pour finir: