Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a view like background view of UIActionSheet in iOS?

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.

enter image description here

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.

enter image description here

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.

like image 824
trungduc Avatar asked Mar 25 '18 14:03

trungduc


2 Answers

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.

enter image description here

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: enter image description here

The view I've created prints the same compositing filter as Apple used: enter image description here

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: enter image description here

like image 58
Maarten Foukhar Avatar answered Oct 26 '22 14:10

Maarten Foukhar


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 enter image description here

Result enter image description here

Original apple's action sheet enter image description here

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

like image 28
Roma Avatar answered Oct 26 '22 13:10

Roma