So schließen Sie ein Storyboard-Popover


73

Ich habe ein Popover aus einem UIBarButtonItemXcode-Storyboard (es gibt also keinen Code) wie folgt erstellt :

Xcode 5.0 Connections Inspector mit Popover

Das Präsentieren des Popovers funktioniert einwandfrei. Ich kann das Popover jedoch nicht verschwinden lassen, wenn ich auf das tippe, das es angezeigt hat UIBarButtonItem.

Wenn die Taste (zum ersten Mal) gedrückt wird, wird das Popover angezeigt. Wenn die Taste erneut gedrückt wird (zum zweiten Mal), wird das gleiche Popover darüber angezeigt. Jetzt habe ich zwei Popover (oder mehr, wenn ich die Taste weiter drücke). Gemäß den Richtlinien für die Benutzeroberfläche von iOS muss das Popover beim ersten Tippen angezeigt und beim zweiten nicht mehr angezeigt werden:

Stellen Sie sicher, dass jeweils nur ein Popover auf dem Bildschirm angezeigt wird. Sie sollten nicht mehr als ein Popover (oder eine benutzerdefinierte Ansicht, die so gestaltet ist, dass sie wie ein Popover aussieht und sich so verhält) gleichzeitig anzeigen. Insbesondere sollten Sie vermeiden, gleichzeitig eine Kaskade oder Hierarchie von Popovers anzuzeigen, in der ein Popover aus einem anderen hervorgeht.

Wie kann ich das Popover schließen, wenn der Benutzer UIBarButtonItemein zweites Mal auf das Popover tippt ?


Wie haben Sie den Segue erstellt? Ist das Quellende des Segues die Schaltfläche oder der Ansichts-Controller? Haben Sie Passthroughs für den Übergang festgelegt?
Rob Mayoff

@rob Ich habe den Segue über den Interface Builder erstellt. Ich würde die Schaltfläche auswählen und das Popover-Segment in die gewünschte Master-Ansicht ziehen. Das Bild oben zeigt dies. Ich bin mir nicht sicher, was Sie mit den letzten beiden Fragen in Ihrem Kommentar meinen.
Sam Spencer

2
Ich habe ein neues Projekt mit der Vorlage "Single View Application" und dem Storyboard erstellt. Ich habe eine Schaltfläche in die Ansicht der Vorlage gezogen und einen zweiten Ansichts-Controller herausgezogen. Ich habe die Steuerung von der Schaltfläche auf die zweite VC gezogen und Popover ausgewählt. Wenn ich dies ausführe, kann ich die Schaltfläche berühren, damit das Popover angezeigt wird. Wenn ich dann eine Stelle außerhalb des Popovers (einschließlich der Schaltfläche) berühre, verschwindet das Popover. Was hast du anders gemacht?
Rob Mayoff

Ich stimme dem obigen Kommentar zu. Wenn Sie ein zweites Mal auf die Schaltfläche tippen, wird das Popover geschlossen.
Memmons

2
@robmayoff Sie haben Recht, dass es mit UIButton gut funktioniert, aber versuchen Sie die gleichen Schritte mit einem UIBarButton-Element in einer Symbolleiste. Das habe ich auch zuerst nicht gesehen, also habe ich die Frage von RazorSharp bearbeitet, um es klarer zu machen.
Matt Andersen

Antworten:


114

BEARBEITEN: Diese Probleme scheinen ab iOS 7.1 / Xcode 5.1.1 behoben zu sein. (Möglicherweise früher, da ich nicht alle Versionen testen konnte. Definitiv nach iOS 7.0, da ich diese getestet habe.) Wenn Sie einen Popover-Übergang von a erstellen UIBarButtonItem, stellt der Übergang sicher, dass durch erneutes Tippen auf den Popover der Popover eher ausgeblendet wird als ein Duplikat zu zeigen. Es funktioniert auch für die neuen UIPresentationControllerPopover-Segmente, die Xcode 6 auch für iOS 8 erstellt.

Da meine Lösung für diejenigen, die noch frühere iOS-Versionen unterstützen, von historischem Interesse sein kann, habe ich sie unten belassen.


