FlutterViewController.mm 34.7 KB
Newer Older
1
// Copyright 2016 The Chromium Authors. All rights reserved.
C
Chinmay Garde 已提交
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7
#define FML_USED_ON_EMBEDDER

#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
C
Chinmay Garde 已提交
8

A
Adam Barth 已提交
9 10
#include <memory>

11
#include "flutter/fml/message_loop.h"
12
#include "flutter/fml/platform/darwin/platform_version.h"
13
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
14
#include "flutter/shell/common/thread_host.h"
15
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
16
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
17 18
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
19
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
20
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_touch_mapper.h"
21
#include "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h"
22
#include "flutter/shell/platform/darwin/ios/platform_view_ios.h"
23

24
@interface FlutterViewController () <FlutterTextInputDelegate>
25 26
@end

27
@implementation FlutterViewController {
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
  fml::scoped_nsobject<FlutterDartProject> _dartProject;
  shell::ThreadHost _threadHost;
  std::unique_ptr<shell::Shell> _shell;

  // Channels
  fml::scoped_nsobject<FlutterPlatformPlugin> _platformPlugin;
  fml::scoped_nsobject<FlutterTextInputPlugin> _textInputPlugin;
  fml::scoped_nsobject<FlutterMethodChannel> _localizationChannel;
  fml::scoped_nsobject<FlutterMethodChannel> _navigationChannel;
  fml::scoped_nsobject<FlutterMethodChannel> _platformChannel;
  fml::scoped_nsobject<FlutterMethodChannel> _textInputChannel;
  fml::scoped_nsobject<FlutterBasicMessageChannel> _lifecycleChannel;
  fml::scoped_nsobject<FlutterBasicMessageChannel> _systemChannel;
  fml::scoped_nsobject<FlutterBasicMessageChannel> _settingsChannel;

  // We keep a separate reference to this and create it ahead of time because we want to be able to
  // setup a shell along with its platform view before the view has to appear.
  fml::scoped_nsobject<FlutterView> _flutterView;
  fml::scoped_nsobject<UIView> _launchView;
47
  UIInterfaceOrientationMask _orientationPreferences;
48
  UIStatusBarStyle _statusBarStyle;
49
  blink::ViewportMetrics _viewportMetrics;
50
  shell::TouchMapper _touchMapper;
51
  int64_t _nextTextureId;
52
  BOOL _initialized;
53 54
}

55 56
#pragma mark - Manage and override all designated initializers

57 58 59 60
- (instancetype)initWithProject:(FlutterDartProject*)project
                        nibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
61

62
  if (self) {
63
    if (project == nil)
64
      _dartProject.reset([[FlutterDartProject alloc] initFromDefaultSourceForConfiguration]);
65 66
    else
      _dartProject.reset([project retain]);
67 68

    [self performCommonViewControllerInitialization];
69
  }
70

71
  return self;
C
Chinmay Garde 已提交
72 73
}

74
- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
75
  return [self initWithProject:nil nibName:nil bundle:nil];
C
Chinmay Garde 已提交
76 77
}

78
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
79
  return [self initWithProject:nil nibName:nil bundle:nil];
80 81
}

82
#pragma mark - Common view controller initialization tasks
C
Chinmay Garde 已提交
83

84
- (void)performCommonViewControllerInitialization {
85
  if (_initialized)
86
    return;
87

88
  _initialized = YES;
C
Chinmay Garde 已提交
89

90
  _orientationPreferences = UIInterfaceOrientationMaskAll;
91
  _statusBarStyle = UIStatusBarStyleDefault;
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171

  if ([self setupShell]) {
    [self setupChannels];
    [self setupNotificationCenterObservers];
  }
}

- (shell::Shell&)shell {
  FXL_DCHECK(_shell);
  return *_shell;
}

- (fml::WeakPtr<shell::PlatformViewIOS>)iosPlatformView {
  FXL_DCHECK(_shell);
  return _shell->GetPlatformView();
}

