FlutterViewController.mm 27.9 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
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
C
Chinmay Garde 已提交
6

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

#include "flutter/common/threads.h"
10
#include "flutter/fml/platform/darwin/platform_version.h"
11 12
#include "flutter/fml/platform/darwin/scoped_block.h"
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
13
#include "flutter/shell/platform/darwin/common/buffer_conversions.h"
14
#include "flutter/shell/platform/darwin/common/platform_mac.h"
15
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h"
16
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
17
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
18 19
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
20
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_main_ios.h"
21
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_touch_mapper.h"
22
#include "flutter/shell/platform/darwin/ios/platform_view_ios.h"
A
Adam Barth 已提交
23
#include "lib/ftl/functional/make_copyable.h"
24
#include "lib/ftl/time/time_delta.h"
25

26 27
namespace {

28
typedef void (^PlatformMessageResponseCallback)(NSData*);
29 30 31 32 33 34 35 36 37

class PlatformMessageResponseDarwin : public blink::PlatformMessageResponse {
  FRIEND_MAKE_REF_COUNTED(PlatformMessageResponseDarwin);

 public:
  void Complete(std::vector<uint8_t> data) override {
    ftl::RefPtr<PlatformMessageResponseDarwin> self(this);
    blink::Threads::Platform()->PostTask(
        ftl::MakeCopyable([ self, data = std::move(data) ]() mutable {
38
          self->callback_.get()(shell::GetNSDataFromVector(data));
39 40 41
        }));
  }

42 43 44
  void CompleteEmpty() override {
    ftl::RefPtr<PlatformMessageResponseDarwin> self(this);
    blink::Threads::Platform()->PostTask(
45
        ftl::MakeCopyable([self]() mutable { self->callback_.get()(nil); }));
46
  }
47 48

 private:
49
  explicit PlatformMessageResponseDarwin(PlatformMessageResponseCallback callback)
50
      : callback_(callback, fml::OwnershipPolicy::Retain) {}
51

52
  fml::ScopedBlock<PlatformMessageResponseCallback> callback_;
53 54 55 56
};

}  // namespace

57
@interface FlutterViewController ()<UIAlertViewDelegate, FlutterTextInputDelegate>
58 59
@end

60
@implementation FlutterViewController {
61
  fml::scoped_nsprotocol<FlutterDartProject*> _dartProject;
62
  UIInterfaceOrientationMask _orientationPreferences;
63
  UIStatusBarStyle _statusBarStyle;
64
  blink::ViewportMetrics _viewportMetrics;
65
  shell::TouchMapper _touchMapper;
M
Michael Goderbauer 已提交
66
  std::shared_ptr<shell::PlatformViewIOS> _platformView;
67 68 69 70 71 72
  fml::scoped_nsprotocol<FlutterPlatformPlugin*> _platformPlugin;
  fml::scoped_nsprotocol<FlutterTextInputPlugin*> _textInputPlugin;
  fml::scoped_nsprotocol<FlutterMethodChannel*> _localizationChannel;
  fml::scoped_nsprotocol<FlutterMethodChannel*> _navigationChannel;
  fml::scoped_nsprotocol<FlutterMethodChannel*> _platformChannel;
  fml::scoped_nsprotocol<FlutterMethodChannel*> _textInputChannel;
73 74
  fml::scoped_nsprotocol<FlutterBasicMessageChannel*> _lifecycleChannel;
  fml::scoped_nsprotocol<FlutterBasicMessageChannel*> _systemChannel;
75
  fml::scoped_nsprotocol<UIView*> _launchView;
76 77 78
  bool _platformSupportsTouchTypes;
  bool _platformSupportsTouchPressure;
  bool _platformSupportsTouchOrientationAndTilt;
79
  BOOL _initialized;
80
  BOOL _connected;
81 82
}

83 84 85 86 87 88
+ (void)initialize {
  if (self == [FlutterViewController class]) {
    shell::FlutterMain();
  }
}

89 90
#pragma mark - Manage and override all designated initializers

91 92 93 94
- (instancetype)initWithProject:(FlutterDartProject*)project
                        nibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
95

