Ich habe mich immer gefragt, wie man einen UIPageViewController ähnlich wie einen UITabBarController innerhalb eines Storyboards konfigurieren kann. Es wäre schön, standardmäßig eingebettete Übergänge zu den View Controllern zu erstellen, aus denen die Seiten bestehen. Leider ist so etwas nicht möglich und derzeit können Sie in einem Storyboard nicht viel mit Seiten-View-Controllern machen. Also dachte ich mir, dass ich einen eigenen Weg finden würde, um Seiten durch Segues zu verbinden. Dieser Beitrag erklärt, wie das geht.
Ohne Übergänge
Lassen Sie uns zunächst ein Beispiel ohne Segues erstellen und später versuchen, es so zu ändern, dass es Segues verwendet.
In dem obigen Storyboard haben wir 2 Szenen. Einen Controller für die Seitenansicht und einen weiteren für die einzelnen Seiten, den Content View Controller. Der Seitenansichts-Controller hat 4 Seiten, die jeweils ihre Seitenzahl anzeigen. Einfacher kann ein Page View Controller nicht sein. Nachfolgend der Code unseres einfachen Content View Controllers: [code language="obj-c"] class MyContentViewController: UIViewController { @IBOutlet weak var pageNumberLabel: UILabel! var pageNumber: Int! override func viewDidLoad() { super.viewDidLoad() pageNumberLabel.text = "Page (pageNumber)" [/code] } } Das bedeutet, dass unser PageViewController nur eine Instanz unseres MyContentViewControllers für jede Seite instanziieren und die pageNumber setzen muss. Und da es keinen Übergang zwischen den beiden Szenen gibt, ist die einzige Möglichkeit, eine Instanz des MyContentViewController zu erstellen, programmatisch mit der UIStoryboard.instantiateViewControllerWithIdentifier(:). Damit das funktioniert, müssen wir der Content View Controller-Szene natürlich einen Bezeichner geben. Wir wählen MyContentViewController, um dem Namen der Klasse zu entsprechen. Unser Seitenansicht-Controller wird wie folgt aussehen: [code language="obj-c"] class MyPageViewController: UIPageViewController { let numberOfPages = 4 override func viewDidLoad() { super.viewDidLoad() setViewControllers([createViewController(1)], direction: .Forward, animated: false, completion: nil) dataSource = self } func createViewController(pageNumber: Int) -> UIViewController { let contentViewController = storyboard?.instantiateViewControllerWithIdentifier("MyContentViewController") as! MyContentViewController contentViewController.pageNumber = pageNumber return contentViewController } } Erweiterung MyPageViewController: UIPageViewControllerDataSource { func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? { return createViewController( mod((viewController as! MyContentViewController).pageNumber, numberOfPages) + 1) } func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? { return createViewController( mod((viewController as! MyContentViewController).pageNumber - 2, numberOfPages) + 1) } } func mod(x: Int, m: Int) -> Int{ let r = x % m return r < 0 ? r + m : r } [/code] Hier haben wir die Methode createViewController(: ) erstellt, die die Seitenzahl als Argument hat und die Instanz von MyContentViewController erstellt und die Seitenzahl festlegt. Diese Methode wird von viewDidLoad() aufgerufen, um die Anfangsseite festzulegen, und von den beiden UIPageViewControllerDataSource-Methoden, die wir hier implementieren, um zur nächsten und zur vorherigen Seite zu gelangen. Die benutzerdefinierte Funktion mod(:) wird verwendet, um eine kontinuierliche Seitennavigation zu haben, bei der der Benutzer von der letzten Seite zur ersten und umgekehrt wechseln kann (der eingebaute %-Operator führt keine echte Moduloperation durch, die wir hier benötigen).
Zwischensequenzen verwenden
Das obige Beispiel ist ziemlich einfach. Wie können wir es also ändern, um eine Überblendung zu verwenden? Zunächst einmal müssen wir eine Überblendung zwischen den beiden Szenen erstellen. Da wir hier nichts Standardmäßiges tun, muss es eine benutzerdefinierte Überleitung sein. Nun brauchen wir eine Möglichkeit, eine Instanz unseres Content View Controllers über die Segue zu erhalten, die wir von unserem createViewController(:) aus nutzen können. Die einzige Methode, die wir verwenden können, um etwas mit der Segue zu tun, ist UIViewController.performSegueWithIdentifier(:sender:). Wir wissen, dass durch den Aufruf dieser Methode eine Instanz unseres Content View Controllers erstellt wird, der das Ziel der Segue ist, aber wir brauchen dann eine Möglichkeit, diese Instanz zurück in unsere createViewController(:) Methode zu bekommen. Der einzige Ort, an dem wir die neue Instanz des Content View Controllers referenzieren können, ist innerhalb der benutzerdefinierten Segue. In seiner init-Methode können wir ihn auf eine Variable setzen, auf die auch die Methode createViewController(: ) zugreifen kann. Das sieht in etwa wie folgt aus. Zuerst erstellen wir die Variable: [code language="obj-c"] var nextViewController: MyContentViewController? [/code] Als nächstes erstellen wir eine neue benutzerdefinierte Segue-Klasse, die dieser Variablen den Ziel-View-Controller (den neuen MyContentViewController) zuweist. [code language="obj-c"] public class PageSegue: UIStoryboardSegue { public override init!(identifier: String?, source: UIViewController, Ziel: UIViewController) { super.init(identifier: identifier, source: source, destination: destination) if let pageViewController = source as? MyPageViewController { pageViewController.nextViewController = destinationViewController as? MyContentViewController } } public override func perform() {} } [/code] Da wir nur daran interessiert sind, den Verweis auf den erstellten View-Controller zu erhalten, brauchen wir in der perform() -Methode nichts weiter zu tun. Und der Seiten-View-Controller selbst wird die Anzeige der Seiten übernehmen, so dass unsere Segue-Implementierung ziemlich einfach bleibt. Jetzt können wir unsere createViewController(_:) -Methode ändern: [code language="obj-c"] func createViewController(pageNumber: Int) -> UIViewController { performSegueWithIdentifier("Page", sender: self) nextViewController!.pageNumber = pageNumber return nextViewController! } [/code] Die Methode sieht etwas merkwürdig aus, da wir nextViewController nirgendwo in diesem View Controller zuweisen. Und wir verlassen uns darauf, dass die Segue synchron mit dem performSegueWithIdentifier-Aufruf erstellt wird. Sonst würde es nicht funktionieren. Jetzt können wir die Segue in unserem Storyboard erstellen. Wir müssen ihm denselben Bezeichner wie oben geben und die Segue-Klasse auf PageSegue setzen
Generische Klasse Jetzt können wir endlich Segues erstellen, um die Beziehung zwischen PageViewController und ContentViewController zu visualisieren. Aber lassen Sie uns sehen, ob wir eine generische Klasse schreiben können, die den Großteil der Logik enthält und die wir für jeden UIPageViewController wiederverwenden können. Wir erstellen eine Klasse namens SeguePageViewController, die die Superklasse für unseren MyPageViewController sein wird. Wir werden die PageSegue in dieselbe Quelldatei verschieben und einige Codeänderungen vornehmen, um sie generischer zu machen: [code language="obj-c"] public class SeguePageViewController: UIPageViewController { var pageSegueIdentifier = "Page" var nextViewController: UIViewController? public func createViewController(sender: AnyObject?) -> MyContentViewController { performSegueWithIdentifier(pageSegueIdentifier, sender: sender) return nextViewController! } } public class PageSegue: UIStoryboardSegue { public override init!(identifier: String?, source: UIViewController, Ziel: UIViewController) { super.init(identifier: identifier, source: source, destination: destination) if let pageViewController = source as? SeguePageViewController { pageViewController.nextViewController = destinationViewController as? UIViewController } } public override func perform() {} } [/code] Wie Sie sehen können, haben wir die Variablen nextViewController und createViewController(:) in diese Klasse verschoben und verwenden UIViewController anstelle unserer konkreten Klasse MyContentViewController. Wir haben auch eine neue Variable pageSegueIdentifier eingeführt, um den Identifikator der Segue ändern zu können. Jetzt fehlt nur noch das Setzen der pageNumber unseres MyContentViewControllers. Da wir die Dinge gerade erst generisch gemacht haben, können wir sie von hier aus nicht festlegen, wie gehen wir also am besten damit um? Sie haben vielleicht bemerkt, dass die Methode createViewController(: ) jetzt einen Absender hat : AnyObject? als Argument hat, was in unserem Fall immer noch die Seitennummer ist. Und wir kennen eine weitere Methode, die dieses Absenderobjekt erhält: prepareForSegue(_:sender:). Alles, was wir tun müssen, ist, dies in unserer Klasse MyPageViewController zu implementieren. [code language="obj-c"] override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == pageSegueIdentifier { let vc = segue.destinationViewController as! MyContentViewController vc.pageNumber = sender as! Int } } [/code] Wenn Sie überrascht sind, dass das Absender-Argument auf diese Weise verwendet wird, sollten Sie meinen vorherigen Beitrag zum Thema Den 'Absender' in Segues verstehen und verwenden, um Daten an einen anderen View Controller weiterzugeben lesen
Fazit
Ob Sie diesen Ansatz der Verwendung von Zwischensequenzen für Seitenansichtscontroller verwenden möchten oder nicht, ist eine Frage der persönlichen Vorliebe. Es bringt keine großen Vorteile, aber es gibt Ihnen einen visuellen Hinweis darauf, was im Storyboard passiert. Es spart Ihnen nicht wirklich Code, da die Menge an Code in MyPageViewController ungefähr gleich geblieben ist. Wir haben lediglich die Methode createViewController(:) durch die Methode prepareForSegue(:sender:) ersetzt. Es wäre schön, wenn Apple eine bessere Lösung anbieten würde, die weniger von der Datenquelle eines PageViewControllers abhängt und Sie die Paging-Logik direkt im Storyboard definieren lässt.
Verfasst von

Lammert Westerhoff
Contact