NewsBlur/clients/ios/Other Sources/WYPopoverController/WYPopoverController.m

2948 lines
105 KiB
Objective-C
Executable file

/*
Version 0.3.6
WYPopoverController is available under the MIT license.
Copyright © 2013 Nicolas CHENG
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import "WYPopoverController.h"
#import <objc/runtime.h>
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
#define WY_BASE_SDK_7_ENABLED
#endif
#ifdef DEBUG
#define WY_LOG(fmt, ...) NSLog((@"%s (%d) : " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define WY_LOG(...)
#endif
#define WY_IS_IOS_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define WY_IS_IOS_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define WY_IS_IOS_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define WY_IS_IOS_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface WYKeyboardListener : NSObject
+ (BOOL)isVisible;
+ (CGRect)rect;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation WYKeyboardListener
static BOOL _isVisible;
static CGRect _keyboardRect;
+ (void)load {
@autoreleasepool {
_keyboardRect = CGRectZero;
_isVisible = NO;
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillHide) name:UIKeyboardWillHideNotification object:nil];
}
}
+ (void)keyboardWillShow:(NSNotification *)notification {
NSDictionary *info = [notification userInfo];
_keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
_isVisible = YES;
}
+ (void)keyboardWillHide {
_keyboardRect = CGRectZero;
_isVisible = NO;
}
+ (BOOL)isVisible {
return _isVisible;
}
+ (CGRect)rect {
return _keyboardRect;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface UIColor (WYPopover)
- (BOOL)wy_getValueOfRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)apha;
- (NSString *)wy_hexString;
- (UIColor *)wy_colorByLighten:(float)d;
- (UIColor *)wy_colorByDarken:(float)d;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation UIColor (WYPopover)
- (BOOL)wy_getValueOfRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha {
// model: kCGColorSpaceModelRGB, num_comps: 4
// model: kCGColorSpaceModelMonochrome, num_comps: 2
CGColorSpaceRef colorSpace = CGColorSpaceRetain(CGColorGetColorSpace([self CGColor]));
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
CGColorSpaceRelease(colorSpace);
CGFloat rFloat = 0.0, gFloat = 0.0, bFloat = 0.0, aFloat = 0.0;
BOOL result = NO;
if (colorSpaceModel == kCGColorSpaceModelRGB) {
result = [self getRed:&rFloat green:&gFloat blue:&bFloat alpha:&aFloat];
} else if (colorSpaceModel == kCGColorSpaceModelMonochrome) {
result = [self getWhite:&rFloat alpha:&aFloat];
gFloat = rFloat;
bFloat = rFloat;
}
if (red) *red = rFloat;
if (green) *green = gFloat;
if (blue) *blue = bFloat;
if (alpha) *alpha = aFloat;
return result;
}
- (NSString *)wy_hexString {
CGFloat rFloat, gFloat, bFloat, aFloat;
int r, g, b, a;
[self wy_getValueOfRed:&rFloat green:&gFloat blue:&bFloat alpha:&aFloat];
r = (int)(255.0 * rFloat);
g = (int)(255.0 * gFloat);
b = (int)(255.0 * bFloat);
a = (int)(255.0 * aFloat);
return [NSString stringWithFormat:@"#%02x%02x%02x%02x", r, g, b, a];
}
- (UIColor *)wy_colorByLighten:(float)d {
CGFloat rFloat, gFloat, bFloat, aFloat;
[self wy_getValueOfRed:&rFloat green:&gFloat blue:&bFloat alpha:&aFloat];
return [UIColor colorWithRed:MIN(rFloat + d, 1.0)
green:MIN(gFloat + d, 1.0)
blue:MIN(bFloat + d, 1.0)
alpha:1.0];
}
- (UIColor *)wy_colorByDarken:(float)d {
CGFloat rFloat, gFloat, bFloat, aFloat;
[self wy_getValueOfRed:&rFloat green:&gFloat blue:&bFloat alpha:&aFloat];
return [UIColor colorWithRed:MAX(rFloat - d, 0.0)
green:MAX(gFloat - d, 0.0)
blue:MAX(bFloat - d, 0.0)
alpha:1.0];
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface UINavigationController (WYPopover)
@property(nonatomic, assign, getter = wy_isEmbedInPopover) BOOL wy_embedInPopover;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation UINavigationController (WYPopover)
static char const * const UINavigationControllerEmbedInPopoverTagKey = "UINavigationControllerEmbedInPopoverTagKey";
@dynamic wy_embedInPopover;
+ (void)load {
Method original, swizzle;
original = class_getInstanceMethod(self, @selector(pushViewController:animated:));
swizzle = class_getInstanceMethod(self, @selector(sizzled_pushViewController:animated:));
method_exchangeImplementations(original, swizzle);
original = class_getInstanceMethod(self, @selector(setViewControllers:animated:));
swizzle = class_getInstanceMethod(self, @selector(sizzled_setViewControllers:animated:));
method_exchangeImplementations(original, swizzle);
}
- (BOOL)wy_isEmbedInPopover {
BOOL result = NO;
NSNumber *value = objc_getAssociatedObject(self, UINavigationControllerEmbedInPopoverTagKey);
if (value) {
result = [value boolValue];
}
return result;
}
- (void)setWy_embedInPopover:(BOOL)value
{
objc_setAssociatedObject(self, UINavigationControllerEmbedInPopoverTagKey, [NSNumber numberWithBool:value], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (CGSize)contentSize:(UIViewController *)aViewController {
CGSize result = CGSizeZero;
#pragma clang diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
if ([aViewController respondsToSelector:@selector(contentSizeForViewInPopover)]) {
result = aViewController.contentSizeForViewInPopover;
}
#pragma clang diagnostic pop
#ifdef WY_BASE_SDK_7_ENABLED
if ([aViewController respondsToSelector:@selector(preferredContentSize)]) {
result = aViewController.preferredContentSize;
}
#endif
return result;
}
- (void)setContentSize:(CGSize)aContentSize {
#pragma clang diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
[self setContentSizeForViewInPopover:aContentSize];
#pragma clang diagnostic pop
#ifdef WY_BASE_SDK_7_ENABLED
if ([self respondsToSelector:@selector(setPreferredContentSize:)]) {
[self setPreferredContentSize:aContentSize];
}
#endif
}
- (void)sizzled_pushViewController:(UIViewController *)aViewController animated:(BOOL)aAnimated {
if (self.wy_isEmbedInPopover) {
#ifdef WY_BASE_SDK_7_ENABLED
if ([aViewController respondsToSelector:@selector(setEdgesForExtendedLayout:)]) {
aViewController.edgesForExtendedLayout = UIRectEdgeNone;
}
#endif
CGSize contentSize = [self contentSize:aViewController];
[self setContentSize:contentSize];
}
[self sizzled_pushViewController:aViewController animated:aAnimated];
if (self.wy_isEmbedInPopover) {
CGSize contentSize = [self contentSize:aViewController];
[self setContentSize:contentSize];
}
}
- (void)sizzled_setViewControllers:(NSArray *)aViewControllers animated:(BOOL)aAnimated {
NSUInteger count = [aViewControllers count];
#ifdef WY_BASE_SDK_7_ENABLED
if (self.wy_isEmbedInPopover && count > 0) {
for (UIViewController *viewController in aViewControllers) {
if ([viewController respondsToSelector:@selector(setEdgesForExtendedLayout:)]) {
viewController.edgesForExtendedLayout = UIRectEdgeNone;
}
}
}
#endif
[self sizzled_setViewControllers:aViewControllers animated:aAnimated];
if (self.wy_isEmbedInPopover && count > 0) {
UIViewController *topViewController = [aViewControllers objectAtIndex:(count - 1)];
CGSize contentSize = [self contentSize:topViewController];
[self setContentSize:contentSize];
}
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface UIViewController (WYPopover)
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation UIViewController (WYPopover)
+ (void)load {
Method original, swizzle;
#pragma clang diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
original = class_getInstanceMethod(self, @selector(setContentSizeForViewInPopover:));
swizzle = class_getInstanceMethod(self, @selector(sizzled_setContentSizeForViewInPopover:));
method_exchangeImplementations(original, swizzle);
#pragma clang diagnostic pop
#ifdef WY_BASE_SDK_7_ENABLED
original = class_getInstanceMethod(self, @selector(setPreferredContentSize:));
swizzle = class_getInstanceMethod(self, @selector(sizzled_setPreferredContentSize:));
if (original != NULL) {
method_exchangeImplementations(original, swizzle);
}
#endif
}
- (void)sizzled_setContentSizeForViewInPopover:(CGSize)aSize {
[self sizzled_setContentSizeForViewInPopover:aSize];
if ([self isKindOfClass:[UINavigationController class]] == NO && self.navigationController != nil) {
#pragma clang diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
[self.navigationController setContentSizeForViewInPopover:aSize];
#pragma clang diagnostic pop
}
}
- (void)sizzled_setPreferredContentSize:(CGSize)aSize {
[self sizzled_setPreferredContentSize:aSize];
if ([self isKindOfClass:[UINavigationController class]] == NO && self.navigationController != nil)
{
#ifdef WY_BASE_SDK_7_ENABLED
if ([self.navigationController wy_isEmbedInPopover] == NO) {
return;
} else if ([self respondsToSelector:@selector(setPreferredContentSize:)]) {
[self.navigationController sizzled_setPreferredContentSize:aSize];
}
#endif
}
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface WYPopoverArea : NSObject
@property (nonatomic, assign) WYPopoverArrowDirection arrowDirection;
@property (nonatomic, assign) CGSize areaSize;
@property (nonatomic, assign, readonly) float value;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - WYPopoverArea
@implementation WYPopoverArea
- (NSString*)description {
const NSDictionary *directionMap = @{@(WYPopoverArrowDirectionUp) : @"UP",
@(WYPopoverArrowDirectionDown) : @"DOWN",
@(WYPopoverArrowDirectionLeft) : @"LEFT",
@(WYPopoverArrowDirectionRight) : @"RIGHT",
@(WYPopoverArrowDirectionNone) : @"NONE"};
NSString *direction = directionMap[@(_arrowDirection)];
return [NSString stringWithFormat:@"%@ [ %f x %f ]", direction, _areaSize.width, _areaSize.height];
}
- (float)value {
float result = 0;
if (_areaSize.width > 0 && _areaSize.height > 0) {
float w1 = ceilf(_areaSize.width / 10.0);
float h1 = ceilf(_areaSize.height / 10.0);
result = (w1 * h1);
}
return result;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface WYPopoverTheme ()
- (NSArray *)observableKeypaths;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation WYPopoverTheme
+ (id)theme {
WYPopoverTheme *result = nil;
if (WY_IS_IOS_LESS_THAN(@"7.0")) {
result = [WYPopoverTheme themeForIOS6];
} else {
result = [WYPopoverTheme themeForIOS7];
}
return result;
}
+ (id)themeForIOS6 {
WYPopoverTheme *result = [[WYPopoverTheme alloc] init];
result.usesRoundedArrow = NO;
result.dimsBackgroundViewsTintColor = YES;
result.tintColor = [UIColor colorWithRed:55./255. green:63./255. blue:71./255. alpha:1.0];
result.outerStrokeColor = nil;
result.innerStrokeColor = nil;
result.fillTopColor = result.tintColor;
result.fillBottomColor = [result.tintColor wy_colorByDarken:0.4];
result.glossShadowColor = nil;
result.glossShadowOffset = CGSizeMake(0, 1.5);
result.glossShadowBlurRadius = 0;
result.borderWidth = 6;
result.arrowBase = 42;
result.arrowHeight = 18;
result.outerShadowColor = [UIColor colorWithWhite:0 alpha:0.75];
result.outerShadowBlurRadius = 8;
result.outerShadowOffset = CGSizeMake(0, 2);
result.outerCornerRadius = 8;
result.minOuterCornerRadius = 0;
result.innerShadowColor = [UIColor colorWithWhite:0 alpha:0.75];
result.innerShadowBlurRadius = 2;
result.innerShadowOffset = CGSizeMake(0, 1);
result.innerCornerRadius = 6;
result.viewContentInsets = UIEdgeInsetsMake(3, 0, 0, 0);
result.overlayColor = [UIColor clearColor];
result.preferredAlpha = 1.0f;
return result;
}
+ (id)themeForIOS7 {
WYPopoverTheme *result = [[WYPopoverTheme alloc] init];
result.usesRoundedArrow = YES;
result.dimsBackgroundViewsTintColor = YES;
result.tintColor = [UIColor colorWithRed:244./255. green:244./255. blue:244./255. alpha:1.0];
result.outerStrokeColor = [UIColor clearColor];
result.innerStrokeColor = [UIColor clearColor];
result.fillTopColor = nil;
result.fillBottomColor = nil;
result.glossShadowColor = nil;
result.glossShadowOffset = CGSizeZero;
result.glossShadowBlurRadius = 0;
result.borderWidth = 0;
result.arrowBase = 25;
result.arrowHeight = 13;
result.outerShadowColor = [UIColor clearColor];
result.outerShadowBlurRadius = 0;
result.outerShadowOffset = CGSizeZero;
result.outerCornerRadius = 5;
result.minOuterCornerRadius = 0;
result.innerShadowColor = [UIColor clearColor];
result.innerShadowBlurRadius = 0;
result.innerShadowOffset = CGSizeZero;
result.innerCornerRadius = 0;
result.viewContentInsets = UIEdgeInsetsZero;
result.overlayColor = [UIColor colorWithWhite:0 alpha:0.15];
result.preferredAlpha = 1.0f;
return result;
}
- (NSUInteger)innerCornerRadius {
float result = _innerCornerRadius;
if (_borderWidth == 0) {
result = 0;
if (_outerCornerRadius > 0) {
result = _outerCornerRadius;
}
}
return result;
}
- (CGSize)outerShadowOffset {
CGSize result = _outerShadowOffset;
result.width = MIN(result.width, _outerShadowBlurRadius);
result.height = MIN(result.height, _outerShadowBlurRadius);
return result;
}
- (UIColor *)innerStrokeColor {
return _innerStrokeColor?: [self.fillTopColor wy_colorByDarken:0.6];
}
- (UIColor *)outerStrokeColor {
return _outerStrokeColor?: [self.fillTopColor wy_colorByDarken:0.6];
}
- (UIColor *)glossShadowColor {
return _glossShadowColor?: [self.fillTopColor wy_colorByLighten:0.2];
}
- (UIColor *)fillTopColor {
return _fillTopColor?: _tintColor;
}
- (UIColor *)fillBottomColor {
return _fillBottomColor?: self.fillTopColor;
}
- (NSArray *)observableKeypaths {
return [NSArray arrayWithObjects:@"tintColor", @"outerStrokeColor", @"innerStrokeColor", @"fillTopColor", @"fillBottomColor", @"glossShadowColor", @"glossShadowOffset", @"glossShadowBlurRadius", @"borderWidth", @"arrowBase", @"arrowHeight", @"outerShadowColor", @"outerShadowBlurRadius", @"outerShadowOffset", @"outerCornerRadius", @"innerShadowColor", @"innerShadowBlurRadius", @"innerShadowOffset", @"innerCornerRadius", @"viewContentInsets", @"overlayColor", nil];
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface UIImage (WYPopover)
+ (UIImage *)wy_imageWithColor:(UIColor *)color;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - UIImage (WYPopover)
@implementation UIImage (WYPopover)
static float edgeSizeFromCornerRadius(float cornerRadius) {
return cornerRadius * 2 + 1;
}
+ (UIImage *)wy_imageWithColor:(UIColor *)color {
return [self imageWithColor:color size:CGSizeMake(8, 8) cornerRadius:0];
}
+ (UIImage *)imageWithColor:(UIColor *)color
cornerRadius:(float)cornerRadius {
float min = edgeSizeFromCornerRadius(cornerRadius);
CGSize minSize = CGSizeMake(min, min);
return [self imageWithColor:color size:minSize cornerRadius:cornerRadius];
}
+ (UIImage *)imageWithColor:(UIColor *)color
size:(CGSize)aSize
cornerRadius:(float)cornerRadius {
CGRect rect = CGRectMake(0, 0, aSize.width, aSize.height);
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius];
roundedRect.lineWidth = 0;
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0f);
[color setFill];
[roundedRect fill];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [image resizableImageWithCapInsets:UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius)];
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface WYPopoverBackgroundInnerView : UIView
@property (nonatomic, strong) UIColor *innerStrokeColor;
@property (nonatomic, strong) UIColor *gradientTopColor;
@property (nonatomic, strong) UIColor *gradientBottomColor;
@property (nonatomic, assign) float gradientHeight;
@property (nonatomic, assign) float gradientTopPosition;
@property (nonatomic, strong) UIColor *innerShadowColor;
@property (nonatomic, assign) CGSize innerShadowOffset;
@property (nonatomic, assign) float innerShadowBlurRadius;
@property (nonatomic, assign) float innerCornerRadius;
@property (nonatomic, assign) float navigationBarHeight;
@property (nonatomic, assign) BOOL wantsDefaultContentAppearance;
@property (nonatomic, assign) float borderWidth;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - WYPopoverInnerView
@implementation WYPopoverBackgroundInnerView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = NO;
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
//// Gradient Declarations
NSArray* fillGradientColors = [NSArray arrayWithObjects:
(id)_gradientTopColor.CGColor,
(id)_gradientBottomColor.CGColor, nil];
CGFloat fillGradientLocations[2] = { 0, 1 };
CGGradientRef fillGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)fillGradientColors, fillGradientLocations);
//// innerRect Drawing
float barHeight = (_wantsDefaultContentAppearance == NO) ? _navigationBarHeight : 0;
float cornerRadius = (_wantsDefaultContentAppearance == NO) ? _innerCornerRadius : 0;
CGRect innerRect = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect) + barHeight, CGRectGetWidth(rect) , CGRectGetHeight(rect) - barHeight);
UIBezierPath* rectPath = [UIBezierPath bezierPathWithRect:innerRect];
UIBezierPath* roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:innerRect cornerRadius:cornerRadius + 1];
if (_wantsDefaultContentAppearance == NO && _borderWidth > 0) {
CGContextSaveGState(context);
{
[rectPath appendPath:roundedRectPath];
rectPath.usesEvenOddFillRule = YES;
[rectPath addClip];
CGContextDrawLinearGradient(context, fillGradient,
CGPointMake(0, -_gradientTopPosition),
CGPointMake(0, -_gradientTopPosition + _gradientHeight),
0);
}
CGContextRestoreGState(context);
}
CGContextSaveGState(context);
{
if (_wantsDefaultContentAppearance == NO && _borderWidth > 0) {
[roundedRectPath addClip];
CGContextSetShadowWithColor(context, _innerShadowOffset, _innerShadowBlurRadius, _innerShadowColor.CGColor);
}
UIBezierPath* inRoundedRectPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(innerRect, 0.5, 0.5) cornerRadius:cornerRadius];
if (_borderWidth == 0) {
inRoundedRectPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(innerRect, 0.5, 0.5) byRoundingCorners:UIRectCornerBottomLeft|UIRectCornerBottomRight cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
}
[self.innerStrokeColor setStroke];
inRoundedRectPath.lineWidth = 1;
[inRoundedRectPath stroke];
}
CGContextRestoreGState(context);
CGGradientRelease(fillGradient);
CGColorSpaceRelease(colorSpace);
}
- (void)dealloc
{
_innerShadowColor = nil;
_innerStrokeColor = nil;
_gradientTopColor = nil;
_gradientBottomColor = nil;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@protocol WYPopoverOverlayViewDelegate;
@interface WYPopoverOverlayView : UIView {
BOOL _testHits;
}
@property(nonatomic, assign) id <WYPopoverOverlayViewDelegate> delegate;
@property(nonatomic, unsafe_unretained) NSArray *passthroughViews;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - WYPopoverOverlayViewDelegate
@protocol WYPopoverOverlayViewDelegate <NSObject>
@optional
- (BOOL)dismissOnPassthroughViewTap;
- (void)popoverOverlayViewDidTouch:(WYPopoverOverlayView *)overlayView;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - WYPopoverOverlayView
@implementation WYPopoverOverlayView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (_testHits) {
return nil;
}
UIView *view = [super hitTest:point withEvent:event];
if (view == self) {
_testHits = YES;
UIView *superHitView = [self.superview hitTest:point withEvent:event];
_testHits = NO;
if ([self isPassthroughView:superHitView]) {
if ([self.delegate dismissOnPassthroughViewTap]) {
dispatch_async(dispatch_get_main_queue(), ^ {
if ([self.delegate respondsToSelector:@selector(popoverOverlayViewDidTouch:)]) {
[self.delegate popoverOverlayViewDidTouch:self];
}
});
}
return superHitView;
}
}
return view;
}
- (BOOL)isPassthroughView:(UIView *)view {
if (view == nil) {
return NO;
}
if ([self.passthroughViews containsObject:view]) {
return YES;
}
return [self isPassthroughView:view.superview];
}
/**
* @note This empty method is meaningful.
* If the method is not defined, touch event isn't capture in iOS6.
*/
- (void)drawRect:(CGRect)rect {}
#pragma mark - UIAccessibility
- (void)accessibilityElementDidBecomeFocused {
self.accessibilityLabel = NSLocalizedString(@"Double-tap to dismiss pop-up window.", nil);
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - WYPopoverBackgroundViewDelegate
@protocol WYPopoverBackgroundViewDelegate <NSObject>
@optional
- (void)popoverBackgroundViewDidTouchOutside:(WYPopoverBackgroundView *)backgroundView;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface WYPopoverBackgroundView () {
WYPopoverBackgroundInnerView *_innerView;
CGSize _contentSize;
}
@property(nonatomic, assign) id <WYPopoverBackgroundViewDelegate> delegate;
@property (nonatomic, assign) WYPopoverArrowDirection arrowDirection;
@property (nonatomic, strong, readonly) UIView *contentView;
@property (nonatomic, assign, readonly) float navigationBarHeight;
@property (nonatomic, assign, readonly) UIEdgeInsets outerShadowInsets;
@property (nonatomic, assign) float arrowOffset;
@property (nonatomic, assign) BOOL wantsDefaultContentAppearance;
@property (nonatomic, assign, getter = isAppearing) BOOL appearing;
- (void)tapOut;
- (void)setViewController:(UIViewController *)viewController;
- (CGRect)outerRect;
- (CGRect)innerRect;
- (CGRect)arrowRect;
- (CGRect)outerRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection;
- (CGRect)innerRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection;
- (CGRect)arrowRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection;
- (id)initWithContentSize:(CGSize)contentSize;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - WYPopoverBackgroundView
@implementation WYPopoverBackgroundView
- (id)initWithContentSize:(CGSize)aContentSize {
self = [super initWithFrame:CGRectMake(0, 0, aContentSize.width, aContentSize.height)];
if (self != nil) {
_contentSize = aContentSize;
self.autoresizesSubviews = NO;
self.backgroundColor = [UIColor clearColor];
self.arrowDirection = WYPopoverArrowDirectionDown;
self.arrowOffset = 0;
self.layer.name = @"parent";
if (WY_IS_IOS_GREATER_THAN_OR_EQUAL_TO(@"6.0")) {
self.layer.drawsAsynchronously = YES;
}
self.layer.contentsScale = [UIScreen mainScreen].scale;
//self.layer.edgeAntialiasingMask = kCALayerLeftEdge | kCALayerRightEdge | kCALayerBottomEdge | kCALayerTopEdge;
self.layer.delegate = self;
}
return self;
}
- (void)tapOut {
[self.delegate popoverBackgroundViewDidTouchOutside:self];
}
- (UIEdgeInsets)outerShadowInsets {
UIEdgeInsets result = UIEdgeInsetsMake(_outerShadowBlurRadius, _outerShadowBlurRadius, _outerShadowBlurRadius, _outerShadowBlurRadius);
result.top -= self.outerShadowOffset.height;
result.bottom += self.outerShadowOffset.height;
result.left -= self.outerShadowOffset.width;
result.right += self.outerShadowOffset.width;
return result;
}
- (void)setArrowOffset:(float)value {
float coef = 1;
if (value != 0) {
coef = value / ABS(value);
value = ABS(value);
CGRect outerRect = [self outerRect];
float delta = self.arrowBase / 2. + .5;
delta += MIN(_minOuterCornerRadius, _outerCornerRadius);
outerRect = CGRectInset(outerRect, delta, delta);
if (_arrowDirection == WYPopoverArrowDirectionUp || _arrowDirection == WYPopoverArrowDirectionDown) {
value += coef * self.outerShadowOffset.width;
value = MIN(value, CGRectGetWidth(outerRect) / 2);
}
if (_arrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
value += coef * self.outerShadowOffset.height;
value = MIN(value, CGRectGetHeight(outerRect) / 2);
}
} else {
if (_arrowDirection == WYPopoverArrowDirectionUp || _arrowDirection == WYPopoverArrowDirectionDown) {
value += self.outerShadowOffset.width;
}
if (_arrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
value += self.outerShadowOffset.height;
}
}
_arrowOffset = value * coef;
}
- (void)setViewController:(UIViewController *)viewController {
_contentView = viewController.view;
_contentView.frame = CGRectIntegral(CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height));
[self addSubview:_contentView];
_navigationBarHeight = 0;
if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)viewController;
_navigationBarHeight = navigationController.navigationBarHidden? 0 : navigationController.navigationBar.bounds.size.height;
}
_contentView.frame = CGRectIntegral([self innerRect]);
if (_innerView == nil) {
_innerView = [[WYPopoverBackgroundInnerView alloc] initWithFrame:_contentView.frame];
_innerView.userInteractionEnabled = NO;
_innerView.gradientTopColor = self.fillTopColor;
_innerView.gradientBottomColor = self.fillBottomColor;
_innerView.innerShadowColor = _innerShadowColor;
_innerView.innerStrokeColor = self.innerStrokeColor;
_innerView.innerShadowOffset = _innerShadowOffset;
_innerView.innerCornerRadius = self.innerCornerRadius;
_innerView.innerShadowBlurRadius = _innerShadowBlurRadius;
_innerView.borderWidth = self.borderWidth;
}
_innerView.navigationBarHeight = _navigationBarHeight;
_innerView.gradientHeight = self.frame.size.height - 2 * _outerShadowBlurRadius;
_innerView.gradientTopPosition = _contentView.frame.origin.y - self.outerShadowInsets.top;
_innerView.wantsDefaultContentAppearance = _wantsDefaultContentAppearance;
[self insertSubview:_innerView aboveSubview:_contentView];
_innerView.frame = CGRectIntegral(_contentView.frame);
[self.layer setNeedsDisplay];
}
- (CGSize)sizeThatFits:(CGSize)size {
CGSize result = size;
result.width += 2 * (_borderWidth + _outerShadowBlurRadius);
result.height += _borderWidth + 2 * _outerShadowBlurRadius;
if (_navigationBarHeight == 0) {
result.height += _borderWidth;
}
if (_arrowDirection == WYPopoverArrowDirectionUp || _arrowDirection == WYPopoverArrowDirectionDown) {
result.height += _arrowHeight;
}
if (_arrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
result.width += _arrowHeight;
}
return result;
}
- (void)sizeToFit {
CGSize size = [self sizeThatFits:_contentSize];
self.bounds = CGRectMake(0, 0, size.width, size.height);
}
#pragma mark Drawing
- (void)setNeedsDisplay {
[super setNeedsDisplay];
[self.layer setNeedsDisplay];
self.alpha = self.preferredAlpha;
if (_innerView) {
_innerView.gradientTopColor = self.fillTopColor;
_innerView.gradientBottomColor = self.fillBottomColor;
_innerView.innerShadowColor = _innerShadowColor;
_innerView.innerStrokeColor = self.innerStrokeColor;
_innerView.innerShadowOffset = _innerShadowOffset;
_innerView.innerCornerRadius = self.innerCornerRadius;
_innerView.innerShadowBlurRadius = _innerShadowBlurRadius;
_innerView.borderWidth = self.borderWidth;
_innerView.navigationBarHeight = _navigationBarHeight;
_innerView.gradientHeight = self.frame.size.height - 2 * _outerShadowBlurRadius;
_innerView.gradientTopPosition = _contentView.frame.origin.y - self.outerShadowInsets.top;
_innerView.wantsDefaultContentAppearance = _wantsDefaultContentAppearance;
[_innerView setNeedsDisplay];
}
}
#pragma mark CALayerDelegate
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context {
if ([layer.name isEqualToString:@"parent"]) {
UIGraphicsPushContext(context);
//CGContextSetShouldAntialias(context, YES);
//CGContextSetAllowsAntialiasing(context, YES);
//// General Declarations
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
//// Gradient Declarations
NSArray* fillGradientColors = [NSArray arrayWithObjects:
(id)self.fillTopColor.CGColor,
(id)self.fillBottomColor.CGColor, nil];
CGFloat fillGradientLocations[2] = {0, 1};
CGGradientRef fillGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)fillGradientColors, fillGradientLocations);
// Frames
CGRect rect = self.bounds;
CGRect outerRect = [self outerRect:rect arrowDirection:self.arrowDirection];
CGRect insetRect = CGRectInset(outerRect, 0.5, 0.5);
if (!CGRectIsEmpty(insetRect) && !CGRectIsInfinite(insetRect)) {
outerRect = insetRect;
}
// Inner Path
CGMutablePathRef outerPathRef = CGPathCreateMutable();
CGPoint arrowTipPoint = CGPointZero;
CGPoint arrowBasePointA = CGPointZero;
CGPoint arrowBasePointB = CGPointZero;
float reducedOuterCornerRadius = 0;
if (_arrowDirection == WYPopoverArrowDirectionUp || _arrowDirection == WYPopoverArrowDirectionDown) {
if (_arrowOffset >= 0) {
reducedOuterCornerRadius = CGRectGetMaxX(outerRect) - (CGRectGetMidX(outerRect) + _arrowOffset + _arrowBase / 2);
} else {
reducedOuterCornerRadius = (CGRectGetMidX(outerRect) + _arrowOffset - _arrowBase / 2) - CGRectGetMinX(outerRect);
}
} else if (_arrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
if (_arrowOffset >= 0) {
reducedOuterCornerRadius = CGRectGetMaxY(outerRect) - (CGRectGetMidY(outerRect) + _arrowOffset + _arrowBase / 2);
} else {
reducedOuterCornerRadius = (CGRectGetMidY(outerRect) + _arrowOffset - _arrowBase / 2) - CGRectGetMinY(outerRect);
}
}
reducedOuterCornerRadius = MIN(reducedOuterCornerRadius, _outerCornerRadius);
CGFloat roundedArrowControlLength = _arrowBase / 5.0f;
if (_arrowDirection == WYPopoverArrowDirectionUp) {
arrowTipPoint = CGPointMake(CGRectGetMidX(outerRect) + _arrowOffset,
CGRectGetMinY(outerRect) - _arrowHeight);
arrowBasePointA = CGPointMake(arrowTipPoint.x - _arrowBase / 2,
arrowTipPoint.y + _arrowHeight);
arrowBasePointB = CGPointMake(arrowTipPoint.x + _arrowBase / 2,
arrowTipPoint.y + _arrowHeight);
CGPathMoveToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
if (self.usesRoundedArrow) {
CGPathAddCurveToPoint(outerPathRef, NULL,
arrowBasePointA.x + roundedArrowControlLength, arrowBasePointA.y,
arrowTipPoint.x - (roundedArrowControlLength * 0.75f), arrowTipPoint.y,
arrowTipPoint.x, arrowTipPoint.y);
CGPathAddCurveToPoint(outerPathRef, NULL,
arrowTipPoint.x + (roundedArrowControlLength * 0.75f), arrowTipPoint.y,
arrowBasePointB.x - roundedArrowControlLength, arrowBasePointB.y,
arrowBasePointB.x, arrowBasePointB.y);
} else {
CGPathAddLineToPoint(outerPathRef, NULL, arrowTipPoint.x, arrowTipPoint.y);
CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointB.x, arrowBasePointB.y);
}
CGPathAddArcToPoint(outerPathRef, NULL, CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
(_arrowOffset >= 0) ? reducedOuterCornerRadius : _outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL, CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL, CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL, CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
(_arrowOffset < 0) ? reducedOuterCornerRadius : _outerCornerRadius);
CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
} else if (_arrowDirection == WYPopoverArrowDirectionDown) {
arrowTipPoint = CGPointMake(CGRectGetMidX(outerRect) + _arrowOffset,
CGRectGetMaxY(outerRect) + _arrowHeight);
arrowBasePointA = CGPointMake(arrowTipPoint.x + _arrowBase / 2,
arrowTipPoint.y - _arrowHeight);
arrowBasePointB = CGPointMake(arrowTipPoint.x - _arrowBase / 2,
arrowTipPoint.y - _arrowHeight);
CGPathMoveToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
if (self.usesRoundedArrow) {
CGPathAddCurveToPoint(outerPathRef, NULL,
arrowBasePointA.x - roundedArrowControlLength, arrowBasePointA.y,
arrowTipPoint.x + (roundedArrowControlLength * 0.75f), arrowTipPoint.y,
arrowTipPoint.x, arrowTipPoint.y);
CGPathAddCurveToPoint(outerPathRef, NULL,
arrowTipPoint.x - (roundedArrowControlLength * 0.75f), arrowTipPoint.y,
arrowBasePointB.x + roundedArrowControlLength, arrowBasePointA.y,
arrowBasePointB.x, arrowBasePointB.y);
} else {
CGPathAddLineToPoint(outerPathRef, NULL, arrowTipPoint.x, arrowTipPoint.y);
CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointB.x, arrowBasePointB.y);
}
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
(_arrowOffset < 0) ? reducedOuterCornerRadius : _outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
(_arrowOffset >= 0) ? reducedOuterCornerRadius : _outerCornerRadius);
CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
} else if (_arrowDirection == WYPopoverArrowDirectionLeft) {
arrowTipPoint = CGPointMake(CGRectGetMinX(outerRect) - _arrowHeight,
CGRectGetMidY(outerRect) + _arrowOffset);
arrowBasePointA = CGPointMake(arrowTipPoint.x + _arrowHeight,
arrowTipPoint.y + _arrowBase / 2);
arrowBasePointB = CGPointMake(arrowTipPoint.x + _arrowHeight,
arrowTipPoint.y - _arrowBase / 2);
CGPathMoveToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
if (self.usesRoundedArrow) {
CGPathAddCurveToPoint(outerPathRef, NULL,
arrowBasePointA.x, arrowBasePointA.y - roundedArrowControlLength,
arrowTipPoint.x, arrowTipPoint.y + (roundedArrowControlLength * 0.75f),
arrowTipPoint.x, arrowTipPoint.y);
CGPathAddCurveToPoint(outerPathRef, NULL,
arrowTipPoint.x, arrowTipPoint.y - (roundedArrowControlLength * 0.75f),
arrowBasePointB.x, arrowBasePointB.y + roundedArrowControlLength,
arrowBasePointB.x, arrowBasePointB.y);
} else {
CGPathAddLineToPoint(outerPathRef, NULL, arrowTipPoint.x, arrowTipPoint.y);
CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointB.x, arrowBasePointB.y);
}
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
(_arrowOffset < 0) ? reducedOuterCornerRadius : _outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
(_arrowOffset >= 0) ? reducedOuterCornerRadius : _outerCornerRadius);
CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
} else if (_arrowDirection == WYPopoverArrowDirectionRight) {
arrowTipPoint = CGPointMake(CGRectGetMaxX(outerRect) + _arrowHeight,
CGRectGetMidY(outerRect) + _arrowOffset);
arrowBasePointA = CGPointMake(arrowTipPoint.x - _arrowHeight,
arrowTipPoint.y - _arrowBase / 2);
arrowBasePointB = CGPointMake(arrowTipPoint.x - _arrowHeight,
arrowTipPoint.y + _arrowBase / 2);
CGPathMoveToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
if (self.usesRoundedArrow) {
CGPathAddCurveToPoint(outerPathRef, NULL,
arrowBasePointA.x, arrowBasePointA.y + roundedArrowControlLength,
arrowTipPoint.x, arrowTipPoint.y - (roundedArrowControlLength * 0.75f),
arrowTipPoint.x, arrowTipPoint.y);
CGPathAddCurveToPoint(outerPathRef, NULL,
arrowTipPoint.x, arrowTipPoint.y + (roundedArrowControlLength * 0.75f),
arrowBasePointB.x, arrowBasePointB.y - roundedArrowControlLength,
arrowBasePointB.x, arrowBasePointB.y);
} else {
CGPathAddLineToPoint(outerPathRef, NULL, arrowTipPoint.x, arrowTipPoint.y);
CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointB.x, arrowBasePointB.y);
}
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
(_arrowOffset >= 0) ? reducedOuterCornerRadius : _outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
(_arrowOffset < 0) ? reducedOuterCornerRadius : _outerCornerRadius);
CGPathAddLineToPoint(outerPathRef, NULL, arrowBasePointA.x, arrowBasePointA.y);
} else if (_arrowDirection == WYPopoverArrowDirectionNone) {
CGPoint origin = CGPointMake(CGRectGetMaxX(outerRect), CGRectGetMidY(outerRect));
CGPathMoveToPoint(outerPathRef, NULL, origin.x, origin.y);
CGPathAddLineToPoint(outerPathRef, NULL, CGRectGetMaxX(outerRect), CGRectGetMidY(outerRect));
CGPathAddLineToPoint(outerPathRef, NULL, CGRectGetMaxX(outerRect), CGRectGetMidY(outerRect));
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMinX(outerRect), CGRectGetMaxY(outerRect),
CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMinX(outerRect), CGRectGetMinY(outerRect),
CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
_outerCornerRadius);
CGPathAddArcToPoint(outerPathRef, NULL,
CGRectGetMaxX(outerRect), CGRectGetMinY(outerRect),
CGRectGetMaxX(outerRect), CGRectGetMaxY(outerRect),
_outerCornerRadius);
CGPathAddLineToPoint(outerPathRef, NULL, origin.x, origin.y);
}
CGPathCloseSubpath(outerPathRef);
UIBezierPath* outerRectPath = [UIBezierPath bezierPathWithCGPath:outerPathRef];
CGContextSaveGState(context);
{
CGContextSetShadowWithColor(context, self.outerShadowOffset, _outerShadowBlurRadius, _outerShadowColor.CGColor);
CGContextBeginTransparencyLayer(context, NULL);
[outerRectPath addClip];
CGRect outerRectBounds = CGPathGetPathBoundingBox(outerRectPath.CGPath);
CGContextDrawLinearGradient(context, fillGradient,
CGPointMake(CGRectGetMidX(outerRectBounds), CGRectGetMinY(outerRectBounds)),
CGPointMake(CGRectGetMidX(outerRectBounds), CGRectGetMaxY(outerRectBounds)),
0);
CGContextEndTransparencyLayer(context);
}
CGContextRestoreGState(context);
////// outerRect Inner Shadow
CGRect outerRectBorderRect = CGRectInset([outerRectPath bounds], -_glossShadowBlurRadius, -_glossShadowBlurRadius);
outerRectBorderRect = CGRectOffset(outerRectBorderRect, -_glossShadowOffset.width, -_glossShadowOffset.height);
outerRectBorderRect = CGRectInset(CGRectUnion(outerRectBorderRect, [outerRectPath bounds]), -1, -1);
UIBezierPath* outerRectNegativePath = [UIBezierPath bezierPathWithRect: outerRectBorderRect];
[outerRectNegativePath appendPath: outerRectPath];
outerRectNegativePath.usesEvenOddFillRule = YES;
CGContextSaveGState(context);
{
float xOffset = _glossShadowOffset.width + round(outerRectBorderRect.size.width);
float yOffset = _glossShadowOffset.height;
CGContextSetShadowWithColor(context,
CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
_glossShadowBlurRadius,
self.glossShadowColor.CGColor);
[outerRectPath addClip];
CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(outerRectBorderRect.size.width), 0);
[outerRectNegativePath applyTransform: transform];
[[UIColor grayColor] setFill];
[outerRectNegativePath fill];
}
CGContextRestoreGState(context);
[self.outerStrokeColor setStroke];
outerRectPath.lineWidth = 1;
[outerRectPath stroke];
//// Cleanup
CFRelease(outerPathRef);
CGGradientRelease(fillGradient);
CGColorSpaceRelease(colorSpace);
UIGraphicsPopContext();
}
}
#pragma mark Private
- (CGRect)outerRect {
return [self outerRect:self.bounds arrowDirection:self.arrowDirection];
}
- (CGRect)innerRect {
return [self innerRect:self.bounds arrowDirection:self.arrowDirection];
}
- (CGRect)arrowRect {
return [self arrowRect:self.bounds arrowDirection:self.arrowDirection];
}
- (CGRect)outerRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection{
CGRect result = rect;
if (aArrowDirection == WYPopoverArrowDirectionUp || _arrowDirection == WYPopoverArrowDirectionDown) {
result.size.height -= _arrowHeight;
if (aArrowDirection == WYPopoverArrowDirectionUp) {
result = CGRectOffset(result, 0, _arrowHeight);
}
}
if (aArrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
result.size.width -= _arrowHeight;
if (aArrowDirection == WYPopoverArrowDirectionLeft) {
result = CGRectOffset(result, _arrowHeight, 0);
}
}
result = CGRectInset(result, _outerShadowBlurRadius, _outerShadowBlurRadius);
result.origin.x -= self.outerShadowOffset.width;
result.origin.y -= self.outerShadowOffset.height;
return result;
}
- (CGRect)innerRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection {
CGRect result = [self outerRect:rect arrowDirection:aArrowDirection];
result.origin.x += _borderWidth;
result.origin.y += 0;
result.size.width -= 2 * _borderWidth;
result.size.height -= _borderWidth;
if (_navigationBarHeight == 0 || _wantsDefaultContentAppearance) {
result.origin.y += _borderWidth;
result.size.height -= _borderWidth;
}
result.origin.x += _viewContentInsets.left;
result.origin.y += _viewContentInsets.top;
result.size.width = result.size.width - _viewContentInsets.left - _viewContentInsets.right;
result.size.height = result.size.height - _viewContentInsets.top - _viewContentInsets.bottom;
if (_borderWidth > 0) {
result = CGRectInset(result, -1, -1);
}
return result;
}
- (CGRect)arrowRect:(CGRect)rect arrowDirection:(WYPopoverArrowDirection)aArrowDirection {
CGRect result = CGRectZero;
if (_arrowHeight > 0) {
result.size = CGSizeMake(_arrowBase, _arrowHeight);
if (aArrowDirection == WYPopoverArrowDirectionLeft || _arrowDirection == WYPopoverArrowDirectionRight) {
result.size = CGSizeMake(_arrowHeight, _arrowBase);
}
CGRect outerRect = [self outerRect:rect arrowDirection:aArrowDirection];
if (aArrowDirection == WYPopoverArrowDirectionDown) {
result.origin.x = CGRectGetMidX(outerRect) - result.size.width / 2 + _arrowOffset;
result.origin.y = CGRectGetMaxY(outerRect);
}
if (aArrowDirection == WYPopoverArrowDirectionUp) {
result.origin.x = CGRectGetMidX(outerRect) - result.size.width / 2 + _arrowOffset;
result.origin.y = CGRectGetMinY(outerRect) - result.size.height;
}
if (aArrowDirection == WYPopoverArrowDirectionRight) {
result.origin.x = CGRectGetMaxX(outerRect);
result.origin.y = CGRectGetMidY(outerRect) - result.size.height / 2 + _arrowOffset;
}
if (aArrowDirection == WYPopoverArrowDirectionLeft) {
result.origin.x = CGRectGetMinX(outerRect) - result.size.width;
result.origin.y = CGRectGetMidY(outerRect) - result.size.height / 2 + _arrowOffset;
}
}
return result;
}
#pragma mark Memory Management
- (void)dealloc {
_contentView = nil;
_innerView = nil;
_tintColor = nil;
_outerStrokeColor = nil;
_innerStrokeColor = nil;
_fillTopColor = nil;
_fillBottomColor = nil;
_glossShadowColor = nil;
_outerShadowColor = nil;
_innerShadowColor = nil;
}
@end
////////////////////////////////////////////////////////////////////////////
@interface WYPopoverController () <WYPopoverOverlayViewDelegate, WYPopoverBackgroundViewDelegate> {
UIViewController *_viewController;
CGRect _rect;
UIView *_inView;
WYPopoverOverlayView *_overlayView;
WYPopoverBackgroundView *_backgroundView;
WYPopoverArrowDirection _permittedArrowDirections;
BOOL _animated;
BOOL _isListeningNotifications;
BOOL _isObserverAdded;
BOOL _isInterfaceOrientationChanging;
BOOL _ignoreOrientation;
__weak UIBarButtonItem *_barButtonItem;
WYPopoverAnimationOptions options;
BOOL themeUpdatesEnabled;
BOOL themeIsUpdating;
}
- (void)dismissPopoverAnimated:(BOOL)aAnimated
options:(WYPopoverAnimationOptions)aAptions
completion:(void (^)(void))aCompletion
callDelegate:(BOOL)aCallDelegate;
- (WYPopoverArrowDirection)arrowDirectionForRect:(CGRect)aRect
inView:(UIView*)aView
contentSize:(CGSize)aContentSize
arrowHeight:(float)aArrowHeight
permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections;
- (CGSize)sizeForRect:(CGRect)aRect
inView:(UIView *)aView
arrowHeight:(float)aArrowHeight
arrowDirection:(WYPopoverArrowDirection)aArrowDirection;
- (void)registerTheme;
- (void)unregisterTheme;
- (void)updateThemeUI;
- (CGSize)topViewControllerContentSize;
@end
////////////////////////////////////////////////////////////////////////////
#pragma mark - WYPopoverController
@implementation WYPopoverController
static WYPopoverTheme *defaultTheme_ = nil;
@synthesize popoverContentSize = popoverContentSize_;
+ (void)setDefaultTheme:(WYPopoverTheme *)aTheme {
defaultTheme_ = aTheme;
@autoreleasepool {
WYPopoverBackgroundView *appearance = [WYPopoverBackgroundView appearance];
appearance.usesRoundedArrow = aTheme.usesRoundedArrow;
appearance.dimsBackgroundViewsTintColor = aTheme.dimsBackgroundViewsTintColor;
appearance.tintColor = aTheme.tintColor;
appearance.outerStrokeColor = aTheme.outerStrokeColor;
appearance.innerStrokeColor = aTheme.innerStrokeColor;
appearance.fillTopColor = aTheme.fillTopColor;
appearance.fillBottomColor = aTheme.fillBottomColor;
appearance.glossShadowColor = aTheme.glossShadowColor;
appearance.glossShadowOffset = aTheme.glossShadowOffset;
appearance.glossShadowBlurRadius = aTheme.glossShadowBlurRadius;
appearance.borderWidth = aTheme.borderWidth;
appearance.arrowBase = aTheme.arrowBase;
appearance.arrowHeight = aTheme.arrowHeight;
appearance.outerShadowColor = aTheme.outerShadowColor;
appearance.outerShadowBlurRadius = aTheme.outerShadowBlurRadius;
appearance.outerShadowOffset = aTheme.outerShadowOffset;
appearance.outerCornerRadius = aTheme.outerCornerRadius;
appearance.minOuterCornerRadius = aTheme.minOuterCornerRadius;
appearance.innerShadowColor = aTheme.innerShadowColor;
appearance.innerShadowBlurRadius = aTheme.innerShadowBlurRadius;
appearance.innerShadowOffset = aTheme.innerShadowOffset;
appearance.innerCornerRadius = aTheme.innerCornerRadius;
appearance.viewContentInsets = aTheme.viewContentInsets;
appearance.overlayColor = aTheme.overlayColor;
appearance.preferredAlpha = aTheme.preferredAlpha;
}
}
+ (WYPopoverTheme *)defaultTheme {
return defaultTheme_;
}
+ (void)load {
[WYPopoverController setDefaultTheme:[WYPopoverTheme theme]];
}
- (id)init {
self = [super init];
if (self) {
// ignore orientation in iOS8
_ignoreOrientation = (compileUsingIOS8SDK() && [[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]);
_popoverLayoutMargins = UIEdgeInsetsMake(10, 10, 10, 10);
_animationDuration = WY_POPOVER_DEFAULT_ANIMATION_DURATION;
themeUpdatesEnabled = NO;
[self setTheme:[WYPopoverController defaultTheme]];
themeIsUpdating = YES;
WYPopoverBackgroundView *appearance = [WYPopoverBackgroundView appearance];
_theme.usesRoundedArrow = appearance.usesRoundedArrow;
_theme.dimsBackgroundViewsTintColor = appearance.dimsBackgroundViewsTintColor;
_theme.tintColor = appearance.tintColor;
_theme.outerStrokeColor = appearance.outerStrokeColor;
_theme.innerStrokeColor = appearance.innerStrokeColor;
_theme.fillTopColor = appearance.fillTopColor;
_theme.fillBottomColor = appearance.fillBottomColor;
_theme.glossShadowColor = appearance.glossShadowColor;
_theme.glossShadowOffset = appearance.glossShadowOffset;
_theme.glossShadowBlurRadius = appearance.glossShadowBlurRadius;
_theme.borderWidth = appearance.borderWidth;
_theme.arrowBase = appearance.arrowBase;
_theme.arrowHeight = appearance.arrowHeight;
_theme.outerShadowColor = appearance.outerShadowColor;
_theme.outerShadowBlurRadius = appearance.outerShadowBlurRadius;
_theme.outerShadowOffset = appearance.outerShadowOffset;
_theme.outerCornerRadius = appearance.outerCornerRadius;
_theme.minOuterCornerRadius = appearance.minOuterCornerRadius;
_theme.innerShadowColor = appearance.innerShadowColor;
_theme.innerShadowBlurRadius = appearance.innerShadowBlurRadius;
_theme.innerShadowOffset = appearance.innerShadowOffset;
_theme.innerCornerRadius = appearance.innerCornerRadius;
_theme.viewContentInsets = appearance.viewContentInsets;
_theme.overlayColor = appearance.overlayColor;
_theme.preferredAlpha = appearance.preferredAlpha;
themeIsUpdating = NO;
themeUpdatesEnabled = YES;
popoverContentSize_ = CGSizeZero;
}
return self;
}
- (id)initWithContentViewController:(UIViewController *)aViewController {
self = [self init];
if (self) {
_viewController = aViewController;
}
return self;
}
- (void)setTheme:(WYPopoverTheme *)value {
[self unregisterTheme];
_theme = value;
[self registerTheme];
[self updateThemeUI];
themeIsUpdating = NO;
}
- (void)registerTheme {
if (_theme == nil) return;
NSArray *keypaths = [_theme observableKeypaths];
for (NSString *keypath in keypaths) {
[_theme addObserver:self forKeyPath:keypath options:NSKeyValueObservingOptionNew context:NULL];
}
}
- (void)unregisterTheme {
if (_theme == nil) return;
@try {
NSArray *keypaths = [_theme observableKeypaths];
for (NSString *keypath in keypaths) {
[_theme removeObserver:self forKeyPath:keypath];
}
}
@catch (NSException * __unused exception) {}
}
- (void)updateThemeUI {
if (_theme == nil || themeUpdatesEnabled == NO || themeIsUpdating == YES) return;
if (_backgroundView != nil) {
_backgroundView.usesRoundedArrow = _theme.usesRoundedArrow;
_backgroundView.dimsBackgroundViewsTintColor = _theme.dimsBackgroundViewsTintColor;
_backgroundView.tintColor = _theme.tintColor;
_backgroundView.outerStrokeColor = _theme.outerStrokeColor;
_backgroundView.innerStrokeColor = _theme.innerStrokeColor;
_backgroundView.fillTopColor = _theme.fillTopColor;
_backgroundView.fillBottomColor = _theme.fillBottomColor;
_backgroundView.glossShadowColor = _theme.glossShadowColor;
_backgroundView.glossShadowOffset = _theme.glossShadowOffset;
_backgroundView.glossShadowBlurRadius = _theme.glossShadowBlurRadius;
_backgroundView.borderWidth = _theme.borderWidth;
_backgroundView.arrowBase = _theme.arrowBase;
_backgroundView.arrowHeight = _theme.arrowHeight;
_backgroundView.outerShadowColor = _theme.outerShadowColor;
_backgroundView.outerShadowBlurRadius = _theme.outerShadowBlurRadius;
_backgroundView.outerShadowOffset = _theme.outerShadowOffset;
_backgroundView.outerCornerRadius = _theme.outerCornerRadius;
_backgroundView.minOuterCornerRadius = _theme.minOuterCornerRadius;
_backgroundView.innerShadowColor = _theme.innerShadowColor;
_backgroundView.innerShadowBlurRadius = _theme.innerShadowBlurRadius;
_backgroundView.innerShadowOffset = _theme.innerShadowOffset;
_backgroundView.innerCornerRadius = _theme.innerCornerRadius;
_backgroundView.viewContentInsets = _theme.viewContentInsets;
_backgroundView.preferredAlpha = _theme.preferredAlpha;
[_backgroundView setNeedsDisplay];
}
if (_overlayView != nil) {
_overlayView.backgroundColor = _theme.overlayColor;
}
[self positionPopover:NO];
[self setPopoverNavigationBarBackgroundImage];
}
- (void)beginThemeUpdates {
themeIsUpdating = YES;
}
- (void)endThemeUpdates {
themeIsUpdating = NO;
[self updateThemeUI];
}
- (BOOL)isPopoverVisible {
BOOL result = (_overlayView != nil);
return result;
}
- (UIViewController *)contentViewController {
return _viewController;
}
- (CGSize)topViewControllerContentSize {
CGSize result = CGSizeZero;
UIViewController *topViewController = _viewController;
if ([_viewController isKindOfClass:[UINavigationController class]] == YES) {
UINavigationController *navigationController = (UINavigationController *)_viewController;
topViewController = [navigationController topViewController];
}
#ifdef WY_BASE_SDK_7_ENABLED
if ([topViewController respondsToSelector:@selector(preferredContentSize)]) {
result = topViewController.preferredContentSize;
}
#endif
if (CGSizeEqualToSize(result, CGSizeZero)) {
#pragma clang diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
result = topViewController.contentSizeForViewInPopover;
#pragma clang diagnostic pop
}
if (CGSizeEqualToSize(result, CGSizeZero)) {
CGSize windowSize = [[UIApplication sharedApplication] keyWindow].bounds.size;
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
result = CGSizeMake(UIInterfaceOrientationIsPortrait(orientation) ? windowSize.width : windowSize.height, UIInterfaceOrientationIsLandscape(orientation) ? windowSize.width : windowSize.height);
}
return result;
}
- (CGSize)popoverContentSize {
CGSize result = popoverContentSize_;
if (CGSizeEqualToSize(result, CGSizeZero)) {
result = [self topViewControllerContentSize];
}
return result;
}
- (void)setPopoverContentSize:(CGSize)size {
popoverContentSize_ = size;
[self positionPopover:YES];
}
- (void)setPopoverContentSize:(CGSize)size animated:(BOOL)animated {
popoverContentSize_ = size;
[self positionPopover:animated];
}
- (void)performWithoutAnimation:(void (^)(void))aBlock {
if (aBlock) {
self.implicitAnimationsDisabled = YES;
aBlock();
self.implicitAnimationsDisabled = NO;
}
}
- (void)presentPopoverFromRect:(CGRect)aRect
inView:(UIView *)aView
permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
animated:(BOOL)aAnimated {
[self presentPopoverFromRect:aRect
inView:aView
permittedArrowDirections:aArrowDirections
animated:aAnimated
completion:nil];
}
- (void)presentPopoverFromRect:(CGRect)aRect
inView:(UIView *)aView
permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
animated:(BOOL)aAnimated
completion:(void (^)(void))completion {
[self presentPopoverFromRect:aRect
inView:aView
permittedArrowDirections:aArrowDirections
animated:aAnimated
options:WYPopoverAnimationOptionFade
completion:completion];
}
- (void)presentPopoverFromRect:(CGRect)aRect
inView:(UIView *)aView
permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
animated:(BOOL)aAnimated
options:(WYPopoverAnimationOptions)aOptions {
[self presentPopoverFromRect:aRect
inView:aView
permittedArrowDirections:aArrowDirections
animated:aAnimated
options:aOptions
completion:nil];
}
- (void)presentPopoverFromRect:(CGRect)aRect
inView:(UIView *)aView
permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
animated:(BOOL)aAnimated
options:(WYPopoverAnimationOptions)aOptions
completion:(void (^)(void))completion {
NSAssert((aArrowDirections != WYPopoverArrowDirectionUnknown), @"WYPopoverArrowDirection must not be UNKNOWN");
_rect = aRect;
_inView = aView;
_permittedArrowDirections = aArrowDirections;
_animated = aAnimated;
options = aOptions;
if (!_inView) {
_inView = [UIApplication sharedApplication].keyWindow.rootViewController.view;
if (CGRectIsEmpty(_rect)) {
_rect = CGRectMake((int)_inView.bounds.size.width / 2 - 5, (int)_inView.bounds.size.height / 2 - 5, 10, 10);
}
}
CGSize contentViewSize = self.popoverContentSize;
if (_overlayView == nil) {
_overlayView = [[WYPopoverOverlayView alloc] initWithFrame:_inView.window.bounds];
_overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_overlayView.autoresizesSubviews = NO;
_overlayView.delegate = self;
_overlayView.passthroughViews = _passthroughViews;
_backgroundView = [[WYPopoverBackgroundView alloc] initWithContentSize:contentViewSize];
_backgroundView.appearing = YES;
_backgroundView.delegate = self;
_backgroundView.hidden = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:_backgroundView action:@selector(tapOut)];
tap.cancelsTouchesInView = NO;
[_overlayView addGestureRecognizer:tap];
if (self.dismissOnTap) {
tap = [[UITapGestureRecognizer alloc] initWithTarget:_backgroundView action:@selector(tapOut)];
tap.cancelsTouchesInView = NO;
[_backgroundView addGestureRecognizer:tap];
}
[_inView.window addSubview:_backgroundView];
[_inView.window insertSubview:_overlayView belowSubview:_backgroundView];
}
[self updateThemeUI];
__weak __typeof__(self) weakSelf = self;
void (^completionBlock)(BOOL) = ^(BOOL animated) {
__typeof__(self) strongSelf = weakSelf;
if (strongSelf) {
if (_isObserverAdded == NO) {
_isObserverAdded = YES;
if ([strongSelf->_viewController respondsToSelector:@selector(preferredContentSize)]) {
[strongSelf->_viewController addObserver:self forKeyPath:NSStringFromSelector(@selector(preferredContentSize)) options:0 context:nil];
} else {
[strongSelf->_viewController addObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeForViewInPopover)) options:0 context:nil];
}
}
strongSelf->_backgroundView.appearing = NO;
}
if (completion) {
completion();
} else if (strongSelf && strongSelf->_delegate && [strongSelf->_delegate respondsToSelector:@selector(popoverControllerDidPresentPopover:)]) {
[strongSelf->_delegate popoverControllerDidPresentPopover:strongSelf];
}
};
void (^adjustTintDimmed)() = ^() {
#ifdef WY_BASE_SDK_7_ENABLED
if (_backgroundView.dimsBackgroundViewsTintColor && [_inView.window respondsToSelector:@selector(setTintAdjustmentMode:)]) {
for (UIView *subview in _inView.window.subviews) {
if (subview != _backgroundView) {
[subview setTintAdjustmentMode:UIViewTintAdjustmentModeDimmed];
}
}
}
#endif
};
_backgroundView.hidden = NO;
if (_animated) {
if ((options & WYPopoverAnimationOptionFade) == WYPopoverAnimationOptionFade) {
_overlayView.alpha = 0;
_backgroundView.alpha = 0;
}
CGAffineTransform endTransform = _backgroundView.transform;
if ((options & WYPopoverAnimationOptionScale) == WYPopoverAnimationOptionScale) {
CGAffineTransform startTransform = [self transformForArrowDirection:_backgroundView.arrowDirection];
_backgroundView.transform = startTransform;
}
[UIView animateWithDuration:_animationDuration animations:^{
__typeof__(self) strongSelf = weakSelf;
if (strongSelf) {
strongSelf->_overlayView.alpha = 1;
strongSelf->_backgroundView.alpha = strongSelf->_backgroundView.preferredAlpha;
strongSelf->_backgroundView.transform = endTransform;
}
adjustTintDimmed();
} completion:^(BOOL finished) {
completionBlock(YES);
}];
} else {
adjustTintDimmed();
completionBlock(NO);
}
if (_isListeningNotifications == NO) {
_isListeningNotifications = YES;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didChangeStatusBarOrientation:)
name:UIApplicationDidChangeStatusBarOrientationNotification
object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didChangeDeviceOrientation:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification object:nil];
}
}
- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)aItem
permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
animated:(BOOL)aAnimated {
[self presentPopoverFromBarButtonItem:aItem
permittedArrowDirections:aArrowDirections
animated:aAnimated
completion:nil];
}
- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)aItem
permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
animated:(BOOL)aAnimated
completion:(void (^)(void))completion {
[self presentPopoverFromBarButtonItem:aItem
permittedArrowDirections:aArrowDirections
animated:aAnimated
options:WYPopoverAnimationOptionFade
completion:completion];
}
- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)aItem
permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
animated:(BOOL)aAnimated
options:(WYPopoverAnimationOptions)aOptions {
[self presentPopoverFromBarButtonItem:aItem
permittedArrowDirections:aArrowDirections
animated:aAnimated
options:aOptions
completion:nil];
}
- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)aItem
permittedArrowDirections:(WYPopoverArrowDirection)aArrowDirections
animated:(BOOL)aAnimated
options:(WYPopoverAnimationOptions)aOptions
completion:(void (^)(void))completion {
_barButtonItem = aItem;
UIView *itemView = [_barButtonItem valueForKey:@"view"];
aArrowDirections = WYPopoverArrowDirectionDown | WYPopoverArrowDirectionUp;
[self presentPopoverFromRect:itemView.bounds
inView:itemView
permittedArrowDirections:aArrowDirections
animated:aAnimated
options:aOptions
completion:completion];
}
- (void)presentPopoverAsDialogAnimated:(BOOL)aAnimated {
[self presentPopoverAsDialogAnimated:aAnimated
completion:nil];
}
- (void)presentPopoverAsDialogAnimated:(BOOL)aAnimated
completion:(void (^)(void))completion {
[self presentPopoverAsDialogAnimated:aAnimated
options:WYPopoverAnimationOptionFade
completion:completion];
}
- (void)presentPopoverAsDialogAnimated:(BOOL)aAnimated
options:(WYPopoverAnimationOptions)aOptions {
[self presentPopoverAsDialogAnimated:aAnimated
options:aOptions
completion:nil];
}
- (void)presentPopoverAsDialogAnimated:(BOOL)aAnimated
options:(WYPopoverAnimationOptions)aOptions
completion:(void (^)(void))completion {
[self presentPopoverFromRect:CGRectZero
inView:nil
permittedArrowDirections:WYPopoverArrowDirectionNone
animated:aAnimated
options:aOptions
completion:completion];
}
- (CGAffineTransform)transformForArrowDirection:(WYPopoverArrowDirection)arrowDirection {
CGAffineTransform transform = _backgroundView.transform;
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGSize containerViewSize = _backgroundView.frame.size;
if (_backgroundView.arrowHeight > 0) {
if (UIInterfaceOrientationIsLandscape(orientation)) {
containerViewSize.width = _backgroundView.frame.size.height;
containerViewSize.height = _backgroundView.frame.size.width;
}
//WY_LOG(@"containerView.arrowOffset = %f", containerView.arrowOffset);
//WY_LOG(@"containerViewSize = %@", NSStringFromCGSize(containerViewSize));
//WY_LOG(@"orientation = %@", WYStringFromOrientation(orientation));
if (arrowDirection == WYPopoverArrowDirectionDown) {
transform = CGAffineTransformTranslate(transform, _backgroundView.arrowOffset, containerViewSize.height / 2);
}
if (arrowDirection == WYPopoverArrowDirectionUp) {
transform = CGAffineTransformTranslate(transform, _backgroundView.arrowOffset, -containerViewSize.height / 2);
}
if (arrowDirection == WYPopoverArrowDirectionRight) {
transform = CGAffineTransformTranslate(transform, containerViewSize.width / 2, _backgroundView.arrowOffset);
}
if (arrowDirection == WYPopoverArrowDirectionLeft) {
transform = CGAffineTransformTranslate(transform, -containerViewSize.width / 2, _backgroundView.arrowOffset);
}
}
transform = CGAffineTransformScale(transform, 0.01, 0.01);
return transform;
}
- (void)setPopoverNavigationBarBackgroundImage {
if ([_viewController isKindOfClass:[UINavigationController class]] == YES) {
UINavigationController *navigationController = (UINavigationController *)_viewController;
navigationController.wy_embedInPopover = YES;
#ifdef WY_BASE_SDK_7_ENABLED
if ([navigationController respondsToSelector:@selector(setEdgesForExtendedLayout:)]) {
UIViewController *topViewController = [navigationController topViewController];
[topViewController setEdgesForExtendedLayout:UIRectEdgeNone];
}
#endif
if (_wantsDefaultContentAppearance == NO) {
[navigationController.navigationBar setBackgroundImage:[UIImage wy_imageWithColor:[UIColor clearColor]] forBarMetrics:UIBarMetricsDefault];
}
}
_viewController.view.clipsToBounds = YES;
if (_backgroundView.borderWidth == 0) {
_viewController.view.layer.cornerRadius = _backgroundView.outerCornerRadius;
}
}
- (void)positionPopover:(BOOL)aAnimated {
CGRect savedContainerFrame = _backgroundView.frame;
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGSize contentViewSize = self.popoverContentSize;
CGSize minContainerSize = WY_POPOVER_MIN_SIZE;
CGRect viewFrame;
CGRect containerFrame = CGRectZero;
float minX, maxX, minY, maxY, offset = 0;
CGSize containerViewSize = CGSizeZero;
float overlayWidth;
float overlayHeight;
float keyboardHeight;
if (_ignoreOrientation) {
overlayWidth = _overlayView.window.frame.size.width;
overlayHeight = _overlayView.window.frame.size.height;
CGRect convertedFrame = [_overlayView.window convertRect:WYKeyboardListener.rect toView:_overlayView];
keyboardHeight = convertedFrame.size.height;
} else {
overlayWidth = UIInterfaceOrientationIsPortrait(orientation) ? _overlayView.bounds.size.width : _overlayView.bounds.size.height;
overlayHeight = UIInterfaceOrientationIsPortrait(orientation) ? _overlayView.bounds.size.height : _overlayView.bounds.size.width;
keyboardHeight = UIInterfaceOrientationIsPortrait(orientation) ? WYKeyboardListener.rect.size.height : WYKeyboardListener.rect.size.width;
}
if (_delegate && [_delegate respondsToSelector:@selector(popoverControllerShouldIgnoreKeyboardBounds:)]) {
BOOL shouldIgnore = [_delegate popoverControllerShouldIgnoreKeyboardBounds:self];
if (shouldIgnore) {
keyboardHeight = 0;
}
}
WYPopoverArrowDirection arrowDirection = _permittedArrowDirections;
_overlayView.bounds = _inView.window.bounds;
_backgroundView.transform = CGAffineTransformIdentity;
viewFrame = [_inView convertRect:_rect toView:nil];
viewFrame = WYRectInWindowBounds(viewFrame, orientation);
minX = _popoverLayoutMargins.left;
maxX = overlayWidth - _popoverLayoutMargins.right;
minY = WYStatusBarHeight() + _popoverLayoutMargins.top;
maxY = overlayHeight - _popoverLayoutMargins.bottom - keyboardHeight;
// Which direction ?
//
arrowDirection = [self arrowDirectionForRect:_rect
inView:_inView
contentSize:contentViewSize
arrowHeight:_backgroundView.arrowHeight
permittedArrowDirections:arrowDirection];
// Position of the popover
//
minX -= _backgroundView.outerShadowInsets.left;
maxX += _backgroundView.outerShadowInsets.right;
minY -= _backgroundView.outerShadowInsets.top;
maxY += _backgroundView.outerShadowInsets.bottom;
if (arrowDirection == WYPopoverArrowDirectionDown) {
_backgroundView.arrowDirection = WYPopoverArrowDirectionDown;
containerViewSize = [_backgroundView sizeThatFits:contentViewSize];
containerFrame = CGRectZero;
containerFrame.size = containerViewSize;
containerFrame.size.width = MIN(maxX - minX, containerFrame.size.width);
containerFrame.size.height = MIN(maxY - minY, containerFrame.size.height);
_backgroundView.frame = CGRectIntegral(containerFrame);
_backgroundView.center = CGPointMake(viewFrame.origin.x + viewFrame.size.width / 2, viewFrame.origin.y + viewFrame.size.height / 2);
containerFrame = _backgroundView.frame;
offset = 0;
if (containerFrame.origin.x < minX) {
offset = minX - containerFrame.origin.x;
containerFrame.origin.x = minX;
offset = -offset;
} else if (containerFrame.origin.x + containerFrame.size.width > maxX) {
offset = (_backgroundView.frame.origin.x + _backgroundView.frame.size.width) - maxX;
containerFrame.origin.x -= offset;
}
_backgroundView.arrowOffset = offset;
offset = _backgroundView.frame.size.height / 2 + viewFrame.size.height / 2 - _backgroundView.outerShadowInsets.bottom;
containerFrame.origin.y -= offset;
if (containerFrame.origin.y < minY) {
offset = minY - containerFrame.origin.y;
containerFrame.size.height -= offset;
if (containerFrame.size.height < minContainerSize.height) {
// popover is overflowing
offset -= (minContainerSize.height - containerFrame.size.height);
containerFrame.size.height = minContainerSize.height;
}
containerFrame.origin.y += offset;
}
}
if (arrowDirection == WYPopoverArrowDirectionUp) {
_backgroundView.arrowDirection = WYPopoverArrowDirectionUp;
containerViewSize = [_backgroundView sizeThatFits:contentViewSize];
containerFrame = CGRectZero;
containerFrame.size = containerViewSize;
containerFrame.size.width = MIN(maxX - minX, containerFrame.size.width);
containerFrame.size.height = MIN(maxY - minY, containerFrame.size.height);
_backgroundView.frame = containerFrame;
_backgroundView.center = CGPointMake(viewFrame.origin.x + viewFrame.size.width / 2, viewFrame.origin.y + viewFrame.size.height / 2);
containerFrame = _backgroundView.frame;
offset = 0;
if (containerFrame.origin.x < minX) {
offset = minX - containerFrame.origin.x;
containerFrame.origin.x = minX;
offset = -offset;
}
else if (containerFrame.origin.x + containerFrame.size.width > maxX) {
offset = (_backgroundView.frame.origin.x + _backgroundView.frame.size.width) - maxX;
containerFrame.origin.x -= offset;
}
_backgroundView.arrowOffset = offset;
offset = _backgroundView.frame.size.height / 2 + viewFrame.size.height / 2 - _backgroundView.outerShadowInsets.top;
containerFrame.origin.y += offset;
if (containerFrame.origin.y + containerFrame.size.height > maxY) {
offset = (containerFrame.origin.y + containerFrame.size.height) - maxY;
containerFrame.size.height -= offset;
if (containerFrame.size.height < minContainerSize.height) {
// popover is overflowing
containerFrame.size.height = minContainerSize.height;
}
}
}
if (arrowDirection == WYPopoverArrowDirectionRight) {
_backgroundView.arrowDirection = WYPopoverArrowDirectionRight;
containerViewSize = [_backgroundView sizeThatFits:contentViewSize];
containerFrame = CGRectZero;
containerFrame.size = containerViewSize;
containerFrame.size.width = MIN(maxX - minX, containerFrame.size.width);
containerFrame.size.height = MIN(maxY - minY, containerFrame.size.height);
_backgroundView.frame = CGRectIntegral(containerFrame);
_backgroundView.center = CGPointMake(viewFrame.origin.x + viewFrame.size.width / 2, viewFrame.origin.y + viewFrame.size.height / 2);
containerFrame = _backgroundView.frame;
offset = _backgroundView.frame.size.width / 2 + viewFrame.size.width / 2 - _backgroundView.outerShadowInsets.right;
containerFrame.origin.x -= offset;
if (containerFrame.origin.x < minX) {
offset = minX - containerFrame.origin.x;
containerFrame.size.width -= offset;
if (containerFrame.size.width < minContainerSize.width) {
// popover is overflowing
offset -= (minContainerSize.width - containerFrame.size.width);
containerFrame.size.width = minContainerSize.width;
}
containerFrame.origin.x += offset;
}
offset = 0;
if (containerFrame.origin.y < minY) {
offset = minY - containerFrame.origin.y;
containerFrame.origin.y = minY;
offset = -offset;
} else if (containerFrame.origin.y + containerFrame.size.height > maxY) {
offset = (_backgroundView.frame.origin.y + _backgroundView.frame.size.height) - maxY;
containerFrame.origin.y -= offset;
}
_backgroundView.arrowOffset = offset;
}
if (arrowDirection == WYPopoverArrowDirectionLeft) {
_backgroundView.arrowDirection = WYPopoverArrowDirectionLeft;
containerViewSize = [_backgroundView sizeThatFits:contentViewSize];
containerFrame = CGRectZero;
containerFrame.size = containerViewSize;
containerFrame.size.width = MIN(maxX - minX, containerFrame.size.width);
containerFrame.size.height = MIN(maxY - minY, containerFrame.size.height);
_backgroundView.frame = containerFrame;
_backgroundView.center = CGPointMake(viewFrame.origin.x + viewFrame.size.width / 2, viewFrame.origin.y + viewFrame.size.height / 2);
containerFrame = CGRectIntegral(_backgroundView.frame);
offset = _backgroundView.frame.size.width / 2 + viewFrame.size.width / 2 - _backgroundView.outerShadowInsets.left;
containerFrame.origin.x += offset;
if (containerFrame.origin.x + containerFrame.size.width > maxX) {
offset = (containerFrame.origin.x + containerFrame.size.width) - maxX;
containerFrame.size.width -= offset;
if (containerFrame.size.width < minContainerSize.width) {
// popover is overflowing
containerFrame.size.width = minContainerSize.width;
}
}
offset = 0;
if (containerFrame.origin.y < minY) {
offset = minY - containerFrame.origin.y;
containerFrame.origin.y = minY;
offset = -offset;
} else if (containerFrame.origin.y + containerFrame.size.height > maxY) {
offset = (_backgroundView.frame.origin.y + _backgroundView.frame.size.height) - maxY;
containerFrame.origin.y -= offset;
}
_backgroundView.arrowOffset = offset;
}
if (arrowDirection == WYPopoverArrowDirectionNone) {
_backgroundView.arrowDirection = WYPopoverArrowDirectionNone;
containerViewSize = [_backgroundView sizeThatFits:contentViewSize];
containerFrame = CGRectZero;
containerFrame.size = containerViewSize;
containerFrame.size.width = MIN(maxX - minX, containerFrame.size.width);
containerFrame.size.height = MIN(maxY - minY, containerFrame.size.height);
_backgroundView.frame = CGRectIntegral(containerFrame);
_backgroundView.center = CGPointMake(minX + (maxX - minX) / 2, minY + (maxY - minY) / 2);
containerFrame = _backgroundView.frame;
_backgroundView.arrowOffset = offset;
}
containerFrame = CGRectIntegral(containerFrame);
_backgroundView.frame = containerFrame;
_backgroundView.wantsDefaultContentAppearance = _wantsDefaultContentAppearance;
[_backgroundView setViewController:_viewController];
// keyboard support
if (keyboardHeight > 0) {
float keyboardY = UIInterfaceOrientationIsPortrait(orientation) ? WYKeyboardListener.rect.origin.y : WYKeyboardListener.rect.origin.x;
float yOffset = containerFrame.origin.y + containerFrame.size.height - keyboardY;
if (yOffset > 0) {
if (containerFrame.origin.y - yOffset < minY) {
yOffset -= minY - (containerFrame.origin.y - yOffset);
}
if ([_delegate respondsToSelector:@selector(popoverController:willTranslatePopoverWithYOffset:)]) {
[_delegate popoverController:self willTranslatePopoverWithYOffset:&yOffset];
}
containerFrame.origin.y -= yOffset;
}
}
CGPoint containerOrigin = containerFrame.origin;
_backgroundView.transform = CGAffineTransformMakeRotation(WYInterfaceOrientationAngleOfOrientation(orientation));
containerFrame = _backgroundView.frame;
containerFrame.origin = WYPointRelativeToOrientation(containerOrigin, containerFrame.size, orientation);
if (aAnimated == YES && !self.implicitAnimationsDisabled) {
_backgroundView.frame = savedContainerFrame;
__weak __typeof__(self) weakSelf = self;
[UIView animateWithDuration:0.10f animations:^{
__typeof__(self) strongSelf = weakSelf;
strongSelf->_backgroundView.frame = containerFrame;
}];
} else {
_backgroundView.frame = containerFrame;
}
[_backgroundView setNeedsDisplay];
// WY_LOG(@"popoverContainerView.frame = %@", NSStringFromCGRect(_backgroundView.frame));
}
- (void)dismissPopoverAnimated:(BOOL)aAnimated {
[self dismissPopoverAnimated:aAnimated
options:options
completion:nil];
}
- (void)dismissPopoverAnimated:(BOOL)aAnimated
completion:(void (^)(void))completion {
[self dismissPopoverAnimated:aAnimated
options:options
completion:completion];
}
- (void)dismissPopoverAnimated:(BOOL)aAnimated
options:(WYPopoverAnimationOptions)aOptions {
[self dismissPopoverAnimated:aAnimated
options:aOptions
completion:nil];
}
- (void)dismissPopoverAnimated:(BOOL)aAnimated
options:(WYPopoverAnimationOptions)aOptions
completion:(void (^)(void))completion {
[self dismissPopoverAnimated:aAnimated
options:aOptions
completion:completion
callDelegate:NO];
}
- (void)dismissPopoverAnimated:(BOOL)aAnimated
options:(WYPopoverAnimationOptions)aOptions
completion:(void (^)(void))completion
callDelegate:(BOOL)callDelegate {
float duration = self.animationDuration;
WYPopoverAnimationOptions style = aOptions;
__weak __typeof__(self) weakSelf = self;
void (^adjustTintAutomatic)() = ^() {
#ifdef WY_BASE_SDK_7_ENABLED
if ([_inView.window respondsToSelector:@selector(setTintAdjustmentMode:)]) {
for (UIView *subview in _inView.window.subviews) {
if (subview != _backgroundView) {
[subview setTintAdjustmentMode:UIViewTintAdjustmentModeAutomatic];
}
}
}
#endif
};
void (^completionBlock)() = ^() {
__typeof__(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf->_backgroundView removeFromSuperview];
strongSelf->_backgroundView = nil;
[strongSelf->_overlayView removeFromSuperview];
strongSelf->_overlayView = nil;
// inView is captured strongly in presentPopoverInRect:... method, so it needs to be released in dismiss method to avoid potential retain cycles
strongSelf->_inView = nil;
}
if (completion) {
completion();
}
else if (callDelegate && strongSelf && strongSelf->_delegate && [strongSelf->_delegate respondsToSelector:@selector(popoverControllerDidDismissPopover:)]) {
[strongSelf->_delegate popoverControllerDidDismissPopover:strongSelf];
}
if (self.dismissCompletionBlock) {
self.dismissCompletionBlock(strongSelf);
}
};
if (_isListeningNotifications == YES) {
_isListeningNotifications = NO;
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidChangeStatusBarOrientationNotification
object:nil];
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIDeviceOrientationDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
@try {
if (_isObserverAdded == YES) {
_isObserverAdded = NO;
if ([_viewController respondsToSelector:@selector(preferredContentSize)]) {
[_viewController removeObserver:self forKeyPath:NSStringFromSelector(@selector(preferredContentSize))];
} else {
[_viewController removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeForViewInPopover))];
}
}
}
@catch (NSException * __unused exception) {}
if (aAnimated && !self.implicitAnimationsDisabled) {
[UIView animateWithDuration:duration animations:^{
__typeof__(self) strongSelf = weakSelf;
if (strongSelf) {
if ((style & WYPopoverAnimationOptionFade) == WYPopoverAnimationOptionFade) {
strongSelf->_backgroundView.alpha = 0;
}
if ((style & WYPopoverAnimationOptionScale) == WYPopoverAnimationOptionScale) {
CGAffineTransform endTransform = [self transformForArrowDirection:strongSelf->_backgroundView.arrowDirection];
strongSelf->_backgroundView.transform = endTransform;
}
strongSelf->_overlayView.alpha = 0;
}
adjustTintAutomatic();
} completion:^(BOOL finished) {
completionBlock();
}];
} else {
adjustTintAutomatic();
completionBlock();
}
}
#pragma mark KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == _viewController) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(preferredContentSize))]
|| [keyPath isEqualToString:NSStringFromSelector(@selector(contentSizeForViewInPopover))]) {
CGSize contentSize = [self topViewControllerContentSize];
[self setPopoverContentSize:contentSize];
}
} else if (object == _theme) {
[self updateThemeUI];
}
}
#pragma mark WYPopoverOverlayViewDelegate
- (void)popoverOverlayViewDidTouch:(WYPopoverOverlayView *)aOverlayView {
BOOL shouldDismiss = !_viewController.modalInPopover;
if (shouldDismiss && _delegate && [_delegate respondsToSelector:@selector(popoverControllerShouldDismissPopover:)]) {
shouldDismiss = [_delegate popoverControllerShouldDismissPopover:self];
}
if (shouldDismiss) {
[self dismissPopoverAnimated:_animated options:options completion:nil callDelegate:YES];
}
}
#pragma mark WYPopoverBackgroundViewDelegate
- (void)popoverBackgroundViewDidTouchOutside:(WYPopoverBackgroundView *)aBackgroundView {
[self popoverOverlayViewDidTouch:nil];
}
#pragma mark Private
- (WYPopoverArrowDirection)arrowDirectionForRect:(CGRect)aRect
inView:(UIView *)aView
contentSize:(CGSize)contentSize
arrowHeight:(float)arrowHeight
permittedArrowDirections:(WYPopoverArrowDirection)arrowDirections {
WYPopoverArrowDirection arrowDirection = WYPopoverArrowDirectionUnknown;
NSMutableArray *areas = [NSMutableArray arrayWithCapacity:0];
WYPopoverArea *area;
if ((arrowDirections & WYPopoverArrowDirectionDown) == WYPopoverArrowDirectionDown) {
area = [[WYPopoverArea alloc] init];
area.areaSize = [self sizeForRect:aRect inView:aView arrowHeight:arrowHeight arrowDirection:WYPopoverArrowDirectionDown];
area.arrowDirection = WYPopoverArrowDirectionDown;
[areas addObject:area];
}
if ((arrowDirections & WYPopoverArrowDirectionUp) == WYPopoverArrowDirectionUp) {
area = [[WYPopoverArea alloc] init];
area.areaSize = [self sizeForRect:aRect inView:aView arrowHeight:arrowHeight arrowDirection:WYPopoverArrowDirectionUp];
area.arrowDirection = WYPopoverArrowDirectionUp;
[areas addObject:area];
}
if ((arrowDirections & WYPopoverArrowDirectionLeft) == WYPopoverArrowDirectionLeft) {
area = [[WYPopoverArea alloc] init];
area.areaSize = [self sizeForRect:aRect inView:aView arrowHeight:arrowHeight arrowDirection:WYPopoverArrowDirectionLeft];
area.arrowDirection = WYPopoverArrowDirectionLeft;
[areas addObject:area];
}
if ((arrowDirections & WYPopoverArrowDirectionRight) == WYPopoverArrowDirectionRight) {
area = [[WYPopoverArea alloc] init];
area.areaSize = [self sizeForRect:aRect inView:aView arrowHeight:arrowHeight arrowDirection:WYPopoverArrowDirectionRight];
area.arrowDirection = WYPopoverArrowDirectionRight;
[areas addObject:area];
}
if ((arrowDirections & WYPopoverArrowDirectionNone) == WYPopoverArrowDirectionNone) {
area = [[WYPopoverArea alloc] init];
area.areaSize = [self sizeForRect:aRect inView:aView arrowHeight:arrowHeight arrowDirection:WYPopoverArrowDirectionNone];
area.arrowDirection = WYPopoverArrowDirectionNone;
[areas addObject:area];
}
if ([areas count] > 1) {
NSIndexSet* indexes = [areas indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
WYPopoverArea* popoverArea = (WYPopoverArea*)obj;
BOOL result = (popoverArea.areaSize.width > 0 && popoverArea.areaSize.height > 0);
return result;
}];
areas = [NSMutableArray arrayWithArray:[areas objectsAtIndexes:indexes]];
}
[areas sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
WYPopoverArea *area1 = (WYPopoverArea *)obj1;
WYPopoverArea *area2 = (WYPopoverArea *)obj2;
float val1 = area1.value;
float val2 = area2.value;
NSComparisonResult result = NSOrderedSame;
if (val1 > val2) {
result = NSOrderedAscending;
} else if (val1 < val2) {
result = NSOrderedDescending;
}
return result;
}];
for (NSUInteger i = 0; i < [areas count]; i++) {
WYPopoverArea *popoverArea = (WYPopoverArea *)[areas objectAtIndex:i];
if (popoverArea.areaSize.width >= contentSize.width) {
arrowDirection = popoverArea.arrowDirection;
break;
}
}
if (arrowDirection == WYPopoverArrowDirectionUnknown) {
if ([areas count] > 0) {
arrowDirection = ((WYPopoverArea *)[areas objectAtIndex:0]).arrowDirection;
} else {
if ((arrowDirections & WYPopoverArrowDirectionDown) == WYPopoverArrowDirectionDown) {
arrowDirection = WYPopoverArrowDirectionDown;
} else if ((arrowDirections & WYPopoverArrowDirectionUp) == WYPopoverArrowDirectionUp) {
arrowDirection = WYPopoverArrowDirectionUp;
} else if ((arrowDirections & WYPopoverArrowDirectionLeft) == WYPopoverArrowDirectionLeft) {
arrowDirection = WYPopoverArrowDirectionLeft;
} else {
arrowDirection = WYPopoverArrowDirectionRight;
}
}
}
return arrowDirection;
}
- (CGSize)sizeForRect:(CGRect)aRect
inView:(UIView *)aView
arrowHeight:(float)arrowHeight
arrowDirection:(WYPopoverArrowDirection)arrowDirection {
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGRect viewFrame = [aView convertRect:aRect toView:nil];
viewFrame = WYRectInWindowBounds(viewFrame, orientation);
float minX, maxX, minY, maxY = 0;
float keyboardHeight = WYKeyboardListener.rect.size.height;
float overlayWidth = _overlayView.bounds.size.width;
float overlayHeight = _overlayView.bounds.size.height;
if (!_ignoreOrientation && UIInterfaceOrientationIsLandscape(orientation)) {
keyboardHeight = WYKeyboardListener.rect.size.width;
overlayWidth = _overlayView.bounds.size.height;
overlayHeight = _overlayView.bounds.size.width;
}
if (_delegate && [_delegate respondsToSelector:@selector(popoverControllerShouldIgnoreKeyboardBounds:)]) {
BOOL shouldIgnore = [_delegate popoverControllerShouldIgnoreKeyboardBounds:self];
if (shouldIgnore) {
keyboardHeight = 0;
}
}
minX = _popoverLayoutMargins.left;
maxX = overlayWidth - _popoverLayoutMargins.right;
minY = WYStatusBarHeight() + _popoverLayoutMargins.top;
maxY = overlayHeight - _popoverLayoutMargins.bottom - keyboardHeight;
CGSize result = CGSizeZero;
if (arrowDirection == WYPopoverArrowDirectionLeft) {
result.width = maxX - (viewFrame.origin.x + viewFrame.size.width);
result.width -= arrowHeight;
result.height = maxY - minY;
} else if (arrowDirection == WYPopoverArrowDirectionRight) {
result.width = viewFrame.origin.x - minX;
result.width -= arrowHeight;
result.height = maxY - minY;
} else if (arrowDirection == WYPopoverArrowDirectionDown) {
result.width = maxX - minX;
result.height = viewFrame.origin.y - minY;
result.height -= arrowHeight;
} else if (arrowDirection == WYPopoverArrowDirectionUp) {
result.width = maxX - minX;
result.height = maxY - (viewFrame.origin.y + viewFrame.size.height);
result.height -= arrowHeight;
} else if (arrowDirection == WYPopoverArrowDirectionNone) {
result.width = maxX - minX;
result.height = maxY - minY;
}
return result;
}
#pragma mark Inline functions
static BOOL compileUsingIOS8SDK() {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
return YES;
#endif
return NO;
}
__unused static NSString* WYStringFromOrientation(NSInteger orientation) {
NSString *result = @"Unknown";
switch (orientation) {
case UIInterfaceOrientationPortrait:
result = @"Portrait";
break;
case UIInterfaceOrientationPortraitUpsideDown:
result = @"Portrait UpsideDown";
break;
case UIInterfaceOrientationLandscapeLeft:
result = @"Landscape Left";
break;
case UIInterfaceOrientationLandscapeRight:
result = @"Landscape Right";
break;
default:
break;
}
return result;
}
static float WYStatusBarHeight() {
if (compileUsingIOS8SDK() && [[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) {
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
return statusBarFrame.size.height;
} else {
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
float statusBarHeight = 0;
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
statusBarHeight = statusBarFrame.size.height;
if (UIInterfaceOrientationIsLandscape(orientation))
{
statusBarHeight = statusBarFrame.size.width;
}
return statusBarHeight;
}
}
static float WYInterfaceOrientationAngleOfOrientation(UIInterfaceOrientation orientation) {
float angle;
// no transformation needed in iOS 8
if (compileUsingIOS8SDK() && [[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) {
angle = 0.0;
} else {
switch (orientation) {
case UIInterfaceOrientationPortraitUpsideDown:
angle = M_PI;
break;
case UIInterfaceOrientationLandscapeLeft:
angle = -M_PI_2;
break;
case UIInterfaceOrientationLandscapeRight:
angle = M_PI_2;
break;
default:
angle = 0.0;
break;
}
}
return angle;
}
static CGRect WYRectInWindowBounds(CGRect rect, UIInterfaceOrientation orientation) {
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
float windowWidth = keyWindow.bounds.size.width;
float windowHeight = keyWindow.bounds.size.height;
CGRect result = rect;
if (!(compileUsingIOS8SDK() && [[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)])) {
if (orientation == UIInterfaceOrientationLandscapeRight) {
result.origin.x = rect.origin.y;
result.origin.y = windowWidth - rect.origin.x - rect.size.width;
result.size.width = rect.size.height;
result.size.height = rect.size.width;
}
if (orientation == UIInterfaceOrientationLandscapeLeft) {
result.origin.x = windowHeight - rect.origin.y - rect.size.height;
result.origin.y = rect.origin.x;
result.size.width = rect.size.height;
result.size.height = rect.size.width;
}
if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
result.origin.x = windowWidth - rect.origin.x - rect.size.width;
result.origin.y = windowHeight - rect.origin.y - rect.size.height;
}
}
return result;
}
static CGPoint WYPointRelativeToOrientation(CGPoint origin, CGSize size, UIInterfaceOrientation orientation) {
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
float windowWidth = keyWindow.bounds.size.width;
float windowHeight = keyWindow.bounds.size.height;
CGPoint result = origin;
if (!(compileUsingIOS8SDK() && [[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)])) {
if (orientation == UIInterfaceOrientationLandscapeRight) {
result.x = windowWidth - origin.y - size.width;
result.y = origin.x;
}
if (orientation == UIInterfaceOrientationLandscapeLeft) {
result.x = origin.y;
result.y = windowHeight - origin.x - size.height;
}
if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
result.x = windowWidth - origin.x - size.width;
result.y = windowHeight - origin.y - size.height;
}
}
return result;
}
#pragma mark Selectors
- (void)didChangeStatusBarOrientation:(NSNotification *)notification {
_isInterfaceOrientationChanging = YES;
}
- (void)didChangeDeviceOrientation:(NSNotification *)notification {
if (_isInterfaceOrientationChanging == NO) return;
_isInterfaceOrientationChanging = NO;
if ([_viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)_viewController;
if (navigationController.navigationBarHidden == NO) {
navigationController.navigationBarHidden = YES;
navigationController.navigationBarHidden = NO;
}
}
if (_barButtonItem) {
_inView = [_barButtonItem valueForKey:@"view"];
_rect = _inView.bounds;
} else if ([_delegate respondsToSelector:@selector(popoverController:willRepositionPopoverToRect:inView:)]) {
CGRect anotherRect;
UIView *anotherInView;
[_delegate popoverController:self willRepositionPopoverToRect:&anotherRect inView:&anotherInView];
#pragma clang diagnostic push
#pragma GCC diagnostic ignored "-Wtautological-pointer-compare"
if (&anotherRect != NULL) {
_rect = anotherRect;
}
if (&anotherInView != NULL) {
_inView = anotherInView;
}
#pragma GCC diagnostic pop
}
[self positionPopover:NO];
}
- (void)keyboardWillShow:(NSNotification *)notification {
//UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
//WY_LOG(@"orientation = %@", WYStringFromOrientation(orientation));
//WY_LOG(@"WYKeyboardListener.rect = %@", NSStringFromCGRect(WYKeyboardListener.rect));
BOOL shouldIgnore = NO;
if (_delegate && [_delegate respondsToSelector:@selector(popoverControllerShouldIgnoreKeyboardBounds:)]) {
shouldIgnore = [_delegate popoverControllerShouldIgnoreKeyboardBounds:self];
}
if (shouldIgnore == NO) {
[self positionPopover:YES];
}
}
- (void)keyboardWillHide:(NSNotification *)notification {
BOOL shouldIgnore = NO;
if (_delegate && [_delegate respondsToSelector:@selector(popoverControllerShouldIgnoreKeyboardBounds:)]) {
shouldIgnore = [_delegate popoverControllerShouldIgnoreKeyboardBounds:self];
}
if (shouldIgnore == NO) {
[self positionPopover:YES];
}
}
#pragma mark Memory management
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_backgroundView removeFromSuperview];
[_backgroundView setDelegate:nil];
[_overlayView removeFromSuperview];
[_overlayView setDelegate:nil];
@try {
if (_isObserverAdded == YES) {
_isObserverAdded = NO;
if ([_viewController respondsToSelector:@selector(preferredContentSize)]) {
[_viewController removeObserver:self forKeyPath:NSStringFromSelector(@selector(preferredContentSize))];
} else {
[_viewController removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSizeForViewInPopover))];
}
}
}
@catch (NSException *exception) {
}
@finally {
_viewController = nil;
}
[self unregisterTheme];
_barButtonItem = nil;
_passthroughViews = nil;
_inView = nil;
_overlayView = nil;
_backgroundView = nil;
_theme = nil;
}
@end