Schreiben einer iOS-App zum Abrufen von Outlook-Mail, -Kalender und -Kontakten

In diesem Leitfaden werden Sie schrittweise durch den Prozess des Erstellens einer einfachen Swift-App zum Abrufen von Nachrichten in Office 365 oder Outlook.com geführt. Wenn Sie die hier beschriebenen Schritte ausführen, sollte der Quellcode in diesem Repository das Ergebnis sein.

In diesem Leitfaden wird Microsoft Graph zum Zugriff auf Outlook-Mail verwendet. Microsoft empfiehlt die Verwendung von Microsoft Graph für den Zugriff auf Outlook-Mail, -Kalender und -Kontakte. Verwenden Sie die Outlook-APIs nur dann direkt (über https://outlook.office.com/api), wenn Sie ein Feature benötigen, das in den Graph-Endpunkten nicht verfügbar ist.

In diesem Leitfaden wird davon ausgegangen, dass Sie Xcode bereits installiert haben und zusammen mit CocoaPods Version 1.2 auf Ihrem Entwicklungscomputer ausführen.

Erstellen der App

Lassen Sie uns direkt loslegen. Öffnen Sie Xcode, und wählen Sie im Menü File die Option New und dann Project aus. Wählen Sie im linken Bereich Application unter iOS aus, und wählen Sie dann Tabbed Application. Klicken Sie auf Next.

Wählen Sie die Vorlage „Single View Application“ aus.

Geben Sie swift-tutorial in das Feld Product Name, Swift für Language und Universal für Devices ein, klicken Sie dann auf Next.

Wählen Sie Optionen für Ihr neues Projekt aus.

Wählen Sie einen Speicherort für das Projekt, und klicken Sie auf Create. Sobald Xcode die Erstellung des Projekts abgeschlossen hat, schließen Sie Xcode.

Verwenden Sie als Nächstes CocoaPods zum Installieren von Abhängigkeiten. In diesem Lernprogramm benötigen wir Folgendes:

Öffnen Sie Terminal, und wechseln Sie in das Verzeichnis, in dem Ihr swift-tutorial-Projekt gespeichert ist. Führen Sie den folgenden Befehl aus, um eine Podfile-Datei für das Projekt zu initialisieren.

pod init

Öffnen Sie dann die Podfile-Datei mit dem folgenden Befehl.

open Podfile

Ersetzen Sie den gesamten Inhalt der Datei durch Folgendes:

Inhalt der Podfile-Datei

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'swift-tutorial' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for swift-tutorial
  pod 'p2.OAuth2'
  pod 'SwiftyJSON'

  target 'swift-tutorialTests' do
    inherit! :search_paths
    # Pods for testing
  end

end

Schließen Sie die Podfile-Datei, und führen Sie dann den folgenden Befehl aus, um die Abhängigkeiten zu installieren:

pod install

Sobald dieser Befehl abgeschlossen ist, öffnen Sie die neu erstellte Datei swift-tutorial.xcworkspace in Xcode.

Entwerfen der App

Die App selbst ist ziemlich einfach. Wir ändern den Zweck des generierten FirstViewController so, dass er den Posteingang des Benutzers anzeigt.

  1. Erweitern Sie im Project Navigator swift-tutorial->swift-tutorial, und wählen Sie dann FirstViewController.swift aus. Ersetzen Sie alle Instanzen von FirstViewController durch MailViewController.
  2. Öffnen Sie den File Inspector, und ändern Sie den Dateinamen in MailViewController.swift.
  3. Wählen Sie Main.storyboard aus. Erweitern Sie in der Dokumentgliederung First Scene, und wählen Sie dann First aus.

    Auswählen des Ansichtscontrollers in der Dokumentgliederung.

  4. Öffnen Sie den Attributes Inspector, und ändern Sie den Wert für Title in Mail.
  5. Öffnen Sie den Identity Inspector, und ändern Sie den Wert für Class in MailViewController.
  6. Erweitern Sie View, und löschen Sie die Beschriftungen First View und Loaded by FirstViewController.
  7. Wählen Sie das Balkenelement First am unteren Rand der Ansicht aus. Ändern Sie im Attributes Inspector den Wert für Title in Mail. Vergewissern Sie sich, dass für Image der Wert first angegeben ist.

Jetzt fügen wir eine Anmeldeschaltfläche hinzu. Suchen Sie in der Object Library (untere rechte Ecke) nach Button. Ziehen Sie Button in die visuelle Darstellung der Ansicht. Doppelklicken Sie auf die Schaltfläche, und legen Sie den Text auf Log In fest.

Hinzufügen einer Anmeldeschaltfläche.

Wählen Sie jetzt MailViewController.swift im Project Navigator aus. Fügen Sie eine neue Eigenschaft zur MailViewController-Klasse hinzu, direkt vor der viewDidLoad-Funktion:

@IBOutlet var logInButton: UIButton!

Fügen Sie dann eine neue Methode zur Klasse hinzu:

logInButtonTapped-Funktion in MailViewController.swift

@IBAction func logInButtonTapped(sender: AnyObject) {
    NSLog("Hello World")
}

Wählen Sie in Main.storyboard die Option Mail in der Dokumentgliederung aus. Wählen Sie die Registerkarte Connections Inspector auf der rechten Seite aus.

Auswählen des Connections Inspector.

Unter Outlets sollte die Eigenschaft loginButton angezeigt werden, die wir zuvor zum Ansichtscontroller hinzugefügt haben. Ziehen Sie den kleinen Kreis neben dieser Eigenschaft auf die Schaltfläche in der Ansicht.

Unter Received Actions sollte logInButtonTappedWithSender angezeigt werden. Ziehen Sie den kleinen Kreis neben dieser Methode auf die Schaltfläche in der Ansicht. Wählen Sie im angezeigten Popupmenü Touch Up Inside aus. Anschließend sollte der Abschnitt Connections Inspector wie folgt aussehen.

Connections Inspector mit der verbundenen Anmeldeschaltfläche unter „Outlets“.

An diesem Punkt sollte die App erstellt und ausgeführt werden. Beim Tippen auf die Schaltfläche Log in sollte die loginButtonTapped-Methode aufgerufen werden, die Hello World in der Xcode-Ausgabe zurückgeben sollte.

Registrieren der App

Wichtig

Neue App-Registrierungen sollten im Anwendungsregistrierungsportal erstellt und verwaltet werden, damit sie mit Outlook.com kompatibel sind. Erstellen Sie neue App-Registrierungen nur dann im Azure-Verwaltungsportal Folgendes auf Ihre App zutrifft:

  • Sie verwendet den OAuth2 Client Credentials Grant-Fluss oder
  • Sie muss neben Outlook auf andere Office 365-Arbeitslasten zugreifen (z. B. OneDrive for Business oder SharePoint)

Beachten Sie, dass mithilfe des Azure-Verwaltungsportals registrierte Apps nicht mit Outlook.com kompatibel sind und dass Berechtigungsbereiche nicht dynamisch angefordert werden können. Vorhandene App-Registrierungen, die im Azure-Verwaltungsportal erstellt wurden, funktionieren weiterhin nur für Office 365. Diese Registrierungen werden nicht im Anwendungsregistrierungsportal angezeigt und müssen im Azure-Verwaltungsportal verwaltet werden.

Kontoanforderungen

Um das Anwendungsregistrierungsportal zu verwenden, benötigen Sie entweder ein Office 365-Geschäfts- oder Schulkonto oder ein Microsoft-Konto. Wenn Sie nicht über eines dieser Konten verfügen, haben Sie verschiedene Möglichkeiten:

  • Registrieren Sie sich hier für ein neues Microsoft-Konto.
  • Sie haben mehrere Möglichkeiten, ein Office 365-Abonnement zu erhalten:

REST-API-Verfügbarkeit

Die REST-API ist derzeit auf allen Office 365-Konten, die über Exchange Online verfügen, sowie auf allen Outlook.com-Konten aktiviert.

Wechseln Sie zum App-Registrierungsportal, um schnell eine App-ID zu erhalten.

  1. Melden Sie sich über den Link Anmelden mit Ihrem Microsoft-Konto (Outlook.com) oder Ihrem Geschäfts-, Schul- oder Unikonto (Office 365) an.
  2. Klicken Sie auf die Schaltfläche App hinzufügen. Geben Sie swift-tutorial für den Namen ein, und klicken Sie auf Erstellen.
  3. Suchen Sie den Abschnitt Plattformen, und klicken Sie auf Plattform hinzufügen. Wählen Sie Native Anwendung aus.
  4. Ersetzen Sie den Wert für Benutzerdefinierte Umleitungs-URIs durch swift-tutorial://oauth2/callback.
  5. Klicken Sie auf Speichern, um die Registrierung abzuschließen. Kopieren Sie die Anwendungs-ID, und speichern Sie sie. Wir benötigen sie bald.

So sollten die Details Ihrer App-Registrierung aussehen, wenn Sie fertig sind.

Screenshot der abgeschlossenen App-Registrierung im App-Registrierungsportal

Implementieren von OAuth2

Unser Ziel in diesem Abschnitt ist, die Schaltfläche Log in so zu konfigurieren, dass sie OAuth2 zum Abrufen eines Zugriffstokens verwendet, das wir mit der Mail-API verwenden können. Wir verwenden das p2.OAuth2-Framework.

Zunächst erstellen wir eine Singleton-Klasse, die Authentifizierung und API-Aufrufe verwaltet. Klicken Sie bei gedrückter STRG-TASTE auf den Ordner swift-tutorial im Project Navigator, und wählen Sie New File. Wählen Sie Swift file als Vorlage aus, und klicken Sie auf Next. Geben Sie OutlookService in das Feld Save As ein, und klicken Sie auf Create.

Öffnen Sie die Datei OutlookService.swift, und ersetzen Sie ihren Inhalt durch Folgendes.

Inhalt der Datei OutlookService.swift

import Foundation
import p2_OAuth2

class OutlookService {
    // Configure the OAuth2 framework for Azure
    private static let oauth2Settings = [
        "client_id" : "YOUR APP ID HERE",
        "authorize_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
        "token_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
        "scope": "openid profile offline_access User.Read Mail.Read",
        "redirect_uris": ["swift-tutorial://oauth2/callback"],
        "verbose": true,
        ] as OAuth2JSON

    private static var sharedService: OutlookService = {
        let service = OutlookService()
        return service
    }()

    private let oauth2: OAuth2CodeGrant

    private init() {
        oauth2 = OAuth2CodeGrant(settings: OutlookService.oauth2Settings)
        oauth2.authConfig.authorizeEmbedded = true
    }

    class func shared() -> OutlookService {
        return sharedService
    }

    var isLoggedIn: Bool {
        get {
            return oauth2.hasUnexpiredAccessToken() || oauth2.refreshToken != nil
        }
    }

    func handleOAuthCallback(url: URL) -> Void {
        oauth2.handleRedirectURL(url)
    }

    func login(from: AnyObject, callback: @escaping (String? ) -> Void) -> Void {
        oauth2.authorizeEmbedded(from: from) {
            result, error in
            if let unwrappedError = error {
                callback(unwrappedError.description)
            } else {
                if let unwrappedResult = result, let token = unwrappedResult["access_token"] as? String {
                    // Print the access token to debug log
                    NSLog("Access token: \(token)")
                    callback(nil)
                }
            }
        }
    }

    func logout() -> Void {
        oauth2.forgetTokens()
    }
}

Ersetzen Sie den Wert clientId durch die Anwendungs-ID, die Sie im App-Registrierungsportal generiert haben.

Öffnen Sie AppDelegate.swift, und fügen Sie der AppDelegate-Klasse eine neue Funktion hinzu. Diese Funktion wird die Aktivierung der App ausführen, wenn sie während der Anmeldung vom benutzerdefinierten Umleitungs-URI aktiviert wird.

application-Funktion in AppDelegate.swift

func application(_ app: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
    if url.scheme == "swift-tutorial" {
        let service = OutlookService.shared()
        service.handleOAuthCallback(url: url)
        return true
    }
    else {
        return false
    }
}

Jetzt müssen wir das benutzerdefinierte URI-Schema swift-tutorial registrieren, damit unsere App aufgerufen wird.

  1. Klicken Sie bei gedrückter STRG-TASTE auf die Datei Info.plist, und wählen Sie Öffnen als und dann Quellcode aus.
  2. Navigieren Sie zum Ende der Datei, und fügen Sie den folgenden Code direkt hinter der letzten </array>-Zeile der Datei hinzu:

    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>swift-tutorial</string>
            </array>
        </dict>
    </array>
    

Wechseln Sie zurück zu MailViewController.swift. Fügen Sie der MailViewController-Klasse einen Member hinzu, der die freigegebene Instanz der OutlookService-Klasse enthalten soll.

let service = OutlookService.shared()

Fügen Sie der MailViewController-Klasse eine Funktion hinzu, um die Beschriftung der Anmeldeschaltfläche basierend auf dem aktuellen Anmeldestatus zu ändern.

setLogInState-Funktion in MailViewController.swift

func setLogInState(loggedIn: Bool) {
    if (loggedIn) {
        loginButton.setTitle("Log Out", for: UIControlState.normal)
    }
    else {
        loginButton.setTitle("Log In", for: UIControlState.normal)
    }
}

Fügen Sie der viewDidLoad-Funktion nun Code hinzu, um den Anmeldestatus zu überprüfen und den Text der Schaltfläche entsprechend festzulegen. Der Grund hierfür ist, dass das p2.OAuth2-Framework Token in der Keychain des Benutzers speichert. Wenn die Ansicht geladen wird, enthält die Keychain möglicherweise bereits ein gültiges Token, sodass der Benutzer sich nicht erneut anmelden muss. Fügen Sie den folgenden Code nach der Zeile super.viewDidLoad() hinzu:

setLogInState(loggedIn: service.isLoggedIn)

Abschließend aktualisieren wir die logInButtonTapped-Funktion zum Durchführen der Anmeldung. Ersetzen Sie die vorhandene Funktion durch diese neue.

Aktualisierte loginButtonTapped-Funktion in MailViewController.swift

@IBAction func loginButtonTapped(sender: AnyObject) {
    if (service.isLoggedIn) {
        // Logout
        service.logout()
        setLogInState(loggedIn: false)
    } else {
        // Login
        service.login(from: self) {
            error in
            if let unwrappedError = error {
                NSLog("Error logging in: \(unwrappedError)")
            } else {
                NSLog("Successfully logged in.")
                setLogInState(loggedIn: true)
            }
        }
    }
}

Führen Sie die App aus. Nachdem die App im iOS Simulator geladen wurde, tippen Sie auf die Schaltfläche Log in, um sich anzumelden. Nach der Anmeldung sollte ein Token im Ausgabefenster angezeigt werden, und die Schaltfläche sollte nun die Beschriftung Log out haben.

Das Zugriffstoken, das in Xcode im Ausgabefenster angezeigt wird.

Kopieren Sie den vollständigen Wert des Tokens, und wechseln Sie zu https://jwt.io/. Wenn Sie diesen Wert einfügen, sollte eine JSON-Darstellung eines Zugriffstokens angezeigt werden. Details und alternative Parser finden Sie unter Validating your Office 365 Access Token.

Wenn Sie überzeugt sind, dass das Token den Anforderungen entspricht, machen wir mit der Mail-API weiter.

Verwenden der Mail-API

Wir beginnen mit der Erstellung einer Funktion, die eine API-Anforderung sendet und die Antwort zurückgibt. Wechseln Sie zurück zur Datei OutlookService.swift, und importieren Sie die SwiftyJSON-Bibliothek.

import SwiftyJSON

Wir verwenden diese Bibliothek, um die Ergebnisse der API-Aufrufe zu umschließen, sodass ein wenig einfacher darauf zugegriffen werden kann. Fügen Sie als Nächstes einen Member zur OutlookService-Klasse hinzu, der die E-Mail-Adresse des Benutzers enthalten soll.

private var userEmail: String

Es muss sichergestellt sein, dass dies in der init-Funktion initialisiert wird. Fügen Sie der init-Funktion die folgende Zeile hinzu.

userEmail = ""

Fügen Sie nun eine neue makeApiCall-Funktion hinzu.

makeApiCall-Funktion in OutlookService.swift

func makeApiCall(api: String, params: [String: String]? = nil, callback: @escaping (JSON?) -> Void) -> Void {
    // Build the request URL
    var urlBuilder = URLComponents(string: "https://graph.microsoft.com")!
    urlBuilder.path = api

    if let unwrappedParams = params {
        // Add query parameters to URL
        urlBuilder.queryItems = [URLQueryItem]()
        for (paramName, paramValue) in unwrappedParams {
            urlBuilder.queryItems?.append(
                URLQueryItem(name: paramName, value: paramValue))
        }
    }

    let apiUrl = urlBuilder.url!
    NSLog("Making request to \(apiUrl)")

    var req = oauth2.request(forURL: apiUrl)
    req.addValue("application/json", forHTTPHeaderField: "Accept")
    if (!userEmail.isEmpty) {
        // Add X-AnchorMailbox header to optimize
        // API routing
        req.addValue(userEmail, forHTTPHeaderField: "X-AnchorMailbox")
    }

    let loader = OAuth2DataLoader(oauth2: oauth2)

    // Uncomment this line to get verbose request/response info in 
    // Xcode output window
    //loader.logger = OAuth2DebugLogger(.trace)

    loader.perform(request: req) {
        response in
        do {
            let dict = try response.responseJSON()
            DispatchQueue.main.async {
                let result = JSON(dict)
                callback(result)
            }
        }
        catch let error {
            DispatchQueue.main.async {
                let result = JSON(error)
                callback(result)
            }
        }
    }
}

Sehen wir uns die Arbeit dieser Funktion an.

  • Sie erstellt die API-Anforderungs-URL, die als Basis https://graph.microsoft.com verwendet und die übergebene API-Zeichenfolge hinzufügt (z. B. /v1.0/me).
  • Wenn Abfrageparameter übergeben werden, werden diese ebenfalls an die URL angehängt (z. B. Parameter $top oder $select).
  • Wenn die E-Mail-Adresse des Benutzers bekannt ist, legt sie den X-AnchorMailbox-Header der Anforderung fest. Durch Festlegen dieses Headers auf die E-Mail-Adresse des Benutzers kann der API-Endpunkt API-Aufrufe effizienter an den entsprechenden Back-End-Postfachserver weiterleiten.
  • Es wird ein OAuth2DataLoader-Objekt zum Senden der Anforderung mit Authentifizierung erstellt. Diese Vorgehensweise hat den Vorteil, dass das OAuth2DataLoader-Objekt das Hinzufügen des Zugriffstokens zur Anforderung übernimmt und das Token bei Bedarf automatisch aktualisiert.
  • Es umschließt die Antwort unter Verwendung der SwiftyJSON-Bibliothek und ruft die Rückruffunktion auf.

Jetzt fügen wir eine Funktion hinzu, um die E-Mail-Adresse des Benutzers abzurufen. Hierzu senden wir einen API-Aufruf an den /v1.0/me-Endpunkt und rufen die mail-Eigenschaft vom Benutzerobjekt ab. Fügen Sie die folgende Funktion zur OutlookService-Klasse hinzu:

getUserEmail-Funktion in OutlookService.swift

func getUserEmail(callback: @escaping (String?) -> Void) -> Void {
    // If we don't have the user's email, get it from
    // the API
    if (userEmail.isEmpty) {
        makeApiCall(api: "/v1.0/me") {
            result in
            if let unwrappedResult = result {
                let email = unwrappedResult["mail"].stringValue
                self.userEmail = email
                callback(email)
            } else {
                callback(nil)
            }
        }
    } else {
        callback(userEmail)
    }
}

Und nun testen wir das. Erstellen Sie eine neue Funktion in MailViewController.swift, um Benutzerdaten zu laden.

loadUserData-Funktion in MailViewController.swift

func loadUserData() {
    service.getUserEmail() {
        email in
        if let unwrappedEmail = email {
            NSLog("Hello \(unwrappedEmail)")
        }
    }
}

Aktualisieren Sie die loginButtonTapped-Funktion so, dass sie nach einer erfolgreichen Anmeldung loadUserData aufruft.

Aktualisierte loginButtonTapped-Funktion in MailViewController.swift

@IBAction func loginButtonTapped(sender: AnyObject) {
    if (service.isLoggedIn) {
        // Logout
        service.logout()
        setLogInState(loggedIn: false)
    } else {
        // Login
        service.login(from: self) {
            error in
            if let unwrappedError = error {
                NSLog("Error logging in: \(unwrappedError)")
            } else {
                NSLog("Successfully logged in.")
                self.setLogInState(loggedIn: true)
                self.loadUserData()
            }
        }
    }
}

Aktualisieren Sie außerdem die viewDidLoad-Funktion so, dass loadUserData aufgerufen wird, wenn wir bereits angemeldet sind.

Aktualisierte viewDidLoad-Funktion in MailViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    setLogInState(loggedIn: service.isLoggedIn)
    if (service.isLoggedIn) {
        loadUserData()
    }
}

