I want to have an UIActionSheet
with custom UIAlertAction
but seem like custom UIAlertAction
is not legal.
The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
So I'm trying to create a view which look exactly like UIActionSheet
. By using Debug View Hierarchy
, I can know how Apple applies hierarchy, constraint, color for UIActionSheet
to do exactly which they did.
I can create everything except UIActionSheet
's background. It's a view which contains a non-transparent view and an UIVisualEffectView
.
The UIVisualEffectView
overlap non-transparent view but somehow UIVisualEffectView
still works.
How can UIVisualEffectView
still work when there is a non-transparent below it? If it's possible, how can I make something like this?
Note: Background of UIActionSheet
is not only an UIVisualEffectView
. Please don't give an answer like this.
The view below the UIVisualEffectView
(the one with the red arrow in the question) contains a layer which has a CAFilter "overlayBlendMode" (private class) set as compositing filter. It's applied over the layer behind it. In the screenshot below I changed the background colour of the _UIDimmingKnockoutBackdropView to green and the filter is applied over that.
Private method
To create the same effect, you need to put a white opaque view below the UIVisualEffectView
and apply a CAFilter to it:
CAFilter *filter = [CAFilter filterWithName:@"overlayBlendMode"];
[[contentView layer] setCompositingFilter:filter];
Since CAFilter is private, we need a header for it:
#import <Foundation/Foundation.h>
@interface CAFilter : NSObject <NSCoding, NSCopying, NSMutableCopying> {
void * _attr;
void * _cache;
unsigned int _flags;
NSString * _name;
unsigned int _type;
}
@property BOOL cachesInputImage;
@property (getter=isEnabled) BOOL enabled;
@property (copy) NSString *name;
@property (readonly) NSString *type;
// Image: /System/Library/Frameworks/QuartzCore.framework/QuartzCore
+ (void)CAMLParserStartElement:(id)arg1;
+ (BOOL)automaticallyNotifiesObserversForKey:(id)arg1;
+ (id)filterTypes;
+ (id)filterWithName:(id)arg1;
+ (id)filterWithType:(id)arg1;
- (void)CAMLParser:(id)arg1 setValue:(id)arg2 forKey:(id)arg3;
- (id)CAMLTypeForKey:(id)arg1;
- (struct Object { int (**x1)(); struct Atomic { struct { int x_1_2_1; } x_2_1_1; } x2; }*)CA_copyRenderValue;
- (BOOL)cachesInputImage;
- (id)copyWithZone:(struct _NSZone { }*)arg1;
- (void)dealloc;
- (BOOL)enabled;
- (void)encodeWithCAMLWriter:(id)arg1;
- (void)encodeWithCoder:(id)arg1;
- (id)initWithCoder:(id)arg1;
- (id)initWithName:(id)arg1;
- (id)initWithType:(id)arg1;
- (BOOL)isEnabled;
- (id)mutableCopyWithZone:(struct _NSZone { }*)arg1;
- (id)name;
- (void)setCachesInputImage:(BOOL)arg1;
- (void)setDefaults;
- (void)setEnabled:(BOOL)arg1;
- (void)setName:(id)arg1;
- (void)setValue:(id)arg1 forKey:(id)arg2;
- (id)type;
- (id)valueForKey:(id)arg1;
// Image: /System/Library/PrivateFrameworks/PhotosUICore.framework/PhotosUICore
+ (id)px_filterWithPXCompositingFilterType:(int)arg1;
@end
The result:
The view I've created prints the same compositing filter as Apple used:
Workaround
As a workaround you can create an image of the view on the background and a image of the white view and apply the overlay blend mode with that. This results in a comparable result as using the private class.
Example:
UIImage *image = [self imageForView:contentView];
UIView *superview = [contentView superview];
[contentView setHidden:YES];
UIImage *backgroundImage = [self imageForView:superview];
CGImageRef imageRef = CGImageCreateWithImageInRect([backgroundImage CGImage], [contentView frame]);
backgroundImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
[contentView setHidden:NO];
CIFilter *filter = [CIFilter filterWithName:@"CIOverlayBlendMode"];
[filter setValue:[backgroundImage CIImage] forKey:kCIInputImageKey];
[filter setValue:[image CIImage] forKey:kCIInputBackgroundImageKey];
[contentView setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageWithCIImage:[filter outputImage]]]];
}
- (UIImage *)imageForView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions([view bounds].size, [view isOpaque], 1.0);
[[view layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Result:
Please check my storyboard views set up and result. I used actual apple's action sheet with same image to control the result.
Storyboard set up
Result
Original apple's action sheet
And one of approaches to implement custom action sheet You can check apple's docs for dynamic stack view Dynamically Changing the Stack View’s Content
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With