Flutter iOS Embedder
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
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 
6 
7 #include "flutter/fml/platform/darwin/cf_utils.h"
9 
11 
12 @interface FlutterView ()
13 @property(nonatomic, weak) id<FlutterViewEngineDelegate> delegate;
14 @end
15 
16 @implementation FlutterView {
17  BOOL _isWideGamutEnabled;
18 }
19 
20 - (instancetype)init {
21  NSAssert(NO, @"FlutterView must initWithDelegate");
22  return nil;
23 }
24 
25 - (instancetype)initWithFrame:(CGRect)frame {
26  NSAssert(NO, @"FlutterView must initWithDelegate");
27  return nil;
28 }
29 
30 - (instancetype)initWithCoder:(NSCoder*)aDecoder {
31  NSAssert(NO, @"FlutterView must initWithDelegate");
32  return nil;
33 }
34 
35 - (UIScreen*)screen {
36  if (@available(iOS 13.0, *)) {
37  return self.window.windowScene.screen;
38  }
39  return UIScreen.mainScreen;
40 }
41 
42 - (MTLPixelFormat)pixelFormat {
43  if ([self.layer isKindOfClass:[CAMetalLayer class]]) {
44 // It is a known Apple bug that CAMetalLayer incorrectly reports its supported
45 // SDKs. It is, in fact, available since iOS 8.
46 #pragma clang diagnostic push
47 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
48  CAMetalLayer* layer = (CAMetalLayer*)self.layer;
49  return layer.pixelFormat;
50  }
51  return MTLPixelFormatBGRA8Unorm;
52 }
53 - (BOOL)isWideGamutSupported {
54  if (!self.delegate.isUsingImpeller) {
55  return NO;
56  }
57 
58  FML_DCHECK(self.screen);
59 
60  // This predicates the decision on the capabilities of the iOS device's
61  // display. This means external displays will not support wide gamut if the
62  // device's display doesn't support it. It practice that should be never.
63  return self.screen.traitCollection.displayGamut != UIDisplayGamutSRGB;
64 }
65 
66 - (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate
67  opaque:(BOOL)opaque
68  enableWideGamut:(BOOL)isWideGamutEnabled {
69  if (delegate == nil) {
70  NSLog(@"FlutterView delegate was nil.");
71  return nil;
72  }
73 
74  self = [super initWithFrame:CGRectNull];
75 
76  if (self) {
77  _delegate = delegate;
78  _isWideGamutEnabled = isWideGamutEnabled;
79  self.layer.opaque = opaque;
80  }
81 
82  return self;
83 }
84 
85 static void PrintWideGamutWarningOnce() {
86  static BOOL did_print = NO;
87  if (did_print) {
88  return;
89  }
90  FML_DLOG(WARNING) << "Rendering wide gamut colors is turned on but isn't "
91  "supported, downgrading the color gamut to sRGB.";
92  did_print = YES;
93 }
94 
95 - (void)layoutSubviews {
96  if ([self.layer isKindOfClass:[CAMetalLayer class]]) {
97 // It is a known Apple bug that CAMetalLayer incorrectly reports its supported
98 // SDKs. It is, in fact, available since iOS 8.
99 #pragma clang diagnostic push
100 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
101  CAMetalLayer* layer = (CAMetalLayer*)self.layer;
102 #pragma clang diagnostic pop
103  CGFloat screenScale = self.screen.scale;
104  layer.allowsGroupOpacity = YES;
105  layer.contentsScale = screenScale;
106  layer.rasterizationScale = screenScale;
107  layer.framebufferOnly = flutter::Settings::kSurfaceDataAccessible ? NO : YES;
108  BOOL isWideGamutSupported = self.isWideGamutSupported;
109  if (_isWideGamutEnabled && isWideGamutSupported) {
110  fml::CFRef<CGColorSpaceRef> srgb(CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB));
111  layer.colorspace = srgb;
112  layer.pixelFormat = MTLPixelFormatBGRA10_XR;
113  } else if (_isWideGamutEnabled && !isWideGamutSupported) {
114  PrintWideGamutWarningOnce();
115  }
116  }
117 
118  [super layoutSubviews];
119 }
120 
122 
123 + (BOOL)forceSoftwareRendering {
125 }
126 
127 + (void)setForceSoftwareRendering:(BOOL)forceSoftwareRendering {
128  _forceSoftwareRendering = forceSoftwareRendering;
129 }
130 
131 + (Class)layerClass {
134 }
135 
136 - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
137  TRACE_EVENT0("flutter", "SnapshotFlutterView");
138 
139  if (layer != self.layer || context == nullptr) {
140  return;
141  }
142 
143  auto screenshot = [_delegate takeScreenshot:flutter::Rasterizer::ScreenshotType::UncompressedImage
144  asBase64Encoded:NO];
145 
146  if (!screenshot.data || screenshot.data->isEmpty() || screenshot.frame_size.isEmpty()) {
147  return;
148  }
149 
150  NSData* data = [NSData dataWithBytes:const_cast<void*>(screenshot.data->data())
151  length:screenshot.data->size()];
152 
153  fml::CFRef<CGDataProviderRef> image_data_provider(
154  CGDataProviderCreateWithCFData(reinterpret_cast<CFDataRef>(data)));
155 
156  fml::CFRef<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB());
157 
158  // Defaults for RGBA8888.
159  size_t bits_per_component = 8u;
160  size_t bits_per_pixel = 32u;
161  size_t bytes_per_row_multiplier = 4u;
162  CGBitmapInfo bitmap_info =
163  static_cast<CGBitmapInfo>(static_cast<uint32_t>(kCGImageAlphaPremultipliedLast) |
164  static_cast<uint32_t>(kCGBitmapByteOrder32Big));
165 
166  switch (screenshot.pixel_format) {
167  case flutter::Rasterizer::ScreenshotFormat::kUnknown:
168  case flutter::Rasterizer::ScreenshotFormat::kR8G8B8A8UNormInt:
169  // Assume unknown is Skia and is RGBA8888. Keep defaults.
170  break;
171  case flutter::Rasterizer::ScreenshotFormat::kB8G8R8A8UNormInt:
172  // Treat this as little endian with the alpha first so that it's read backwards.
173  bitmap_info =
174  static_cast<CGBitmapInfo>(static_cast<uint32_t>(kCGImageAlphaPremultipliedFirst) |
175  static_cast<uint32_t>(kCGBitmapByteOrder32Little));
176  break;
177  case flutter::Rasterizer::ScreenshotFormat::kR16G16B16A16Float:
178  bits_per_component = 16u;
179  bits_per_pixel = 64u;
180  bytes_per_row_multiplier = 8u;
181  bitmap_info =
182  static_cast<CGBitmapInfo>(static_cast<uint32_t>(kCGImageAlphaPremultipliedLast) |
183  static_cast<uint32_t>(kCGBitmapFloatComponents) |
184  static_cast<uint32_t>(kCGBitmapByteOrder16Little));
185  break;
186  }
187 
188  fml::CFRef<CGImageRef> image(CGImageCreate(
189  screenshot.frame_size.width(), // size_t width
190  screenshot.frame_size.height(), // size_t height
191  bits_per_component, // size_t bitsPerComponent
192  bits_per_pixel, // size_t bitsPerPixel,
193  bytes_per_row_multiplier * screenshot.frame_size.width(), // size_t bytesPerRow
194  colorspace, // CGColorSpaceRef space
195  bitmap_info, // CGBitmapInfo bitmapInfo
196  image_data_provider, // CGDataProviderRef provider
197  nullptr, // const CGFloat* decode
198  false, // bool shouldInterpolate
199  kCGRenderingIntentDefault // CGColorRenderingIntent intent
200  ));
201 
202  const CGRect frame_rect =
203  CGRectMake(0.0, 0.0, screenshot.frame_size.width(), screenshot.frame_size.height());
204  CGContextSaveGState(context);
205  // If the CGContext is not a bitmap based context, this returns zero.
206  CGFloat height = CGBitmapContextGetHeight(context);
207  if (height == 0) {
208  height = CGFloat(screenshot.frame_size.height());
209  }
210  CGContextTranslateCTM(context, 0.0, height);
211  CGContextScaleCTM(context, 1.0, -1.0);
212  CGContextDrawImage(context, frame_rect, image);
213  CGContextRestoreGState(context);
214 }
215 
216 - (BOOL)isAccessibilityElement {
217  // iOS does not provide an API to query whether the voice control
218  // is turned on or off. It is likely at least one of the assitive
219  // technologies is turned on if this method is called. If we do
220  // not catch it in notification center, we will catch it here.
221  //
222  // TODO(chunhtai): Remove this workaround once iOS provides an
223  // API to query whether voice control is enabled.
224  // https://github.com/flutter/flutter/issues/76808.
225  [self.delegate flutterViewAccessibilityDidCall];
226  return NO;
227 }
228 
229 // Enables keyboard-based navigation when the user turns on
230 // full keyboard access (FKA), using existing accessibility information.
231 //
232 // iOS does not provide any API for monitoring or querying whether FKA is on,
233 // but it does call isAccessibilityElement if FKA is on,
234 // so the isAccessibilityElement implementation above will be called
235 // when the view appears and the accessibility information will most likely
236 // be available by the time the user starts to interact with the app using FKA.
237 //
238 // See SemanticsObject+UIFocusSystem.mm for more details.
239 - (NSArray<id<UIFocusItem>>*)focusItemsInRect:(CGRect)rect {
240  NSObject* rootAccessibilityElement =
241  [self.accessibilityElements count] > 0 ? self.accessibilityElements[0] : nil;
242  return [rootAccessibilityElement isKindOfClass:[SemanticsObjectContainer class]]
243  ? @[ [rootAccessibilityElement accessibilityElementAtIndex:0] ]
244  : nil;
245 }
246 
247 - (NSArray<id<UIFocusEnvironment>>*)preferredFocusEnvironments {
248  // Occasionally we add subviews to FlutterView (text fields for example).
249  // These views shouldn't be directly visible to the iOS focus engine, instead
250  // the focus engine should only interact with the designated focus items
251  // (SemanticsObjects).
252  return nil;
253 }
254 
255 @end
FlutterView::forceSoftwareRendering
BOOL forceSoftwareRendering
Definition: FlutterView.h:48
flutter::GetCoreAnimationLayerClassForRenderingAPI
Class GetCoreAnimationLayerClassForRenderingAPI(IOSRenderingAPI rendering_api)
Definition: rendering_api_selection.mm:59
FlutterViewEngineDelegate-p
Definition: FlutterView.h:14
initWithFrame
instancetype initWithFrame
Definition: FlutterTextInputPlugin.h:172
flutter::GetRenderingAPIForProcess
IOSRenderingAPI GetRenderingAPIForProcess(bool force_software)
Definition: rendering_api_selection.mm:31
initWithCoder
instancetype initWithCoder
Definition: FlutterTextInputPlugin.h:171
SemanticsObject.h
FlutterView
Definition: FlutterView.h:33
SemanticsObjectContainer
Definition: SemanticsObject.h:227
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterView.h
_forceSoftwareRendering
static BOOL _forceSoftwareRendering
Definition: FlutterView.mm:121