Starten Sie die App neu. Nachdem Sie auf die Schaltfläche Log in getippt haben, sollten im Xcode-Ausgabefenster eine Nachricht mit der E-Mail-Adresse des Benutzers angezeigt werden.

Als Nächstes erstellen wir eine Funktion, um die Nachrichten im Posteingang des Benutzers abzurufen. Fügen Sie die folgende Funktion zur OutlookService-Klasse hinzu:

getInboxMessages-Funktion in OutlookService.swift

func getInboxMessages(callback: @escaping (JSON?) -> Void) -> Void {
    let apiParams = [
        "$select": "subject,receivedDateTime,from",
        "$orderby": "receivedDateTime DESC",
        "$top": "10"
    ]

    makeApiCall(api: "/v1.0/me/mailfolders/inbox/messages", params: apiParams) {
        result in
        callback(result)
    }
}

Diese Funktion verwendet die gleiche makeApiCall-Funktion wie die getUserEmail-Funktion. Ein großer Unterschied ist der Benutzer des params-Parameters. Diese Parameter werden an die API-Anforderungs-URL angehängt und steuern das Verhalten der API. In diesem Fall verwenden wir:

  • $select, um zu beschränken, welche Felder in der Antwort zurückgegeben werden. Die Antworten enthalten nur die Felder subject, receivedDateTime und from.
  • $orderby, um die Ergebnisse in absteigender Reihenfolge nach receivedDateTime zu sortieren.
  • $top, um die Antwort auf maximal 10 Elemente zu beschränken.

