| Flutter iOS Embedder |

FlutterView.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
6
7 #include "flutter/fml/platform/darwin/cf_utils.h"
8 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h"
9 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h"
11
12 FLUTTER_ASSERT_ARC
13
14 @interface FlutterView ()
15 @property(nonatomic, weak) id<FlutterViewEngineDelegate> delegate;
16 @property(nonatomic, weak) UIWindowScene* previousScene;
17 @end
18
19 @implementation FlutterAutoResizeLayoutConstraint
20 @end
21
22 @implementation FlutterView {
23 BOOL _isWideGamutEnabled;
24 CGSize _intrinsicSize;
25 }
26
27 - (instancetype)init {
28 NSAssert(NO, @"FlutterView must initWithDelegate");
29 return nil;
30 }
31
32 - (instancetype)initWithFrame:(CGRect)frame {
33 NSAssert(NO, @"FlutterView must initWithDelegate");
34 return nil;
35 }
36
37 - (instancetype)initWithCoder:(NSCoder*)aDecoder {
38 NSAssert(NO, @"FlutterView must initWithDelegate");
39 return nil;
40 }
41
42 - (UIScreen*)screen {
43 return self.window.windowScene.screen;
44 }
45
46 // iOS has a concept of "intrinsicContentSize", which indicates the size a view would like to be
47 // based on its content. When an intrinsicContentSize is set, iOS will automatically add Auto Layout
48 // constraints for the width and/or height. However, the constraints use a private API. There are
49 // situations where we may want to filter these constraints. To avoid using a private API, Flutter
50 // creates a custom constraint called FlutterAutoResizeLayoutConstraint to add a width/height
51 // constraint that reflects the intrinsicContentSize.
52 - (void)setIntrinsicContentSize:(CGSize)size {
53 if (!self.autoResizable) {
54 return;
55 }
56
57 UIWindow* window = self.window;
58 CGFloat scale = window ? self.window.windowScene.screen.scale : self.traitCollection.displayScale;
59 CGSize scaledSize = CGSizeMake(size.width / scale, size.height / scale);
60
61 CGSize roundedScaleSize = CGSizeMake(roundf(scaledSize.width), roundf(scaledSize.height));
62 CGSize roundedIntrinsicSize =
63 CGSizeMake(roundf(_intrinsicSize.width), roundf(_intrinsicSize.height));
64
65 // If the size has not changed, don't update constraints.
66 if (CGSizeEqualToSize(roundedIntrinsicSize, roundedScaleSize)) {
67 return;
68 }
69 _intrinsicSize = scaledSize;
70
71 self.translatesAutoresizingMaskIntoConstraints = false;
72
73 // Remove any existing FlutterAutoResizeLayoutConstraint
74 [self removeAutoResizeLayoutConstraints];
75
76 FlutterAutoResizeLayoutConstraint* widthConstraint =
77 [FlutterAutoResizeLayoutConstraint constraintWithItem:self
78 attribute:NSLayoutAttributeWidth
79 relatedBy:NSLayoutRelationEqual
80 toItem:nil
81 attribute:NSLayoutAttributeNotAnAttribute
82 multiplier:1.0
83 constant:scaledSize.width];
84
85 FlutterAutoResizeLayoutConstraint* heightConstraint =
86 [FlutterAutoResizeLayoutConstraint constraintWithItem:self
87 attribute:NSLayoutAttributeHeight
88 relatedBy:NSLayoutRelationEqual
89 toItem:nil
90 attribute:NSLayoutAttributeNotAnAttribute
91 multiplier:1.0
92 constant:scaledSize.height];
93
94 [NSLayoutConstraint activateConstraints:@[ widthConstraint, heightConstraint ]];
95 [self setNeedsLayout];
96 }
97
98 - (void)resetIntrinsicContentSize {
99 _intrinsicSize = CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
100 [self removeAutoResizeLayoutConstraints];
101 }
102
103 - (void)removeAutoResizeLayoutConstraints {
104 for (NSLayoutConstraint* constraint in self.constraints) {
105 if ([constraint isKindOfClass:[FlutterAutoResizeLayoutConstraint class]]) {
106 constraint.active = NO;
107 }
108 }
109 }
110
111 - (MTLPixelFormat)pixelFormat {
112 if ([self.layer isKindOfClass:[CAMetalLayer class]]) {
113 // It is a known Apple bug that CAMetalLayer incorrectly reports its supported
114 // SDKs. It is, in fact, available since iOS 8.
115 #pragma clang diagnostic push
116 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
117 CAMetalLayer* layer = (CAMetalLayer*)self.layer;
118 return layer.pixelFormat;
119 }
120 return MTLPixelFormatBGRA8Unorm;
121 }
122 - (BOOL)isWideGamutSupported {
123 FML_DCHECK(self.screen);
124
125 // Wide Gamut is not supported for iOS Extensions due to memory limitations
126 // (see https://github.com/flutter/flutter/issues/165086).
127 if (FlutterSharedApplication.isAppExtension) {
128 return NO;
129 }
130
131 // This predicates the decision on the capabilities of the iOS device's
132 // display. This means external displays will not support wide gamut if the
133 // device's display doesn't support it. It practice that should be never.
134 return self.screen.traitCollection.displayGamut != UIDisplayGamutSRGB;
135 }
136
137 - (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate
138 opaque:(BOOL)opaque
139 enableWideGamut:(BOOL)isWideGamutEnabled {
140 if (delegate == nil) {
141 NSLog(@"FlutterView delegate was nil.");
142 return nil;
143 }
144
145 self = [super initWithFrame:CGRectNull];
146
147 if (self) {
148 _delegate = delegate;
149 _isWideGamutEnabled = isWideGamutEnabled;
150 self.layer.opaque = opaque;
151 _autoResizable = NO;
152 _intrinsicSize = CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
153 }
154
155 return self;
156 }
157
158 static void PrintWideGamutWarningOnce() {
159 static BOOL did_print = NO;
160 if (did_print) {
161 return;
162 }
163 FML_DLOG(WARNING) << "Rendering wide gamut colors is turned on but isn't "
164 "supported, downgrading the color gamut to sRGB.";
165 did_print = YES;
166 }
167
168 - (void)layoutSubviews {
169 if ([self.layer isKindOfClass:[CAMetalLayer class]]) {
170 // It is a known Apple bug that CAMetalLayer incorrectly reports its supported
171 // SDKs. It is, in fact, available since iOS 8.
172 #pragma clang diagnostic push
173 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
174 CAMetalLayer* layer = (CAMetalLayer*)self.layer;
175 #pragma clang diagnostic pop
176 CGFloat screenScale = self.screen.scale;
177 layer.allowsGroupOpacity = YES;
178 layer.contentsScale = screenScale;
179 layer.rasterizationScale = screenScale;
180 layer.framebufferOnly = flutter::Settings::kSurfaceDataAccessible ? NO : YES;
181 if (_isWideGamutEnabled && self.isWideGamutSupported) {
182 fml::CFRef<CGColorSpaceRef> srgb(CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB));
183 layer.colorspace = srgb;
184 layer.pixelFormat = MTLPixelFormatBGRA10_XR;
185 } else if (_isWideGamutEnabled && !self.isWideGamutSupported) {
186 PrintWideGamutWarningOnce();
187 }
188 }
189
190 [super layoutSubviews];
191 }
192
193 + (Class)layerClass {
194 return flutter::GetCoreAnimationLayerClassForRenderingAPI(
195 flutter::GetRenderingAPIForProcess(/*force_software=*/false));
196 }
197
198 - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
199 TRACE_EVENT0("flutter", "SnapshotFlutterView");
200
201 if (layer != self.layer || context == nullptr) {
202 return;
203 }
204
205 auto screenshot = [_delegate takeScreenshot:flutter::Rasterizer::ScreenshotType::UncompressedImage
206 asBase64Encoded:NO];
207
208 if (!screenshot.data || screenshot.data->isEmpty() || screenshot.frame_size.IsEmpty()) {
209 return;
210 }
211
212 NSData* data = [NSData dataWithBytes:const_cast<void*>(screenshot.data->data())
213 length:screenshot.data->size()];
214
215 fml::CFRef<CGDataProviderRef> image_data_provider(
216 CGDataProviderCreateWithCFData(reinterpret_cast<CFDataRef>(data)));
217
218 fml::CFRef<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB());
219
220 // Defaults for RGBA8888.
221 size_t bits_per_component = 8u;
222 size_t bits_per_pixel = 32u;
223 size_t bytes_per_row_multiplier = 4u;
224 CGBitmapInfo bitmap_info =
225 static_cast<CGBitmapInfo>(static_cast<uint32_t>(kCGImageAlphaPremultipliedLast) |
226 static_cast<uint32_t>(kCGBitmapByteOrder32Big));
227
228 switch (screenshot.pixel_format) {
229 case flutter::Rasterizer::ScreenshotFormat::kUnknown:
230 case flutter::Rasterizer::ScreenshotFormat::kR8G8B8A8UNormInt:
231 // Assume unknown is Skia and is RGBA8888. Keep defaults.
232 break;
233 case flutter::Rasterizer::ScreenshotFormat::kB8G8R8A8UNormInt:
234 // Treat this as little endian with the alpha first so that it's read backwards.
235 bitmap_info =
236 static_cast<CGBitmapInfo>(static_cast<uint32_t>(kCGImageAlphaPremultipliedFirst) |
237 static_cast<uint32_t>(kCGBitmapByteOrder32Little));
238 break;
239 case flutter::Rasterizer::ScreenshotFormat::kR16G16B16A16Float:
240 bits_per_component = 16u;
241 bits_per_pixel = 64u;
242 bytes_per_row_multiplier = 8u;
243 bitmap_info =
244 static_cast<CGBitmapInfo>(static_cast<uint32_t>(kCGImageAlphaPremultipliedLast) |
245 static_cast<uint32_t>(kCGBitmapFloatComponents) |
246 static_cast<uint32_t>(kCGBitmapByteOrder16Little));
247 break;
248 }
249
250 fml::CFRef<CGImageRef> image(CGImageCreate(
251 screenshot.frame_size.width, // size_t width
252 screenshot.frame_size.height, // size_t height
253 bits_per_component, // size_t bitsPerComponent
254 bits_per_pixel, // size_t bitsPerPixel,
255 bytes_per_row_multiplier * screenshot.frame_size.width, // size_t bytesPerRow
256 colorspace, // CGColorSpaceRef space
257 bitmap_info, // CGBitmapInfo bitmapInfo
258 image_data_provider, // CGDataProviderRef provider
259 nullptr, // const CGFloat* decode
260 false, // bool shouldInterpolate
261 kCGRenderingIntentDefault // CGColorRenderingIntent intent
262 ));
263
264 const CGRect frame_rect =
265 CGRectMake(0.0, 0.0, screenshot.frame_size.width, screenshot.frame_size.height);
266 CGContextSaveGState(context);
267 // If the CGContext is not a bitmap based context, this returns zero.
268 CGFloat height = CGBitmapContextGetHeight(context);
269 if (height == 0) {
270 height = CGFloat(screenshot.frame_size.height);
271 }
272 CGContextTranslateCTM(context, 0.0, height);
273 CGContextScaleCTM(context, 1.0, -1.0);
274 CGContextDrawImage(context, frame_rect, image);
275 CGContextRestoreGState(context);
276 }
277
278 - (BOOL)isAccessibilityElement {
279 // iOS does not provide an API to query whether the voice control
280 // is turned on or off. It is likely at least one of the assitive
281 // technologies is turned on if this method is called. If we do
282 // not catch it in notification center, we will catch it here.
283 //
284 // TODO(chunhtai): Remove this workaround once iOS provides an
285 // API to query whether voice control is enabled.
286 // https://github.com/flutter/flutter/issues/76808.
287 [self.delegate flutterViewAccessibilityDidCall];
288 return NO;
289 }
290
291 // Enables keyboard-based navigation when the user turns on
292 // full keyboard access (FKA), using existing accessibility information.
293 //
294 // iOS does not provide any API for monitoring or querying whether FKA is on,
295 // but it does call isAccessibilityElement if FKA is on,
296 // so the isAccessibilityElement implementation above will be called
297 // when the view appears and the accessibility information will most likely
298 // be available by the time the user starts to interact with the app using FKA.
299 //
300 // See SemanticsObject+UIFocusSystem.mm for more details.
301 - (NSArray<id<UIFocusItem>>*)focusItemsInRect:(CGRect)rect {
302 NSObject* rootAccessibilityElement =
303 [self.accessibilityElements count] > 0 ? self.accessibilityElements[0] : nil;
304 return [rootAccessibilityElement isKindOfClass:[SemanticsObjectContainer class]]
305 ? @[ [rootAccessibilityElement accessibilityElementAtIndex:0] ]
306 : nil;
307 }
308
309 - (NSArray<id<UIFocusEnvironment>>*)preferredFocusEnvironments {
310 // Occasionally we add subviews to FlutterView (text fields for example).
311 // These views shouldn't be directly visible to the iOS focus engine, instead
312 // the focus engine should only interact with the designated focus items
313 // (SemanticsObjects).
314 return nil;
315 }
316
317 - (void)willMoveToWindow:(UIWindow*)newWindow {
318 // When a FlutterView moves windows, it may also be moving scenes. Add/remove the FlutterEngine
319 // from the FlutterSceneLifeCycleProvider.sceneLifeCycleDelegate if it changes scenes.
320 UIWindowScene* newScene = newWindow.windowScene;
321 UIWindowScene* currentScene = self.window.windowScene;
322
323 if (newScene == currentScene) {
324 return;
325 }
326
327 // Remove the engine from the previous scene if it's no longer in that window and scene.
328 FlutterPluginSceneLifeCycleDelegate* previousSceneLifeCycleDelegate =
329 [FlutterPluginSceneLifeCycleDelegate fromScene:self.previousScene];
330 if (previousSceneLifeCycleDelegate) {
331 [previousSceneLifeCycleDelegate removeFlutterManagedEngine:(FlutterEngine*)self.delegate];
332 self.previousScene = nil;
333 }
334
335 if (newScene) {
336 // Add the engine to the new scene's lifecycle delegate.
337 FlutterPluginSceneLifeCycleDelegate* newSceneLifeCycleDelegate =
338 [FlutterPluginSceneLifeCycleDelegate fromScene:newScene];
339 if (newSceneLifeCycleDelegate) {
340 [newSceneLifeCycleDelegate addFlutterManagedEngine:(FlutterEngine*)self.delegate];
341 }
342 } else {
343 // If the view is being removed from a window, store the current scene to remove the engine
344 // from it later when the view is added to a new window.
345 self.previousScene = currentScene;
346 }
347 }
348 @end
FlutterSceneLifeCycle_Internal.h
FlutterSharedApplication.h
initWithFrame
instancetype initWithFrame
Definition: FlutterTextInputPlugin.h:179
initWithCoder
instancetype initWithCoder
Definition: FlutterTextInputPlugin.h:178
FlutterView.h
_intrinsicSize
CGSize _intrinsicSize
Definition: FlutterView.mm:22
SemanticsObject.h
FlutterAutoResizeLayoutConstraint
Definition: FlutterView.h:47
FlutterPluginSceneLifeCycleDelegate
Definition: FlutterSceneLifeCycle.h:149
FlutterSharedApplication
Definition: FlutterSharedApplication.h:13
FlutterSharedApplication::isAppExtension
BOOL isAppExtension
Definition: FlutterSharedApplication.h:17
FlutterView
Definition: FlutterView.h:50
SemanticsObjectContainer
Definition: SemanticsObject.h:228
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
flutter::GetRenderingAPIForProcess
IOSRenderingAPI GetRenderingAPIForProcess(bool force_software)
Definition: rendering_api_selection.mm:31
flutter::GetCoreAnimationLayerClassForRenderingAPI
Class GetCoreAnimationLayerClassForRenderingAPI(IOSRenderingAPI rendering_api)
Definition: rendering_api_selection.mm:59
FlutterViewEngineDelegate-p
Definition: FlutterView.h:14