How do I prevent UIPopoverController passthroughViews from being reset after orientation change when presented from a UIBarButtonItem?

送分小仙女□ 提交于 2019-12-05 20:14:01

So I came up with a solution, that's a little odd, but keeps things modular, works well. I've created a class called PropertyEnforcer which registers itself as a KVO observer of an object's property and re-sets that property any time it changes.

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

Now I can change the change the original code to:

- (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];
}

The PropertyEnforcer registers itself as an associated object so we don't ever have to keep track of it. It will automatically unregister itself as a KVO observer and be destroyed whenever the UIPopoverController is destroyed.

This is the best, least hackish, solution that I could come up with.

The solution I went for was to leave passthroughViews alone and instead disable/re-enable individual buttons (UIBarButtonItem instances) in a toolbar or a navigation bar when UIPopoverPresentationController is presented and dismissed, based on its transition.

(iOS 8: UIPopoverPresentationController instead of 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

Usage:

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;

Also possible:

// disabled UINavigationController buttons
presenter.managedBarButtonItems =
    [[NSArray arrayWithArray:self.navigationItem.leftBarButtonItems]
        arrayByAddingObject:self.navigationItem.rightBarButtonItem];
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!