Как я могу найти UIPopoverController из UIViewController, отображаемого в popover?
Используя экземпляр UIViewController, можно ли каким-либо образом найти UIPopoverController для его представления? Я также хотел бы найти UIViewController, который отображал UIPopoverController в первую очередь.
Я бы обычно использовал делегат или другое оповещение для отправки сигнала с отображаемого контроллера представления на его отображение, но в этом случае я пытаюсь создать повторно используемый пользовательский отступ, который отклоняет popover, а затем перемещается к другому виду на главном экране.
Ответы
Ответ 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;
}