FlutterViewController.mm 40.2 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 27 28 29 30
@property(nonatomic, readonly) NSMutableDictionary* pluginPublications;
@end

@interface FlutterViewControllerRegistrar : NSObject <FlutterPluginRegistrar>
- (instancetype)initWithPlugin:(NSString*)pluginKey
         flutterViewController:(FlutterViewController*)flutterViewController;
31 32
@end

33
@implementation FlutterViewController {
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
  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;
53
  UIInterfaceOrientationMask _orientationPreferences;
54
  UIStatusBarStyle _statusBarStyle;
55
  blink::ViewportMetrics _viewportMetrics;
56
  shell::TouchMapper _touchMapper;
57
  int64_t _nextTextureId;
58
  BOOL _initialized;
59 60
}

61 62
#pragma mark - Manage and override all designated initializers

63
- (instancetype)initWithProject:(FlutterDartProject*)projectOrNil
64 65 66
                        nibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
67
  if (self) {
68 69
    if (projectOrNil == nil)
      _dartProject.reset([[FlutterDartProject alloc] init]);
70
    else
71
      _dartProject.reset([projectOrNil retain]);
72 73

    [self performCommonViewControllerInitialization];
74
  }
75

76
  return self;
C
Chinmay Garde 已提交
77 78
}

79
- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
80
  return [self initWithProject:nil nibName:nil bundle:nil];
C
Chinmay Garde 已提交
81 82
}

83
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
84
  return [self initWithProject:nil nibName:nil bundle:nil];
85 86
}

87 88 89 90
- (instancetype)init {
  return [self initWithProject:nil nibName:nil bundle:nil];
}

91
#pragma mark - Common view controller initialization tasks
C
Chinmay Garde 已提交
92

93
- (void)performCommonViewControllerInitialization {
94
  if (_initialized)
95
    return;
96

97
  _initialized = YES;
C
Chinmay Garde 已提交
98

99
  _orientationPreferences = UIInterfaceOrientationMaskAll;
100
  _statusBarStyle = UIStatusBarStyleDefault;
101 102 103 104

  if ([self setupShell]) {
    [self setupChannels];
    [self setupNotificationCenterObservers];
105 106

    _pluginPublications = [NSMutableDictionary new];
107 108 109 110
  }
}

- (shell::Shell&)shell {
111
  FML_DCHECK(_shell);
112 113 114 115
  return *_shell;
}

- (fml::WeakPtr<shell::PlatformViewIOS>)iosPlatformView {
116
  FML_DCHECK(_shell);
117 118 119 120
  return _shell->GetPlatformView();
}

- (BOOL)setupShell {
121
  FML_DCHECK(_shell == nullptr);
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

  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) {
169
    FML_LOG(ERROR) << "Could not setup a shell to run the Dart application.";
170 171 172 173 174
    return false;
  }

  return true;
}
C
Chinmay Garde 已提交
175

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  [center addObserver:self
             selector:@selector(onLocaleUpdated:)
                 name:NSCurrentLocaleDidChangeNotification
               object:nil];
272 273

  [center addObserver:self
274
             selector:@selector(onAccessibilityStatusChanged:)
275 276
                 name:UIAccessibilityVoiceOverStatusChanged
               object:nil];
277

278 279 280 281 282 283 284 285 286 287
  [center addObserver:self
             selector:@selector(onAccessibilityStatusChanged:)
                 name:UIAccessibilitySwitchControlStatusDidChangeNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onAccessibilityStatusChanged:)
                 name:UIAccessibilitySpeakScreenStatusDidChangeNotification
               object:nil];

288 289 290 291 292 293 294 295 296 297
  [center addObserver:self
             selector:@selector(onAccessibilityStatusChanged:)
                 name:UIAccessibilityInvertColorsStatusDidChangeNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onAccessibilityStatusChanged:)
                 name:UIAccessibilityReduceMotionStatusDidChangeNotification
               object:nil];

298 299 300 301
  [center addObserver:self
             selector:@selector(onMemoryWarning:)
                 name:UIApplicationDidReceiveMemoryWarningNotification
               object:nil];
302 303 304 305 306

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

309
- (void)setInitialRoute:(NSString*)route {
310
  [_navigationChannel.get() invokeMethod:@"setInitialRoute" arguments:route];
311
}
312 313 314 315

#pragma mark - Loading the view

