从 UIBarButtonItem 呈现方向更改后,如何防止 UIPopoverController passthroughViews 被重置?

Posted

技术标签:

【中文标题】从 UIBarButtonItem 呈现方向更改后,如何防止 UIPopoverController passthroughViews 被重置?【英文标题】:How do I prevent UIPopoverController passthroughViews from being reset after orientation change when presented from a UIBarButtonItem? 【发布时间】:2014-06-07 15:50:27 【问题描述】:

我有一个从 UIBarButtonItem 呈现的 UIPopoverController。我想在弹出框之外进行触摸以关闭弹出框。从条形按钮呈现弹出框时,其他条形按钮会自动包含在弹出框传递视图中。为了防止我在显示弹出框后将直通视图设置为 nil(或 @[ ]),如下所示:

- (IBAction) consoleBarButtonHit:(id)sender 
UIViewController *consoleNavigationController=[self.storyboard instantiateViewControllerWithIdentifier:@"consoleNavigationController"];
_consolePopoverController=[[UIPopoverController alloc] initWithContentViewController:consoleNavigationController];
_consolePopoverController.delegate=self;

[_consolePopoverController presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];

// this must be done _after_ presenting the popover in order to work
_consolePopoverController.passthroughViews=nil;

这一切都很好,但是我遇到的问题是,在弹出窗口可见时旋转设备后,条形按钮会自动重新添加为直通视图,不会导致弹出窗口被解雇

如果我能以某种方式获得条形按钮视图(或矩形),那么我可以使用显示弹出框

-presentPopoverFromRect:inView:permittedArrowDirections:animated:

这可能会解决这个问题,但我不知道有任何非黑客方式从 UIBarButtonItem 中找到该矩形/视图。

我真的不希望在点击其他条形按钮以编程方式关闭弹出框时调用选择器,这不是他们的责任,以后可能会给我带来问题。

有什么想法吗?

【问题讨论】:

【参考方案1】:

所以我想出了一个解决方案,这有点奇怪,但保持模块化,效果很好。我创建了一个名为 PropertyEnforcer 的类,它将自己注册为对象属性的 KVO 观察者,并在该属性更改时重新设置该属性。

PropertyEnforcer.h:

#import <Foundation/Foundation.h>

@interface PropertyEnforcer : NSObject

+ (void) enforceProperty:(NSString*)keyPath ofObject:(id)target toValue:(id)value;

@end

PropertyEnforcer.m:

#import "PropertyEnforcer.h"
#import <objc/runtime.h>

@interface PropertyEnforcer ()

@property (retain) NSString *keyPath;
@property (retain) id value;
@property (assign) id target;

@end

@implementation PropertyEnforcer

- (void) dealloc 
    [_target removeObserver:self forKeyPath:_keyPath context:NULL];
    [super dealloc];


- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
    if( (([_target valueForKey:_keyPath] == nil) && (_value==nil)) || [[_target valueForKey:_keyPath] isEqual:_value]) 
        return;
     else 
        [_target setValue:_value forKeyPath:_keyPath];
    


+ (void) enforceProperty:(NSString*)keyPath ofObject:(id)target toValue:(id)value 
    PropertyEnforcer *enforcer=[[PropertyEnforcer alloc] init];
    enforcer.value=value;
    enforcer.keyPath=keyPath;
    enforcer.target=target;

    [target addObserver:enforcer forKeyPath:keyPath options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:NULL];

    objc_setAssociatedObject(target,
                             _cmd, // using this technique we can only attach one PropertyEnforcer per target
                             enforcer,
                             OBJC_ASSOCIATION_RETAIN);

    [enforcer release];


@end

现在我可以把原来的代码改成:

- (IBAction) consoleBarButtonHit:(id)sender 
    UIViewController *consoleNavigationController=[self.storyboard instantiateViewControllerWithIdentifier:@"consoleNavigationController"];
    _consolePopoverController=[[UIPopoverController alloc] initWithContentViewController:consoleNavigationController];
    _consolePopoverController.delegate=self;

    [_consolePopoverController presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];

    // make sure those passthroughViews are always nil !
    [PropertyEnforcer enforceProperty:@"passthroughViews" ofObject:_consolePopoverController toValue:nil];

