Tutorial: Webbrowser in 10 Minuten (Teil 2)

ml, den 2. März 2008
Xcode
Xcode

Nachdem wir uns im ersten Teil des Tutorials mit wenigen Klicks und Null Zeilen Programmcode einen einfachen Webbrowser gestrickt haben, werden wir diesmal etwas mehr Hand anlegen und den Browser erweitern. Dabei werden wir etwas über Delegates, das MVC-Paradigma und weitere Cocoa-Interna kennenlernen. Und diesmal werden wir auch eigenen Code schreiben. Versprochen!

Grundlagen

Mit Cocoa bezeichnet Apple die native Programmierschnittstelle von Mac OS X. Die Programmiersprache der Wahl ist Objective-C, eine objektorientierte Untermenge der Programmiersprache C. Die beiden wichtigsten Frameworks von Cocoa sind das Foundation- und das Application-Framework. Im Application-Framework werden die Oberflächen-Elemente von Mac OS X zur Verfügung gestellt. Das Foundation-Framework stellt Objekte zur Verfügung, die keine Anzeige benötigen.

Das Model-View-Controller-Paradigma (MVC) ist ein Design-Schema aus der objektorientierten Programmierung. Daten werden durch ein Modell repräsentiert und in einem View angezeigt. Das Controller-Objekt sorgt für die Synchronisation von Modell und View. Ändern sich die Daten im Modell muss der View und bei einer Änderung durch den Anwender im View das Modell aktualisiert werden. Im Cocoa-Framework macht Apple intensiv von diesem Paradigma Gebrauch. So kann man das Foundation-Framework als Modell-Framework und das Application-Framework als View-Framework sehen.

Delegates sind ein weiteres Programmierkonzept um Aufgaben, wie der Name schon andeutet, zu delegieren. Im Cocoa-Framework werden Delegates benutzt, um weitere, nicht zwingend benötigte Methoden von Objekten zu implementieren. Mit Delegates lässt sich auch das Standardverhalten von Objekten anpassen, indem der Delegate das gewünschte Verhalten implementiert.

Do it, baby!

So, dann wollen wir mal loslegen. Basis ist das fertiggestellte Projekt aus dem ersten Teil dieses Tutorials. Das Ziel für heute ist es, einen einfachen Einstellungsdialog zu implementieren, der die Einstellung für eine Standard-Webseite unseres Browsers in der User-Defaults-Datenbank von Mac OS X speichert.

Der erste Schritt ist kosmetischer Natur. Im Interface Builder polieren wir erstmal ein wenig das Benutzerinterface und ändern den Button-Typ für die Vorwärts- und Rückwärtsbuttons in „Bevel“. Den Text ersetzen wir durch < und >. Dann noch ein wenig die Größe anpassen, das Textfeld für die URL verschieben und den WebView neu ausrichten.

Als nächstes legen wir zwei Controller-Klassen an. Ein Controller wird für das Hauptfenster, der zweite für das Fenster mit den Einstellungen benötigt. Dazu im Xcode-Projektfenster mit einem Rechtsklick auf den Ordner Classes klicken und Add > New File auswählen. Im folgenden Dialog wählt man eine normale Objective-C-Klasse aus. Die beiden Controller-Klassen nennen wir MNController und MNPreferencesController

Jetzt haben wir wieder etwas im Interface Builder zu tun. Über das File-Menü können wir unsere neuen Klassen im Interface Builder einlesen und verwenden. Tipp: Drückt man die Apfel- bzw. Command-Taste beim Auswählen der Header-Dateien, so lassen sich mehrere auf einmal auswählen und einlesen. Alternativ kann man die Header-Dateien auch per Drag and Drop in das MainMenu.nib-Fenster im Interface Builder ziehen.

Anschließend öffnen wir die Library und suchen dort nach „NSObject“. Dieses Objekt ziehen wir anschließend in das MainMenu.nib-Fenster. Im Inspektor in der Rubrik „Identity Inspector“ (Apfel+6) wählt man dann aus dem Aufklappmenü für die Klasse unsere Klasse MNController. Für die Klasse MNPreferencesController verfahren wir genauso.