- (BOOL)setupShell {
  FXL_DCHECK(_shell == nullptr);

  static size_t shell_count = 1;

  auto threadLabel = [NSString stringWithFormat:@"io.flutter.%zu", shell_count++];

  _threadHost = {
      threadLabel.UTF8String,  // label
      shell::ThreadHost::Type::UI | shell::ThreadHost::Type::GPU | shell::ThreadHost::Type::IO};

  // The current thread will be used as the platform thread. Ensure that the message loop is
  // initialized.
  fml::MessageLoop::EnsureInitializedForCurrentThread();

  blink::TaskRunners task_runners(threadLabel.UTF8String,                          // label
                                  fml::MessageLoop::GetCurrent().GetTaskRunner(),  // platform
                                  _threadHost.gpu_thread->GetTaskRunner(),         // gpu
                                  _threadHost.ui_thread->GetTaskRunner(),          // ui
                                  _threadHost.io_thread->GetTaskRunner()           // io
  );

  _flutterView.reset([[FlutterView alloc] init]);

  // Lambda captures by pointers to ObjC objects are fine here because the create call is
  // synchronous.
  shell::Shell::CreateCallback<shell::PlatformView> on_create_platform_view =
      [flutter_view_controller = self, flutter_view = _flutterView.get()](shell::Shell& shell) {
        auto platform_view_ios = std::make_unique<shell::PlatformViewIOS>(
            shell,                    // delegate
            shell.GetTaskRunners(),   // task runners
            flutter_view_controller,  // flutter view controller owner
            flutter_view              // flutter view owner
        );
        return platform_view_ios;
      };

  shell::Shell::CreateCallback<shell::Rasterizer> on_create_rasterizer = [](shell::Shell& shell) {
    return std::make_unique<shell::Rasterizer>(shell.GetTaskRunners());
  };

  // Create the shell.
  _shell = shell::Shell::Create(std::move(task_runners),  //
                                [_dartProject settings],  //
                                on_create_platform_view,  //
                                on_create_rasterizer      //
  );

  if (!_shell) {
    FXL_LOG(ERROR) << "Could not setup a shell to run the Dart application.";
    return false;
  }

  // Launch the Dart application with the inferred run configuration.
  _shell->GetTaskRunners().GetUITaskRunner()->PostTask(
      fxl::MakeCopyable([engine = _shell->GetEngine(),                   //
                         config = [_dartProject.get() runConfiguration]  //
  ]() mutable {
        if (engine) {
          auto result = engine->Run(std::move(config));
          if (!result) {
            FXL_LOG(ERROR) << "Could not launch engine with configuration.";
          }
172
        }
173 174 175
      }));
  return true;
}
C
Chinmay Garde 已提交
176

177
- (void)setupChannels {
178
  _localizationChannel.reset([[FlutterMethodChannel alloc]
179
         initWithName:@"flutter/localization"
180
      binaryMessenger:self
181
                codec:[FlutterJSONMethodCodec sharedInstance]]);
182 183

  _navigationChannel.reset([[FlutterMethodChannel alloc]
184
         initWithName:@"flutter/navigation"
185
      binaryMessenger:self
186
                codec:[FlutterJSONMethodCodec sharedInstance]]);
187 188

  _platformChannel.reset([[FlutterMethodChannel alloc]
189
         initWithName:@"flutter/platform"
190
      binaryMessenger:self
191
                codec:[FlutterJSONMethodCodec sharedInstance]]);
192 193

  _textInputChannel.reset([[FlutterMethodChannel alloc]
194
         initWithName:@"flutter/textinput"
195
      binaryMessenger:self
196
                codec:[FlutterJSONMethodCodec sharedInstance]]);
197

198
  _lifecycleChannel.reset([[FlutterBasicMessageChannel alloc]
199
         initWithName:@"flutter/lifecycle"
200
      binaryMessenger:self
201
                codec:[FlutterStringCodec sharedInstance]]);
202

203
  _systemChannel.reset([[FlutterBasicMessageChannel alloc]
204
         initWithName:@"flutter/system"
205
      binaryMessenger:self
206
                codec:[FlutterJSONMessageCodec sharedInstance]]);
207

208 209 210 211 212
  _settingsChannel.reset([[FlutterBasicMessageChannel alloc]
         initWithName:@"flutter/settings"
      binaryMessenger:self
                codec:[FlutterJSONMessageCodec sharedInstance]]);

213
  _platformPlugin.reset([[FlutterPlatformPlugin alloc] init]);
214 215 216
  [_platformChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    [_platformPlugin.get() handleMethodCall:call result:result];
  }];
217

218 219
  _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
  _textInputPlugin.get().textInputDelegate = self;
220 221 222
  [_textInputChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    [_textInputPlugin.get() handleMethodCall:call result:result];
  }];
223 224
  static_cast<shell::PlatformViewIOS*>(_shell->GetPlatformView().get())
      ->SetTextInputPlugin(_textInputPlugin);
C
Chinmay Garde 已提交
225 226
}

227 228 229 230
- (void)setupNotificationCenterObservers {
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center addObserver:self
             selector:@selector(onOrientationPreferencesUpdated:)
A
Adam Barth 已提交
231
                 name:@(shell::kOrientationUpdateNotificationName)
232 233
               object:nil];

234 235
  [center addObserver:self
             selector:@selector(onPreferredStatusBarStyleUpdated:)
A
Adam Barth 已提交
236
                 name:@(shell::kOverlayStyleUpdateNotificationName)
237 238
               object:nil];

