Подтвердить что ты не робот

Как я могу найти UIPopoverController из UIViewController, отображаемого в popover?

Используя экземпляр UIViewController, можно ли каким-либо образом найти UIPopoverController для его представления? Я также хотел бы найти UIViewController, который отображал UIPopoverController в первую очередь.

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

4b9b3361

Ответ 1

Вы могли бы подумать, что это было бы просто (UIViewController даже имеет частное свойство _popoverController!), но это не так.

Общий ответ заключается в том, что вы должны сохранить ссылку на UIPopoverController в UIViewController, которую она представляет, в момент создания UIViewController.

  • Если вы создаете UIPopoverController программно, то время для хранения ссылки в подклассе UIViewController.

  • Если вы используете Storyboards и Segues, вы можете вывести UIPopoverController из segue в методе prepareForSegue:

    UIPopoverController* popover = [(UIStoryboardPopoverSegue*)segue popoverController];
    

Конечно, убедитесь, что ваш segue действительно является UIStoryboardPopoverSegue!

Ответ 2

Моя рекомендация - использовать комбинацию собственного пользовательского свойства и частных API в UIKit. Чтобы избежать отклонения от магазина приложений, любые частные API-интерфейсы должны собираться для сборки релизов и должны использоваться только для проверки вашей реализации.

Сначала создадим пользовательское свойство в категории на UIViewController. Это позволяет некоторым преимуществам в реализации, и не требует от вас возврата и получения каждого класса из какого-либо пользовательского подкласса контроллера.

// UIViewController+isPresentedInPopover.h

#import <UIKit/UIKit.h>

@interface UIViewController (isPresentedInPopover)