Als nächstes legen wir für den Einstellungsdialog ein neues Fenster an. Aus der Library wählen wir ein Standard-Fenster aus und ziehen es in das MainMenu.nib-Fenster. Anschließend fügen wir dem Fenster 2 Buttons, ein Textfeld und ein Label hinzu und layouten das Ganze wie im Screenshot gezeigt. Dabei soll das Fenster beim Start nicht angezeigt werden und keinen Toolbar-Button haben. Diese Einstellungen kann man ebenfalls im Inspector vornehmen. Hinweis: Die Resize-Optionen für die einzelnen Elemente nicht vergessen einzustellen oder die Fenstergröße festlegen.

Jetzt ist es an der Zeit ein paar Actions und Outlets zu definieren. Outlets sind Referenzen eines Controller-Objekts auf ein Element in der Oberfläche. Actions hingegen werden von Elementen der Benutzeroberfläche, z. B. durch Klick, ausgelöst. Um einem Objekt im Interface Builder ein Outlet oder eine Action hinzufügen, wählt man das Objekt aus und lässt sich im Inspektor den „Identity Inspector“ anzeigen.

Unserer Klasse MNPreferencesController fügen wir ein Outlet mit dem Namen homepageTextfield hinzu. Außerdem kommen noch die zwei Actions applyPreferences: und cancelPreferences: hinzu. Der MNController bekommt drei Outlets: webView, urlTextfield und preferencesController. Wichtig: der Doppelpunkt hinter dem Namen für die Actions muss zwingend eingegeben werden.

Verbindung gesucht

Jetzt können wir ein paar Verbindungen knüpfen. Und zwar müssen jetzt die Outlets und Actions entsprechend verbunden werden. Das erledigt man wie aus dem ersten Teil bekannt mit einen Ctrl-Klick und dem Ziehen der entsprechenden Verbindung. Die folgende Tabelle gibt eine Übersicht was mit wem verbunden werden muss

Outlet/Action Verbindung zu
webView WebView im Hauptfenster
urlTextfield Textfeld für die URL-Eingabe
preferencesController MNPreferencesController-Instanz
homepageTextfield Textfeld im Preferences-Fenster
applyPreferences: OK-Button im Preferences-Fenster
cancelPreferences: Cancel-Button im Preferences-Fenster

Damit unser Einstellungsdialog später mal angezeigt werden kann, müssen wir eine Verbindung von unserem Menü zu unserem Fenster ziehen. Vom Menüpunkt „Preferences“ ziehen wir eine Verbindung zum Preferences-Fenster und verbinden die makeKeyAndOrderFront: Methode.

Als vorletzten Schritt im Interface Builder müssen wir noch zwei Delegates verbinden. Der Delegate des File’s Owner wird mit dem MNController verbunden. Und das Fenster des Einstellungsdialogs bekommt den MNPreferencesController als Delegate zugewiesen.

Damit wir im Programmcode auch auf unsere Outlets zugreifen können, müssen wir diese in den entsprechenden Header-Dateien auflisten. Das kann man per Hand machen oder aber auch einfach per Drag and Drop. Dazu einfach das entsprechende Outlet aus dem Inspektor-Fenster im Interface Builder in das entsprechende Header-File ziehen. Die Outlets müssen in der @interface-Sektion definiert sein.

Etwas Code

Als letzten Schritt müssen wir jetzt noch die entsprechenden Funktionen und Actions für unser Programm implementieren. Doch bevor wir beginnen, erinnern wir uns nochmal an die Zielstellung: wir wollen, dass wenn der Webbrowser gestartet wird, eine von uns definierte Seite geladen wird. Diese Seite soll über einen Einstellungsdialog definiert werden.