239 240 241 242 243 244 245 246 247 248
  [center addObserver:self
             selector:@selector(applicationBecameActive:)
                 name:UIApplicationDidBecomeActiveNotification
               object:nil];

  [center addObserver:self
             selector:@selector(applicationWillResignActive:)
                 name:UIApplicationWillResignActiveNotification
               object:nil];

249 250 251 252 253 254 255 256 257 258
  [center addObserver:self
             selector:@selector(applicationDidEnterBackground:)
                 name:UIApplicationDidEnterBackgroundNotification
               object:nil];

  [center addObserver:self
             selector:@selector(applicationWillEnterForeground:)
                 name:UIApplicationWillEnterForegroundNotification
               object:nil];

259
  [center addObserver:self
260 261
             selector:@selector(keyboardWillChangeFrame:)
                 name:UIKeyboardWillChangeFrameNotification
262 263 264 265 266 267 268 269 270 271 272
               object:nil];

  [center addObserver:self
             selector:@selector(keyboardWillBeHidden:)
                 name:UIKeyboardWillHideNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onLocaleUpdated:)
                 name:NSCurrentLocaleDidChangeNotification
               object:nil];
273 274 275 276 277

  [center addObserver:self
             selector:@selector(onVoiceOverChanged:)
                 name:UIAccessibilityVoiceOverStatusChanged
               object:nil];
278 279 280 281 282

  [center addObserver:self
             selector:@selector(onMemoryWarning:)
                 name:UIApplicationDidReceiveMemoryWarningNotification
               object:nil];
283 284 285 286 287

  [center addObserver:self
             selector:@selector(onUserSettingsChanged:)
                 name:UIContentSizeCategoryDidChangeNotification
               object:nil];
C
Chinmay Garde 已提交
288 289
}

290
- (void)setInitialRoute:(NSString*)route {
291
  [_navigationChannel.get() invokeMethod:@"setInitialRoute" arguments:route];
292
}
293 294 295 296

#pragma mark - Loading the view

- (void)loadView {
297
  self.view = _flutterView.get();
298
  self.view.multipleTouchEnabled = YES;
299
  self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
300

301 302 303 304
  [self installLaunchViewIfNecessary];
}

#pragma mark - Managing launch views
305

306
- (void)installLaunchViewIfNecessary {
307 308
  // Show the launch screen view again on top of the FlutterView if available.
  // This launch screen view will be removed once the first Flutter frame is rendered.
309 310
  [_launchView.get() removeFromSuperview];
  _launchView.reset();
311 312 313 314
  NSString* launchStoryboardName =
      [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
  if (launchStoryboardName && !self.isBeingPresented && !self.isMovingToParentViewController) {
    UIViewController* launchViewController =
315 316
        [[UIStoryboard storyboardWithName:launchStoryboardName bundle:nil]
            instantiateInitialViewController];
317 318 319 320 321 322
    _launchView.reset([launchViewController.view retain]);
    _launchView.get().frame = self.view.bounds;
    _launchView.get().autoresizingMask =
        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:_launchView.get()];
  }
323 324
}

325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
- (void)removeLaunchViewIfPresent {
  if (!_launchView) {
    return;
  }

  [UIView animateWithDuration:0.2
      animations:^{
        _launchView.get().alpha = 0;
      }
      completion:^(BOOL finished) {
        [_launchView.get() removeFromSuperview];
        _launchView.reset();
      }];
}

- (void)installLaunchViewCallback {
  if (!_shell || !_launchView) {
    return;
  }
  auto weak_platform_view = _shell->GetPlatformView();
  if (!weak_platform_view) {
    return;
  }
  __unsafe_unretained auto weak_flutter_view_controller = self;
  // This is on the platform thread.
  weak_platform_view->SetNextFrameCallback(
      [weak_platform_view, weak_flutter_view_controller,
       task_runner = _shell->GetTaskRunners().GetPlatformTaskRunner()]() {
        // This is on the GPU thread.
        task_runner->PostTask([weak_platform_view, weak_flutter_view_controller]() {
          // We check if the weak platform view is alive. If it is alive, then the view controller
          // also has to be alive since the view controller owns the platform view via the shell
          // association. Thus, we are not convinced that the unsafe unretained weak object is in
          // fact alive.
          if (weak_platform_view) {
            [weak_flutter_view_controller removeLaunchViewIfPresent];
          }
        });
      });
}

366 367 368
#pragma mark - Surface creation and teardown updates

