Как уволить раскадровку поповера - PullRequest
73 голосов
/ 27 ноября 2011

Я создал поповер из UIBarButtonItem с использованием раскадровок Xcode (так что кода нет), например:

Xcode 5.0 Connections Inspector with Popover

Представление поповера работает просто отлично.Тем не менее, я не могу заставить всплывающее окно с исчезнуть , когда я нажимаю UIBarButtonItem, который заставил его появиться.

При первом нажатии кнопки появляется всплывающее окно.При повторном нажатии кнопки (второй раз) поверх нее появляется тот же самый поповер, поэтому теперь у меня есть два всплывающих окна (или больше, если я продолжаю нажимать кнопку).В соответствии с Руководством по интерфейсу пользователя iOS Мне нужно, чтобы всплывающее окно появлялось при первом касании и исчезало при втором:

Убедитесь, что на экране одновременно виден только один всплывающий элемент,Вы не должны отображать более одного поповера (или пользовательского представления, разработанного так, чтобы выглядеть и вести себя как поповер) одновременно.В частности, вам следует избегать одновременного отображения каскада или иерархии всплывающих окон, в которых один всплывающий элемент появляется из другого.

Как я могу отклонить всплывающее окно, когда пользователь нажимает UIBarButtonItem во второй раз?

Ответы [ 6 ]

114 голосов
/ 20 апреля 2012

РЕДАКТИРОВАТЬ: Эти проблемы, кажется, исправлены в iOS 7.1 / Xcode 5.1.1. (Возможно, раньше, так как я не смог протестировать все версии. Определенно после iOS 7.0, так как я тестировал ту.) Когда вы создаете переход с попсовером из UIBarButtonItem, переход гарантирует, что нажатие на всплывающее окно снова скрывает поповер, а не показывать дубликаты. Это работает правильно для новых UIPresentationController основанных на попугах сегментов, которые Xcode 6 создает и для iOS 8.

Поскольку мое решение может представлять исторический интерес для тех, кто все еще поддерживает более ранние версии iOS, я оставил его ниже.


Если вы сохраняете ссылку на поповерный контроллер segue, отклоняя ее перед установкой ее нового значения при повторных вызовах prepareForSegue:sender:, все, чего вы избегаете, это проблема получения нескольких всплывающих окон с многократным нажатием кнопки - Вы все еще не можете использовать кнопку, чтобы отклонить поповер, как рекомендует HIG (и как видно в приложениях Apple и т. д.)

Вы можете воспользоваться тем, чтобы ARC обнулял слабые ссылки для простого решения, однако:

1: переход с кнопки

Начиная с iOS 5, вы не могли сделать это с помощью перехода с UIBarButtonItem, но вы можете это сделать на iOS 6 и более поздних версиях. (В iOS 5 вам нужно будет перейти от самого контроллера представления, а затем вызвать действие кнопки performSegueWithIdentifier: после проверки на всплывающее окно.)

2: использовать ссылку на всплывающее окно в -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: Нет третьего шага!

Приятной особенностью использования слабой ссылки обнуления здесь является то, что после того, как контроллер поповера отключается - либо программно в shouldPerformSegueWithIdentifier:, либо автоматически пользователем, нажимающим где-то еще за пределами поповера - ивар переходит к nil снова, поэтому мы вернулись к нашему начальному состоянию.

Без обнуления слабых ссылок, мы также должны:

  • установить myPopover = nil при отклонении в shouldPerformSegueWithIdentifier: и
  • установите себя в качестве делегата контроллера поповера, чтобы поймать popoverControllerDidDismissPopover:, а также установите там myPopover = nil (таким образом, мы ловим, когда поповер автоматически отклоняется).
13 голосов
/ 28 ноября 2011

Я нашел решение здесь https://stackoverflow.com/a/7938513/665396 В первом prepareForSegue: sender: сохраните в свойстве ivar / указатель на UIPopoverController и пользователь, который указывает на отклонение всплывающего окна в последующих вызовах.

...
@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;
...
}
2 голосов
/ 19 ноября 2013

Я решил эту проблему без необходимости хранить копию UIPopoverController.Просто обработайте все в раскадровке (Панель инструментов, BarButtons и т. Д.), И

  • обработайте видимость всплывающего окна с помощью логического значения,
  • убедитесь, что есть делегат, и он установленк себе

Вот весь код:

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
2 голосов
/ 05 апреля 2012

Я решил, создав пользовательский ixPopoverBarButtonItem, который либо запускает переход, либо отклоняет отображаемый поповер.

Что я делаю: я переключаю действие и цель кнопки, чтобы она либо запускала переход, либо выбрасывала всплывающее окно с текущим показом.

Мне понадобилось много гуглить это решение, я не хочу брать кредиты за идею переключить действие. Помещение кода в пользовательскую кнопку было моим подходом, чтобы сводить шаблонный код на мой взгляд к минимуму.

В раскадровке я определяю класс BarButtonItem для своего пользовательского класса:

custom bar button

Затем я передаю всплывающее окно, созданное с помощью segue, моей пользовательской реализации кнопки в методе prepareForSegue:sender::

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

Кстати ... так как у меня более одной кнопки, запускающей всплывающие окна, мне все равно приходится сохранять ссылку на отображаемый в данный момент всплывающий элемент и отклонять его, когда я делаю новый видимым, но это был не ваш вопрос ...

Вот как я реализовал свой пользовательский UIBarButtonItem:

... интерфейс:

@interface ixPopoverBarButtonItem : UIBarButtonItem

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

@end

... и импл:

#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: я новичок в ARC, поэтому я не совсем уверен, протекаю ли здесь Пожалуйста, скажи мне, если я ...

2 голосов
/ 11 марта 2012

Я использовал для этого пользовательский переход.

1

создать пользовательский переход для использования в раскадровке:

@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

в контроллере представления, который является источником / входом segue, например. начать переход с действия:

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

3

ссылки назначаются segue, который создает UIPopoverController - при отклонении popover

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

С уважением, Питер

1 голос
/ 03 ноября 2012

Я взял ответ Рикстера и упаковал его в класс, производный от UIViewController.Для этого решения требуется следующее:

  • iOS 6 (или более поздняя версия) с ARC
  • Извлеките контроллер представлений из этого класса
  • , обязательно вызовите "super"версии prepareForSegue: sender и shouldPerformSegueWithIdentifier: sender, если вы переопределяете эти методы
  • Используйте именованный переход popover

Приятная вещь в том, что вам не нужно ничего делать«специальное» кодирование для поддержки правильной обработки всплывающих окон.

Интерфейс :

@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

Реализация :

@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

Источник доступен на GitHub

...