Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Geleitwort
Vorwort
1 Hello iPhone
2 Die Reise nach iOS
3 Sehen und anfassen
4 Alles unter Kontrolle
5 Daten, Tabellen und Controller
6 Models, Layer, Animationen
7 Programmieren, aber sicher
8 Datenserialisierung und Internetzugriff
9 Multimedia
10 Jahrmarkt der Nützlichkeiten
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
Apps programmieren für iPhone und iPad von Klaus M. Rodewig, Clemens Wagner
Das umfassende Handbuch
Buch: Apps programmieren für iPhone und iPad

Apps programmieren für iPhone und iPad
Rheinwerk Computing
1172 S., geb., mit DVD
49,90 Euro, ISBN 978-3-8362-2734-6
Pfeil 6 Models, Layer, Animationen
Pfeil 6.1 Modell und Controller
Pfeil 6.1.1 iOS Next Topmodel
Pfeil 6.1.2 View an Controller
Pfeil 6.1.3 Gerätebewegungen auswerten
Pfeil 6.1.4 Modell an Controller
Pfeil 6.1.5 Undo und Redo
Pfeil 6.1.6 Unit-Tests
Pfeil 6.2 Als die Views das Laufen lernten
Pfeil 6.2.1 Animationen mit Blöcken
Pfeil 6.2.2 Transitionen
Pfeil 6.2.3 Zur Animation? Bitte jeder nur einen Block!
Pfeil 6.3 Core Animation
Pfeil 6.3.1 Layer
Pfeil 6.3.2 Vordefinierte Layer-Klassen
Pfeil 6.3.3 Der Layer mit der Maske
Pfeil 6.3.4 Unser Button soll schöner werden
Pfeil 6.3.5 Spieglein, Spieglein an der Wand
Pfeil 6.3.6 Der bewegte Layer
Pfeil 6.3.7 Daumenkino
Pfeil 6.3.8 Relativitätstheorie
Pfeil 6.3.9 Der View, der Layer, seine Animation und ihr Liebhaber
Pfeil 6.3.10 Transaktionen
Pfeil 6.3.11 Die 3. Dimension
Pfeil 6.4 Scrollviews und gekachelte Layer
Pfeil 6.4.1 Scrollen und Zoomen
Pfeil 6.4.2 Die Eventverarbeitung
Pfeil 6.4.3 Scharfe Kurven
Pfeil 6.4.4 Ganz großes Kino
Pfeil 6.4.5 PDF-Dateien anzeigen
Pfeil 6.5 Über diese Brücke musst du gehen
Pfeil 6.5.1 Toll-free Bridging und ARC
Pfeil 6.5.2 C-Frameworks und ARC
Pfeil 6.6 Was Sie schon immer über Instruments wissen wollten, aber nie zu fragen wagten
Pfeil 6.6.1 Spiel mir das Lied vom Leak
Pfeil 6.6.2 Ich folgte einem Zombie
Pfeil 6.6.3 Time Bandits
Pfeil 6.6.4 Instruments und der Analyzer

Rheinwerk Computing - Zum Seitenanfang

6.4Scrollviews und gekachelte LayerZur nächsten Überschrift

Ein View kann in der Regel nur so viel anzeigen, wie in seine Fläche passt. Wenn Sie Objekte darstellen möchten, die größer als die verfügbare Fläche sind, können Sie dafür einen Scrollview verwenden. Ein Scrollview zeigt von seinen enthaltenen Views nur einen Ausschnitt an, der auf die Fläche des Scrollviews passt. Der Nutzer kann diesen Ausschnitt durch Wischbewegungen verändern. Scrollviews unterstützen außerdem das Zoomen des Inhalts. Dabei bewegen Sie zwei Finger aufeinander zu, um den Inhalt zu verkleinern und somit den angezeigten Ausschnitt zu vergrößern, oder Sie bewegen die Finger voneinander weg, um den Inhalt zu vergrößern und somit den angezeigten Ausschnitt zu verkleinern. Diese beiden Gesten bezeichnet man übrigens als Pinchgesten.


Rheinwerk Computing - Zum Seitenanfang

6.4.1Scrollen und ZoomenZur nächsten ÜberschriftZur vorigen Überschrift

Wenn Sie einen Scrollview verwenden möchten, müssen Sie einen View mit der Klasse UIScrollView anlegen. Scrollviews verfügen über eine Zeichenfläche für die Darstellung des Inhalts, die Sie über zwei Propertys beschreiben. Über die Property contentSize legen Sie die Größe dieser Fläche fest und über contentOffset die relative Verschiebung zur linken oberen Ecke des Scrollviews. Dabei misst sich dieser Abstand von der Ecke des Scrollviews zu der Ecke der Fläche, so dass die beiden Koordinatenwerte immer positiv sind. Abbildung 6.33 stellt den Content-Offset und die Content-Size durch einen beziehungsweise zwei Pfeile dar.

Abbildung

Abbildung 6.33 Inhaltsfläche des Scrollviews

Die weiße Scrollview-Fläche in der Abbildung entspricht dem Ausschnitt, den der Nutzer sehen kann. Über die Property contentInset können Sie außerdem einen Rand um den Inhalt legen, dessen Breite Sie über den Größeninspektor des Interface Builders festlegen können. Falls Sie die Werte per Programmcode erzeugen wollen, können Sie über die Funktion UIEdgeInsetsMake die notwendige C-Struktur anlegen.

Es kommt auf die Größe an

Der Scrollview betrachtet nur die durch die contentSize beschriebene Fläche als scrollbar. Nach der Erzeugung des Scrollviews hat diese Fläche die Größe (0, 0). Wenn Sie diesen Wert nicht ändern, sehen Sie zwar den Inhalt, können ihn allerdings nicht verschieben – beziehungsweise springt er nach einer Verschiebung wieder in seine Ursprungsposition zurück. Denken Sie also immer daran, die Content-Size des Scrollviews zu setzen.

Diese Innenabstände sind allerdings nicht nur mit festen Werten nützlich; sie erlauben Ihnen auch, den Contentview im Scrollview zu zentrieren, wenn seine Fläche kleiner als die Fläche des Scrollviews ist. Die Methode updateInsetsForScrollView: aus Listing 6.81 zeigt den dafür notwendigen Code. Da sie über die Funktion fmaxf verhindert, dass die Methode negative Werte als Insets verwendet, können Sie diese Methode auch dann getrost aufrufen, wenn der Inhalt nur in einer oder sogar keiner Dimension zu klein ist.

- (void)updateInsetsForScrollView:(UIScrollView *)inView {
CGSize tSize = inView.bounds.size;
CGSize theContentSize = inView.contentSize;
CGFloat theX = fmaxf((theSize.width –
theContentSize.width) / 2.0, 0.0);
CGFloat theY = fmaxf((theSize.height –
theContentSize.height) / 2.0, 0.0);

inView.contentInset =
UIEdgeInsetsMake(theY, theX, theY, theX);
}

Listing 6.81 Kleinen Inhalt im Scrollview zentrieren

In diese Inhaltsfläche können Sie beliebige Views über die Methode addSubview: des Scrollviews legen. Dabei interpretiert Cocoa Touch die Framekoordinaten relativ zu der Inhaltsfläche des Scrollviews.