- (void)surfaceUpdated:(BOOL)appeared {
369
  // NotifyCreated/NotifyDestroyed are synchronous and require hops between the UI and GPU thread.
370
  if (appeared) {
371 372 373
    [self installLaunchViewCallback];
    _shell->GetPlatformView()->NotifyCreated();

374
  } else {
375
    _shell->GetPlatformView()->NotifyDestroyed();
376 377 378
  }
}

379 380
#pragma mark - UIViewController lifecycle notifications

381
- (void)viewWillAppear:(BOOL)animated {
382 383 384 385 386 387 388
  TRACE_EVENT0("flutter", "viewWillAppear");
  // Only recreate surface on subsequent appearances when viewport metrics are known.
  // First time surface creation is done on viewDidLayoutSubviews.
  if (_viewportMetrics.physical_width)
    [self surfaceUpdated:YES];
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];

389 390 391
  [super viewWillAppear:animated];
}

392 393 394
- (void)viewDidAppear:(BOOL)animated {
  TRACE_EVENT0("flutter", "viewDidAppear");
  [self onLocaleUpdated:nil];
395
  [self onUserSettingsChanged:nil];
396 397 398 399 400 401 402 403 404 405 406 407 408
  [self onVoiceOverChanged:nil];
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.resumed"];

  [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
  TRACE_EVENT0("flutter", "viewWillDisappear");
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];

  [super viewWillDisappear:animated];
}

409
- (void)viewDidDisappear:(BOOL)animated {
410 411
  TRACE_EVENT0("flutter", "viewDidDisappear");
  [self surfaceUpdated:NO];
412 413
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];

414
  [super viewDidDisappear:animated];
415 416
}

417 418 419 420 421
- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  [super dealloc];
}

422 423 424
#pragma mark - Application lifecycle notifications

- (void)applicationBecameActive:(NSNotification*)notification {
425
  TRACE_EVENT0("flutter", "applicationBecameActive");
426
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.resumed"];
427 428 429
}

- (void)applicationWillResignActive:(NSNotification*)notification {
430
  TRACE_EVENT0("flutter", "applicationWillResignActive");
431 432 433 434
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];
}

- (void)applicationDidEnterBackground:(NSNotification*)notification {
435
  TRACE_EVENT0("flutter", "applicationDidEnterBackground");
436
  [self surfaceUpdated:NO];
437
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];
438 439
}

440
- (void)applicationWillEnterForeground:(NSNotification*)notification {
441
  TRACE_EVENT0("flutter", "applicationWillEnterForeground");
442 443
  if (_viewportMetrics.physical_width)
    [self surfaceUpdated:YES];
444 445 446
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];
}

447 448 449 450 451 452 453 454
#pragma mark - Touch event handling

enum MapperPhase {
  Accessed,
  Added,
  Removed,
};

455 456
using PointerChangeMapperPhase = std::pair<blink::PointerData::Change, MapperPhase>;
static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(UITouchPhase phase) {
457 458
  switch (phase) {
    case UITouchPhaseBegan:
459
      return PointerChangeMapperPhase(blink::PointerData::Change::kDown, MapperPhase::Added);
460 461 462 463
    case UITouchPhaseMoved:
    case UITouchPhaseStationary:
      // There is no EVENT_TYPE_POINTER_STATIONARY. So we just pass a move type
      // with the same coordinates
464
      return PointerChangeMapperPhase(blink::PointerData::Change::kMove, MapperPhase::Accessed);
465
    case UITouchPhaseEnded:
466
      return PointerChangeMapperPhase(blink::PointerData::Change::kUp, MapperPhase::Removed);
467
    case UITouchPhaseCancelled:
468
      return PointerChangeMapperPhase(blink::PointerData::Change::kCancel, MapperPhase::Removed);
469 470
  }

471
  return PointerChangeMapperPhase(blink::PointerData::Change::kCancel, MapperPhase::Accessed);
472
}
C
Chinmay Garde 已提交
473

474 475 476 477 478 479 480 481 482 483
static inline blink::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) {
  if (@available(iOS 9, *)) {
    switch (touch.type) {
      case UITouchTypeDirect:
      case UITouchTypeIndirect:
        return blink::PointerData::DeviceKind::kTouch;
      case UITouchTypeStylus:
        return blink::PointerData::DeviceKind::kStylus;
    }
  } else {
484 485 486 487 488 489
    return blink::PointerData::DeviceKind::kTouch;
  }

  return blink::PointerData::DeviceKind::kTouch;
}