96
  if (self) {
97
    if (project == nil)
98
      _dartProject.reset([[FlutterDartProject alloc] initFromDefaultSourceForConfiguration]);
99 100
    else
      _dartProject.reset([project retain]);
101 102

    [self performCommonViewControllerInitialization];
103
  }
104

105
  return self;
C
Chinmay Garde 已提交
106 107
}

108
- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
109
  return [self initWithProject:nil nibName:nil bundle:nil];
C
Chinmay Garde 已提交
110 111
}

112
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
113
  return [self initWithProject:nil nibName:nil bundle:nil];
114 115
}

116
#pragma mark - Common view controller initialization tasks
C
Chinmay Garde 已提交
117

118
- (void)performCommonViewControllerInitialization {
119
  if (_initialized)
120
    return;
121

122
  _initialized = YES;
C
Chinmay Garde 已提交
123

124 125 126 127
  _platformSupportsTouchTypes = fml::IsPlatformVersionAtLeast(9);
  _platformSupportsTouchPressure = fml::IsPlatformVersionAtLeast(9);
  _platformSupportsTouchOrientationAndTilt = fml::IsPlatformVersionAtLeast(9, 1);

128
  _orientationPreferences = UIInterfaceOrientationMaskAll;
129
  _statusBarStyle = UIStatusBarStyleDefault;
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
  _platformView = std::make_shared<shell::PlatformViewIOS>(
      reinterpret_cast<CAEAGLLayer*>(self.view.layer));

  _platformView->Attach(
      // First frame callback.
      [self]() {
        TRACE_EVENT0("flutter", "First Frame");
        if (_launchView) {
          [UIView animateWithDuration:0.2
                           animations:^{ _launchView.get().alpha = 0; }
                           completion:^(BOOL finished){
                             [_launchView.get() removeFromSuperview];
                             _launchView.reset();
                           }];
        }
      });
146
  _platformView->SetupResourceContextOnIOThread();
C
Chinmay Garde 已提交
147

148
  _localizationChannel.reset([[FlutterMethodChannel alloc]
149
         initWithName:@"flutter/localization"
150
      binaryMessenger:self
151
                codec:[FlutterJSONMethodCodec sharedInstance]]);
152 153

  _navigationChannel.reset([[FlutterMethodChannel alloc]
154
         initWithName:@"flutter/navigation"
155
      binaryMessenger:self
156
                codec:[FlutterJSONMethodCodec sharedInstance]]);
157 158

  _platformChannel.reset([[FlutterMethodChannel alloc]
159
         initWithName:@"flutter/platform"
160
      binaryMessenger:self
161
                codec:[FlutterJSONMethodCodec sharedInstance]]);
162 163

  _textInputChannel.reset([[FlutterMethodChannel alloc]
164
         initWithName:@"flutter/textinput"
165
      binaryMessenger:self
166
                codec:[FlutterJSONMethodCodec sharedInstance]]);
167

168
  _lifecycleChannel.reset([[FlutterBasicMessageChannel alloc]
169
         initWithName:@"flutter/lifecycle"
170
      binaryMessenger:self
171
                codec:[FlutterStringCodec sharedInstance]]);
172

173
  _systemChannel.reset([[FlutterBasicMessageChannel alloc]
174
         initWithName:@"flutter/system"
175
      binaryMessenger:self
176
                codec:[FlutterJSONMessageCodec sharedInstance]]);
177

178
  _platformPlugin.reset([[FlutterPlatformPlugin alloc] init]);
179 180 181
  [_platformChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    [_platformPlugin.get() handleMethodCall:call result:result];
  }];
182

183 184
  _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
  _textInputPlugin.get().textInputDelegate = self;
185 186 187
  [_textInputChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    [_textInputPlugin.get() handleMethodCall:call result:result];
  }];
188

189
  [self setupNotificationCenterObservers];
C
Chinmay Garde 已提交
190 191
}

192 193 194 195
- (void)setupNotificationCenterObservers {
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center addObserver:self
             selector:@selector(onOrientationPreferencesUpdated:)
A
Adam Barth 已提交
196
                 name:@(shell::kOrientationUpdateNotificationName)
197 198
               object:nil];

199 200
  [center addObserver:self
             selector:@selector(onPreferredStatusBarStyleUpdated:)
A
Adam Barth 已提交
201
                 name:@(shell::kOverlayStyleUpdateNotificationName)
202 203
               object:nil];

