Es ist seltsam, dass es keinen einfachen Weg gibt, dies zu tun. Stellen Sie sich das folgende Szenario vor:
Ich habe versucht, den View-Controller als untergeordneten View-Controller zu entfernen, nachdem der Übergang abgeschlossen ist, aber ich kann immer noch zu der leeren Seite zurückblättern (die Seitenansicht wird dadurch nicht "angepasst").
Kann ich das machen?
Während die Antworten hier alle informativ sind, gibt es eine alternative Möglichkeit, das Problem zu lösen.
UIPageViewController navigiert mit Scroll-Übergangsstil zu falscher Seite
Als ich zum ersten Mal nach einer Antwort auf dieses Problem gesucht habe, hat mich die Art und Weise, in der ich meine Suche formulierte, an dieser Frage interessiert, und nicht an der, die ich gerade verlinkt habe. Daher fühlte ich mich verpflichtet, eine Antwort auf diese andere Frage zu posten. jetzt, wo ich es gefunden habe, und auch ein wenig ausführlicher.
Das Problem wird von matt hier ziemlich gut beschrieben:
Dies ist tatsächlich ein Fehler in UIPageViewController. Sie tritt nur mit dem Bildlaufstil (UIPageViewControllerTransitionStyleScroll) und nur mit .__ auf. nach dem Aufruf von setViewControllers: direction: animated: completion: mit animiert: JA. Daher gibt es zwei Problemumgehungen:
Verwenden Sie keine UIPageViewControllerTransitionStyleScroll.
Wenn Sie setViewControllers: direction: animated: completion: aufrufen, verwenden Sie nur animiert: NEIN.
Um den Fehler klar zu sehen, rufen Sie setViewControllers: direction: animated: completion: und dann in der Schnittstelle (als Benutzer), navigieren Sie nach links (zurück) zur vorherigen Seite manuell. Sie navigieren zurück zur falschen Seite: nicht zur vorherigen Seite überhaupt, aber die Seite, auf der Sie sich befanden, als setViewControllers: direction: animated: completion: wurde aufgerufen.
Der Grund für den Fehler scheint darin zu liegen, dass bei Verwendung der Bildlaufleiste In diesem Stil führt UIPageViewController eine Art internes Caching aus. Somit, nach dem Aufruf von setViewControllers: direction: animated: completion:, Der interne Cache kann nicht gelöscht werden. Es denkt, es wisse was Vorherige Seite ist. Wenn der Benutzer also nach links zum .__ navigiert. Auf der vorherigen Seite kann UIPageViewController die dataSource .__ nicht aufrufen. Methode pageViewController: viewControllerBeforeViewController: oder ruft es mit dem falschen aktuellen View Controller auf.
Dies ist eine gute Beschreibung, nicht ganz das in dieser Frage erwähnte Problem, aber sehr nahe. Beachten Sie die Zeile, wenn Sie setViewControllers
mit animated:NO
ausführen. Sie werden den UIPageViewController zwingen, seine Datenquelle erneut abzufragen, wenn der Benutzer das nächste Mal mit einer Geste schwenkt, da er nicht mehr "weiß, wo er sich befindet" oder welche Ansichtssteuerungen sich neben seiner aktuellen Position befinden Controller anzeigen.
Dies funktionierte jedoch nicht für mich, da ich manchmal die PageView programmgesteuert um mit einer Animation verschieben musste.
Mein erster Gedanke war also, setViewControllers
mit einer Animation aufzurufen, und dann im Completion-Block die Methode erneut mit dem jetzt angezeigten View-Controller aufzurufen, jedoch ohne Animation. Der Benutzer kann also schwenken, gut, aber dann rufen wir die Methode erneut auf, damit die Seitenansicht zurückgesetzt wird.
Als ich das versuchte, bekam ich leider seltsame "Assertions-Fehler" vom Page-View-Controller. Sie sehen ungefähr so aus:
*** Assertionsfehler in - [UIPageViewController queueingScrollView: ...
Da ich nicht genau wusste, warum dies geschah, ging ich zurück und begann mit Jais Antwort als Lösung. Ich erstellte einen völlig neuen UIPageViewController, schob ihn auf einen UINavigationController und holte dann den alten heraus. Gross, aber es funktioniert - meistens. Ich habe festgestellt, dass ich immer noch gelegentliche Assertionsfehler vom UIPageViewController bekomme, wie hier:
*** Assertionsfehler in - [UIPageViewController queuingScrollView: didEndManualScroll: toRevealView: direction: animated: didFinish: didComplete:], /SourceCache/UIKit_Sim/UIKit-2380.17/UIPageViewController.m:1820 $ 1 = 154507824 Kein Sichtcontroller, der sichtbare Sicht verwaltet>
Und die App stürzt ab. Warum? Nun, auf der Suche fand ich diese andere Frage , die ich oben erwähnt habe, und insbesondere die akzeptierte Antwort die meine ursprüngliche Idee befürwortet, einfach setViewControllers: animated:YES
und dann, sobald der Aufruf von setViewControllers: animated:NO
abgeschlossen ist, mit dem gleichen Aufruf aufzurufen Sehen Sie sich die Controller an, um den UIPageViewController zurückzusetzen, aber er hatte das fehlende Element: den Code in der Hauptwarteschlange zurückrufen! Hier ist der Code:
__weak YourSelfClass *blocksafeSelf = self;
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished){
if(finished)
{
dispatch_async(dispatch_get_main_queue(), ^{
[blocksafeSelf.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];// bug fix for uipageview controller
});
}
}];
Beeindruckend! Der einzige Grund, warum dies tatsächlich für mich sinnvoll war, liegt darin, dass ich die WWDC 2012-Sitzung 211, Erstellen gleichzeitiger Benutzeroberflächen unter iOS (verfügbar/hier -/mit einem Dev-Konto) gesehen habe. Ich erinnere mich jetzt daran, dass der Versuch, Datenquellenobjekte zu ändern, von denen UIKit-Objekte (wie beispielsweise UIPageViewController) abhängen, und dies in einer sekundären Warteschlange zu tun, einige unangenehme Abstürze verursachen kann.
Was ich noch nie besonders dokumentiert gesehen habe, aber jetzt annehmen muss, dass der Beendigungsblock für eine Animation in einer sekundären Warteschlange ausgeführt wird, ist nicht die Hauptwarteschlange. Der Grund, warum UIPageViewController kreischte und Assertionsfehler auslöste, sowohl beim ersten Aufruf von setViewControllers animated:NO
im Beendigungsblock von setViewControllers animated:YES
als auch jetzt, da ich einfach einen UINavigationController verwende, um einen neuen UIPageViewController zu drücken (jedoch wieder in Der Beendigungsblock von setViewControllers animated:YES
) ist darauf zurückzuführen, dass sich alles in dieser sekundären Warteschlange befindet.Aus diesem Grund funktioniert der Code oben perfekt, da Sie aus dem Block für die Animation der Animation kommen und ihn zurück in die Hauptwarteschlange senden, damit Sie die Streams nicht mit UIKit kreuzen. Brillant.
Jedenfalls wollte ich diese Reise teilen, falls jemand auf dieses Problem stößt.
BEARBEITEN: Schnelle Version hier , wenn jemand interessiert ist.
EDIT: Swift version here , if anyone's interested.
Um die großartige Antwort von Matt Mc abzuschließen, könnte die folgende Methode zu einer Unterklasse von UIPageViewController hinzugefügt werden, um so die Verwendung von setViewControllers zu ermöglichen: direction: animated: completion: So sollte es verwendet werden, wenn der Fehler nicht vorhanden wäre.
- (void) setViewControllers:(NSArray*)viewControllers direction:(UIPageViewControllerNavigationDirection)direction animated:(BOOL)animated completion:(void (^)(BOOL))completion {
if (!animated) {
[super setViewControllers:viewControllers direction:direction animated:NO completion:completion];
return;
}
[super setViewControllers:viewControllers direction:direction animated:YES completion:^(BOOL finished){
if (finished) {
dispatch_async(dispatch_get_main_queue(), ^{
[super setViewControllers:viewControllers direction:direction animated:NO completion:completion];
});
} else {
if (completion != NULL) {
completion(finished);
}
}
}];
}
Rufen Sie einfach setViewControllers auf: direction: animated: completion: auf den Klassen/Unterklassen, die diese Methode implementieren, und es sollte wie erwartet funktionieren.
maq ist richtig. Wenn Sie den Bildlauf-Übergang verwenden, wird durch das Entfernen eines untergeordneten View-Controllers aus dem UIPageViewController die gelöschte "Seite" nicht auf dem Bildschirm angezeigt, wenn der Benutzer zu ihr navigiert. Wenn Sie interessiert sind, habe ich den untergeordneten View-Controller aus dem UIPageViewController entfernt.
// deleteVC is a child view controller of the UIPageViewController
[deleteVC willMoveToParentViewController:nil];
[deleteVC.view removeFromSuperview];
[deleteVC removeFromParentViewController];
Der Ansichtscontroller deleteVC wird aus der childViewControllers-Eigenschaft des UIPageViewController entfernt, erscheint jedoch weiterhin auf dem Bildschirm, wenn der Benutzer zu ihm navigiert.
Bis jemand, der klüger als ich ist, eine elegante Lösung findet, folgt hier eine Problemumgehung (es ist ein Hack - Sie müssen sich also fragen, ob Sie wirklich Seiten aus einem UIPageViewController entfernen müssen).
Diese Anweisungen setzen voraus, dass jeweils nur eine Seite angezeigt wird.
Nachdem der Benutzer auf eine Schaltfläche geklickt hat, die angibt, dass er die Seite löschen möchte, navigieren Sie mit der Methode setViewControllers: direction: animated: completion: zur nächsten oder vorherigen Seite. Natürlich müssen Sie dann den Inhalt der Seite aus Ihrem Datenmodell löschen.
Als Nächstes (und hier ist der Hack) erstellen und konfigurieren Sie einen brandneuen UIPageViewController und laden ihn in den Vordergrund (d. H. Vor dem anderen UIPageViewController). Stellen Sie sicher, dass der neue UIPageViewController die gleiche Seite anzeigt, die zuvor angezeigt wurde. Ihr neuer UIPageViewController holt frische View-Controller aus der Datenquelle.
Zum Schluss entladen und zerstören Sie den im Hintergrund befindlichen UIPageViewController.
Trotzdem hat maq eine wirklich gute Frage gestellt. Leider habe ich nicht genügend Reputationspunkte, um die Frage zu stimmen. Ach, wage zu träumen ... eines Tages werde ich 15 Reputationspunkte haben.
Ich werde diese Antwort hier nur als Referenz für meine Zukunft angeben und wenn sie jemandem hilft - was ich letztendlich getan habe, war:
setViewControllers
habe ich/init'ed einen neuen UIPageViewController mit den geänderten Daten erstellt (Element entfernt) und ohne Animation gedrückt, sodass sich nichts auf dem Bildschirm ändert (mein UIPageViewController ist in einem UINavigationController enthalten).viewControllers
-Arrays des UINavigationController ab, und entfernen Sie den vorletzten View-Controller (der alte UIPageViewController).Um dies zu verbessern. Sie sollten feststellen, ob pageView vor setViewControllers scrollt oder nicht.
var isScrolling = false
func viewDidLoad() {
...
for v in view.subviews{
if v.isKindOfClass(UIScrollView) {
(v as! UIScrollView).delegate = self
}
}
}
func scrollViewWillBeginDragging(scrollView: UIScrollView){
isScrolling = true
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView){
isScrolling = false
}
func jumpToVC{
if isScrolling { //you should not jump out when scrolling
return
}
setViewControllers([vc], direction:direction, animated:true, completion:{[unowned self] (succ) -> Void in
if succ {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.setViewControllers([vc], direction:direction, animated:false, completion:nil)
})
}
})
}
Dieses Problem tritt auf, wenn Sie versuchen, viewControllers während der Swipe-Gesten-Animation zwischen viewControllers zu ändern. Um dieses Problem zu lösen, habe ich ein einfaches Flag gesetzt, um herauszufinden, wann sich der Page-View-Controller während des Übergangs befindet.
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers {
self.pageViewControllerTransitionInProgress = YES;
}
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
self.pageViewControllerTransitionInProgress = NO;
}
Und wenn ich versuche, den View-Controller zu überprüfen, überprüfe ich, ob ein Übergang im Gange ist.
- (void)setCurrentPage:(NSString *)newCurrentPageId animated:(BOOL)animated {
if (self.pageViewControllerTransitionInProgress) {
return;
}
[self.pageContentViewController setViewControllers:@[pageDetails] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {
}
}
Ich lerne dies nur selbst, also nehmen Sie es mit einem Salzkorn mit, aber nach meinem Verständnis müssen Sie die Datenquelle des Pageviewcontroller ändern und nicht den Viewcontroller entfernen. Wie viele Seiten in einem Pageviewcontroller angezeigt werden, hängt von seiner Datenquelle ab, nicht von den Viewcontrollern.
NSMutableArray *mArray = [[NSMutableArray alloc] initWithArray:self.userArray];
[mArray removeObject:userToBlock];
self.userArray = mArray;
UIViewController *startingViewController = [self viewControllerAtIndex:atIndex-1];
NSArray *viewControllers = @[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
Ich hatte keine Zeit, die obigen Kommentare durchzulesen, aber das funktionierte für mich. Grundsätzlich entferne ich die Daten (in meinem Fall einen Benutzer) und gehe dann zur vorherigen Seite. Klappt wunderbar. Ich hoffe, das hilft denjenigen, die eine schnelle Lösung suchen.
Ich hatte eine ähnliche Situation, in der ich wollte, dass der Benutzer jede Seite aus dem UIPageViewController "tippen und löschen" kann. Nachdem ich ein bisschen damit gespielt hatte, fand ich eine einfachere Lösung als die oben beschriebenen:
Machen Sie einen vorübergehenden Sprung an einen "sicheren" Ort (idealerweise den letzten). Verwenden Sie animiert: NEIN, damit dies sofort geschieht.
UIViewController *jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
Löscht die ausgewählte Seite aus der Modell-/Datenquelle.
Nun, wenn es die letzte Seite ist:
Springe dazu, diesmal mit animiertem: JA.
jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
Wenn es NICHT die letzte Seite war:
Springe dazu, diesmal mit animiertem: JA.
jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex;
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
Das ist es! Dies funktioniert gut in XCode 6.1.1.
Vollständiger Code unten. Dieser Code befindet sich in meinem UIPageViewController und wird über einen Delegaten der zu löschenden Seite aufgerufen. In meinem Fall war das Löschen der ersten Seite nicht zulässig, da sie andere Inhalte als die übrigen Seiten enthält. Natürlich müssen Sie Folgendes ersetzen:
[YourModel deletePage:] mit dem Code zum Löschen der sterbenden Seite aus Ihrem Modell
- (void)deleteAViewController:(id)sender {
YourUIViewController *dyingGroup = (YourUIViewController *)sender;
NSUInteger dyingPageIndex = dyingGroup.pageIndex;
// Check to see if we are in the last page as it's a special case.
BOOL isTheLastPage = (dyingPageIndex >= YourTotalNumberOfPagesFromModel.count);
// Make a temporary jump back - make sure to use animated:NO to have it jump instantly
UIViewController *jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
// Now delete the selected group from the model, setting the target
[YourModel deletePage:dyingPageIndex];
if (isTheLastPage) {
// Now jump to the definitive controller. In this case, it's the same one, we're just reloading it to refresh the data source.
// This time we're using animated:YES
jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex-1];
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
} else {
// Now jump to the definitive controller. This reloads the data source. This time we're using animated:YES
jumpToAnotherViewController = [self viewControllerAtIndex:dyingPageIndex];
[self setViewControllers:@[jumpToAnotherViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
}
}
Ich glaube, ich habe das Problem gelöst. Folgendes ist in meiner App passiert:
In Swift 3.0 hat es folgendes getan
fileprivate var isAnimated:Bool = false
override func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewControllerNavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) {
if self.isAnimated {
delay(0.5, closure: {
self.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
})
}else {
super.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
}
}
extension SliderViewController:UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
self.isAnimated = true
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
self.isAnimated = finished
}
}
Und hier die Verzögerungsfunktion
func delay(_ delay:Double, closure:@escaping ()->()) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}