C
Chinmay Garde 已提交
490
- (void)dispatchTouches:(NSSet*)touches phase:(UITouchPhase)phase {
491 492 493 494 495
  // Note: we cannot rely on touch.phase, since in some cases, e.g.,
  // handleStatusBarTouches, we synthesize touches from existing events.
  //
  // TODO(cbracken) consider creating out own class with the touch fields we
  // need.
A
Adam Barth 已提交
496
  auto eventTypePhase = PointerChangePhaseFromUITouchPhase(phase);
C
Chinmay Garde 已提交
497
  const CGFloat scale = [UIScreen mainScreen].scale;
A
Adam Barth 已提交
498
  auto packet = std::make_unique<blink::PointerDataPacket>(touches.count);
C
Chinmay Garde 已提交
499

A
Adam Barth 已提交
500
  int i = 0;
C
Chinmay Garde 已提交
501
  for (UITouch* touch in touches) {
502
    int device_id = 0;
503 504 505

    switch (eventTypePhase.second) {
      case Accessed:
506
        device_id = _touchMapper.identifierOf(touch);
507 508
        break;
      case Added:
509
        device_id = _touchMapper.registerTouch(touch);
510 511
        break;
      case Removed:
512
        device_id = _touchMapper.unregisterTouch(touch);
513 514
        break;
    }
515

516
    FXL_DCHECK(device_id != 0);
517
    CGPoint windowCoordinates = [touch locationInView:self.view];
518

A
Adam Barth 已提交
519 520 521
    blink::PointerData pointer_data;
    pointer_data.Clear();

522 523
    constexpr int kMicrosecondsPerSecond = 1000 * 1000;
    pointer_data.time_stamp = touch.timestamp * kMicrosecondsPerSecond;
524

A
Adam Barth 已提交
525
    pointer_data.change = eventTypePhase.first;
526

527
    pointer_data.kind = DeviceKindFromTouchType(touch);
528

529
    pointer_data.device = device_id;
530

A
Adam Barth 已提交
531 532
    pointer_data.physical_x = windowCoordinates.x * scale;
    pointer_data.physical_y = windowCoordinates.y * scale;
533 534

    // pressure_min is always 0.0
535
    if (@available(iOS 9, *)) {
536 537 538 539 540 541 542 543 544 545 546 547 548 549
      // These properties were introduced in iOS 9.0.
      pointer_data.pressure = touch.force;
      pointer_data.pressure_max = touch.maximumPossibleForce;
    } else {
      pointer_data.pressure = 1.0;
      pointer_data.pressure_max = 1.0;
    }

    // These properties were introduced in iOS 8.0
    pointer_data.radius_major = touch.majorRadius;
    pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
    pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;

    // These properties were introduced in iOS 9.1
550
    if (@available(iOS 9.1, *)) {
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
      // iOS Documentation: altitudeAngle
      // A value of 0 radians indicates that the stylus is parallel to the surface. The value of
      // this property is Pi/2 when the stylus is perpendicular to the surface.
      //
      // PointerData Documentation: tilt
      // The angle of the stylus, in radians in the range:
      //    0 <= tilt <= pi/2
      // giving the angle of the axis of the stylus, relative to the axis perpendicular to the input
      // surface (thus 0.0 indicates the stylus is orthogonal to the plane of the input surface,
      // while pi/2 indicates that the stylus is flat on that surface).
      //
      // Discussion:
      // The ranges are the same. Origins are swapped.
      pointer_data.tilt = M_PI_2 - touch.altitudeAngle;

      // iOS Documentation: azimuthAngleInView:
      // With the tip of the stylus touching the screen, the value of this property is 0 radians
      // when the cap end of the stylus (that is, the end opposite of the tip) points along the
      // positive x axis of the device's screen. The azimuth angle increases as the user swings the
      // cap end of the stylus in a clockwise direction around the tip.
      //
      // PointerData Documentation: orientation
      // The angle of the stylus, in radians in the range:
      //    -pi < orientation <= pi
      // giving the angle of the axis of the stylus projected onto the input surface, relative to
      // the positive y-axis of that surface (thus 0.0 indicates the stylus, if projected onto that
      // surface, would go from the contact point vertically up in the positive y-axis direction, pi
      // would indicate that the stylus would go down in the negative y-axis direction; pi/4 would
      // indicate that the stylus goes up and to the right, -pi/2 would indicate that the stylus
      // goes to the left, etc).
      //
      // Discussion:
      // Sweep direction is the same. Phase of M_PI_2.
      pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
    }
A
Adam Barth 已提交
586 587

    packet->SetPointerData(i++, pointer_data);
C
Chinmay Garde 已提交
588
  }
589

590 591 592
  _shell->GetTaskRunners().GetUITaskRunner()->PostTask(
      fxl::MakeCopyable([engine = _shell->GetEngine(), packet = std::move(packet)] {
        if (engine) {
593
          engine->DispatchPointerDataPacket(*packet);
594
        }
595
      }));
C
Chinmay Garde 已提交
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches phase:UITouchPhaseBegan];
}

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches phase:UITouchPhaseMoved];
}

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches phase:UITouchPhaseEnded];
}

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches phase:UITouchPhaseCancelled];
}