204 205 206 207 208 209 210 211 212 213
  [center addObserver:self
             selector:@selector(applicationBecameActive:)
                 name:UIApplicationDidBecomeActiveNotification
               object:nil];

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

214 215 216 217 218 219 220 221 222 223
  [center addObserver:self
             selector:@selector(applicationDidEnterBackground:)
                 name:UIApplicationDidEnterBackgroundNotification
               object:nil];

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

224
  [center addObserver:self
225 226
             selector:@selector(keyboardWillChangeFrame:)
                 name:UIKeyboardWillChangeFrameNotification
227 228 229 230 231 232 233 234 235 236 237
               object:nil];

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

  [center addObserver:self
             selector:@selector(onLocaleUpdated:)
                 name:NSCurrentLocaleDidChangeNotification
               object:nil];
238 239 240 241 242

  [center addObserver:self
             selector:@selector(onVoiceOverChanged:)
                 name:UIAccessibilityVoiceOverStatusChanged
               object:nil];
243 244 245 246 247

  [center addObserver:self
             selector:@selector(onMemoryWarning:)
                 name:UIApplicationDidReceiveMemoryWarningNotification
               object:nil];
C
Chinmay Garde 已提交
248 249
}

250
- (void)setInitialRoute:(NSString*)route {
251
  [_navigationChannel.get() invokeMethod:@"setInitialRoute" arguments:route];
252
}
253
#pragma mark - Initializing the engine
254

255
- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
256 257 258
  exit(0);
}

259
- (void)connectToEngineAndLoad {
260 261 262 263
  if (_connected)
    return;
  _connected = YES;

C
Chinmay Garde 已提交
264
  TRACE_EVENT0("flutter", "connectToEngineAndLoad");
265

266
  // We ask the VM to check what it supports.
267
  const enum VMType type = Dart_IsPrecompiledRuntime() ? VMTypePrecompilation : VMTypeInterpreter;
268

A
Adam Barth 已提交
269
  [_dartProject launchInEngine:&_platformView->engine()
270 271 272
                embedderVMType:type
                        result:^(BOOL success, NSString* message) {
                          if (!success) {
273 274 275 276 277
                            UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Launch Error"
                                                                            message:message
                                                                           delegate:self
                                                                  cancelButtonTitle:@"OK"
                                                                  otherButtonTitles:nil];
278 279 280 281
                            [alert show];
                            [alert release];
                          }
                        }];
282 283 284 285 286
}

#pragma mark - Loading the view

- (void)loadView {
287
  FlutterView* view = [[FlutterView alloc] init];
288

289
  self.view = view;
290
  self.view.multipleTouchEnabled = YES;
291
  self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
292

293
  [view release];
294 295 296 297 298 299 300 301 302 303 304 305 306 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.
  NSString* launchStoryboardName =
      [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
  if (launchStoryboardName && !self.isBeingPresented && !self.isMovingToParentViewController) {
    UIViewController* launchViewController =
        [[UIStoryboard storyboardWithName:launchStoryboardName
                                   bundle:nil] instantiateInitialViewController];
    _launchView.reset([launchViewController.view retain]);
    _launchView.get().frame = self.view.bounds;
    _launchView.get().autoresizingMask =
        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:_launchView.get()];
  }
309 310
}

311 312 313 314 315
#pragma mark - Surface creation and teardown updates

- (void)surfaceUpdated:(BOOL)appeared {
  FTL_CHECK(_platformView != nullptr);

316
  // NotifyCreated/NotifyDestroyed are synchronous and require hops between the UI and GPU thread.
317 318 319 320 321 322 323
  if (appeared) {
    _platformView->NotifyCreated();
  } else {
    _platformView->NotifyDestroyed();
  }
}

324 325
#pragma mark - UIViewController lifecycle notifications

326
- (void)viewWillAppear:(BOOL)animated {
327
  TRACE_EVENT0("flutter", "viewWillAppear");
328
  [self connectToEngineAndLoad];
329 330 331 332 333 334
  // 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"];

335 336 337
  [super viewWillAppear:animated];
}