Aktualisieren Sie nun die loadUserData-Funktion in MailViewController.swift so, dass sie getInboxMessages aufruft, nachdem getUserEmail zurückgegeben wurde.

Aktualisierte loadUserData-Funktion in OutlookService.swift

func loadUserData() {
    service.getUserEmail() {
        email in
        if let unwrappedEmail = email {
            NSLog("Hello \(unwrappedEmail)")

            self.service.getInboxMessages() {
                messages in
                if let unwrappedMessages = messages {
                    for (message) in unwrappedMessages["value"].arrayValue {
                        NSLog(message["subject"].stringValue)
                    }
                }
            }
        }
    }
}

Wenn Sie die App jetzt neu starten, sollte eine Liste von Nachrichtenbetreffzeilen im Xcode-Ausgabefenster angezeigt werden.

Anzeige von Nachrichtenbetreffzeilen im Xcode-Ausgabefenster.

Anzeigen der Ergebnisse

Wir fügen unserer App nun eine Tabellenansicht hinzu, um die Liste der Nachrichten anzuzeigen. Wechseln Sie zu Main.storyboard. Suchen Sie in der Object Library nach Table View. Ziehen Sie Table View in die Szene Mail, direkt unter die Anmeldeschaltfläche. Klicken Sie dann auf die Schaltfläche Add New Constraints in der unteren rechten Ecke des Zeichenbereichs. Legen Sie den linken, rechten und unteren Einschränkungswert auf 0 fest, und klicken Sie auf Add 3 Constraints.