614
#pragma mark - Handle view resizing
615

616
- (void)updateViewportMetrics {
617 618 619 620
  _shell->GetTaskRunners().GetUITaskRunner()->PostTask(
      [engine = _shell->GetEngine(), metrics = _viewportMetrics]() {
        if (engine) {
          engine->SetViewportMetrics(std::move(metrics));
621 622
        }
      });
623 624
}

625
- (CGFloat)statusBarPadding {
626
  UIScreen* screen = self.view.window.screen;
627
  CGRect statusFrame = [UIApplication sharedApplication].statusBarFrame;
628 629
  CGRect viewFrame =
      [self.view convertRect:self.view.bounds toCoordinateSpace:screen.coordinateSpace];
630 631
  CGRect intersection = CGRectIntersection(statusFrame, viewFrame);
  return CGRectIsNull(intersection) ? 0.0 : intersection.size.height;
632 633
}

634
- (void)viewDidLayoutSubviews {
635
  CGSize viewSize = self.view.bounds.size;
636 637
  CGFloat scale = [UIScreen mainScreen].scale;

638 639
  // First time since creation that the dimensions of its view is known.
  bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
640
  _viewportMetrics.device_pixel_ratio = scale;
641 642
  _viewportMetrics.physical_width = viewSize.width * scale;
  _viewportMetrics.physical_height = viewSize.height * scale;
643

644 645 646 647 648 649 650 651 652 653
  [self updateViewportPadding];
  [self updateViewportMetrics];

  // This must run after updateViewportMetrics so that the surface creation tasks are queued after
  // the viewport metrics update tasks.
  if (firstViewBoundsUpdate)
    [self surfaceUpdated:YES];
}

- (void)viewSafeAreaInsetsDidChange {
654 655 656
  [self updateViewportPadding];
  [self updateViewportMetrics];
  [super viewSafeAreaInsetsDidChange];
657 658 659 660 661 662 663
}

// Updates _viewportMetrics physical padding.
//
// Viewport padding represents the iOS safe area insets.
- (void)updateViewportPadding {
  CGFloat scale = [UIScreen mainScreen].scale;
664
  if (@available(iOS 11, *)) {
665 666 667
    _viewportMetrics.physical_padding_top = self.view.safeAreaInsets.top * scale;
    _viewportMetrics.physical_padding_left = self.view.safeAreaInsets.left * scale;
    _viewportMetrics.physical_padding_right = self.view.safeAreaInsets.right * scale;
668
    _viewportMetrics.physical_padding_bottom = self.view.safeAreaInsets.bottom * scale;
669 670 671
  } else {
    _viewportMetrics.physical_padding_top = [self statusBarPadding] * scale;
  }
672 673
}

674
#pragma mark - Keyboard events
675

676
- (void)keyboardWillChangeFrame:(NSNotification*)notification {
677
  NSDictionary* info = [notification userInfo];
678
  CGFloat bottom = CGRectGetHeight([[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]);
679
  CGFloat scale = [UIScreen mainScreen].scale;
680
  _viewportMetrics.physical_view_inset_bottom = bottom * scale;
681
  [self updateViewportMetrics];
682 683
}

684
- (void)keyboardWillBeHidden:(NSNotification*)notification {
685
  _viewportMetrics.physical_view_inset_bottom = 0;
686
  [self updateViewportMetrics];
687 688
}

689 690 691
#pragma mark - Text input delegate

- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
692 693
  [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState"
                              arguments:@[ @(client), state ]];
694 695
}

696 697 698 699 700 701
- (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
  NSString* actionString;
  switch (action) {
    case FlutterTextInputActionDone:
      actionString = @"TextInputAction.done";
      break;
702 703 704
    case FlutterTextInputActionNewline:
      actionString = @"TextInputAction.newline";
      break;
705 706 707 708 709
  }
  [_textInputChannel.get() invokeMethod:@"TextInputClient.performAction"
                              arguments:@[ @(client), actionString ]];
}

710 711 712 713 714 715 716
#pragma mark - Orientation updates

- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
  // Notifications may not be on the iOS UI thread
  dispatch_async(dispatch_get_main_queue(), ^{
    NSDictionary* info = notification.userInfo;

717
    NSNumber* update = info[@(shell::kOrientationUpdateNotificationKey)];
718 719 720 721 722 723 724 725 726 727 728 729

    if (update == nil) {
      return;
    }

    NSUInteger new_preferences = update.unsignedIntegerValue;

    if (new_preferences != _orientationPreferences) {
      _orientationPreferences = new_preferences;
      [UIViewController attemptRotationToDeviceOrientation];
    }
  });