PropertyEnforcer 将自己注册为关联对象,因此我们不必跟踪它。它会自动取消注册为 KVO 观察者,并在 UIPopoverController 被销毁时被销毁。

这是我能想到的最好的、最不老套的解决方案。

【讨论】:

【参考方案2】:

我采用的解决方案是单独留下 passthroughViews,而是在 UIPopoverPresentationController 出现和关闭时,根据其转换禁用/重新启用工具栏或导航栏中的单个按钮(UIBarButtonItem 实例) .

ios 8:UIPopoverPresentationController 而不是 UIPopoverController。)

UIPopoverPresentationController+managedBarButtonItems.h

@interface UIPopoverPresentationController (managedBarButtonItems)

@property (nonatomic, retain) NSArray* managedBarButtonItems;

@end

UIPopoverPresentationController+managedBarButtonItems.m

#import "UIPopoverPresentationController+managedBarButtonItems.h"

#import <objc/runtime.h>

//
// scope: private, in-terms-of
//

@interface UIBarButtonItem (wasEnabled)

@property (nonatomic) BOOL wasEnabled;

@end

@implementation UIBarButtonItem (wasEnabled)

- (BOOL)wasEnabled

    return [objc_getAssociatedObject(self, @selector(wasEnabled)) boolValue];


- (void)setWasEnabled:(BOOL)wasIt

    objc_setAssociatedObject(self, @selector(wasEnabled), [NSNumber numberWithBool:wasIt], OBJC_ASSOCIATION_RETAIN_NONATOMIC);


// FYI: "Associated objects are released [automatically] after the dealloc method of the original object has finished."

@end

//
// scope: consumable
//

@implementation UIPopoverPresentationController (managedBarButtonItems)

- (NSArray*)managedBarButtonItems

    return objc_getAssociatedObject(self, @selector(managedBarButtonItems));


- (void)setManagedBarButtonItems:(NSArray*)items

    objc_setAssociatedObject(self, @selector(managedBarButtonItems), items, OBJC_ASSOCIATION_RETAIN_NONATOMIC);


// FYI: "Associated objects are released [automatically] after the dealloc method of the original object has finished."

- (void)presentationTransitionDidEnd:(BOOL)completed

    [super presentationTransitionDidEnd:completed];

    if (self.barButtonItem && self.managedBarButtonItems)
    
        for (UIBarButtonItem* button in self.managedBarButtonItems)
        
            if (button.action != /* actuator */ self.barButtonItem.action)
            
                button.wasEnabled = button.enabled, button.enabled = NO;
            
        
    


- (void)dismissalTransitionDidEnd:(BOOL)completed

    [super dismissalTransitionDidEnd:completed];

    if (self.barButtonItem && self.managedBarButtonItems)
    
        for (UIBarButtonItem* button in self.managedBarButtonItems)
        
            if (button.action != /* actuator */ self.barButtonItem.action)
            
                button.enabled = button.wasEnabled;
            
        
    


@end

用法:

UIAlertController* actionSheet = [UIAlertController
    alertControllerWithTitle:@"Actions" message:nil
        preferredStyle:UIAlertControllerStyleActionSheet];

UIPopoverPresentationController* presenter = actionSheet.popoverPresentationController;

// chosen anchor UIBarButtonItem
presenter.barButtonItem = anchorButton;

// disabled UIViewController buttons
presenter.managedBarButtonItems = self.toolbarItems;

也可以:

// disabled UINavigationController buttons
presenter.managedBarButtonItems =
    [[NSArray arrayWithArray:self.navigationItem.leftBarButtonItems]
        arrayByAddingObject:self.navigationItem.rightBarButtonItem];

【讨论】:

以上是关于从 UIBarButtonItem 呈现方向更改后,如何防止 UIPopoverController passthroughViews 被重置?的主要内容,如果未能解决你的问题,请参考以下文章

在 UIToolbar 中的 UIBarButtonItem 上更改 tintColor 会导致按钮消失并从左侧动画化

无法更改 UIBarButtonItem

更改 UIBarButtonItem 的 UIToolbar 阴影颜色

iiview 甲板控制器在呈现模态视图时更改框架而不更改界面方向

呈现视图控制器中的方向更改未更新父视图控制器

为啥我的 UIBarButtonItem 在呈现视图控制器时会进行动画滑入?