Damit Sie Scrollviews auch am lebenden Objekt kennenlernen, gibt es dazu ein Beispiel zum Ausschneiden und Nachprogrammieren. Sie brauchen für das Beispiel ein möglichst großes Bild. Dafür können Sie das Bild flower.jpg aus dem Beispielprojekt ScrollView oder auch ein eigenes verwenden. Erstellen Sie in Xcode ein neues Projekt aus der Vorlage Single View Application, und schalten Sie im Storyboard das Autolayout über den Dateiinspektor aus.

Projektinformation

Den Quellcode des folgenden Beispiels finden Sie auf der DVD unter Code/Apps/iOS5/ScrollView oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_3/Apps/iOS5/ScrollView.

Das Storyboard des Projekts enthält drei Viewcontroller für die unterschiedlichen Beispiele in diesem Abschnitt. Sie sind jeweils nach dem zugehörenden Abschnitt benannt. Um den jeweiligen Viewcontroller in Aktion zu sehen, brauchen Sie nur in dessen Attributinspektor das Häkchen Is Initial View Controller zu aktivieren. Zu Beginn sollten Sie also den ersten Viewcontroller mit dem Label Scrollen und zoomen aktivieren.

Nach dem Anlegen des Projekts fügen Sie das Bild zu der Gruppe Supporting Files hinzu. Danach öffnen Sie das Storyboard und legen einen Scrollview in den View des Viewcontrollers. Der Interface Builder zieht dieses neue Objekt genau in der Größe des Views an und stellt auch die Autosizingmask so ein, dass der Scrollview immer die komplette Fläche des Views belegt (siehe Abbildung 6.34).

Abbildung

Abbildung 6.34 Aufbau des Views für das Scrollview-Beispiel

Danach fügen Sie einen Imageview in den Scrollview ein. Über den Attributinspektor des Imageviews können Sie unter Image das Bild einstellen, das der Imageview anzeigen soll. Wenn Sie auf den Pfeil neben dem Eingabefeld klicken, können Sie das Bild auswählen, das Sie zu dem Projekt hinzugefügt haben. Wenn Sie es auswählen, zeigt der View im Interface Builder es wahrscheinlich verzerrt an. Öffnen Sie den Größeninspektor des Imageviews, und ändern Sie die Höhe und die Breite des Views auf die entsprechenden Werte des Bildes. Das Beispielbild flower.jpg hat eine Breite von 1.280 und eine Höhe von 850 Pixeln. Wenn Sie ein eigenes Bild verwenden, sehen Sie seine Größe im Dateiinspektor dieses Bildes in der Rubrik Image Properties.

Nach dem Sie diese Schritte ausgeführt haben, sollte der View im Interface Builder so wie in Abbildung 6.34 aussehen. Sie können die App auch starten. Sie zeigt Ihnen den gleichen Bildausschnitt an, jedoch macht sich der Scrollview noch nicht bemerkbar. Sie können so viel über den Bildschirm wischen, wie Sie wollen. Das Bild lässt sich nicht bewegen.

Das liegt daran, dass Sie die Inhaltsfläche des Scrollviews noch nicht festgelegt haben und die Content-Size somit noch 0 × 0 Punkte groß ist. Um diese Größe festzulegen, brauchen Sie ein Outlet auf den Scrollview. Ziehen Sie also im Interface Builder eine Verbindung vom Scrollview in den Header des Viewcontrollers, und nennen Sie dieses neue Outlet scrollView. Sie können jetzt die Inhaltsfläche in der Methode viewWillAppear: festlegen. Anstatt dafür eine feste Größe zu verwenden, sollten Sie lieber die Größe des Imageviews nehmen. Das geht am einfachsten, indem Sie auch für den Imageview ein Outlet anlegen. Wenn Sie dieses Outlet contentView nennen, können Sie die Größe der Inhaltsfläche festlegen, indem Sie der Property contentSize des Scrollviews die Größe des Imageviews zuweisen:

- (void)viewWillAppear:(BOOL)inAppear {
[super viewWillAppear:inAppear];
self.scrollView.contentSize = self.contentView.frame.size;
}

Listing 6.82 Größe der Inhaltsfläche festlegen

Nach dieser Änderung können Sie den Bildausschnitt im Scrollview ändern, indem Sie mit einem Finger über den Bildschirm des iPhones streichen. Zwar lässt sich nun jeder Ausschnitt des Bildes betrachten, hingegen nicht das gesamte Bild auf einmal, was sich allerdings über die Zoomfunktion des Scrollviews erreichen lässt. Um sie einzuschalten, müssen Sie einen Skalierungsbereich, das Delegate und den zoombaren View festlegen.

Der Skalierungsbereich legt fest, wie stark der Nutzer den Inhalt verkleinern beziehungsweise vergrößern darf, und lässt sich im Attributinspektor unter Zoom einstellen. Dabei sollte der Wert Min größer als 0 und kleiner oder gleich Max sein. Verwenden Sie hier 0,25 und 4 als Werte. Dann können Sie den Inhalt auf ein Viertel seiner Originalgröße verkleinern beziehungsweise auf das Vierfache vergrößern. Per Programmcode können Sie über die Propertys minimumZoomScale und maximumZoomScale auf diese Werte zugreifen.

Sie müssen dem Scrollview jetzt nur noch mitteilen, welchen View er skalieren soll, was über sein Delegate geschieht. Dafür verwenden Sie, wie in den meisten Fällen von Viewdelegates, den Viewcontroller, der den Scrollview anzeigt. Erweitern Sie dazu die Deklaration des Viewcontrollers um das Protokoll UIScrollViewDelegate:

@interface ViewController : 
UIViewController<UIScrollViewDelegate>

@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIImageView *contentView;

@end

Listing 6.83 Erweiterung des Viewcontrollers zu einem Scrollview-Delegate

Danach können Sie das Delegate auf die gewohnte Art über den Verbindungsinspektor des Scrollviews festlegen. Über die Delegate-Methode viewForZoomingInScrollView: legen Sie fest, welchen View der Scrollview vergrößern und verkleinern darf. Geben Sie hier den Imageview so wie in Listing 6.84 zurück. Danach sollten Sie über Pinchgesten das Bild in der App skalieren können.

- (UIView *)viewForZoomingInScrollView:  
(UIScrollView *)inScrollView {
return self.contentView;
}

Listing 6.84 Zooming des Scrollviews einschalten

The One and Only

Damit ein Scrollview zoomen kann, müssen Sie folgende Punkte beachten:

  • Legen Sie den minimalen und maximalen Skalierungsfaktor für den Scrollview fest.
  • Weisen Sie dem Scrollview ein Delegate zu, in dessen Klasse Sie die Methode viewForZoomingInScrollView: implementieren.

Falls das Zoomen in einem Scrollview nicht funktionieren sollte, ist es ratsam, zunächst diese beiden Punkte zu überprüfen.

Wie Sie gesehen haben, kann ein Scrollview allerdings nur einen seiner Subviews skalieren. Es empfiehlt sich deshalb immer, nur einen direkten Subview in den Scrollview zu legen, in den Sie alle anderen Subviews hineinlegen. Für diesen Containerview können Sie beispielsweise ein Objekt der Klasse UIView verwenden. Über diesen View können Sie auch einfach die richtige Content-Size bestimmen, wie Sie in Listing 6.82 gesehen haben.

Scrollviews und Autolayout

Falls Sie Scrollviews zusammen mit Autolayout verwenden möchten, müssen Sie abwarten, bis das Autolayout die Größen und Positionen der Views berechnet hat, um die Content-Size zu setzen. Die Methode viewWillAppear: im Beispielprojekt ist dafür allerdings zu früh, da anscheinend bei eingeschaltetem Autolayout Cocoa Touch die Content-Size danach noch einmal auf (0, 0) setzt.