- (void)loadView {
316
  self.view = _flutterView.get();
317
  self.view.multipleTouchEnabled = YES;
318
  self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
319

320 321 322 323
  [self installLaunchViewIfNecessary];
}

#pragma mark - Managing launch views
324

325
- (void)installLaunchViewIfNecessary {
326 327
  // 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.
328 329
  [_launchView.get() removeFromSuperview];
  _launchView.reset();
330 331 332 333
  NSString* launchStoryboardName =
      [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
  if (launchStoryboardName && !self.isBeingPresented && !self.isMovingToParentViewController) {
    UIViewController* launchViewController =
334 335
        [[UIStoryboard storyboardWithName:launchStoryboardName bundle:nil]
            instantiateInitialViewController];
336 337 338 339 340 341
    _launchView.reset([launchViewController.view retain]);
    _launchView.get().frame = self.view.bounds;
    _launchView.get().autoresizingMask =
        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:_launchView.get()];
  }
342 343
}

344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
- (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];
          }
        });
      });
}

385 386 387
#pragma mark - Surface creation and teardown updates

- (void)surfaceUpdated:(BOOL)appeared {
388
  // NotifyCreated/NotifyDestroyed are synchronous and require hops between the UI and GPU thread.
389
  if (appeared) {
390 391 392
    [self installLaunchViewCallback];
    _shell->GetPlatformView()->NotifyCreated();

393
  } else {
394
    _shell->GetPlatformView()->NotifyDestroyed();
395 396 397
  }
}

398 399
#pragma mark - UIViewController lifecycle notifications

400
- (void)viewWillAppear:(BOOL)animated {
401
  TRACE_EVENT0("flutter", "viewWillAppear");
402 403 404

  // Launch the Dart application with the inferred run configuration.
  _shell->GetTaskRunners().GetUITaskRunner()->PostTask(
405
      fml::MakeCopyable([engine = _shell->GetEngine(),                   //
406 407 408 409 410
                         config = [_dartProject.get() runConfiguration]  //
  ]() mutable {
        if (engine) {
          auto result = engine->Run(std::move(config));
          if (!result) {
411
            FML_LOG(ERROR) << "Could not launch engine with configuration.";
412 413 414 415
          }
        }
      }));

416 417 418 419 420 421
  // 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"];

422 423 424
  [super viewWillAppear:animated];
}

425 426 427
- (void)viewDidAppear:(BOOL)animated {
  TRACE_EVENT0("flutter", "viewDidAppear");
  [self onLocaleUpdated:nil];
428
  [self onUserSettingsChanged:nil];
429
  [self onAccessibilityStatusChanged:nil];
430 431 432 433 434 435 436 437 438 439 440 441
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.resumed"];

  [super viewDidAppear:animated];
}

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

  [super viewWillDisappear:animated];
}

442
- (void)viewDidDisappear:(BOOL)animated {
443 444
  TRACE_EVENT0("flutter", "viewDidDisappear");
  [self surfaceUpdated:NO];
445 446
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];

447
  [super viewDidDisappear:animated];
448 449
}

450 451
- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
452
  [_pluginPublications release];
453 454 455
  [super dealloc];
}

456 457 458
#pragma mark - Application lifecycle notifications

- (void)applicationBecameActive:(NSNotification*)notification {
459
  TRACE_EVENT0("flutter", "applicationBecameActive");
460
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.resumed"];
461 462 463
}

- (void)applicationWillResignActive:(NSNotification*)notification {
464
  TRACE_EVENT0("flutter", "applicationWillResignActive");
465 466 467 468
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];
}

- (void)applicationDidEnterBackground:(NSNotification*)notification {
469
  TRACE_EVENT0("flutter", "applicationDidEnterBackground");
470
  [self surfaceUpdated:NO];
471
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];
472 473
}

474
- (void)applicationWillEnterForeground:(NSNotification*)notification {
475
  TRACE_EVENT0("flutter", "applicationWillEnterForeground");
476 477
  if (_viewportMetrics.physical_width)
    [self surfaceUpdated:YES];
478 479 480
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];
}

481 482 483 484 485 486 487 488
#pragma mark - Touch event handling

enum MapperPhase {
  Accessed,
  Added,
  Removed,
};