Fügen Sie eine Tabellenansicht zur Ansicht im Haupt-Storyboard hinzu.

Hierdurch wird die Tabellenansicht hinzugefügt und so konfiguriert, dass sie den gesamten Bildschirm füllt. Jetzt müssen wir das Layout der einzelnen Tabellenzellen festlegen. Klicken Sie bei ausgewählter Tabellenansicht auf den Attributes Inspector, und legen Sie den Wert von Prototype Cells auf 1 fest.

Screenshot der Prototypzelle in der Tabellenansicht

Suchen Sie in der Object Library nach Label. Ziehen Sie Label in die Prototypzelle der Tabellenansicht, doppelklicken Sie darauf, und ändern Sie den Text in From. Fügen Sie zwei weitere Beschriftungen hinzu, eine mit dem Text Received und eine mit dem Text Subject.

Screenshot der Prototypzelle mit hinzugefügten Beschriftungen

Nachdem Sie die Beschriftungen hinzugefügt haben, wählen Sie Mail in der Dokumentgliederung aus. Wählen Sie im Menü Editor die Option Resolve Auto Layout Issues, dann Add Missing Constraints gefolgt von Reset to Suggested Constraints aus.

Nun fügen wir eine benutzerdefinierte Klasse für die Tabellenansichtszellen hinzu.

  1. Klicken Sie im Project Navigator bei gedrückter STRG-TASTE auf den Ordner swift-tutorial, und wählen Sie New File. Wählen Sie Swift File (unter iOS/Source) aus, und klicken Sie auf Next. Geben Sie MessageCell in das Feld Save As ein, und klicken Sie auf Create.
  2. Fügen Sie den folgenden Code hinzu, um eine einfache Struktur zur Darstellung einer Nachricht zu erstellen.

    import UIKit
    import SwiftyJSON
    
    struct Message {
        let from: String
        let received: String
        let subject: String
    }
    
  3. Fügen Sie nun den folgenden Code hinzu, um eine Klasse zu erstellen, die von UITableViewCell erbt.

    class MessageCell: UITableViewCell {
        @IBOutlet weak var fromLabel: UILabel!
        @IBOutlet weak var receivedLabel: UILabel!
        @IBOutlet weak var subjectLabel: UILabel!
    
        var from: String? {
            didSet {
                fromLabel.text = from
            }
        }
    
        var received: String? {
            didSet {
                receivedLabel.text = received
            }
        }
    
        var subject: String? {
            didSet {
                subjectLabel.text = subject
            }
        }
    }
    
  4. Fügen Sie zum Schluss den folgenden Code hinzu, um eine Klasse zu erstellen, die als Datenquelle einer Tabellenansicht verwendet werden kann.

    class MessagesDataSource: NSObject {
        let messages: [Message]
    
        init(messages: [JSON]?) {
            var msgArray = [Message]()
    
            if let unwrappedMessages = messages {
                for (message) in unwrappedMessages {
                    let newMsg = Message(
                            from: message["from"]["emailAddress"]["name"].stringValue,
                            received: Formatter.dateToString(date: message["receivedDateTime"]),
                            subject: message["subject"].stringValue)
    
                    msgArray.append(newMsg)
                }
            }
    
            self.messages = msgArray
        }
    }
    
    extension MessagesDataSource: UITableViewDataSource {
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return messages.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MessageCell.self)) as! MessageCell
            let message = messages[indexPath.row]
            cell.from = message.from
            cell.received = message.received
            cell.subject = message.subject
            return cell
        }
    }
    