@property (assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover;

@end

Теперь для реализации - мы будем использовать API-интерфейс связанного объекта Objective C, чтобы обеспечить хранилище для этого свойства. Обратите внимание, что селектор является хорошим выбором для уникального ключа, используемого для хранения объекта, поскольку он автоматически уникален компилятором и вряд ли будет использоваться любым другим клиентом для этой цели.

// UIViewController+isPresentedInPopover.m

#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>

@implementation UIViewController (isPresentedInPopover)

- (void)setPresentedInPopover:(BOOL)presentedInPopover
{
    objc_setAssociatedObject(self,
                             @selector(isPresentedInPopover),
                             [NSNumber numberWithBool:presentedInPopover],
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)isPresentedInPopover
{
    NSNumber *wrappedBool = objc_getAssociatedObject(self, @selector(isPresentedInPopover));
    BOOL userValue = [wrappedBool boolValue];
    return userValue ?: [[self parentViewController] isPresentedInPopover];
}

@end

Итак, есть удобный побочный эффект использования этого как категории - вы можете вызвать до parentViewController и посмотреть, содержится ли это в popover. Таким образом вы можете установить свойство, например, UINavigationController, и все его дочерние контроллеры будут правильно отвечать на isPresentedInPopover. Чтобы выполнить это с помощью подклассов, вы попытаетесь установить это на каждом новом контроллере дочерних представлений или подклассифицировать навигационные контроллеры или другие ужасные вещи.

Больше времени выполнения Magic

Есть еще что-то, что Objective C Runtime может предложить для этой конкретной проблемы, и мы можем использовать их, чтобы перейти к частным данным о реализации Apple и проверить ваше собственное приложение против него. Для создания релизов этот дополнительный код будет скомпилирован, поэтому вам не нужно беспокоиться о всевидящем глазу Sauron Apple при отправке в магазин.

Из UIViewController.h видно, что существует ivar, обозначенный как UIPopoverController* _popoverController с областью @package. К счастью, это выполняется только компилятором. Ничто не является священным в отношении времени выполнения, и довольно легко получить доступ к этому ivar из любого места. Мы добавим проверку времени выполнения отладки при каждом доступе к свойству, чтобы убедиться, что мы согласованы.

// UIViewController+isPresentedInPopover.m

#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>

@implementation UIViewController (isPresentedInPopover)

- (void)setPresentedInPopover:(BOOL)presentedInPopover
{
    objc_setAssociatedObject(self,
                             @selector(isPresentedInPopover),
                             [NSNumber numberWithBool:presentedInPopover],
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)isPresentedInPopover
{
    NSNumber *wrappedBool = objc_getAssociatedObject(self, @selector(isPresentedInPopover));
    BOOL userValue = [wrappedBool boolValue];

#if DEBUG
    Ivar privatePopoverIvar = class_getInstanceVariable([UIViewController class], "_popoverController");
    UIPopoverController *popover = object_getIvar(self, privatePopoverIvar);
    BOOL privateAPIValue = popover != nil;

    if (userValue != privateAPIValue) {
        [NSException raise:NSInternalInconsistencyException format:
         @"-[%@ %@] "
         "returning %@ "
         "while private UIViewController API suggests %@. "
         "Did you forget to set 'presentedInPopover'?",
         NSStringFromClass([self class]), NSStringFromSelector(_cmd),
         userValue ? @"YES" : @"NO",
         privateAPIValue ? @"YES" : @"NO"];
    }
#endif

    return userValue ?: [[self parentViewController] isPresentedInPopover];
}

@end

При неправильном использовании свойства вы получите сообщение на консоли:

2012-09-18 14:28:30.375 MyApp[41551:c07] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Consistency error in -[UINavigationController isPresentedInPopover]: returning NO while private UIViewController API suggests YES. Did you forget to set 'presentedInPopover'?'

... но когда компиляция с флагом DEBUG выключена или установлена ​​в 0, она компилируется до того же самого кода, что и раньше.

Для Свободного и Безголового

Возможно, вы делаете Ad-Hoc/Enterprise/личные сборки, или вы достаточно смелы, чтобы увидеть, что Apple думает об этом для App Store. В любом случае, здесь реализована реализация, которая работает только с текущей текучей средой и UIViewController - никаких свойств настройки не требуется!

// UIViewController+isPresentedInPopover.h

#import <UIKit/UIKit.h>

@interface UIViewController (isPresentedInPopover)

@property (readonly, assign, nonatomic, getter = isPresentedInPopover) BOOL presentedInPopover;

@end

// UIViewController+isPresentedInPopover.m

#import "UIViewController+isPresentedInPopover.h"
#import <objc/runtime.h>

@implementation UIViewController (isPresentedInPopover)

- (BOOL)isPresentedInPopover
{
    Ivar privatePopoverIvar = class_getInstanceVariable([UIViewController class], "_popoverController");
    UIPopoverController *popover = object_getIvar(self, privatePopoverIvar);
    BOOL privateAPIValue = popover != nil;
    return privateAPIValue ?: [[self parentViewController] isPresentedInPopover];
}

@end

Ответ 3

Самое полезное, вероятно, было бы сделать popover переменной класса, поэтому в .m файле класса, который собирается представить popover, сделайте что-то вроде этого:

    @interface ExampleViewController()
    @property (nonatomic, strong) UIPopoverController *popover
    @end

    @implementation
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if ([segue.identifier isEqualToString:@"some segue"])
        {
            //prevent stacking popovers
            if ([self.popover isPopoverVisible])
            {
                [self.popover dismissPopoverAnimated:YES];
                self.popover = nil;
            }
            [segue.destinationViewController setDelegate:self];
            self.popover = [(UIStoryboardPopoverSegue *)segue popoverController];
         }
     }
     @end

Ответ 4

Как написал @joey выше, Apple устранила необходимость использования фиктивного элемента управления в iOS 8 с свойством popoverPresentationController, определенным для UIViewController, в качестве "ближайшего предка в иерархии диспетчера представлений, который представляет собой контроллер представления popover. ( только для чтения)".

Вот пример в Swift для сегмента UIPopoverPresentationController, определенного на раскадровке. В этом случае кнопка была добавлена ​​программно и может быть определена таким образом, как попсовый якорь. Отправитель также может быть выбранным UITableViewCell или видом из него.

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "showCallout" {
        let button = sender as UIButton
        let calloutViewController = segue.destinationViewController as CalloutViewController
        if let popover = calloutViewController.popoverPresentationController {
            popover.sourceView = button
            popover.sourceRect = button.bounds
        }
    }
}

Ответ 5

Снятие с ndoc anwser: этот ответ показывает более аккуратный способ в iOS 6, чтобы предотвратить появление popover через несколько раз через segues. Метод в ссылке был тем, который отлично работал у меня для предотвращения стекирования popover.

Ответ 6

Если вы просто хотите узнать, представлен ли ваш контроллер внутри popover (не заинтересованы в получении ссылки на контроллер popover), вы можете просто сделать это, не сохраняя переменные и не взламывая частные API.

-(BOOL)isPresentedInPopover
{
    for (UIView *superview = self.view.superview; superview != nil; superview = superview.superview)
    {
        if ([NSStringFromClass([superview class]) isEqualToString:@"_UIPopoverView"])
            return YES;
    }
    return NO;
}