489 490
using PointerChangeMapperPhase = std::pair<blink::PointerData::Change, MapperPhase>;
static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(UITouchPhase phase) {
491 492
  switch (phase) {
    case UITouchPhaseBegan:
493
      return PointerChangeMapperPhase(blink::PointerData::Change::kDown, MapperPhase::Added);
494 495 496 497
    case UITouchPhaseMoved:
    case UITouchPhaseStationary:
      // There is no EVENT_TYPE_POINTER_STATIONARY. So we just pass a move type
      // with the same coordinates
498
      return PointerChangeMapperPhase(blink::PointerData::Change::kMove, MapperPhase::Accessed);
499
    case UITouchPhaseEnded:
500
      return PointerChangeMapperPhase(blink::PointerData::Change::kUp, MapperPhase::Removed);
501
    case UITouchPhaseCancelled:
502
      return PointerChangeMapperPhase(blink::PointerData::Change::kCancel, MapperPhase::Removed);
503 504
  }

505
  return PointerChangeMapperPhase(blink::PointerData::Change::kCancel, MapperPhase::Accessed);
506
}
C
Chinmay Garde 已提交
507

508 509 510 511 512 513 514 515 516 517
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 {
518 519 520 521 522 523
    return blink::PointerData::DeviceKind::kTouch;
  }

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

