Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 29 additions & 23 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -450,28 +450,34 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
break;
case kBackdropFilter: {
// Only support DlBlurImageFilter for BackdropFilter.
if ((*iter)->GetFilterMutation().GetFilter().asBlur() && canApplyBlurBackdrop) {
// sigma_x is arbitrarily chosen as the radius value because Quartz sets
// sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode
// is not supported in Quartz's gaussianBlur CAFilter, so it is not used
// to blur the PlatformView.
CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x();
CGRect filterRect =
flutter::GetCGRectFromSkRect((*iter)->GetFilterMutation().GetFilterRect());
// `filterRect` reprents the rect that should be filtered inside the `flutter_view_`.
// The `PlatformViewFilter` needs the frame inside the `clipView` that needs to be
// filtered.
CGRect intersection = CGRectIntersection(filterRect, clipView.frame);
if (CGRectContainsRect(clipView.frame, intersection)) {
CGRect frameInClipView = [flutter_view_.get() convertRect:intersection toView:clipView];
UIVisualEffectView* visualEffectView = [[[UIVisualEffectView alloc]
initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]] autorelease];
PlatformViewFilter* filter =
[[[PlatformViewFilter alloc] initWithFrame:frameInClipView
blurRadius:blurRadius
visualEffectView:visualEffectView] autorelease];
[blurFilters addObject:filter];
}
if (!(*iter)->GetFilterMutation().GetFilter().asBlur() || !canApplyBlurBackdrop) {
break;
}
CGRect filterRect =
flutter::GetCGRectFromSkRect((*iter)->GetFilterMutation().GetFilterRect());
// `filterRect` reprents the rect that should be filtered inside the `flutter_view_`.
// The `PlatformViewFilter` needs the frame inside the `clipView` that needs to be
// filtered.
if (CGRectIsNull(CGRectIntersection(filterRect, clipView.frame))) {
break;
}
CGRect intersection = CGRectIntersection(filterRect, clipView.frame);
CGRect frameInClipView = [flutter_view_.get() convertRect:intersection toView:clipView];
// sigma_x is arbitrarily chosen as the radius value because Quartz sets
// sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode
// is not supported in Quartz's gaussianBlur CAFilter, so it is not used
// to blur the PlatformView.
CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x();
UIVisualEffectView* visualEffectView = [[[UIVisualEffectView alloc]
initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]] autorelease];
PlatformViewFilter* filter =
[[[PlatformViewFilter alloc] initWithFrame:frameInClipView
blurRadius:blurRadius
visualEffectView:visualEffectView] autorelease];
if (!filter) {
canApplyBlurBackdrop = NO;
} else {
[blurFilters addObject:filter];
}
break;
}
Expand All @@ -480,7 +486,7 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree {
}

if (canApplyBlurBackdrop) {
canApplyBlurBackdrop = [clipView applyBlurBackdropFilters:blurFilters];
[clipView applyBlurBackdropFilters:blurFilters];
}