338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
- (void)viewDidAppear:(BOOL)animated {
  TRACE_EVENT0("flutter", "viewDidAppear");
  [self onLocaleUpdated:nil];
  [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];
}

354
- (void)viewDidDisappear:(BOOL)animated {
355 356
  TRACE_EVENT0("flutter", "viewDidDisappear");
  [self surfaceUpdated:NO];
357 358
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];

359
  [super viewDidDisappear:animated];
360 361
}

362 363 364 365 366
- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  [super dealloc];
}

367 368 369
#pragma mark - Application lifecycle notifications

- (void)applicationBecameActive:(NSNotification*)notification {
370
  TRACE_EVENT0("flutter", "applicationBecameActive");
371
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.resumed"];
372 373 374
}

- (void)applicationWillResignActive:(NSNotification*)notification {
375
  TRACE_EVENT0("flutter", "applicationWillResignActive");
376 377 378 379
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];
}

- (void)applicationDidEnterBackground:(NSNotification*)notification {
380
  TRACE_EVENT0("flutter", "applicationDidEnterBackground");
381
  [self surfaceUpdated:NO];
382
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];
383 384
}

385
- (void)applicationWillEnterForeground:(NSNotification*)notification {
386
  TRACE_EVENT0("flutter", "applicationWillEnterForeground");
387 388
  if (_viewportMetrics.physical_width)
    [self surfaceUpdated:YES];
389 390 391
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];
}

392 393 394 395 396 397 398 399
#pragma mark - Touch event handling

enum MapperPhase {
  Accessed,
  Added,
  Removed,
};