730 731
}

732 733
- (BOOL)shouldAutorotate {
  return YES;
734 735
}

736 737 738 739
- (NSUInteger)supportedInterfaceOrientations {
  return _orientationPreferences;
}

740 741 742 743 744 745 746
#pragma mark - Accessibility

- (void)onVoiceOverChanged:(NSNotification*)notification {
#if TARGET_OS_SIMULATOR
  // There doesn't appear to be any way to determine whether the accessibility
  // inspector is enabled on the simulator. We conservatively always turn on the
  // accessibility bridge in the simulator.
747
  bool enabled = true;
748
#else
749
  bool enabled = UIAccessibilityIsVoiceOverRunning();
750
#endif
751
  _shell->GetPlatformView()->SetSemanticsEnabled(enabled);
752 753
}

754 755 756
#pragma mark - Memory Notifications

- (void)onMemoryWarning:(NSNotification*)notification {
757
  [_systemChannel.get() sendMessage:@{@"type" : @"memoryPressure"}];
758 759
}

760 761 762 763 764 765
#pragma mark - Locale updates

- (void)onLocaleUpdated:(NSNotification*)notification {
  NSLocale* currentLocale = [NSLocale currentLocale];
  NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode];
  NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
766 767
  if (languageCode && countryCode)
    [_localizationChannel.get() invokeMethod:@"setLocale" arguments:@[ languageCode, countryCode ]];
768 769
}

770 771 772 773 774 775 776 777 778 779 780
#pragma mark - Set user settings

- (void)onUserSettingsChanged:(NSNotification*)notification {
  [_settingsChannel.get() sendMessage:@{
    @"textScaleFactor" : @([self textScaleFactor]),
    @"alwaysUse24HourFormat" : @([self isAlwaysUse24HourFormat]),
  }];
}

- (CGFloat)textScaleFactor {
  UIContentSizeCategory category = [UIApplication sharedApplication].preferredContentSizeCategory;
781 782
  // The delta is computed by approximating Apple's typography guidelines:
  // https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/
783
  //
784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802
  // Specifically:
  // Non-accessibility sizes for "body" text are:
  const CGFloat xs = 14;
  const CGFloat s = 15;
  const CGFloat m = 16;
  const CGFloat l = 17;
  const CGFloat xl = 19;
  const CGFloat xxl = 21;
  const CGFloat xxxl = 23;

  // Accessibility sizes for "body" text are:
  const CGFloat ax1 = 28;
  const CGFloat ax2 = 33;
  const CGFloat ax3 = 40;
  const CGFloat ax4 = 47;
  const CGFloat ax5 = 53;

  // We compute the scale as relative difference from size L (large, the default size), where
  // L is assumed to have scale 1.0.
803
  if ([category isEqualToString:UIContentSizeCategoryExtraSmall])
804
    return xs / l;
805
  else if ([category isEqualToString:UIContentSizeCategorySmall])
806
    return s / l;
807
  else if ([category isEqualToString:UIContentSizeCategoryMedium])
808
    return m / l;
809 810 811
  else if ([category isEqualToString:UIContentSizeCategoryLarge])
    return 1.0;
  else if ([category isEqualToString:UIContentSizeCategoryExtraLarge])
812
    return xl / l;
813
  else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge])
814
    return xxl / l;
815
  else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge])
816 817 818 819 820 821 822 823 824 825 826
    return xxxl / l;
  else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium])
    return ax1 / l;
  else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge])
    return ax2 / l;
  else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge])
    return ax3 / l;
  else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge])
    return ax4 / l;
  else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge])
    return ax5 / l;
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
  else
    return 1.0;
}

- (BOOL)isAlwaysUse24HourFormat {
  // iOS does not report its "24-Hour Time" user setting in the API. Instead, it applies
  // it automatically to NSDateFormatter when used with [NSLocale currentLocale]. It is
  // essential that [NSLocale currentLocale] is used. Any custom locale, even the one
  // that's the same as [NSLocale currentLocale] will ignore the 24-hour option (there
  // must be some internal field that's not exposed to developers).
  //
  // Therefore this option behaves differently across Android and iOS. On Android this
  // setting is exposed standalone, and can therefore be applied to all locales, whether
  // the "current system locale" or a custom one. On iOS it only applies to the current
  // system locale. Widget implementors must take this into account in order to provide
  // platform-idiomatic behavior in their widgets.
  NSString* dateFormat =
      [NSDateFormatter dateFormatFromTemplate:@"j" options:0 locale:[NSLocale currentLocale]];
  return [dateFormat rangeOfString:@"a"].location == NSNotFound;
}