Allerdings können Sie sich vom Viewcontroller informieren lassen, wenn sein View das Layout abgeschlossen hat. Dazu überschreiben Sie die Methode viewDidLayoutSubviews; Listing 6.85 zeigt die entsprechende Implementierung.

- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGSize theSize = self.contentView.frame.size;

self.scrollView.contentSize = theSize;
}

Listing 6.85 Aktualisieren der Content-Size bei Autolayout

Sie finden dazu auch ein entsprechendes Beispielprojekt unter Code/Apps/iOS7/AutolayoutScrollView auf der beiliegenden DVD beziehungsweise unter https://github.com/Cocoaneheads/iPhone/tree/Auflage_3/Apps/iOS7/AutolayoutScrollView im Github-Repository.


Rheinwerk Computing - Zum Seitenanfang

6.4.2Die EventverarbeitungZur nächsten ÜberschriftZur vorigen Überschrift

Sie können jetzt in Ihrem Scrollviews scrollen und zoomen. Viele Apps legen jedoch noch weitere Gesten auf den Scrollview, um seine Nutzung zu vereinfachen. Beispielsweise hat sich die Double-Tab-Geste zum Vergrößern inzwischen eingebürgert. Dabei interpretiert die App ein doppeltes Antippen des Scrollviews als Anweisung, den Inhalt zu vergrößern. Diese Geste können Sie relativ leicht über die Klasse UITapGestureRecognizer umsetzen.

Für die Implementierung ziehen Sie einen solchen Recognizer aus der Bibliothek auf den View im Scrollview. Der Interface Builder legt daraufhin ein entsprechendes Objekt in der obersten Ebene der Szene an. Öffnen Sie den Attributinspektor dieses Objekts, und stellen Sie den Wert für Taps auf 2. Durch diese Einstellung legen Sie fest, dass der Recognizer erst bei zwei aufeinanderfolgenden Taps seine Action-Methode aufruft, die Sie als Nächstes anlegen.

Ziehen Sie dazu eine Verbindung vom Recognizer-Objekt in den Deklarationsblock in der Headerdatei des Viewcontrollers. In dem Dialog für die Verbindung wählen Sie unter Connection den Punkt Action aus, und als Namen der Action-Methode geben Sie zoomIn ein. Die Implementierung dieser Methode verdoppelt einfach den Skalierungswert des Scrollviews:

- (IBAction)zoomIn:(id)inRecognizer {
CGFloat theScale = self.scrollView.zoomScale;
[self.scrollView setZoomScale:theScale * 2.0
animated:YES];
}

Listing 6.86 Action-Methode für Double-Tap

So weit, so einfach. Analog dazu können Sie noch weitere Gesture-Recognizer in den Scrollview legen, um weitere Gesten auszuwerten. Das geht so lange gut, wie die Gesten der Recognizer nicht mit denen des Scrollviews kollidieren. Wenn Sie beispielsweise einen Recognizer der Klasse UIPanGestureRecognizer in den Scrollview legen, können Sie nicht mehr scrollen. Das liegt daran, dass der Scrollview und der Recognizer auf die gleichen Gesten reagieren. Der Scrollview wertet zwar alle Fingerbewegungen auf seiner Fläche aus, er verschiebt seinen Inhalt allerdings nur, wenn er die Ereignisse nicht an seine Subviews weiterleitet. Er kann also immer nur eins gleichzeitig machen: Entweder scrollt er, oder er leitet die Ereignisse weiter.

Scrollviews und Gesten

Solche Kollisionen zwischen Scrollview- und anderen Gesten treten bei der App-Entwicklung unter Cocoa Touch relativ häufig auf. Der erste und wichtigste Schritt zu ihrer Auflösung ist eine gute Planung. Überlegen Sie sich also zuerst klare Kriterien, wie sich die Scrollview-Gesten von den anderen Gesten unterscheiden lassen, bevor Sie zur Implementierung schreiten. Diese Kriterien erleichtern Ihnen nicht nur die Implementierung, sondern schlagen sich meistens auch in einer besseren Bedienbarkeit der App nieder.

Wenn Sie einen Slider in den Scrollview des Beispielprojekts legen, können Sie ihn verstellen und trotzdem auch das Bild scrollen. Obwohl die Geste zum Verstellen des Sliders der zum Scrollen entspricht, kann Cocoa Touch Ihre Fingerbewegung immer dem richtigen Eingabeelement zuordnen. Dabei geschieht die Zuordnung einfach über das Element unter dem Finger. Wenn die Fingerbewegung auf dem Slider beginnt, bekommt dieser alle Ereignisse. Ansonsten sendet Cocoa Touch die Ereignisse an den Scrollview.

Ob der Scrollview oder der View (also hier der Slider) die Ereignisse verarbeiten darf, entscheidet Cocoa Touch anhand von zwei Methoden des Scrollviews. Sie können diese beiden Methoden in Unterklassen überschreiben, um die Ereignisverarbeitung des Scrollviews an Ihre Bedürfnisse anzupassen. Dabei entscheidet der Scrollview anhand der Methode touchesShouldBegin:withEvent:inContentView:, ob der angegebene View überhaupt Ereignisse empfangen soll. Die Implementierung dieser Methode in der Klasse UIScrollView liefert immer YES zurück.

Der Scrollview ruft diese Methode allerdings nur für Subviews auf, deren Property userInteractionEnabled den Wert YES hat. Über die Property canCancelContentTouches können Sie festlegen, ob sich der Scrollview die Ereignisverarbeitung zurückholen darf, wenn der Nutzer den Finger weit genug über den Subview hinausschiebt. Diese Geste kann er ja in diesem Fall als Scrollgeste verstehen. Diese Entscheidung trifft die Methode touchesShouldCancelInContentView:, die der Scrollview befragt, ob er nicht doch lieber scrollen darf. Wenn sie YES zurückliefert, bricht der Scrollview die Ereignisverarbeitung im Subview ab, indem er an diesen View ein touchesCancelled:withEvent: sendet. Die Standardimplementierung dieser Methode liefert nur dann NO, wenn der Contentview die Klasse UIControl oder eine Unterklasse davon hat. Der Slider und der Scrollview im Beispiel harmonieren so schön miteinander, weil die Klasse UISlider eine Unterklasse von UIControl ist. Den kompletten Entscheidungsablauf sehen Sie in Abbildung 6.35.

Das folgende Beispiel verdeutlicht, wie Sie das Verhalten eines Scrollviews so beeinflussen können, dass er mit Ihren Views zusammenarbeitet. Dazu erweitern Sie das Beispielprojekt, indem Sie die Viewcontroller-Szene im Storyboard duplizieren, also kopieren und wieder einfügen. Legen Sie die Kopie als initialen Viewcontroller des Storyboards fest, und löschen Sie den enthaltenen Imageview und den Slider. Stattdessen legen Sie einen neuen View (Klasse UIView) in den Scrollview, dessen Fläche größer als die des Scrollviews ist. In diesen View legen Sie einen weiteren View, der eine Fläche von 200 × 200 Punkten haben sollte. Achten Sie darauf, dass Sie bei beiden Views userInteractionEnabled eingeschaltet haben. Außerdem sollten Sie dem kleineren View eine andere Hintergrundfarbe geben, um ihn vom größeren unterscheiden zu können.