Dieser Code verwendet eine benutzerdefinierte Formatter-Klasse, um den von der API zurückgegebenen Datumswert in einem benutzerfreundlicheren Format zu formatieren. Diese Klasse werden wir nun implementieren.

  1. Klicken Sie im Project Navigator bei gedrückter STRG-TASTE auf den Ordner swift-tutorial, und wählen Sie New File. Wählen Sie Swift File (unter iOS/Source) aus, und klicken Sie auf Next. Geben Sie Formatter in das Feld Save As ein, und klicken Sie auf Create.
  2. Fügen Sie den folgenden Code hinzu.

    import SwiftyJSON
    
    class Formatter {
        class func dateToString(date: JSON) -> String {
            let graphDateString = date.stringValue
            if (graphDateString.isEmpty) {
                return ""
            }
    
            let toDateFormatter = DateFormatter()
            toDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
    
            let dateObj = toDateFormatter.date(from: graphDateString)
            if (dateObj == nil) {
                return ""
            }
    
            let toStringFormatter = DateFormatter()
            toStringFormatter.dateStyle = DateFormatter.Style.medium
            toStringFormatter.timeStyle = DateFormatter.Style.short
            toStringFormatter.timeZone = TimeZone.current
    
            return toStringFormatter.string(from: dateObj!)
        }
    }
    