400 401
using PointerChangeMapperPhase = std::pair<blink::PointerData::Change, MapperPhase>;
static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(UITouchPhase phase) {
402 403
  switch (phase) {
    case UITouchPhaseBegan:
404
      return PointerChangeMapperPhase(blink::PointerData::Change::kDown, MapperPhase::Added);
405 406 407 408
    case UITouchPhaseMoved:
    case UITouchPhaseStationary:
      // There is no EVENT_TYPE_POINTER_STATIONARY. So we just pass a move type
      // with the same coordinates
409
      return PointerChangeMapperPhase(blink::PointerData::Change::kMove, MapperPhase::Accessed);
410
    case UITouchPhaseEnded:
411
      return PointerChangeMapperPhase(blink::PointerData::Change::kUp, MapperPhase::Removed);
412
    case UITouchPhaseCancelled:
413
      return PointerChangeMapperPhase(blink::PointerData::Change::kCancel, MapperPhase::Removed);
414 415
  }

416
  return PointerChangeMapperPhase(blink::PointerData::Change::kCancel, MapperPhase::Accessed);
417
}
C
Chinmay Garde 已提交
418

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
static inline blink::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch,
                                                                     bool touchTypeSupported) {
  if (!touchTypeSupported) {
    return blink::PointerData::DeviceKind::kTouch;
  }

  switch (touch.type) {
    case UITouchTypeDirect:
    case UITouchTypeIndirect:
      return blink::PointerData::DeviceKind::kTouch;
    case UITouchTypeStylus:
      return blink::PointerData::DeviceKind::kStylus;
  }

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

C
Chinmay Garde 已提交
436
- (void)dispatchTouches:(NSSet*)touches phase:(UITouchPhase)phase {
437 438 439 440 441
  // 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 已提交
442
  auto eventTypePhase = PointerChangePhaseFromUITouchPhase(phase);
C
Chinmay Garde 已提交
443
  const CGFloat scale = [UIScreen mainScreen].scale;
A
Adam Barth 已提交
444
  auto packet = std::make_unique<blink::PointerDataPacket>(touches.count);
C
Chinmay Garde 已提交
445

A
Adam Barth 已提交
446
  int i = 0;
C
Chinmay Garde 已提交
447
  for (UITouch* touch in touches) {
448
    int device_id = 0;
449 450 451

    switch (eventTypePhase.second) {
      case Accessed:
452
        device_id = _touchMapper.identifierOf(touch);
453 454
        break;
      case Added:
455
        device_id = _touchMapper.registerTouch(touch);
456 457
        break;
      case Removed:
458
        device_id = _touchMapper.unregisterTouch(touch);
459 460
        break;
    }
461

462
    FTL_DCHECK(device_id != 0);
C
Chinmay Garde 已提交
463
    CGPoint windowCoordinates = [touch locationInView:nil];
464

A
Adam Barth 已提交
465 466 467
    blink::PointerData pointer_data;
    pointer_data.Clear();

468 469
    constexpr int kMicrosecondsPerSecond = 1000 * 1000;
    pointer_data.time_stamp = touch.timestamp * kMicrosecondsPerSecond;
470

A
Adam Barth 已提交
471
    pointer_data.change = eventTypePhase.first;
472 473 474

    pointer_data.kind = DeviceKindFromTouchType(touch, _platformSupportsTouchTypes);

475
    pointer_data.device = device_id;
476

A
Adam Barth 已提交
477 478
    pointer_data.physical_x = windowCoordinates.x * scale;
    pointer_data.physical_y = windowCoordinates.y * scale;
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531

    // pressure_min is always 0.0
    if (_platformSupportsTouchPressure) {
      // 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
    if (_platformSupportsTouchOrientationAndTilt) {
      // 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 已提交
532 533

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

536 537 538 539 540
  blink::Threads::UI()->PostTask(ftl::MakeCopyable(
      [ engine = _platformView->engine().GetWeakPtr(), packet = std::move(packet) ] {
        if (engine.get())
          engine->DispatchPointerDataPacket(*packet);
      }));
C
Chinmay Garde 已提交
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
}

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

559
#pragma mark - Handle view resizing
560

561
- (void)updateViewportMetrics {
562 563 564 565 566 567 568 569
  blink::Threads::UI()->PostTask(
      [ weak_platform_view = _platformView->GetWeakPtr(), metrics = _viewportMetrics ] {
        if (!weak_platform_view) {
          return;
        }
        weak_platform_view->UpdateSurfaceSize();
        weak_platform_view->engine().SetViewportMetrics(metrics);
      });
570 571
}

572
- (CGFloat)statusBarPadding {
573
  UIScreen* screen = self.view.window.screen;
574
  CGRect statusFrame = [UIApplication sharedApplication].statusBarFrame;
575 576
  CGRect viewFrame =
      [self.view convertRect:self.view.bounds toCoordinateSpace:screen.coordinateSpace];
577 578
  CGRect intersection = CGRectIntersection(statusFrame, viewFrame);
  return CGRectIsNull(intersection) ? 0.0 : intersection.size.height;
579 580
}

581
- (void)viewDidLayoutSubviews {
582
  CGSize viewSize = self.view.bounds.size;
583 584
  CGFloat scale = [UIScreen mainScreen].scale;

585 586
  // First time since creation that the dimensions of its view is known.
  bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
587
  _viewportMetrics.device_pixel_ratio = scale;
588 589
  _viewportMetrics.physical_width = viewSize.width * scale;
  _viewportMetrics.physical_height = viewSize.height * scale;
590
  _viewportMetrics.physical_padding_top = [self statusBarPadding] * scale;
591
  [self updateViewportMetrics];
592 593 594 595 596

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

599
#pragma mark - Keyboard events
600

601
- (void)keyboardWillChangeFrame:(NSNotification*)notification {
602
  NSDictionary* info = [notification userInfo];
603
  CGFloat bottom = CGRectGetHeight([[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]);
604
  CGFloat scale = [UIScreen mainScreen].scale;
605 606
  _viewportMetrics.physical_padding_bottom = bottom * scale;
  [self updateViewportMetrics];
607 608
}

609
- (void)keyboardWillBeHidden:(NSNotification*)notification {
610 611
  _viewportMetrics.physical_padding_bottom = 0;
  [self updateViewportMetrics];
612 613
}

614 615 616
#pragma mark - Text input delegate

- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
617 618
  [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState"
                              arguments:@[ @(client), state ]];
619 620
}

621 622 623 624 625 626 627 628 629 630 631
- (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
  NSString* actionString;
  switch (action) {
    case FlutterTextInputActionDone:
      actionString = @"TextInputAction.done";
      break;
  }
  [_textInputChannel.get() invokeMethod:@"TextInputClient.performAction"
                              arguments:@[ @(client), actionString ]];
}

632 633 634 635 636 637 638
#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;

639
    NSNumber* update = info[@(shell::kOrientationUpdateNotificationKey)];
640 641 642 643 644 645 646 647 648 649 650 651

    if (update == nil) {
      return;
    }

    NSUInteger new_preferences = update.unsignedIntegerValue;

    if (new_preferences != _orientationPreferences) {
      _orientationPreferences = new_preferences;
      [UIViewController attemptRotationToDeviceOrientation];
    }
  });
652 653
}

654 655
- (BOOL)shouldAutorotate {
  return YES;
656 657
}

658 659 660 661
- (NSUInteger)supportedInterfaceOrientations {
  return _orientationPreferences;
}

662 663 664 665 666 667 668
#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.
669
  bool enabled = true;
670
#else
671
  bool enabled = UIAccessibilityIsVoiceOverRunning();
672
#endif
673
  _platformView->ToggleAccessibility(self.view, enabled);
674 675
}

676 677 678
#pragma mark - Memory Notifications

- (void)onMemoryWarning:(NSNotification*)notification {
679
  [_systemChannel.get() sendMessage:@{@"type" : @"memoryPressure"}];
680 681
}

682 683 684 685 686 687
#pragma mark - Locale updates

- (void)onLocaleUpdated:(NSNotification*)notification {
  NSLocale* currentLocale = [NSLocale currentLocale];
  NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode];
  NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
688
  [_localizationChannel.get() invokeMethod:@"setLocale" arguments:@[ languageCode, countryCode ]];
689 690
}