Wenn Sie einen Verweis auf den Popover-Controller des Segues speichern und ihn schließen, bevor Sie ihn bei wiederholten Aufrufen von auf einen neuen Wert setzen prepareForSegue:sender:, vermeiden Sie nur das Problem, dass bei wiederholtem Drücken der Taste mehrere Stapel-Popover angezeigt werden - Sie können ihn immer noch nicht verwenden die Schaltfläche zum Schließen des Popovers, wie von der HIG empfohlen (und wie in Apples Apps usw. zu sehen)

Sie können jedoch ARC nutzen, um schwache Referenzen für eine einfache Lösung auf Null zu setzen:

1: Segue von der Schaltfläche

Ab iOS 5 konnten Sie diese Funktion nicht mit einem Übergang von a ausführen UIBarButtonItem, dies ist jedoch unter iOS 6 und höher möglich. (Unter iOS 5 müssen Sie vom Ansichts-Controller selbst performSegueWithIdentifier:trennen und dann den Aktionsaufruf der Schaltfläche ausführen, nachdem Sie nach dem Popover gesucht haben.)

2: Verwenden Sie einen Verweis auf das Popover in -shouldPerformSegue...

@interface ViewController
@property (weak) UIPopoverController *myPopover;
@end

@implementation ViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // if you have multiple segues, check segue.identifier
    self.myPopover = [(UIStoryboardPopoverSegue *)segue popoverController];
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
    if (self.myPopover) {
        [self.myPopover dismissPopoverAnimated:YES];
        return NO;
    } else {
        return YES;
    }
}
@end

3: Es gibt keinen dritten Schritt!

Das Schöne an der Verwendung einer schwachen Nullreferenz ist, dass nach dem Entlassen des Popover-Controllers - ob programmgesteuert shouldPerformSegueWithIdentifier:oder automatisch durch den Benutzer, der auf eine andere Stelle außerhalb des Popovers tippt - der Ivar nilwieder wechselt, sodass wir wieder zu unserem zurückkehren Ausgangszustand.

Ohne schwache Referenzen auf Null zu setzen, müssten wir auch:

  • eingestellt, myPopover = nilwenn es entlassen wird shouldPerformSegueWithIdentifier:, und
  • Setzen Sie sich als Delegat des Popover-Controllers, um zu fangen popoverControllerDidDismissPopover:und auch dort zu setzen myPopover = nil(also fangen wir, wenn das Popover automatisch geschlossen wird).

3
Wir bitten @wcochran um Hilfe, um dies herauszufinden.
Rickster

1
Vielen Dank. Verkapselung ist gut! (In der Tat behandelt Apples neueste Version der primären ObjC-Sprachdokumentation Implementierungs-Ivars als Standard.)
Rickster

2
Ja, ein __weakIvar und eine weakImmobilie sind hier gleichwertig. Ob Sie eine Immobilie oder einen Ivar für etwas in Ihrer Klasse verwenden, bleibt Gegenstand vieler Debatten. Ich neige dazu, bei einem Ivar zu bleiben, wenn ich vermute, dass benutzerdefinierte Accessoren und KVO niemals benötigt werden, aber die Strategie "Eigenschaften für alles" hat auch ihre Vorzüge.
Rickster

1
Wie wird in der iOS6-Version durch ein zweites Tippen das Popover geschlossen? Das tut es nicht, oder?
Mahboudz

1
Die Lösung fehlt das Bit , wo die popover tatsächlich entlassen wird: [myPopover dismissPopoverAnimated: YES]
Scot

13

Ich habe die Lösung hier gefunden: https://stackoverflow.com/a/7938513/665396 In first prepareForSegue: sender: Speichern Sie in einem ivar / property den Zeiger auf den UIPopoverController und verwenden Sie diesen Zeiger, um das Popover in den nachfolgenden Aufrufen zu schließen.

...
@property (nonatomic, weak) UIPopoverController* storePopover;
...