Nun ordnen wir diese Klasse der Ansicht zu und verbinden die Beschriftungen mit den IBOutlet-Variablen in der Klasse.

  1. Wechseln Sie zu Main.storyboard, und wählen Sie die Prototypzelle in der Tabellenansicht aus.
  2. Ändern Sie im Identity Inspector den Wert für Class in MessageCell.
  3. Legen Sie im Attribute Inspector für Identifier den Wert MessageCell fest.
  4. Im Connections Inspector sollten unter Outlets die Eigenschaften fromLabel, receivedLabel und subjectLabel aus der MessageCell-Klasse angezeigt werden. Ziehen Sie den kleinen Kreis neben den einzelnen Eigenschaften auf die entsprechende Beschriftung in der Ansicht.

    Screenshot des Connections Inspector für die Prototypzelle

Nun aktualisieren wir die MailViewController-Klasse, um die Ansicht zu aktualisieren.

  1. Fügen Sie in MailViewController.swift die folgenden Member zur Klasse hinzu.

    @IBOutlet weak var tableView: UITableView!
    var dataSource: MessagesDataSource?
    
  2. Ersetzen Sie die vorhandene loadUserData-Funktion durch die folgende, die die for-in-Schleife durch Code zum Erstellen einer neuen MessagesDataSource ersetzt, und aktualisieren Sie die Tabelle.

    func loadUserData() {
        service.getUserEmail() {
            email in
            if let unwrappedEmail = email {
                NSLog("Hello \(unwrappedEmail)")
    
                self.service.getInboxMessages() {
                    messages in
                    if let unwrappedMessages = messages {
                        self.dataSource = MessagesDataSource(messages: unwrappedMessages["value"].arrayValue)
                        self.tableView.dataSource = self.dataSource
                        self.tableView.reloadData()
                    }
                }
            }
        }
    }
    
  3. Fügen Sie nach der Zeile super.viewDidLoad() die folgenden Zeilen zu viewDidLoad hinzu:

    tableView.estimatedRowHeight = 90;
    tableView.rowHeight = UITableViewAutomaticDimension
    

Als Letztes müssen wir die IBOutlet-Variable, die wir dem Ansichtscontroller hinzugefügt haben, mit der Tabellenansicht verbinden. Wechseln Sie zu Main.storyboard, und wählen Sie Mail in der Dokumentgliederung aus. Im Connections Inspector sollte die Eigenschaft tableView angezeigt werden. Ziehen Sie den kleinen Kreis neben der Eigenschaft in die Tabellenansicht.

Wenn Sie die App ausführen, sollten Sie jetzt eine Tabelle mit Nachrichten erhalten, durch die Sie scrollen können.

Die Beispiel-App, die Nachrichten anzeigt.

Hinzufügen von Kalender- und Kontakte-APIs

Da Sie nun das Aufrufen von Outlook-Mail aus Graph gemeistert haben, dürfte es kein Problem mehr sein, das gleiche für Kalender- und Kontakte-APIs zu tun.

Tipp

Wenn Sie die Schritte in diesem Lernprogramm befolgt haben, haben Sie wahrscheinlich ein Zugriffstoken in Ihrer Keychain gespeichert. Dieses Token ist nur für den Mail.Read-Bereich gültig. Um die Kalender- oder Kontakte-API aufzurufen, müssen wir neue Bereiche hinzufügen. Melden Sie sich unbedingt von der App ab, um die gespeicherten Token zu entfernen, damit Sie den Anmeldevorgang von vorne beginnen können, um ein neues Zugriffstoken zu erhalten.

Für die Kalender-API:

Hinzufügen einer Klasse zum Abrufen von Ereignissen

  1. Wechseln Sie zu OutlookService.swift.
  2. Aktualisieren Sie den Wert für scope im oauth2Settings-Wörterbuch, um den Calendars.Read-Bereich einzuschließen.

    "scope": "openid profile offline_access User.Read Mail.Read Calendars.Read",
    
  3. Fügen Sie die folgende Funktion zur OutlookService-Klasse hinzu:

    func getEvents(callback: @escaping (JSON?) -> Void) -> Void {
        let apiParams = [
            "$select": "subject,start,end",
            "$orderby": "start/dateTime ASC",
            "$top": "10"
        ]
    
        makeApiCall(api: "/v1.0/me/events", params: apiParams) {
            result in
            callback(result)
        }
    }
    

Hinzufügen eines Formatierers für die Start- und Endzeiten

Die Eigenschaften start und end für ein Ereignis weisen den Typ dateTimeTimeZone auf, der ein wenig anders formatiert ist als andere Datumsangaben. Um dies zu berücksichtigen, fügen wir der Formatter-Klasse eine weitere Funktion hinzu.

  1. Wechseln Sie zu Formatter.swift.
  2. Fügen Sie die folgende Funktion hinzu.

    class func dateTimeTimeZoneToString(date: JSON) -> String {
        let graphTimeZone = date["timeZone"].stringValue
        let graphDateString = date["dateTime"].stringValue
        if (graphDateString.isEmpty) {
            return ""
        }
    
        let toDateFormatter = DateFormatter()
        toDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.sss"
        toDateFormatter.timeZone = TimeZone(identifier: graphTimeZone)
    
        let dateObj = toDateFormatter.date(from: graphDateString)
        if (dateObj == nil) {
            return ""
        }
    
        let toStringFormatter = DateFormatter()
        toStringFormatter.dateStyle = DateFormatter.Style.medium
        toStringFormatter.timeStyle = DateFormatter.Style.short
        toStringFormatter.timeZone = TimeZone.current
    
        return toStringFormatter.string(from: dateObj!)
    }
    