Der kleinere View bekommt die Klasse LineView, deren Deklaration und Implementierung Sie in Listing 6.87 beziehungsweise in Listing 6.88 finden.

@interface LineView : UIView

@property(nonatomic) CGPoint startPoint;
@property(nonatomic) CGPoint endPoint;

@end

Listing 6.87 Deklaration der Klasse »LineView«

Abbildung

Abbildung 6.35 Ereigniszuordnung des Scrollviews

@implementation LineView

@synthesize startPoint;
@synthesize endPoint;

- (void)drawRect:(CGRect)inRect {
UIBezierPath *thePath = [UIBezierPath bezierPath];

[[UIColor redColor] setStroke];
[thePath moveToPoint:self.startPoint];
[thePath addLineToPoint:self.endPoint];
[thePath stroke];
}
- (void)touchesBegan:(NSSet *)inTouches
withEvent:(UIEvent *)inEvent {
UITouch *theTouch = [inTouches anyObject];

self.startPoint = [theTouch locationInView:self];
}
- (void)touchesMoved:(NSSet *)inTouches
withEvent:(UIEvent *)inEvent {
UITouch *theTouch = [inTouches anyObject];

self.endPoint = [theTouch locationInView:self];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)inTouches
withEvent:(UIEvent *)inEvent {
UITouch *theTouch = [inTouches anyObject];

self.endPoint = [theTouch locationInView:self];
[self setNeedsDisplay];
}
@end

Listing 6.88 Implementierung der Klasse »LineView«

Die Views dieser Klasse zeichnen jeweils eine rote Linie vom Start- zum Endpunkt einer Geste. Sie verbinden also den Punkt der ersten Berührung des Views mit dem Punkt, an dem der Nutzer den Finger wieder vom Bildschirm genommen hat. Weisen Sie diese Klasse dem kleinen View über seinen Identitätsinspektor zu. Den größeren View sollten Sie außerdem mit dem Outlet contentView des Viewcontrollers verbinden. Dazu müssen Sie allerdings erst den Typ der Property von UIImageView auf UIView oder id ändern. Die Struktur des neuen Views sehen Sie in Abbildung 6.36.

Im Attributinspektor des Scrollviews schalten Sie die Einstellungen Delays Content Touches und Cancellable Content Touches aus. Wenn Sie die App mit dieser Einstellung starten, können Sie zwar rote Linien im Subview ziehen, jedoch nicht den Inhalt des Scrollviews verschieben. Das liegt daran, dass der Scrollview die Ereignisse immer an seine Subviews weiterleitet. Da er allerdings begonnene Gesten nicht abbrechen darf, hat er keine Möglichkeit, die Gesten in dem großen View zu beenden und die Fingerbewegungen in Scrollbewegungen umzusetzen. Das können Sie leicht nachvollziehen, wenn Sie den Entscheidungsweg für diese Einstellung in Abbildung 6.35 verfolgen.

Abbildung

Abbildung 6.36 Beispielview für die Ereignisverarbeitung

Schalten Sie nun Cancellable Content Touches im Attributinspektor des Scrollviews ein. Jetzt können Sie wieder scrollen, allerdings allenfalls einen kurzen roten Strich ziehen. Da der Lineview kein Control ist, liefert die Methode touchesShouldCancelInContentView: für diesen View immer YES und bricht die Gestenverarbeitung ab. An dieser Stelle können Sie durch eine eigene Implementierung der Methode in einer Unterklasse von UIScrollView eingreifen. Diese Implementierung sollte für den Lineview NO zurückliefern, damit dieser die Geste weiterverarbeiten kann.

Erstellen Sie dazu eine Unterklasse von UIScrollView. Es gibt mehrere Möglichkeiten, wie Sie die Methode touchesShouldCancelInContentView: überschreiben können. Der hier vorgestellte Weg vermeidet Abhängigkeiten zwischen der Scrollview-Unterklasse und der Klasse LineView. Dafür definieren Sie einfach eine neue Methode namens shouldCancelContentTouches in einer Kategorie der Klasse UIView. Dadurch können Sie diese Nachricht an alle Views schicken. Die Kategorie können Sie in die Implementierungsdatei der neuen Scrollview-Klasse schreiben, so dass sie folgendermaßen aussieht:

@implementation UIView(ScrollView)

- (BOOL)shouldCancelContentTouches {
return YES;
}

@end

@implementation ScrollView

- (BOOL)touchesShouldCancelInContentView:(UIView *)inView {
return [inView shouldCancelContentTouches] &&
[super touchesShouldCancelInContentView:inView];
}

@end

Listing 6.89 Abbruch der Gestenverarbeitung über die Views

Wenn Sie diese Unterklasse für Ihre Scrollviews verwenden, können deren Subviews selbst festlegen, ob der Scrollview eine Geste abbrechen darf. Bei Lineviews soll das beispielsweise nicht möglich sein. Dafür braucht diese Klasse nur die Methode shouldCancelContentTouches zu überschreiben und NO als Ergebnis zu liefern. Wenn Sie also die Methode in der Klasse LineView entsprechend implementieren und im Identitätsinspektor des Scrollviews im Storyboard die Klasse ScrollView einstellen, dann können Sie im LineView Linien ziehen und mit der restlichen Fläche des Scrollviews scrollen.

Verzögerte Gestenverarbeitung

Es gibt noch eine weitere Möglichkeit, wie der Scrollview das Ziel einer Nutzereingabe erkennen kann. Dabei wartet er eine kurze Zeitspanne, um die Intention des Nutzers zu erraten. Sie können diese Option über die Einstellung Delays Content Touches im Attributinspektor des Interface Builders oder die Property delaysContentTouches einschalten. Dann interpretiert der Scrollview eine Bewegung des Fingers über den Scrollview als Scrollgeste, wenn der Nutzer den Finger von Beginn an bewegt hat. Wenn er hingegen den Scrollview berührt, ohne über den Bildschirm zu streichen, und erst nach einem kurzen Moment den Finger bewegt, leitet der Scrollview die Touch-Events an den Contentview weiter.


Rheinwerk Computing - Zum Seitenanfang

6.4.3Scharfe KurvenZur nächsten ÜberschriftZur vorigen Überschrift

In Abschnitt 6.4.1, »Scrollen und Zoomen«, haben Sie gesehen, wie Sie die Zoomfunktion des Scrollviews nutzen können. Wenn Sie allerdings bei dem Lineview-Beispiel die größte Vergrößerung einstellen, stellen Sie fest, dass der View die Linie extrem unscharf darstellt. Das liegt daran, dass der Scrollview die Vergrößerung des Inhalts über die Transformationsmatrix des Views beziehungsweise dessen Layer realisiert. Diese Transformation ändert jedoch nicht die Anzahl der Pixel im Layer, sondern vergrößert nur jeden Pixel. Die Unschärfe entsteht schließlich durch das Antialiasing. Antialiasing bezeichnet Algorithmen in der Computergrafik zur Kantenglättung. Dabei verwenden die Verfahren zum Zeichnen der Punkte Farbabstufungen, wodurch gewollte Unschärfen entstehen, die Treppeneffekte bei schrägen oder gebogenen Linien vermeiden oder abschwächen.

Abbildung

Abbildung 6.37 Linie mit 1 und ohne 2 Antialiasing sowie pixelgenau 3