- (void)prepareForSegue:(UIStoryboardSegue *)segue 
                 sender:(id)sender {
if ([segue.identifier isEqualToString:@"My segue"]) {
// setup segue here

[self.storePopover dismissPopoverAnimated:YES];
self.storePopover = ((UIStoryboardPopoverSegue*)segue).popoverController;
...
}

1
Danke dafür. Ich habe das popoverControllerim gespeichert, destinationViewControllerdamit ich später problemlos darauf zugreifen kann, wenn mein benutzerdefinierter Delegat zurückruft.
Besi

2

Ich habe dafür einen benutzerdefinierten Übergang verwendet.

1

Erstellen Sie einen benutzerdefinierten Abschnitt zur Verwendung in Storyboard:

@implementation CustomPopoverSegue
-(void)perform
{
    // "onwer" of popover - it needs to use "strong" reference to retain UIPopoverReference
    ToolbarSearchViewController *source = self.sourceViewController;
    UIViewController *destination = self.destinationViewController;
    // create UIPopoverController
    UIPopoverController *popoverController = [[UIPopoverController alloc] initWithContentViewController:destination];
    // source is delegate and owner of popover
    popoverController.delegate = source;
    popoverController.passthroughViews = [NSArray arrayWithObject:source.searchBar];
    source.recentSearchesPopoverController = popoverController;
    // present popover
    [popoverController presentPopoverFromRect:source.searchBar.bounds 
                                       inView:source.searchBar
                     permittedArrowDirections:UIPopoverArrowDirectionAny
                                     animated:YES];

}
@end

2

In der Ansichtssteuerung, die Quelle / Eingabe des Segues ist, z. B. Segue mit Aktion starten:

-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
    if(nil == self.recentSearchesPopoverController)
    {
        NSString *identifier = NSStringFromClass([CustomPopoverSegue class]);
        [self performSegueWithIdentifier:identifier sender:self];
    } 
}

3

Referenzen werden von segue zugewiesen, wodurch UIPopoverController erstellt wird - wenn Popover geschlossen wird

-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
    if(self.recentSearchesPopoverController)
    {
        [self.recentSearchesPopoverController dismissPopoverAnimated:YES];
        self.recentSearchesPopoverController = nil;
    }    
}

Grüße, Peter


2

Ich habe es gelöst und einen Brauch erstellt ixPopoverBarButtonItem, der entweder den Übergang auslöst oder das angezeigte Popover schließt.

Was ich tue: Ich schalte die Aktion und das Ziel der Schaltfläche um, sodass entweder der Übergang ausgelöst wird oder das aktuell angezeigte Popover gelöscht wird.

Ich habe viel gegoogelt, um diese Lösung zu finden. Ich möchte nicht die Credits für die Idee nehmen, die Aktion umzuschalten. Das Einfügen des Codes in eine benutzerdefinierte Schaltfläche war mein Ansatz, um den Boilerplate-Code aus meiner Sicht auf ein Minimum zu beschränken.

Im Storyboard definiere ich die Klasse des BarButtonItem für meine benutzerdefinierte Klasse:

benutzerdefinierte Leistenschaltfläche

Dann übergebe ich das vom Segue erstellte Popover an meine benutzerdefinierte Schaltflächenimplementierung in der prepareForSegue:sender:Methode:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender  
{
    if ([segue.identifier isEqualToString:@"myPopoverSegue"]) {
        UIStoryboardPopoverSegue* popSegue = (UIStoryboardPopoverSegue*)segue;
        [(ixPopoverBarButtonItem *)sender showingPopover:popSegue.popoverController];
    }
}

Übrigens ... da ich mehr als eine Schaltfläche habe, die Popover auslöst, muss ich immer noch eine Referenz des aktuell angezeigten Popovers behalten und es schließen, wenn ich das neue sichtbar mache, aber das war nicht Ihre Frage ...

So habe ich mein benutzerdefiniertes UIBarButtonItem implementiert:

...Schnittstelle:

@interface ixPopoverBarButtonItem : UIBarButtonItem

- (void) showingPopover:  (UIPopoverController *)popoverController;

@end

... und impl:

#import "ixPopoverBarButtonItem.h"
@interface ixPopoverBarButtonItem  ()
@property (strong, nonatomic) UIPopoverController *popoverController;
@property (nonatomic)         SEL                  tempAction;           
@property (nonatomic,assign)  id                   tempTarget; 

- (void) dismissPopover;

@end

@implementation ixPopoverBarButtonItem

@synthesize popoverController = _popoverController;
@synthesize tempAction = _tempAction;
@synthesize tempTarget = _tempTarget;