Hinzufügen der benutzerdefinierten Klasse „EventCell“.

  1. Klicken Sie im Project Navigator bei gedrückter STRG-TASTE auf den Ordner swift-tutorial, und wählen Sie New File. Wählen Sie Swift File (unter iOS/Source) aus, und klicken Sie auf Next. Geben Sie EventCell in das Feld Save As ein, und klicken Sie auf Create.
  2. Fügen Sie den folgenden Code hinzu.

    import UIKit
    import SwiftyJSON
    
    struct Event {
        let subject: String
        let start: String
        let end: String
    }
    
    class EventCell: UITableViewCell {
        @IBOutlet weak var subjectLabel: UILabel!
        @IBOutlet weak var startLabel: UILabel!
        @IBOutlet weak var endLabel: UILabel!
    
        var subject: String? {
            didSet {
                subjectLabel.text = subject
            }
        }
    
        var start: String? {
            didSet {
                startLabel.text = start
            }
        }
    
        var end: String? {
            didSet {
                endLabel.text = end
            }
        }
    }
    
    class EventsDataSource: NSObject {
        let events: [Event]
    
        init(events: [JSON]?) {
            var evtArray = [Event]()
    
            if let unwrappedEvents = events {
                for (event) in unwrappedEvents {
                    let newEvent = Event(
                        subject: event["subject"].stringValue,
                        start: Formatter.dateTimeTimeZoneToString(date: event["start"]),
                        end: Formatter.dateTimeTimeZoneToString(date: event["end"]))
    
                    evtArray.append(newEvent)
                }
            }
    
            self.events = evtArray
        }
    }
    
    extension EventsDataSource: UITableViewDataSource {
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return events.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: EventCell.self)) as! EventCell
            let event = events[indexPath.row]
            cell.subject = event.subject
            cell.start = event.start
            cell.end = event.end
            return cell
        }
    }
    

Implementieren des Ansichtscontrollers

  1. Erweitern Sie im Project Navigator swift-tutorial->swift-tutorial, und wählen Sie dann SecondViewController.swift aus.
  2. Öffnen Sie den File Inspector, und ändern Sie den Dateinamen in CalendarViewController.swift.
  3. Ersetzen Sie den Dateiinhalt durch Folgendes.

    import UIKit
    
    class CalendarViewController: UIViewController {
    
        @IBOutlet weak var tableView: UITableView!
    
        var dataSource:EventsDataSource?
    
        let service = OutlookService.shared()
    
        func loadUserData() {
            service.getUserEmail() {
                email in
                if let unwrappedEmail = email {
                    NSLog("Hello \(unwrappedEmail)")
    
                    self.service.getEvents() {
                        events in
                        if let unwrappedEvents = events {
                            self.dataSource = EventsDataSource(events: unwrappedEvents["value"].arrayValue)
                            self.tableView.dataSource = self.dataSource
                            self.tableView.reloadData()
                        }
                    }
                }
            }
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            tableView.estimatedRowHeight = 90;
            tableView.rowHeight = UITableViewAutomaticDimension
    
            if (service.isLoggedIn) {
                loadUserData()
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    }
    

Hinzufügen der Kalender-Benutzeroberfläche

  1. Wählen Sie Main.storyboard im Project Navigator aus. Erweitern Sie in der Dokumentgliederung Second Scene, und wählen Sie dann Second aus.
  2. Öffnen Sie den Attributes Inspector, und ändern Sie den Wert für Title in Calendar.
  3. Öffnen Sie den Identity Inspector, und ändern Sie den Wert für Class in CalendarViewController.
  4. Erweitern Sie View, und löschen Sie die Beschriftungen Second View und Loaded by SecondViewController.
  5. Wählen Sie das Balkenelement Second am unteren Rand der Ansicht aus. Ändern Sie im Attributes Inspector den Wert für Title in Calendar.
  6. Suchen Sie in der Object Library nach Table View. Ziehen Sie Table View in die Szene Calendar. Klicken Sie dann auf die Schaltfläche Add New Constraints in der unteren rechten Ecke des Zeichenbereichs. Legen Sie den linken, rechten und unteren Einschränkungswert auf 0 fest, und klicken Sie auf Add 4 Constraints.
  7. Klicken Sie bei ausgewählter Tabellenansicht auf den Attributes Inspector, und legen Sie den Wert von Prototype Cells auf 1 fest.
  8. Suchen Sie in der Object Library nach Label. Ziehen Sie Label in die Prototypzelle der Tabellenansicht, doppelklicken Sie darauf, und ändern Sie den Text in Subject. Fügen Sie zwei weitere Beschriftungen hinzu, eine mit dem Text Start und eine mit dem Text End.
  9. Nachdem Sie die Beschriftungen hinzugefügt haben, wählen Sie Calendar in der Dokumentgliederung aus. Wählen Sie im Menü Editor die Option Resolve Auto Layout Issues, dann Add Missing Constraints gefolgt von Reset to Suggested Constraints aus.
  10. Wählen Sie Calendar in der Dokumentgliederung aus. Ziehen Sie im Connections Inspector den kleinen Kreis neben tableView (unter Outlets) in die Tabellenansicht im Storyboard.
  11. Wählen Sie EventCell in der Dokumentgliederung aus. Ziehen Sie im Connections Inspector den kleinen Kreis neben subjectLabel, startLabel und endLabel (unter Outlets) auf die entsprechenden Beschriftungen im Storyboard.

Führen Sie die App aus. Denken Sie daran, sich ab- und erneut anzumelden, um den neuen Bereich abzurufen.

Für die Kontakte-API:

Hinzufügen einer Funktion zum Abrufen von Kontakten

  1. Wechseln Sie zu OutlookService.swift.
  2. Aktualisieren Sie den Wert für scope im oauth2Settings-Wörterbuch, um den Contacts.Read-Bereich einzuschließen.

    "scope": "openid profile offline_access User.Read Mail.Read Contacts.Read",
    
  3. Fügen Sie die folgende Funktion zur OutlookService-Klasse hinzu:

    func getContacts(callback: @escaping (JSON?) -> Void) -> Void {
        let apiParams = [
            "$select": "givenName,surname,emailAddresses",
            "$orderby": "givenName ASC",
            "$top": "10"
        ]
    
        makeApiCall(api: "/v1.0/me/contacts", params: apiParams) {
            result in
            callback(result)
        }
    }
    

Hinzufügen der benutzerdefinierten Klasse „ContactCell“.

  1. Klicken Sie im Project Navigator bei gedrückter STRG-TASTE auf den Ordner swift-tutorial, und wählen Sie New File. Wählen Sie Swift File (unter iOS/Source) aus, und klicken Sie auf Next. Geben Sie ContactCell in das Feld Save As ein, und klicken Sie auf Create.
  2. Fügen Sie den folgenden Code hinzu.

    import UIKit
    import SwiftyJSON
    
    struct Contact {
        let givenName: String
        let surname: String
        let email: String
    }
    
    class ContactCell: UITableViewCell {
        @IBOutlet weak var nameLabel: UILabel!
        @IBOutlet weak var emailLabel: UILabel!
    
        var givenName: String? {
            didSet {
                nameLabel.text = givenName! + " " + (surname ?? "")
            }
        }
    
        var surname: String? {
            didSet {
                nameLabel.text = (givenName ?? "") + " " + surname!
            }
        }
    
        var email: String? {
            didSet {
                emailLabel.text = email
            }
        }
    }
    
    class ContactsDataSource: NSObject {
        let contacts: [Contact]
    
        init(contacts: [JSON]?) {
            var ctctArray = [Contact]()
    
            if let unwrappedContacts = contacts {
                for (contact) in unwrappedContacts {
                    let newContact = Contact(
                        givenName: contact["givenName"].stringValue,
                        surname: contact["surname"].stringValue,
                        email: contact["emailAddresses"][0]["address"].stringValue)
    
                    ctctArray.append(newContact)
                }
            }
    
            self.contacts = ctctArray
        }
    }
    
    extension ContactsDataSource: UITableViewDataSource {
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return contacts.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ContactCell.self)) as! ContactCell
            let contact = contacts[indexPath.row]
            cell.givenName = contact.givenName
            cell.surname = contact.surname
            cell.email = contact.email
            return cell
        }
    }
    