In Abbildung 6.37 sehen Sie, wie der Antialiasingeffekt bei einer vergrößerten Linie aussieht 1. Diese Unschärfen wirken manchmal sehr störend, weswegen Sie sie auch abschalten können 2. Dazu verwenden Sie die Core-Graphics-Funktion CGContextSetAllowsAntialiasing. Wenn Sie die Linie in der Klasse LineView beispielsweise ohne Antialiasing zeichnen möchten, können Sie die drawRect:-Methode folgendermaßen ändern:

- (void)drawRect:(CGRect)inRect {
UIBezierPath *thePath = [UIBezierPath bezierPath];

CGContextSetAllowsAntialiasing(
UIGraphicsGetCurrentContext(), NO);
[[UIColor redColor] setStroke];
[thePath moveToPoint:self.startPoint];
[thePath addLineToPoint:self.endPoint];
[thePath stroke];
}

Listing 6.90 Ausschalten des Antialiasings

Die Darstellung der Linie ist allerdings weder mit an- noch mit abgeschaltetem Antialiasing zufriedenstellend. Sie sieht entweder unscharf oder stufig aus. Eine gute Darstellung sollte vielmehr Bild 3 in Abbildung 6.37 entsprechen. Dazu müssen Sie dem View beibringen, dass er bei einer Vergrößerung mehr Pixel für die Darstellung verwendet. Mit der Klasse CATiledLayer stellt Ihnen Core Animation dafür eine Möglichkeit zur Verfügung.

Mehr Pixel für’s Geld

Über die Property contentsScale können Sie die Anzahl der Pixel eines Layers im Verhältnis zu seiner Größe ändern. Für den Wert 1 entspricht die Anzahl der Pixel des Layer in jeder Dimension genau der Größe, für den Wert 2 hat der Layer jeweils doppelt so viele Pixel usw. Der Standard-Layer eines Views hat für ein normales Display den Wert 1 und auf einem Retina-Display den Wert 2.

Sie können Wert dieser Property erhöhen, um eine genauere Darstellung zu erhalten. Allerdings vervierfachen Sie mit jeder Stufe auch den Speicherverbrauch des Layers. Sie sollten diese Möglichkeit also allenfalls für kleine Layer in Betracht ziehen.

Damit die Klasse LineView diese Layer-Klasse verwendet, müssen Sie natürlich ihre Klassenmethode layerClass überschreiben, wie Sie das bereits in Abschnitt 6.3.1, »Layer«, gemacht haben. Diesmal verwenden Sie allerdings die Klasse CATiledLayer. Außerdem müssen Sie das QuartzCore-Framework zum Projekt hinzufügen und dessen Header in LineView.m importieren (siehe Abschnitt 6.3, »Core Animation«). Listing 6.91 zeigt den Beginn der Implementierung dieser Klasse.

#import "LineView.h"
#import <QuartzCore/QuartzCore.h>

@implementation LineView

+ (id)layerClass {
return [CATiledLayer class];
}

Listing 6.91 Verwendung von »CATiledLayer« als Layer-Klasse

Sie brauchen jetzt nur noch den Layer in der Methode awakeFromNib zu konfigurieren. Sie müssen dem Layer nämlich mitteilen, wie stark Sie seinen Inhalt vergrößern wollen. Dazu verwenden Sie die Property levelsOfDetailBias, die standardmäßig den Wert 0 hat. Dieser Wert entspricht der Originalgröße des Inhalts, und bei größeren Werten stellt der Layer jeweils eine höhere Detailstufe zur Verfügung. Dabei verdoppelt jede Stufe jeweils die Detailgenauigkeit ihres Vorgängers. Der Wert 1 entspricht also der doppelten und der Wert 2 der vierfachen Genauigkeit. Da Sie die maximale Vergrößerung des Scrollviews auf 4 eingestellt haben, sollte also der Wert 2 ausreichen. Bei einer maximalen Vergrößerung von 8 sollten Sie hingegen den Wert 3 wählen.

- (void)awakeFromNib {
[super awakeFromNib];
CATiledLayer *theLayer = (CATiledLayer *)self.layer;

theLayer.levelsOfDetailBias = 2;
}

Listing 6.92 Einstellung der Detailgenauigkeit des Layers

Sie können außerdem die Anzahl der Detailebenen des Layers über die Property levelsOfDetail festlegen. Sie hat den Standardwert 1, was bedeutet, dass der Layer eine Detailebene besitzt. Diese Detailebene entspricht der höchsten Detailstufe, und der Layer stellt zum Zeichnen immer eine Fläche mit so vielen Pixeln zur Verfügung, wie dieser Stufe entsprechen.

Beispielsweise hat der Lineview im Projekt eine Größe von 200 × 200 Pixeln, und der Layer stellt zwei Vergrößerungsstufen – also eine vierfache Vergrößerung – noch pixelgenau dar. Somit hat die Zeichenfläche 800 × 800 Pixel, und alle Zeichenoperationen erfolgen in dieser Auflösung, auch wenn Sie die Fläche verkleinern. In diesem Fall verkleinert Cocoa Touch die Pixelgrafik auf die Anzeigegröße. Wenn Sie einen höheren Wert für die Property levelsOfDetail angeben, verwendet der Layer für kleinere Skalierungsstufen auch kleinere Pixelflächen. Dabei halbiert er für jede Stufe jeweils die Breite und die Höhe ihres Vorgängers. Wenn Sie beispielsweise levelsOfDetail auf 5 setzen, hat der Layer folgende Detailstufen:

Tabelle 6.8 Die Detailstufen des Beispielprojekts

Stufe Skalierungfaktor Auflösung

0

4

800 × 800

1

2

400 × 400

2

1

200 × 200

3

1/2

100 × 100

4

1/4

50 × 50

Detailstufen

Im Beispielprojekt machen sich die höheren Detailstufen nicht bemerkbar, weil die Grafik dafür zu wenig Details besitzt. Bei fein strukturierten Grafiken können Sie indes durch höhere Detailstufen durchaus eine Verbesserung der Darstellung erreichen.

Für die glatten Detailstufen zeichnet Cocoa Touch jeweils den Inhalt des Layers. Bei den Zwischenwerten verkleinert der Layer jeweils die Grafik mit dem nächsthöheren Skalierungsfaktor. Eine Skalierung von 1,5 stellt der Layer beispielsweise über eine Verkleinerung der Grafik mit dem Faktor 2 dar.


Rheinwerk Computing - Zum Seitenanfang

6.4.4Ganz großes KinoZur nächsten ÜberschriftZur vorigen Überschrift

Scrollviews stellen Inhalte dar, die größer als die verfügbare Fläche sind. Die Verwendung der Klasse CATiledLayer ermöglicht dabei sogar die Darstellung von Inhalten, die so groß sind, dass sie nicht komplett in den verfügbaren Speicher des Gerätes passen. Für die Anzeige muss dabei ja das Bild unkomprimiert vorliegen, und Sie können seinen Speicherbedarf folgendermaßen berechnen:

Breite × Höhe × Anzahl der Farben

Angenommen, Sie möchten ein RGB-Bild mit 2.000 × 3.000 Pixeln in Echtfarben und mit einem Alphakanal anzeigen. Dann hat es 4 Farben (Rot, Grün, Blau und Alpha), und es verbraucht 2.000 × 3.000 × 4 = 24 Millionen Byte. Diese Datenmenge führt auf vielen Geräten bereits zu Speicherwarnungen, auch wenn sie noch wesentlich mehr Speicher zur Verfügung haben. Ein entsprechendes Bild, das auf einem iPad 2 den kompletten Bildschirm belegt, braucht hingegen nur 1.024 × 768 × 4 ≈ 3 Millionen Byte. Das unbedachte Laden kompletter Bilder kann also sehr problematisch sein.