691 692 693 694 695
#pragma mark - Status Bar touch event handling

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

696
- (void)handleStatusBarTouches:(UIEvent*)event {
697 698 699 700 701 702 703 704
  // 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;
  if (statusBarFrame.size.height != kStandardStatusBarHeight) {
    return;
  }

  // If we detect a touch in the status bar, synthesize a fake touch begin/end.
705
  for (UITouch* touch in event.allTouches) {
706 707 708 709
    if (touch.phase == UITouchPhaseBegan && touch.tapCount > 0) {
      CGPoint windowLoc = [touch locationInView:nil];
      CGPoint screenLoc = [touch.window convertPoint:windowLoc toWindow:nil];
      if (CGRectContainsPoint(statusBarFrame, screenLoc)) {
710
        NSSet* statusbarTouches = [NSSet setWithObject:touch];
711 712 713 714 715 716 717 718
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseBegan];
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseEnded];
        return;
      }
    }
  }
}

719 720 721 722 723 724 725 726 727 728 729
#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;

730
    NSNumber* update = info[@(shell::kOverlayStyleUpdateNotificationKey)];
731 732 733 734 735 736 737 738 739 740 741 742 743 744

    if (update == nil) {
      return;
    }

    NSInteger style = update.integerValue;

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

745
#pragma mark - FlutterBinaryMessenger
746

747 748
- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
  [self sendOnChannel:channel message:message binaryReply:nil];
749 750
}

751 752 753
- (void)sendOnChannel:(NSString*)channel
              message:(NSData*)message
          binaryReply:(FlutterBinaryReply)callback {
754
  NSAssert(channel, @"The channel must not be null");
755 756 757 758 759 760 761 762 763
  ftl::RefPtr<PlatformMessageResponseDarwin> response =
      (callback == nil) ? nullptr
                        : ftl::MakeRefCounted<PlatformMessageResponseDarwin>(^(NSData* reply) {
                            callback(reply);
                          });
  ftl::RefPtr<blink::PlatformMessage> platformMessage =
      (message == nil) ? ftl::MakeRefCounted<blink::PlatformMessage>(channel.UTF8String, response)
                       : ftl::MakeRefCounted<blink::PlatformMessage>(
                             channel.UTF8String, shell::GetVectorFromNSData(message), response);
764
  _platformView->DispatchPlatformMessage(platformMessage);
765 766
}

767 768 769
- (void)setMessageHandlerOnChannel:(NSString*)channel
              binaryMessageHandler:(FlutterBinaryMessageHandler)handler {
  NSAssert(channel, @"The channel must not be null");
770
  _platformView->platform_message_router().SetMessageHandler(channel.UTF8String, handler);
771
}
C
Chinmay Garde 已提交
772
@end