// Reverse the offset of the clipView.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1103,15 +1103,15 @@ - (void)testApplyBackdropFilterAPIChanged {
[[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
blurRadius:5
visualEffectView:visualEffectView1];
XCTAssertNotNil([platformViewFilter1 backdropFilterView]);
XCTAssertNotNil(platformViewFilter1);

// Invalid UIVisualEffectView initialization
UIVisualEffectView* visualEffectView2 = [[UIVisualEffectView alloc] init];
PlatformViewFilter* platformViewFilter2 =
[[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
blurRadius:5
visualEffectView:visualEffectView2];
XCTAssertNil([platformViewFilter2 backdropFilterView]);
XCTAssertNil(platformViewFilter2);

// Invalid UIVisualEffectView API for "name"
UIVisualEffectView* editedUIVisualEffectView1 = [[UIVisualEffectView alloc]
Expand All @@ -1133,7 +1133,7 @@ - (void)testApplyBackdropFilterAPIChanged {
[[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
blurRadius:5
visualEffectView:editedUIVisualEffectView1];
XCTAssertNil([platformViewFilter4 backdropFilterView]);
XCTAssertNil(platformViewFilter4);

// Invalid UIVisualEffectView API for "inputRadius"
UIVisualEffectView* editedUIVisualEffectView2 = [[UIVisualEffectView alloc]
Expand All @@ -1155,7 +1155,7 @@ - (void)testApplyBackdropFilterAPIChanged {
[[PlatformViewFilter alloc] initWithFrame:CGRectMake(0, 0, 10, 10)
blurRadius:5
visualEffectView:editedUIVisualEffectView2];
XCTAssertNil([platformViewFilter5 backdropFilterView]);
XCTAssertNil(platformViewFilter5);
}

- (void)testBackdropFilterVisualEffectSubviewBackgroundColor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,30 @@
// The inputRadius can be customized and it doesn't add any color saturation to the blurred view.
@property(nonatomic, retain, readonly) UIVisualEffectView* backdropFilterView;

- (instancetype)init NS_UNAVAILABLE;

// Initialize the filter object.
//
// The `frame` determines the rect of the blur effect in the coordinate system of
// `backdropFilterView`'s parentView. The `blurRadius` determines the blur intensity. It is set as
// the value of `inputRadius` of the `gaussianFilter` that is internally used. The
// `UIVisualEffectView` is the view that is used to add the blur effects. It is modified to become
// `backdropFilterView`, which better supports the need of Flutter.
//
// Note: if the implementation of UIVisualEffectView changes in a way that affects the
// implementation in `PlatformViewFilter`, this method will return nil.
- (instancetype)initWithFrame:(CGRect)frame
blurRadius:(CGFloat)blurRadius
visualEffectView:(UIVisualEffectView*)visualEffectView;
visualEffectView:(UIVisualEffectView*)visualEffectView NS_DESIGNATED_INITIALIZER;

@end

// The parent view handles clipping to its subViews.
@interface ChildClippingView : UIView

// Applies blur backdrop filters to the ChildClippingView with blur radius values from
// blurRadii. Returns NO if Apple's API has changed and blurred backdrop filters cannot
// be applied, otherwise returns YES.
- (BOOL)applyBlurBackdropFilters:(NSMutableArray<PlatformViewFilter*>*)filters;
// Applies blur backdrop filters to the ChildClippingView with blur values from
// filters.
- (void)applyBlurBackdropFilters:(NSMutableArray<PlatformViewFilter*>*)filters;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2) {

} // namespace flutter

static NSObject* GaussianBlurFilter = nil;
// The index of "_UIVisualEffectBackdropView" in UIVisualEffectView's subViews.
static NSUInteger IndexOfBackdropView = -1;
// The index of "_UIVisualEffectSubview" in UIVisualEffectView's subViews.
static NSUInteger IndexOfVisualEffectSubview = -1;
static BOOL IsUIVisualEffectViewImplementationValid = NO;

@implementation PlatformViewFilter

- (instancetype)initWithFrame:(CGRect)frame
Expand All @@ -75,38 +82,51 @@ - (instancetype)initWithFrame:(CGRect)frame
if (self = [super init]) {
_frame = frame;
_blurRadius = blurRadius;
BOOL isUIVisualEffectViewImplementationValid = NO;
for (UIView* view in visualEffectView.subviews) {
if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) {
for (NSObject* filter in view.layer.filters) {
if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"] &&
[[filter valueForKey:@"inputRadius"] isKindOfClass:[NSNumber class]]) {
isUIVisualEffectViewImplementationValid = YES;
[filter setValue:@(_blurRadius) forKey:@"inputRadius"];
view.layer.filters = @[ filter ];

// Stop looping through other filters because the filter array is changed.
break;
}
}
} else if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectSubview")]) {
// Make `_UIVisualEffectSubview` transparanet so it does not add addtional color to the
// blurred PlatformView.
view.layer.backgroundColor = UIColor.clearColor.CGColor;
}
}
if (isUIVisualEffectViewImplementationValid) {
_backdropFilterView = [visualEffectView retain];
_backdropFilterView.frame = _frame;
} else {
[PlatformViewFilter prepareIfNecessary:visualEffectView];
if (IsUIVisualEffectViewImplementationValid == NO) {
FML_DLOG(ERROR) << "Apple's API for UIVisualEffectView changed. Update the implementation to "
"access the gaussianBlur CAFilter.";
_backdropFilterView = nil;
[self release];
return nil;
}
NSObject* gaussianBlurFilter = [[GaussianBlurFilter copy] autorelease];
FML_DCHECK(gaussianBlurFilter);
UIView* backdropView = visualEffectView.subviews[IndexOfBackdropView];
[gaussianBlurFilter setValue:@(_blurRadius) forKey:@"inputRadius"];
backdropView.layer.filters = @[ gaussianBlurFilter ];

UIView* visualEffectSubview = visualEffectView.subviews[IndexOfVisualEffectSubview];
visualEffectSubview.layer.backgroundColor = UIColor.clearColor.CGColor;

_backdropFilterView = [visualEffectView retain];
_backdropFilterView.frame = _frame;
}
return self;
}

+ (void)prepareIfNecessary:(UIVisualEffectView*)visualEffectView {
if (GaussianBlurFilter) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is GaussianBlurFilter for? It's seems it's not referenced anywhere else?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you were somehow looking at an old version of the code.

return;
}
NSUInteger index = 0;
for (UIView* view in visualEffectView.subviews) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: if you need the index, use for(int i; i < length; i++) instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought fast enumeration (for x in y) is faster? Probably doesn't really matter in this case because the number of subviews won't be too big. I will take the suggestion to avoid future confusion since the performance difference will be very insignificant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enumeration is considerably more efficient than, for example, using NSEnumerator directly.

It's faster than NSEnumerator. I think with NSArray you have random access so it probably won't be slower than for(x in y).

if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectBackdropView")]) {
IndexOfBackdropView = index;
for (NSObject* filter in view.layer.filters) {
if ([[filter valueForKey:@"name"] isEqual:@"gaussianBlur"] &&
[[filter valueForKey:@"inputRadius"] isKindOfClass:[NSNumber class]]) {
GaussianBlurFilter = filter;
IsUIVisualEffectViewImplementationValid = YES;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you still need this? You can check if the index < 0 I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you were somehow looking at an old version of the code.

break;
}
}
} else if ([view isKindOfClass:NSClassFromString(@"_UIVisualEffectSubview")]) {
IndexOfVisualEffectSubview = index;
}
++index;
}
}

- (void)dealloc {
[_backdropFilterView release];
_backdropFilterView = nil;
Expand Down Expand Up @@ -136,7 +156,7 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
return NO;
}

- (BOOL)applyBlurBackdropFilters:(NSMutableArray<PlatformViewFilter*>*)filters {
- (void)applyBlurBackdropFilters:(NSMutableArray<PlatformViewFilter*>*)filters {
BOOL needUpdateFilterViews = NO;
if (self.filters.count != filters.count) {
needUpdateFilterViews = YES;
Expand All @@ -159,13 +179,9 @@ - (BOOL)applyBlurBackdropFilters:(NSMutableArray<PlatformViewFilter*>*)filters {
// Add new filter views.
for (PlatformViewFilter* filter in self.filters) {
UIView* backdropFilterView = [filter backdropFilterView];
if (!backdropFilterView) {
return NO;
}
[self addSubview:backdropFilterView];
}
}
return YES;
}

- (void)dealloc {
Expand Down