Auch bei diesem Problem hilft die Klasse CATiledLayer. Wie ihr Name schon verrät, unterteilt sie den Inhalt in gleich große Rechtecke oder auch Kacheln (Tiles) und stellt nur den Inhalt der Rechtecke dar, die die angezeigte Fläche des Layers schneiden. Ein Beispiel dafür finden Sie in Abbildung 6.38. Das große weiße Rechteck über der Blüte stellt die angezeigte Fläche des Layers dar, und die dünnen weißen Linien veranschaulichen die Unterteilungsrechtecke des Layers. Dabei zeichnet der Layer nur die nicht ausgegrauten Rechtecke. Über die Property tileSize der Klasse CATiledLayer legen Sie die Größe der Kacheln fest.

Abbildung

Abbildung 6.38 Unterteilung der Zeichenfläche in Rechtecke

Wenn Sie große Bilder über einen gekachelten Layer darstellen möchten, sollten Sie das Bild bereits als Kacheln laden. Andernfalls müssten Sie ja das komplette Bild in den Speicher laden, und der gekachelte Layer brächte keinen Vorteil.

Das Beispielprojekt ScrollView enthält die Klasse TiledImageView, die eine gekachelte Version des Blumenbildes anzeigt. Sie legt die Klasse CATiledLayer auf die bekannte Weise als Layer-Klasse fest und initialisiert die Kachelgröße in der Methode awakeFromNib.

const static CGFloat kImageWidth = 320.0;
const static CGFloat kImageHeight = 170.0;

+ (Class)layerClass {
return [CATiledLayer class];
}

- (void)awakeFromNib {
[super awakeFromNib];
CATiledLayer *theLayer = (CATiledLayer *)self.layer;

theLayer.tileSize = CGSizeMake(kImageWidth, kImageHeight);
}

Listing 6.93 Initialisierung des Tiled-Layers für die gekachelte Darstellung

Den Inhalt für den Layer stellen Sie über die Methode drawRect: zur Verfügung. Im Gegensatz zu den bisherigen Implementierungen dieser Methode in diesem Buch ist hier allerdings der Parameter relevant. Er enthält das Rechteck der Kachel, die der View zeichnen soll. Wenn die Kacheln kleiner als die sichtbare Fläche des Views sind, ruft Cocoa Touch diese Methode mehrmals mit unterschiedlichen Rechtecken auf.

In Listing 6.94 sehen Sie die Implementierung der drawRect:-Methode der Klasse TiledImageView. Sie berechnet nacheinander die Rechtecke für alle Kacheln des Bildes. Bei jeder Kachel, die das Rechteck des darzustellenden Bereiches schneidet, lädt die Methode das entsprechende Bild und zeichnet es in den aktuellen Grafikkontext.

Achtung, Cache

Die Implementierung in Listing 6.94 liest die Kachelbilder nicht über den Convenience-Konstruktor imageNamed: von UIImage, da Cocoa Touch sich diese Bilder im Speicher merkt (sie also cacht). Bei sehr großen Bildern würde das wieder zu Speicherwarnungen führen, die ja die gekachelte Darstellung gerade vermeiden soll. Sie sollten also diese Methode bei großen oder vielen Bildern nur mit Vorsicht einsetzen.

- (void)drawRect:(CGRect)inRect {
CGRect theTileFrame = CGRectMake(0.0, 0.0,
kImageWidth, kImageHeight);
NSBundle *theBundle = [NSBundle mainBundle];

NSLog(@"drawRect:%@", NSStringFromCGRect(inRect));
for(NSUInteger i = 0; i < 5; ++i) {
for(NSUInteger j = 0; j < 4; ++j) {
theTileFrame.origin.x = j * kImageWidth;
theTileFrame.origin.y = i * kImageHeight;

if(CGRectIntersectsRect(inRect, theTileFrame)) {
NSString *theFile = [NSString
stringWithFormat:@"flower_%ux%u", i, j];
NSString *thePath = [theBundle
pathForResource:theFile ofType:@"jpg"];
UIImage *theImage = [UIImage
imageWithContentsOfFile:thePath];

[theImage drawAtPoint:theTileFrame.origin];
}
}
}
}

Listing 6.94 Zeichnen der Kacheln

Mit dieser Implementierung stellt der View das Bild dar. Dass er es dabei tatsächlich über einzelne Kacheln zeichnet, sehen Sie einerseits an der Log-Ausgabe, die die Rechtecke der Kacheln anzeigt. Andererseits sehen Sie aber auch bei der ersten Anzeige auf dem Bildschirm oder beim Scrollen, wie der View die einzelnen Rechtecke mit den Bildern füllt. An der Log-Ausgabe sehen Sie außerdem, dass der Layer die Rechtecke nicht von oben links nach unten rechts zeichnet.

Darstellung über den Layer

Es ist natürlich auch möglich, den Layer-Inhalt über die Delegate-Methode drawLayer:inContext: bereitzustellen. Allerdings müssen Sie das Clipping-Rechteck selbst bestimmen und das Koordinatensystem spiegeln. Die Implementierung über die Delegate-Methode des Layers ähnelt der Implementierung in Listing 6.94 sehr (die Änderungen sind hervorgehoben):

- (void)drawLayer:(CALayer *)inLayer 
inContext:(CGContextRef)inContext {
CGContextSaveGState(inContext);
CGContextScaleCTM(inContext, 1.0,1.0);
CGContextTranslateCTM(inContext,
0.0, -CGRectGetHeight(self.bounds));

CGRect theRect = CGContextGetClipBoundingBox(inContext);

CGRect theTileFrame = CGRectMake(0.0, 0.0,
kImageWidth, kImageHeight);
NSBundle *theBundle = [NSBundle mainBundle];
NSLog(@"drawLayer:inContext:%@",
NSStringFromCGRect(theRect));
for(NSUInteger i = 0; i < 5; ++i) {
for(NSUInteger j = 0; j < 4; ++j) {
theTileFrame.origin.x = j * kImageWidth;
theTileFrame.origin.y = i * kImageHeight;

if(CGRectIntersectsRect(theRect, theTileFrame)) {
NSString *theFile = [NSString
stringWithFormat:@"flower_%ux%u",
4i, j];
NSString *thePath = [theBundle
pathForResource:theFile ofType:@"jpg"];
UIImage *theImage = [UIImage
imageWithContentsOfFile:thePath];

CGContextDrawImage(inContext,
theTileFrame, theImage.CGImage);
}
}
}
CGContextRestoreGState(inContext);
}

Auch diese Methode befindet sich in der Klasse TiledImageView, und Sie können sie aktivieren, indem Sie den Wert des Makros USE_DELEGATION am Anfang der Datei von 0 auf 1 setzen.

Sie können den Aufbau des Inhalts übrigens mit kleinen Kachelgrößen sehr schön beobachten. Ändern Sie dazu einfach in der Methode awakeFromNib die Zuweisung in:

theLayer.tileSize = CGSizeMake(10.0, 10.0);

Der Layer zeichnet dann sehr kleine Kacheln, und Sie können die Reihenfolge beim Aufbau erkennen. Er zeichnet die Kacheln kreisförmig von innen nach außen (siehe Abbildung 6.39). Außerdem sehen Sie an diesem Beispiel, dass bei der Klasse TiledImageView die Kachelgröße des Layers unabhängig von der Größe der Einzelbilder ist.