C
Chinmay Garde 已提交
524
- (void)dispatchTouches:(NSSet*)touches phase:(UITouchPhase)phase {
525 526 527 528 529
  // 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 已提交
530
  auto eventTypePhase = PointerChangePhaseFromUITouchPhase(phase);
C
Chinmay Garde 已提交
531
  const CGFloat scale = [UIScreen mainScreen].scale;
A
Adam Barth 已提交
532
  auto packet = std::make_unique<blink::PointerDataPacket>(touches.count);
C
Chinmay Garde 已提交
533

A
Adam Barth 已提交
534
  int i = 0;
C
Chinmay Garde 已提交
535
  for (UITouch* touch in touches) {
536
    int device_id = 0;
537 538 539

    switch (eventTypePhase.second) {
      case Accessed:
540
        device_id = _touchMapper.identifierOf(touch);
541 542
        break;
      case Added:
543
        device_id = _touchMapper.registerTouch(touch);
544 545
        break;
      case Removed:
546
        device_id = _touchMapper.unregisterTouch(touch);
547 548
        break;
    }
549

550
    FML_DCHECK(device_id != 0);
551
    CGPoint windowCoordinates = [touch locationInView:self.view];
552

A
Adam Barth 已提交
553 554 555
    blink::PointerData pointer_data;
    pointer_data.Clear();

556 557
    constexpr int kMicrosecondsPerSecond = 1000 * 1000;
    pointer_data.time_stamp = touch.timestamp * kMicrosecondsPerSecond;
558

A
Adam Barth 已提交
559
    pointer_data.change = eventTypePhase.first;
560

561
    pointer_data.kind = DeviceKindFromTouchType(touch);
562

563
    pointer_data.device = device_id;
564

A
Adam Barth 已提交
565 566
    pointer_data.physical_x = windowCoordinates.x * scale;
    pointer_data.physical_y = windowCoordinates.y * scale;
567 568

    // pressure_min is always 0.0
569
    if (@available(iOS 9, *)) {
570 571 572 573 574 575 576 577 578 579 580 581 582 583
      // 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
584
    if (@available(iOS 9.1, *)) {
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
      // 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 已提交
620 621

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

624
  _shell->GetTaskRunners().GetUITaskRunner()->PostTask(
625
      fml::MakeCopyable([engine = _shell->GetEngine(), packet = std::move(packet)] {
626
        if (engine) {
627
          engine->DispatchPointerDataPacket(*packet);
628
        }
629
      }));
C
Chinmay Garde 已提交
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
}

- (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];
}

648
#pragma mark - Handle view resizing
649

650
- (void)updateViewportMetrics {
651 652 653 654
  _shell->GetTaskRunners().GetUITaskRunner()->PostTask(
      [engine = _shell->GetEngine(), metrics = _viewportMetrics]() {
        if (engine) {
          engine->SetViewportMetrics(std::move(metrics));
655 656
        }
      });
657 658
}

659
- (CGFloat)statusBarPadding {
660
  UIScreen* screen = self.view.window.screen;
661
  CGRect statusFrame = [UIApplication sharedApplication].statusBarFrame;
662 663
  CGRect viewFrame =
      [self.view convertRect:self.view.bounds toCoordinateSpace:screen.coordinateSpace];
664 665
  CGRect intersection = CGRectIntersection(statusFrame, viewFrame);
  return CGRectIsNull(intersection) ? 0.0 : intersection.size.height;
666 667
}

668
- (void)viewDidLayoutSubviews {
669
  CGSize viewSize = self.view.bounds.size;
670 671
  CGFloat scale = [UIScreen mainScreen].scale;

672 673
  // First time since creation that the dimensions of its view is known.
  bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
674
  _viewportMetrics.device_pixel_ratio = scale;
675 676
  _viewportMetrics.physical_width = viewSize.width * scale;
  _viewportMetrics.physical_height = viewSize.height * scale;
677

678 679 680 681 682 683 684 685 686 687
  [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 {
688 689 690
  [self updateViewportPadding];
  [self updateViewportMetrics];
  [super viewSafeAreaInsetsDidChange];
691 692 693 694 695 696 697
}

// Updates _viewportMetrics physical padding.
//
// Viewport padding represents the iOS safe area insets.
- (void)updateViewportPadding {
  CGFloat scale = [UIScreen mainScreen].scale;
698
  if (@available(iOS 11, *)) {
699 700 701
    _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;
702
    _viewportMetrics.physical_padding_bottom = self.view.safeAreaInsets.bottom * scale;
703 704 705
  } else {
    _viewportMetrics.physical_padding_top = [self statusBarPadding] * scale;
  }
706 707
}

708
#pragma mark - Keyboard events
709

710
- (void)keyboardWillChangeFrame:(NSNotification*)notification {
711
  NSDictionary* info = [notification userInfo];
712
  CGFloat bottom = CGRectGetHeight([[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]);
713
  CGFloat scale = [UIScreen mainScreen].scale;
714
  _viewportMetrics.physical_view_inset_bottom = bottom * scale;
715
  [self updateViewportMetrics];
716 717
}

718
- (void)keyboardWillBeHidden:(NSNotification*)notification {
719
  _viewportMetrics.physical_view_inset_bottom = 0;
720
  [self updateViewportMetrics];
721 722
}

723 724 725
#pragma mark - Text input delegate

- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
726 727
  [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState"
                              arguments:@[ @(client), state ]];
728 729
}

730 731 732
- (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
  NSString* actionString;
  switch (action) {
733 734 735 736 737 738 739
    case FlutterTextInputActionUnspecified:
      // Where did the term "unspecified" come from? iOS has a "default" and Android
      // has "unspecified." These 2 terms seem to mean the same thing but we need
      // to pick just one. "unspecified" was chosen because "default" is often a
      // reserved word in languages with switch statements (dart, java, etc).
      actionString = @"TextInputAction.unspecified";
      break;
740 741 742
    case FlutterTextInputActionDone:
      actionString = @"TextInputAction.done";
      break;
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
    case FlutterTextInputActionGo:
      actionString = @"TextInputAction.go";
      break;
    case FlutterTextInputActionSend:
      actionString = @"TextInputAction.send";
      break;
    case FlutterTextInputActionSearch:
      actionString = @"TextInputAction.search";
      break;
    case FlutterTextInputActionNext:
      actionString = @"TextInputAction.next";
      break;
    case FlutterTextInputActionContinue:
      actionString = @"TextInputAction.continue";
      break;
    case FlutterTextInputActionJoin:
      actionString = @"TextInputAction.join";
      break;
    case FlutterTextInputActionRoute:
      actionString = @"TextInputAction.route";
      break;
    case FlutterTextInputActionEmergencyCall:
      actionString = @"TextInputAction.emergencyCall";
      break;
767 768 769
    case FlutterTextInputActionNewline:
      actionString = @"TextInputAction.newline";
      break;
770 771 772 773 774
  }
  [_textInputChannel.get() invokeMethod:@"TextInputClient.performAction"
                              arguments:@[ @(client), actionString ]];
}

775 776 777 778 779 780 781
#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;

782
    NSNumber* update = info[@(shell::kOrientationUpdateNotificationKey)];
783 784 785 786 787 788 789 790 791 792 793 794

    if (update == nil) {
      return;
    }

    NSUInteger new_preferences = update.unsignedIntegerValue;

    if (new_preferences != _orientationPreferences) {
      _orientationPreferences = new_preferences;
      [UIViewController attemptRotationToDeviceOrientation];
    }
  });
795 796
}

797 798
- (BOOL)shouldAutorotate {
  return YES;
799 800
}

801 802 803 804
- (NSUInteger)supportedInterfaceOrientations {
  return _orientationPreferences;
}

805 806
#pragma mark - Accessibility

807
- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
808
  auto platformView = _shell->GetPlatformView();
809 810 811 812 813
  int32_t flags = 0;
  if (UIAccessibilityIsInvertColorsEnabled())
    flags ^= static_cast<int32_t>(blink::AccessibilityFeatureFlag::kInvertColors);
  if (UIAccessibilityIsReduceMotionEnabled())
    flags ^= static_cast<int32_t>(blink::AccessibilityFeatureFlag::kDisableAnimations);
814 815 816
#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
817 818
  // accessibility bridge in the simulator, but never assistive technology.
  platformView->SetSemanticsEnabled(true);
819
  platformView->SetAccessibilityFeatures(flags);
820
#else
821
  bool enabled = UIAccessibilityIsVoiceOverRunning() || UIAccessibilityIsSwitchControlRunning();
822 823
  if (UIAccessibilityIsVoiceOverRunning() || UIAccessibilityIsSwitchControlRunning())
    flags ^= static_cast<int32_t>(blink::AccessibilityFeatureFlag::kAccessibleNavigation);
824
  platformView->SetSemanticsEnabled(enabled || UIAccessibilityIsSpeakScreenEnabled());
825
  platformView->SetAccessibilityFeatures(flags);
826 827 828
#endif
}