848 849 850 851 852
#pragma mark - Status Bar touch event handling

// Standard iOS status bar height in pixels.
constexpr CGFloat kStandardStatusBarHeight = 20.0;

853
- (void)handleStatusBarTouches:(UIEvent*)event {
854
  CGFloat standardStatusBarHeight = kStandardStatusBarHeight;
855
  if (@available(iOS 11, *)) {
856 857 858
    standardStatusBarHeight = self.view.safeAreaInsets.top;
  }

859 860 861
  // If the status bar is double-height, don't handle status bar taps. iOS
  // should open the app associated with the status bar.
  CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
862
  if (statusBarFrame.size.height != standardStatusBarHeight) {
863 864 865 866
    return;
  }

  // If we detect a touch in the status bar, synthesize a fake touch begin/end.
867
  for (UITouch* touch in event.allTouches) {
868 869 870 871
    if (touch.phase == UITouchPhaseBegan && touch.tapCount > 0) {
      CGPoint windowLoc = [touch locationInView:nil];
      CGPoint screenLoc = [touch.window convertPoint:windowLoc toWindow:nil];
      if (CGRectContainsPoint(statusBarFrame, screenLoc)) {
872
        NSSet* statusbarTouches = [NSSet setWithObject:touch];
873 874 875 876 877 878 879 880
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseBegan];
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseEnded];
        return;
      }
    }
  }
}

881 882 883 884 885 886 887 888 889 890 891
#pragma mark - Status bar style

- (UIStatusBarStyle)preferredStatusBarStyle {
  return _statusBarStyle;
}

- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
  // Notifications may not be on the iOS UI thread
  dispatch_async(dispatch_get_main_queue(), ^{
    NSDictionary* info = notification.userInfo;

892
    NSNumber* update = info[@(shell::kOverlayStyleUpdateNotificationKey)];
893 894 895 896 897 898 899 900 901 902 903 904 905 906

    if (update == nil) {
      return;
    }

    NSInteger style = update.integerValue;

    if (style != _statusBarStyle) {
      _statusBarStyle = static_cast<UIStatusBarStyle>(style);
      [self setNeedsStatusBarAppearanceUpdate];
    }
  });
}

907
#pragma mark - FlutterBinaryMessenger
908

909 910
- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
  [self sendOnChannel:channel message:message binaryReply:nil];
911 912
}

913 914 915
- (void)sendOnChannel:(NSString*)channel
              message:(NSData*)message
          binaryReply:(FlutterBinaryReply)callback {
916
  NSAssert(channel, @"The channel must not be null");
917
  fxl::RefPtr<shell::PlatformMessageResponseDarwin> response =
918
      (callback == nil) ? nullptr
919 920 921 922 923
                        : fxl::MakeRefCounted<shell::PlatformMessageResponseDarwin>(
                              ^(NSData* reply) {
                                callback(reply);
                              },
                              _shell->GetTaskRunners().GetPlatformTaskRunner());
924 925 926
  fxl::RefPtr<blink::PlatformMessage> platformMessage =
      (message == nil) ? fxl::MakeRefCounted<blink::PlatformMessage>(channel.UTF8String, response)
                       : fxl::MakeRefCounted<blink::PlatformMessage>(
927
                             channel.UTF8String, shell::GetVectorFromNSData(message), response);
928 929

  _shell->GetPlatformView()->DispatchPlatformMessage(platformMessage);
930 931
}

932 933 934
- (void)setMessageHandlerOnChannel:(NSString*)channel
              binaryMessageHandler:(FlutterBinaryMessageHandler)handler {
  NSAssert(channel, @"The channel must not be null");
935 936
  [self iosPlatformView] -> GetPlatformMessageRouter().SetMessageHandler(channel.UTF8String,
                                                                         handler);
937
}
938 939 940 941 942

#pragma mark - FlutterTextureRegistry

- (int64_t)registerTexture:(NSObject<FlutterTexture>*)texture {
  int64_t textureId = _nextTextureId++;
943
  [self iosPlatformView] -> RegisterExternalTexture(textureId, texture);
944 945 946 947
  return textureId;
}

- (void)unregisterTexture:(int64_t)textureId {
948
  _shell->GetPlatformView()->UnregisterTexture(textureId);
949 950 951
}

- (void)textureFrameAvailable:(int64_t)textureId {
952
  _shell->GetPlatformView()->MarkTextureFrameAvailable(textureId);
953
}
954 955

- (NSString*)lookupKeyForAsset:(NSString*)asset {
956
  return [FlutterDartProject lookupKeyForAsset:asset];
957 958 959
}

- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
960
  return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package];
961 962
}

C
Chinmay Garde 已提交
963
@end