Implementieren des Ansichtscontrollers

  1. Erweitern Sie im Project Navigator swift-tutorial->swift-tutorial, und wählen Sie dann SecondViewController.swift aus.
  2. Öffnen Sie den File Inspector, und ändern Sie den Dateinamen in ContactsViewController.swift.
  3. Ersetzen Sie den Dateiinhalt durch Folgendes.

    import UIKit
    
    class ContactsViewController: UIViewController {
    
        @IBOutlet weak var tableView: UITableView!
    
        var dataSource: ContactsDataSource?
    
        let service = OutlookService.shared()
    
        func loadUserData() {
            service.getUserEmail() {
                email in
                if let unwrappedEmail = email {
                    NSLog("Hello \(unwrappedEmail)")
    
                    self.service.getContacts() {
                        contacts in
                        if let unwrappedContacts = contacts {
                            self.dataSource = ContactsDataSource(contacts: unwrappedContacts["value"].arrayValue)
                            self.tableView.dataSource = self.dataSource
                            self.tableView.reloadData()
                        }
                    }
                }
            }
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            tableView.estimatedRowHeight = 90;
            tableView.rowHeight = UITableViewAutomaticDimension
    
            if (service.isLoggedIn) {
                loadUserData()
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    }
    

Hinzufügen der Kalender-Benutzeroberfläche

  1. Wählen Sie Main.storyboard im Project Navigator aus. Erweitern Sie in der Dokumentgliederung Second Scene, und wählen Sie dann Second aus.
  2. Öffnen Sie den Attributes Inspector, und ändern Sie den Wert für Title in Contacts.
  3. Öffnen Sie den Identity Inspector, und ändern Sie den Wert für Class in ContactsViewController.
  4. Erweitern Sie View, und löschen Sie die Beschriftungen Second View und Loaded by SecondViewController.
  5. Wählen Sie das Balkenelement Second am unteren Rand der Ansicht aus. Ändern Sie im Attributes Inspector den Wert für Title in Contacts.
  6. Suchen Sie in der Object Library nach Table View. Ziehen Sie Table View in die Szene Contacts. Klicken Sie dann auf die Schaltfläche Add New Constraints in der unteren rechten Ecke des Zeichenbereichs. Legen Sie den linken, rechten und unteren Einschränkungswert auf 0 fest, und klicken Sie auf Add 4 Constraints.
  7. Klicken Sie bei ausgewählter Tabellenansicht auf den Attributes Inspector, und legen Sie den Wert von Prototype Cells auf 1 fest.
  8. Suchen Sie in der Object Library nach Label. Ziehen Sie Label in die Prototypzelle der Tabellenansicht, doppelklicken Sie darauf, und ändern Sie den Text in Name. Fügen Sie eine weitere Beschriftung mit dem Text Email hinzu.
  9. Nachdem Sie die Beschriftungen hinzugefügt haben, wählen Sie Contacts in der Dokumentgliederung aus. Wählen Sie im Menü Editor die Option Resolve Auto Layout Issues, dann Add Missing Constraints gefolgt von Reset to Suggested Constraints aus.
  10. Wählen Sie Contacts in der Dokumentgliederung aus. Ziehen Sie im Connections Inspector den kleinen Kreis neben tableView (unter Outlets) in die Tabellenansicht im Storyboard.
  11. Wählen Sie ContactCell in der Dokumentgliederung aus. Ziehen Sie im Connections Inspector den kleinen Kreis neben nameLabel und emailLabel (unter Outlets) auf die entsprechenden Beschriftungen im Storyboard.

Führen Sie die App aus. Denken Sie daran, sich ab- und erneut anzumelden, um den neuen Bereich abzurufen.