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 all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ FLUTTER_EXPORT
*/
@property(strong, nonatomic) UIView* splashScreenView;

/**
* Controls whether the created view will be opaque or not.
*
* Default is `YES`. Note that setting this to `NO` may negatively impact performance
* when using hardware acceleration, and toggling this will trigger a re-layout of the
* view.
*/
@property(nonatomic, getter=isViewOpaque) BOOL viewOpaque;

@end

#endif // FLUTTER_FLUTTERVIEWCONTROLLER_H_
15 changes: 15 additions & 0 deletions shell/platform/darwin/ios/framework/Source/FlutterView.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,25 @@

#include <memory>

#include "flutter/fml/memory/weak_ptr.h"
#include "flutter/shell/common/shell.h"
#include "flutter/shell/platform/darwin/ios/ios_surface.h"

@protocol FlutterScreenshotDelegate <NSObject>
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 plan to use this elsewhere? If not, it's clear to me what advantage this provides over passing the FlutterViewController down directly like you did in the previous version.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, but it feels pretty wrong to pass a view controller directly into a view. I was looking at passing in just the shell, and after discussion with @chinmaygarde decided to go with a delegate - this way we avoid trying to share a pointer to the shell, and we avoid tying the ViewController implementation details to the View.


- (shell::Rasterizer::Screenshot)takeScreenshot:(shell::Rasterizer::ScreenshotType)type
asBase64Encoded:(BOOL)base64Encode;

@end

@interface FlutterView : UIView

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;

- (instancetype)initWithDelegate:(id<FlutterScreenshotDelegate>)delegate
opaque:(BOOL)opaque NS_DESIGNATED_INITIALIZER;
- (std::unique_ptr<shell::IOSSurface>)createSurface;

@end
Expand Down
58 changes: 31 additions & 27 deletions shell/platform/darwin/ios/framework/Source/FlutterView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
#include "flutter/fml/trace_event.h"
#include "flutter/shell/common/platform_view.h"
#include "flutter/shell/common/rasterizer.h"
#include "flutter/shell/common/shell.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
#include "flutter/shell/platform/darwin/ios/ios_surface_gl.h"
#include "flutter/shell/platform/darwin/ios/ios_surface_software.h"
#include "third_party/skia/include/utils/mac/SkCGUtils.h"
Expand All @@ -24,28 +22,42 @@ @interface FlutterView () <UIInputViewAudioFeedback>

@implementation FlutterView

- (FlutterViewController*)flutterViewController {
// Find the first view controller in the responder chain and see if it is a FlutterViewController.
for (UIResponder* responder = self.nextResponder; responder != nil;
responder = responder.nextResponder) {
if ([responder isKindOfClass:[UIViewController class]]) {
if ([responder isKindOfClass:[FlutterViewController class]]) {
return reinterpret_cast<FlutterViewController*>(responder);
} else {
// Should only happen if a non-FlutterViewController tries to somehow (via dynamic class
// resolution or reparenting) set a FlutterView as its view.
return nil;
}
}
id<FlutterScreenshotDelegate> _delegate;

- (instancetype)init {
@throw([NSException exceptionWithName:@"FlutterView must initWithDelegate"
reason:nil
userInfo:nil]);
}

- (instancetype)initWithFrame:(CGRect)frame {
@throw([NSException exceptionWithName:@"FlutterView must initWithDelegate"
reason:nil
userInfo:nil]);
}

- (instancetype)initWithCoder:(NSCoder*)aDecoder {
@throw([NSException exceptionWithName:@"FlutterView must initWithDelegate"
reason:nil
userInfo:nil]);
}

- (instancetype)initWithDelegate:(id<FlutterScreenshotDelegate>)delegate opaque:(BOOL)opaque {
FML_DCHECK(delegate) << "Delegate must not be nil.";
self = [super initWithFrame:CGRectNull];

if (self) {
_delegate = delegate;
self.layer.opaque = opaque;
}
return nil;

return self;
}

- (void)layoutSubviews {
if ([self.layer isKindOfClass:[CAEAGLLayer class]]) {
CAEAGLLayer* layer = reinterpret_cast<CAEAGLLayer*>(self.layer);
layer.allowsGroupOpacity = YES;
layer.opaque = YES;
CGFloat screenScale = [UIScreen mainScreen].scale;
layer.contentsScale = screenScale;
layer.rasterizationScale = screenScale;
Expand Down Expand Up @@ -84,16 +96,8 @@ - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
return;
}

FlutterViewController* controller = [self flutterViewController];

if (controller == nil) {
return;
}

auto& shell = [controller shell];

auto screenshot = shell.Screenshot(shell::Rasterizer::ScreenshotType::UncompressedImage,
false /* base64 encode */);
auto screenshot = [_delegate takeScreenshot:shell::Rasterizer::ScreenshotType::UncompressedImage
asBase64Encoded:NO];

if (!screenshot.data || screenshot.data->isEmpty() || screenshot.frame_size.isEmpty()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#include "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h"
#include "flutter/shell/platform/darwin/ios/platform_view_ios.h"

@interface FlutterViewController () <FlutterTextInputDelegate>
@interface FlutterViewController () <FlutterTextInputDelegate, FlutterScreenshotDelegate>
@property(nonatomic, readonly) NSMutableDictionary* pluginPublications;
@end

Expand Down Expand Up @@ -58,6 +58,7 @@ @implementation FlutterViewController {
blink::ViewportMetrics _viewportMetrics;
int64_t _nextTextureId;
BOOL _initialized;
BOOL _viewOpaque;

fml::scoped_nsobject<FlutterObservatoryPublisher> _publisher;
}
Expand All @@ -75,6 +76,8 @@ - (instancetype)initWithProject:(FlutterDartProject*)projectOrNil
else
_dartProject.reset([projectOrNil retain]);

self.viewOpaque = YES;

[self performCommonViewControllerInitialization];
}

Expand Down Expand Up @@ -146,7 +149,7 @@ - (BOOL)setupShell {
_threadHost.io_thread->GetTaskRunner() // io
);

_flutterView.reset([[FlutterView alloc] init]);
_flutterView.reset([[FlutterView alloc] initWithDelegate:self opaque:self.isViewOpaque]);

// Lambda captures by pointers to ObjC objects are fine here because the create call is
// synchronous.
Expand Down Expand Up @@ -180,6 +183,18 @@ - (BOOL)setupShell {
return true;
}

- (BOOL)isViewOpaque {
return _viewOpaque;
}

- (void)viewOpaque:(BOOL)value {
_viewOpaque = value;
if (_flutterView.get().layer.opaque != value) {
_flutterView.get().layer.opaque = value;
[_flutterView.get().layer setNeedsLayout];
}
}

- (void)setupChannels {
_localizationChannel.reset([[FlutterMethodChannel alloc]
initWithName:@"flutter/localization"
Expand Down Expand Up @@ -840,6 +855,14 @@ - (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
arguments:@[ @(client), actionString ]];
}

#pragma mark - Screenshot Delegate

- (shell::Rasterizer::Screenshot)takeScreenshot:(shell::Rasterizer::ScreenshotType)type
asBase64Encoded:(BOOL)base64Encode {
FML_DCHECK(_shell) << "Cannot takeScreenshot without a shell";
return _shell->Screenshot(type, base64Encode);
}

#pragma mark - Orientation updates

- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
Expand Down