829 830 831
#pragma mark - Memory Notifications

- (void)onMemoryWarning:(NSNotification*)notification {
832
  [_systemChannel.get() sendMessage:@{@"type" : @"memoryPressure"}];
833 834
}

835 836 837 838 839 840
#pragma mark - Locale updates

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

845 846 847 848 849 850 851 852 853 854 855
#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;
856 857
  // The delta is computed by approximating Apple's typography guidelines:
  // https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/
858
  //
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877
  // 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.
878
  if ([category isEqualToString:UIContentSizeCategoryExtraSmall])
879
    return xs / l;
880
  else if ([category isEqualToString:UIContentSizeCategorySmall])
881
    return s / l;
882
  else if ([category isEqualToString:UIContentSizeCategoryMedium])
883
    return m / l;
884 885 886
  else if ([category isEqualToString:UIContentSizeCategoryLarge])
    return 1.0;
  else if ([category isEqualToString:UIContentSizeCategoryExtraLarge])
887
    return xl / l;
888
  else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge])
889
    return xxl / l;
890
  else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge])
891 892 893 894 895 896 897 898 899 900 901
    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;
902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922
  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;
}

923 924 925 926 927
#pragma mark - Status Bar touch event handling

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

928
- (void)handleStatusBarTouches:(UIEvent*)event {
929
  CGFloat standardStatusBarHeight = kStandardStatusBarHeight;
930
  if (@available(iOS 11, *)) {
931 932 933
    standardStatusBarHeight = self.view.safeAreaInsets.top;
  }

934 935 936
  // 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;
937
  if (statusBarFrame.size.height != standardStatusBarHeight) {
938 939 940 941
    return;
  }

  // If we detect a touch in the status bar, synthesize a fake touch begin/end.
942
  for (UITouch* touch in event.allTouches) {
943 944 945 946
    if (touch.phase == UITouchPhaseBegan && touch.tapCount > 0) {
      CGPoint windowLoc = [touch locationInView:nil];
      CGPoint screenLoc = [touch.window convertPoint:windowLoc toWindow:nil];
      if (CGRectContainsPoint(statusBarFrame, screenLoc)) {
947
        NSSet* statusbarTouches = [NSSet setWithObject:touch];
948 949 950 951 952 953 954 955
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseBegan];
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseEnded];
        return;
      }
    }
  }
}

956 957 958 959 960 961 962 963 964 965 966
#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;

967
    NSNumber* update = info[@(shell::kOverlayStyleUpdateNotificationKey)];
968 969 970 971 972 973 974 975 976 977 978 979 980 981

    if (update == nil) {
      return;
    }

    NSInteger style = update.integerValue;

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

982
#pragma mark - FlutterBinaryMessenger
983

984 985
- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
  [self sendOnChannel:channel message:message binaryReply:nil];
986 987
}