-(void)showingPopover:(UIPopoverController *)popoverController {

    self.popoverController = popoverController;
    self.tempAction = self.action;
    self.tempTarget = self.target;
    self.action = @selector(dismissPopover);
    self.target = self;
}    

-(void)dismissPopover {
    [self.popoverController dismissPopoverAnimated:YES];
    self.action = self.tempAction;
    self.target = self.tempTarget;

    self.popoverController = nil;
    self.tempAction = nil;
    self.tempTarget = nil;
}


@end

ps: Ich bin neu bei ARC, daher bin ich mir nicht ganz sicher, ob ich hier undicht bin. Bitte sag mir, ob ich ...


1
Genial! Das ist ein großartiger Ansatz und funktioniert gut. ARC erledigt den größten Teil der Speicherverwaltung für Sie, sodass Sie niemals Release, Retain usw. verwenden müssen. Dies ist ein großartiger Artikel über ARC: longweekendmobile.com/2011/09/07/…
Sam Spencer

2

Ich habe dieses Problem gelöst, ohne eine Kopie von a aufbewahren zu müssen UIPopoverController. Behandeln Sie einfach alles im Storyboard (Symbolleiste, BarButtons usw.) und

  • Behandeln Sie die Sichtbarkeit des Popovers mit einem Booleschen Wert.
  • Stellen Sie sicher, dass es einen Delegierten gibt, der auf sich selbst eingestellt ist

Hier ist der ganze Code:

ViewController.h

@interface ViewController : UIViewController <UIPopoverControllerDelegate>
@end

ViewController.m

@interface ViewController ()
@property BOOL isPopoverVisible;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.isPopoverVisible = NO;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // add validations here... 
    self.isPopoverVisible = YES;
    [[(UIStoryboardPopoverSegue*)segue popoverController] setDelegate:self];
}

- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
    return !self.isPopoverVisible;
}

- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
    self.isPopoverVisible = NO;
}
@end

1

Ich nahm Ricksters Antwort und packte sie in eine von UIViewController abgeleitete Klasse. Diese Lösung erfordert Folgendes:

  • iOS 6 (oder höher) mit ARC
  • Leiten Sie Ihren View Controller aus dieser Klasse ab
  • Stellen Sie sicher, dass Sie die "Super" -Versionen von prepareForSegue: sender und shouldPerformSegueWithIdentifier: sender aufrufen, wenn Sie diese Methoden überschreiben
  • Verwenden Sie einen benannten Popover-Abschnitt

Das Schöne daran ist, dass Sie keine "spezielle" Codierung vornehmen müssen, um den richtigen Umgang mit Popovers zu unterstützen.

Schnittstelle :

@interface FLStoryboardViewController : UIViewController
{
    __strong NSString            *m_segueIdentifier;
    __weak   UIPopoverController *m_popoverController;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender;
@end

Implementierung :

@implementation FLStoryboardViewController

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if( [segue isKindOfClass:[UIStoryboardPopoverSegue class]] )
    {
        UIStoryboardPopoverSegue *popoverSegue = (id)segue;

        if( m_popoverController  ==  nil )
        {
            assert( popoverSegue.identifier.length >  0 );    // The Popover segue should be named for this to work fully
            m_segueIdentifier   = popoverSegue.identifier;
            m_popoverController = popoverSegue.popoverController;
        }
        else
        {
            [m_popoverController dismissPopoverAnimated:YES];
            m_segueIdentifier = nil;
            m_popoverController = nil;
        }
    }
    else
    {
        [super prepareForSegue:segue sender:sender];
    }
}


- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
    // If this is an unnamed segue go ahead and allow it
    if( identifier.length != 0 )
    {
        if( [identifier compare:m_segueIdentifier]  ==  NSOrderedSame )
        {
            if( m_popoverController == NULL )
            {
                m_segueIdentifier = nil;
                return YES;
            }
            else
            {
                [m_popoverController dismissPopoverAnimated:YES];
                m_segueIdentifier = nil;
                m_popoverController = nil;
                return NO;
            }
        }
    }

    return [super shouldPerformSegueWithIdentifier:identifier sender:sender];
}

@end

Quelle auf GitHub verfügbar

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.