Der Controller für unser Hauptfenster ist MNController. Er ist also dafür verantwortlich nach dem Programmstart die richtige Seite zu laden. Um bestimmte Aktionen nach dem Start eines Programms auszuführen, können Cocoa-Programme die Delegate-Methode applicationDidFinishLaunching implementieren. Deshalb haben wir den MNController als Delegate des File’s Owners eingestellt. Bei der Initialisierung von Cocoa-Objekten wird standardmäßig die Init-Methode aufgerufen, in der Objekt-spezifische Initialisierungen vorgenommen werden können.

Im MNPreferencesController müssen wir zusätzlich zur Init-Methode unsere beiden im Interface-Builder definierten Action-Methoden implementieren. Außerdem müssen wir die Delegate-Methode windowDidBecomeKey implementieren, damit wir bei der Anzeige des Fensters das Textfeld für die Homepage-URL mit dem entsprechenden Text befüllen können. Zusätzlich implementieren wir eine Methode homePageURL die von anderen Objekten aufgerufen werden kann, um die aktuelle URL der Homepage zu ermitteln.

MNController

Da wir in unserer Init-Methode für den MNController keine speziellen Initialisierungen vornehmen müssen schaut die Methode recht übersichtlich aus:


-(id)init
{
  // init super class
  self = [super init];
  // custom inits here
  return self;
}

Die Methode applicationDidFinishLaunching schaut wie folgt aus:


-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
  NSString *url = [preferencesController homePageURL];
  if(url != nil)
  {
    [urlTextfield setStringValue: url];
    [[webView mainFrame] loadRequest:
        [NSURLRequest requestWithURL:
            [NSURL URLWithString: url]]];
  }
}

Im MNPreferencesController müssen wir zunächst in der Header-Datei noch eine weitere Variable und eine Funktion definieren. In der @interface-Sektion definieren wir NSUserDefaults *myDefaults; und darunter die Funktion -(NSString *)homePageURL;.

Die Implementierung der Init-Methode enthält diesmal die Initialisierung einer Referenz auf die systemweite Einstellungsdatenbank:


-(id)init
{
  self = [super init];   
  if(self)
    // reference to system pref database
    myDefaults = [NSUserDefaults standardUserDefaults];
  return self;
}

Wenn der Einstellungsdialog angezeigt wird, dann soll im Textfeld auch die URL der eingestellten Homepage angezeigt werden. Daher implementieren wir die Delegate-Methode windowDidBecomeKey:


- (void)windowDidBecomeKey:(NSNotification *)aNotification
{
  NSString *url = (NSString *)[myDefaults objectForKey: @"homePageURL"];
	
  if(url != nil)
    [homepageTextfield setStringValue: url];
}

Die beiden Action-Methoden sind wie folgt implementiert:


-(IBAction)applyPreferences:(id)sender
{
  // save changes to defauls database
  [myDefaults setObject:
      [homepageTextfield stringValue] forKey:
          @"homePageURL"];
  [window close];
}

-(IBAction)cancelPreferences:(id)sender
{
	[window close];
}

Damit andere Objekte die Homepage-URL abfragen können, liefert die Methode homePageURL die aktuell gespeicherte URL zurück:


-(NSString *)homePageURL
{
  return (NSString *)[myDefaults objectForKey:
      @"homePageURL"];
}

Das war’s. Jetzt noch kompilieren und wenn wir keine Syntax-Fehler gemacht haben, dann sollte jetzt alles laufen. Damit man die Implementierung nochmal etwas besser nachlesen kann, habe ich die Dateien zusammengepackt und zum Download bereitgestellt.

Ausblick

Beim nächsten Mal werden wir den heute geschriebenen Code wieder vereinfachen, denn dann beschäftigen wir uns mit Bindings und vorgefertigten Controller-Klassen. Außerdem werden wir uns etwas um die Oberfläche kümmern und diese weiter aufhübschen.

Wer sich bis dahin nicht ausgelastet fühlt, kann sich ja an der Implementierung eines Home-Buttons im Hauptfenster versuchen. Alle dafür benötigten Fertigkeiten und Methoden habe ich in diesem Tutorial gezeigt, so dass es nicht allzu schwer sein sollte.


Ähnliche Nachrichten