988 989 990
- (void)sendOnChannel:(NSString*)channel
              message:(NSData*)message
          binaryReply:(FlutterBinaryReply)callback {
991
  NSAssert(channel, @"The channel must not be null");
992
  fml::RefPtr<shell::PlatformMessageResponseDarwin> response =
993
      (callback == nil) ? nullptr
994
                        : fml::MakeRefCounted<shell::PlatformMessageResponseDarwin>(
995 996 997 998
                              ^(NSData* reply) {
                                callback(reply);
                              },
                              _shell->GetTaskRunners().GetPlatformTaskRunner());
999 1000 1001
  fml::RefPtr<blink::PlatformMessage> platformMessage =
      (message == nil) ? fml::MakeRefCounted<blink::PlatformMessage>(channel.UTF8String, response)
                       : fml::MakeRefCounted<blink::PlatformMessage>(
1002
                             channel.UTF8String, shell::GetVectorFromNSData(message), response);
1003 1004

  _shell->GetPlatformView()->DispatchPlatformMessage(platformMessage);
1005 1006
}

1007 1008 1009
- (void)setMessageHandlerOnChannel:(NSString*)channel
              binaryMessageHandler:(FlutterBinaryMessageHandler)handler {
  NSAssert(channel, @"The channel must not be null");
1010 1011
  [self iosPlatformView] -> GetPlatformMessageRouter().SetMessageHandler(channel.UTF8String,
                                                                         handler);
1012
}
1013 1014 1015 1016 1017

#pragma mark - FlutterTextureRegistry

- (int64_t)registerTexture:(NSObject<FlutterTexture>*)texture {
  int64_t textureId = _nextTextureId++;
1018
  [self iosPlatformView] -> RegisterExternalTexture(textureId, texture);
1019 1020 1021 1022
  return textureId;
}

- (void)unregisterTexture:(int64_t)textureId {
1023
  _shell->GetPlatformView()->UnregisterTexture(textureId);
1024 1025 1026
}

- (void)textureFrameAvailable:(int64_t)textureId {
1027
  _shell->GetPlatformView()->MarkTextureFrameAvailable(textureId);
1028
}
1029 1030

- (NSString*)lookupKeyForAsset:(NSString*)asset {
1031
  return [FlutterDartProject lookupKeyForAsset:asset];
1032 1033 1034
}

- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
1035
  return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package];
1036 1037
}

1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
- (id<FlutterPluginRegistry>)pluginRegistry {
  return self;
}

#pragma mark - FlutterPluginRegistry

- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
  NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey);
  self.pluginPublications[pluginKey] = [NSNull null];
  return
      [[FlutterViewControllerRegistrar alloc] initWithPlugin:pluginKey flutterViewController:self];
}

- (BOOL)hasPlugin:(NSString*)pluginKey {
  return _pluginPublications[pluginKey] != nil;
}

- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
  return _pluginPublications[pluginKey];
}
@end

@implementation FlutterViewControllerRegistrar {
  NSString* _pluginKey;
  FlutterViewController* _flutterViewController;
}

- (instancetype)initWithPlugin:(NSString*)pluginKey
         flutterViewController:(FlutterViewController*)flutterViewController {
  self = [super init];
  NSAssert(self, @"Super init cannot be nil");
  _pluginKey = [pluginKey retain];
  _flutterViewController = [flutterViewController retain];
  return self;
}

- (void)dealloc {
  [_pluginKey release];
  [_flutterViewController release];
  [super dealloc];
}

- (NSObject<FlutterBinaryMessenger>*)messenger {
  return _flutterViewController;
}

- (NSObject<FlutterTextureRegistry>*)textures {
  return _flutterViewController;
}

- (void)publish:(NSObject*)value {
  _flutterViewController.pluginPublications[_pluginKey] = value;
}

- (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate
                      channel:(FlutterMethodChannel*)channel {
  [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    [delegate handleMethodCall:call result:result];
  }];
}

- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate {
  id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
  if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) {
    id<FlutterAppLifeCycleProvider> lifeCycleProvider =
        (id<FlutterAppLifeCycleProvider>)appDelegate;
    [lifeCycleProvider addApplicationLifeCycleDelegate:delegate];
  }
}

- (NSString*)lookupKeyForAsset:(NSString*)asset {
  return [_flutterViewController lookupKeyForAsset:asset];
}

- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
  return [_flutterViewController lookupKeyForAsset:asset fromPackage:package];
}

C
Chinmay Garde 已提交
1116
@end