Abbildung

Abbildung 6.39 Der Tiled Layer bei der Arbeit


Rheinwerk Computing - Zum Seitenanfang

6.4.5PDF-Dateien anzeigenZur vorigen Überschrift

Sie können kachelnde Layer indes nicht nur für Bilder, sondern natürlich auch für beliebige Inhalte verwenden. Beispielsweise kann auch die Anzeige von PDF-Dateien sehr viel Speicher verbrauchen. Core Graphics erlaubt Ihnen, mit relativ wenig Aufwand PDF-Dokumente darzustellen. Im Beispielprojekt PDFView finden Sie die gleichnamige Klasse, die ein PDF-Dokument anzeigt. Dieser View zeichnet dabei alle Seiten des Dokuments untereinander, wobei er sie durch graue Rahmen voneinander trennt.

Projektinformation

Den Quellcode des folgenden Beispiels finden Sie auf der DVD unter Code/Apps/iOS5/PDFView oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_3/Apps/iOS5/PDFView.

Dabei gibt die Methode drawPage:inRect:context: in Listing 6.95 die angegebene PDF-Seite in den angegebenen Grafikkontext aus und skaliert die Seite so, dass sie in das übergebene Rechteck hineinpasst. Für die Darstellung verwendet sie drei Core-Graphics-Funktionen. CGPDFPageGetDrawingTransform berechnet eine Transformationsmatrix für die Seite, so dass die Seite in das angegebene Rechteck passt. Diese Matrix wendet die Methode auf den Grafikkontext an, bevor sie die Seite über die Funktion CGContextDrawPDFPage zeichnet.

- (void)drawPage:(CGPDFPageRef)inPage inRect:(CGRect)inRect 
context:(CGContextRef)inContext {
CGPDFBox theBox = kCGPDFMediaBox;
CGAffineTransform theTransform =
CGPDFPageGetDrawingTransform(inPage, kCGPDFMediaBox,
inRect, 0, YES);

CGContextConcatCTM(inContext, theTransform);
CGContextDrawPDFPage(inContext, inPage);
}

Listing 6.95 Zeichnen einer einzelnen PDF-Seite

Die drawRect:-Methode kann nun diese Methode verwenden, um alle Seiten auf die Fläche des Views zu zeichnen. Dazu initialisiert sie zunächst einige Variablen. Neben dem Grafikkontext und dem Bounds-Rechteck braucht die Methode die Anzahl der Seiten, die sie in der Variablen theCount ablegt. Außerdem berechnet sie in der Variablen thePageFrame das Rechteck für eine einzelne Seite. Es hat die gleiche Position und Breite wie das Bounds-Rechteck. Da der View das komplette Dokument anzeigt, entspricht die Höhe einer Seite dem Verhältnis von der Gesamthöhe zu der Anzahl der Seiten.

CGContextRef theContext = UIGraphicsGetCurrentContext();
CGRect theBounds = self.bounds;
size_t theCount = self.countOfPages;
CGFloat theHeight = CGRectGetHeight(theBounds) / theCount;
CGRect thePageFrame = theBounds;

thePageFrame.size.height = theHeight;

Listing 6.96 Variablendefinitionen für das Zeichnen der PDF-Seiten

PDF-Dokumente und Seiten

Die Methode countOfPages liefert die Anzahl der Seiten im PDF-Dokument des Views, und der View speichert das Dokument in dem Attribut document ab, zu dem es die entsprechenden Accessoren document und setDocument: gibt. Über die Funktion CGPDFDocumentGetNumberOfPages ermitteln Sie die Seitenzahl des Dokuments:

- (size_t)countOfPages {
return CGPDFDocumentGetNumberOfPages(self.document);
}

Die Funktion CGPDFDocumentGetPage liefert Ihnen einzelne Seiten aus dem Dokument, wobei Sie im zweiten Parameter die Seitenzahl der gewünschten Seite übergeben. Achtung: Dabei beginnt die Zählung nicht wie üblich bei 0, sondern bei 1.

Die Methode braucht nur die Teile des Dokuments zu zeichnen, die in einer der angezeigten Kacheln liegen. Die Rechtecke dieser Kacheln bekommen Sie dabei als Parameterwerte der Methode drawRect: übergeben. Zum Zeichnen müssen Sie also die Seiten ermitteln, die sich mit dem übergebenen Rechteck schneiden. Dazu berechnen Sie für jede PDF-Seite ihr umschließendes Rechteck und überprüfen über die Funktion CGRectIntersectsRect, ob sich die beiden Rechtecke schneiden. Das umschließende Rechteck der Seite berechnet sich dabei einfach aus einer vertikalen Verschiebung des Rechtecks in der Variablen thePageFrame. Listing 6.97 enthält den Code für diese Seitenfilterung.

for(int i = 0; i < theCount; ++i) {
thePageFrame.origin.y = i * theHeight;
if(CGRectIntersectsRect(inRect, thePageFrame)) {
...
}
}

Listing 6.97 Filterung der überlappenden Seiten

Innerhalb des if-Blocks muss die Methode den Rahmen und die PDF-Seite zeichnen. Damit der Seitenrahmen nicht am Rand des Views klebt, berechnet sie über die Funktion CGRectInset ein Rechteck, dessen Ränder jeweils zehn Punkte Abstand zum Seitenrahmen haben.

Das Koordinatensystem eines Grafikkontexts hat seinen Ursprung unten links, während der eines Views oben links liegt. Cocoa Touch spiegelt den Inhalt des Grafikkontexts, damit der Grafikkontext genau auf dem View liegt. Dadurch sieht die Seite so wie in Abbildung 6.40 aus. Diese Transformation veranschaulicht auch Abbildung 6.41. Der View spiegelt sich im Grafikkontext mit der Seite 1 an der horizontalen Achse, und dadurch entsteht die gespiegelte Anzeige im View 2.

Vertikale Spiegelung

Der Grafikkontext stellt nicht nur PDF-Seiten horizontal gespiegelt dar, sondern auch Texte und Bilder. Um eine nicht gespiegelte Darstellung dieser Objekte zu erhalten, können Sie das im Folgenden beschriebene Vorgehen verwenden.

Diese Spiegelung lässt sich durch eine Skalierung, die das Vorzeichen der y-Achse umkehrt, wieder aufheben, und Sie können sie durch den Funktionsaufruf CGContextScaleCTM(theContext, 1.0, -1.0); auf den Grafikkontext anwenden. Allerdings klappt diese Transformation das Bild wieder um die Nulllinie und somit aus dem sichtbaren Bereich des Views hinaus 3. Um das Bild wieder in die sichtbare Fläche des Views zu bekommen, müssen Sie es vertikal verschieben. Das erreichen Sie durch eine weitere Transformation 4 oder durch eine Korrektur des Seitenrechtecks.

Abbildung

Abbildung 6.40 Bei Core Graphics steht die Welt kopf.

Abbildung

Abbildung 6.41 Korrektur durch Spiegelung und Verschiebung

Diese Korrektur ist relativ einfach durchzuführen, wenn Sie die Seite im Kontext an die Stelle zeichnen, wo der View liegt. In Abbildung 6.41 bedeutet das, dass Sie die erste Seite in das Rechteck 1 zeichnen. Die Spiegelung von Cocoa Touch, die den Kontext auf den View abbildet, klappt die Seite dann nach oben, so dass sie auf dem gestrichelten Rechteck 2 liegt. Wenn Sie dann den Kontext erneut spiegeln, landet der View genau an der Stelle – also Rechteck 3 –, wo er hingehört. Listing 6.98 implementiert diese Korrektur der Koordinaten.

CGPDFPageRef thePage = CGPDFDocumentGetPage(  
self.document, i + 1);

CGContextSaveGState(theContext);
CGContextSetGrayStrokeColor(theContext, 0.5, 1.0);
CGContextStrokeRect(theContext, CGRectInset(
thePageFrame, 10.0, 10.0));
CGContextScaleCTM(theContext, 1.0, –1.0);
// Verschiebt die Seite auf die Koordinaten des Views
thePageFrame.origin.y = -(i + 1) * theHeight;
[self drawPage:thePage inRect:thePageFrame
context:theContext];
CGContextRestoreGState(theContext);

Listing 6.98 Rahmen und Seite mit Korrektur der Spiegelung zeichnen

Damit haben Sie den komplizierten Teil der Implementierung geschafft. Der View speichert das PDF-Dokument in dem Attribut document, das den Typ CGPDFDocumentRef hat. Die zu iOS 5 gehörende Objective-C-Version erlaubt auch, im Implementierungsblock der Klasse Attribute zu deklarieren. Davon macht die Klasse PDFView Gebrauch, wie Sie in Listing 6.99 sehen:

@implementation PDFView {
CGPDFDocumentRef document;
}
...
@end

Listing 6.99 Implementierungsblock mit Attributdeklaration

Bei dieser Variante haben Sie gegenüber der Attributdeklaration im Interface-Block den Vorteil, dass Sie dieses Attribut vor einem Verwender der Klasse verstecken. Das ist sinnvoll, da es ein Implementierungsdetail der Klasse ist.

Außerdem stellt die Klasse entsprechende Accessoren für das Attribut zur Verfügung, und die dealloc-Methode gibt das Dokument am Ende der Lebenszeit des Views frei:

- (void)dealloc {
CGPDFDocumentRelease(document);
}

- (CGPDFDocumentRef)document {
return document;
}

- (void)setDocument:(CGPDFDocumentRef)inDocument {
if(inDocument != document) {
CGPDFDocumentRelease(document);
document = inDocument;
CGPDFDocumentRetain(document);
}
}

Listing 6.100 »dealloc«-Methode und Accessoren der Klasse »PDFView«

Die Speicherverwaltung in Core Foundation

Die Typen und Funktionen, die mit CGPDFDocument beginnen, gehören zu dem Framework Core Graphics, das ein C-Framework ist. Es basiert auf dem C-Framework Core Foundation. Beide Frameworks enthalten keinen Objective-C-Code und greifen auch nicht auf Objective-C-Klassen zu. Core Foundation verwendet das manuelle Referenzzählen zur Speicherverwaltung, und es hat drei Speicherverwaltungsregeln, die denen für Objective-C sehr stark ähneln:

  • Sie halten das Objekt einer Referenz, wenn Sie die Referenz über eine Funktion mit dem Namensbestandteil Create oder Copy erzeugen.
  • Wenn Sie eine Referenz auf einem anderen Weg erhalten, halten Sie das Objekt nicht, außer Sie rufen die Funktion CFRetain dafür auf.
  • Für alle Referenzen, die ein Objekt halten, müssen Sie die Funktion CFRelease aufrufen, um sie freizugeben.

Viele C-Datentypen haben eigene Funktionen für das Retain und Release. Deren Name beginnt dann mit dem Namen des Typs. Beispielsweise heißen diese Funktionen CGPDFDocumentRetain und CGPDFDocumentRelease für PDF-Dokumente. Im Gegensatz zu der Speicherverwaltung in Objective-C bietet Core Foundation allerdings keinen Autoreleasepool.

Da die PDF-Dokumente keine Objective-C-Klasse haben, implementiert der PDF-View die Accessoren manuell.

Der View muss noch seine Layer-Klasse setzen und den Layer initialisieren. Dabei sollen die Kacheln jeweils die volle Breite des Views haben, aber nur ein Viertel der sichtbaren Höhe. Der volle View enthält alle Seiten des Dokuments, wobei allerdings jeweils nur eine Seite die sichtbare Fläche vollständig bedecken kann. Um die Kachelhöhe zu erhalten, müssen Sie also die Höhe des Views zuerst durch die Anzahl der Seiten teilen und danach durch 4. Ein guter Ort für diese Berechnung ist die Methode layoutSubviews, da der View sie bei allen Größenänderungen aufruft. Die vollständige Verwaltung des Layers finden Sie in Listing 6.101.

+ (id)layerClass {
return [CATiledLayer class];
}

- (void)awakeFromNib {
[super awakeFromNib];
CATiledLayer *theLayer = (CATiledLayer *)self.layer;

theLayer.levelsOfDetail = 4;
theLayer.levelsOfDetailBias = 4;
}

- (void)layoutSubviews {
[super layoutSubviews];
CATiledLayer *theLayer = (CATiledLayer *)self.layer;
CGSize theSize = self.bounds.size;

theSize.height /= 4 * self.countOfPages;
theLayer.tileSize = theSize;
}

Listing 6.101 Die Layer-Verwaltung des PDF-Views

Damit der View auch tatsächlich ein Dokument anzeigt, müssen Sie ihm eine Referenz auf ein Dokument übergeben, die Sie über die Funktion CGPDFDocumentCreateWithURL erzeugen können. Der View erzeugt natürlich nicht selbst die Referenz, sondern der Controller. Im Beispielprogramm ist das die Klasse PDFViewController, die ein Beispieldokument aus dem Ressourcenordner des Programms lädt.

- (void)viewDidLoad {
[super viewDidLoad];
NSURL *theURL = [[NSBundle mainBundle]
URLForResource:@"lorem-ipsum" withExtension:@"pdf"];
CGPDFDocumentRef theDocument = CGPDFDocumentCreateWithURL(
(__bridge CFURLRef) theURL);

self.pdfView.document = theDocument;
CGPDFDocumentRelease(theDocument);

Listing 6.102 Laden eines PDF-Dokuments

Schließlich muss der Controller noch die Höhe des PDF-Views an das PDF-Dokument anpassen. Da jeweils eine Seite die angezeigte Fläche des Views einnehmen soll, multiplizieren Sie für die Anpassung einfach die Höhe des Scrollviews mit der Anzahl der Seiten.

- (void)viewWillAppear:(BOOL)inAnimated {
[super viewWillAppear:inAnimated];
CGRect theFrame = self.pdfView.frame;

theFrame.size.height =
CGRectGetHeight(self.scrollView.frame) *
self.pdfView.countOfPages;
self.scrollView.contentSize = theFrame.size;
self.pdfView.frame = theFrame;
}

Listing 6.103 Anpassung der View-Höhe an das Dokument



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück




Copyright © Rheinwerk Verlag GmbH, Bonn 2014
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern


  Zum Katalog
Zum Katalog: Apps programmieren für iPhone und iPad






Neuauflage: Apps programmieren für iPhone und iPad
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Einstieg in Objective-C 2.0 und Cocoa






Einstieg in Objective-C 2.0 und Cocoa


Zum Katalog: Spieleprogrammierung mit Android Studio






Spieleprogrammierung mit Android Studio


Zum Katalog: Android 5






Android 5


Zum Katalog: iPhone und iPad-Apps entwickeln






iPhone und iPad-Apps entwickeln


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo