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

7 8 9
#include "base/mac/scoped_block.h"
#include "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h"
10
#include "flutter/services/platform/ios/system_chrome_impl.h"
11
#include "flutter/sky/engine/wtf/MakeUnique.h"
12 13 14 15
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_touch_mapper.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
#include "flutter/shell/platform/darwin/ios/platform_view_ios.h"
#include "flutter/shell/platform/darwin/common/platform_mac.h"
16

17
@interface FlutterViewController ()<UIAlertViewDelegate>
18 19
@end

20 21 22
void FlutterInit(int argc, const char* argv[]) {
  NSBundle* bundle = [NSBundle bundleForClass:[FlutterViewController class]];
  NSString* icuDataPath = [bundle pathForResource:@"icudtl" ofType:@"dat"];
23
  shell::PlatformMacMain(argc, argv, icuDataPath.UTF8String);
24 25
}

26
@implementation FlutterViewController {
27
  base::scoped_nsprotocol<FlutterDartProject*> _dartProject;
28
  UIInterfaceOrientationMask _orientationPreferences;
29
  UIStatusBarStyle _statusBarStyle;
30
  sky::ViewportMetricsPtr _viewportMetrics;
31 32
  shell::TouchMapper _touchMapper;
  std::unique_ptr<shell::PlatformViewIOS> _platformView;
33

34
  BOOL _initialized;
35 36
}

37 38
#pragma mark - Manage and override all designated initializers

39 40 41 42
- (instancetype)initWithProject:(FlutterDartProject*)project
                        nibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
43

44
  if (self) {
45
    if (project == nil)
46 47
      _dartProject.reset(
          [[FlutterDartProject alloc] initFromDefaultSourceForConfiguration]);
48 49
    else
      _dartProject.reset([project retain]);
50 51

    [self performCommonViewControllerInitialization];
52
  }
53

54
  return self;
C
Chinmay Garde 已提交
55 56
}

57 58
- (instancetype)initWithNibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
59
  return [self initWithProject:nil nibName:nil bundle:nil];
C
Chinmay Garde 已提交
60 61
}

62
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
63
  return [self initWithProject:nil nibName:nil bundle:nil];
64 65
}

66
#pragma mark - Common view controller initialization tasks
C
Chinmay Garde 已提交
67

68
- (void)performCommonViewControllerInitialization {
69
  if (_initialized)
70 71
    return;
  _initialized = YES;
C
Chinmay Garde 已提交
72

73
  _orientationPreferences = UIInterfaceOrientationMaskAll;
74
  _statusBarStyle = UIStatusBarStyleDefault;
75
  _viewportMetrics = sky::ViewportMetrics::New();
76
  _platformView = WTF::MakeUnique<shell::PlatformViewIOS>(
77 78
      reinterpret_cast<CAEAGLLayer*>(self.view.layer));
  _platformView->SetupResourceContextOnIOThread();
C
Chinmay Garde 已提交
79

80
  [self setupNotificationCenterObservers];
C
Chinmay Garde 已提交
81

82
  [self connectToEngineAndLoad];
C
Chinmay Garde 已提交
83 84
}

85 86 87 88 89 90 91
- (void)setupNotificationCenterObservers {
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center addObserver:self
             selector:@selector(onOrientationPreferencesUpdated:)
                 name:@(flutter::platform::kOrientationUpdateNotificationName)
               object:nil];

92 93 94 95 96
  [center addObserver:self
             selector:@selector(onPreferredStatusBarStyleUpdated:)
                 name:@(flutter::platform::kOverlayStyleUpdateNotificationName)
               object:nil];

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
  [center addObserver:self
             selector:@selector(applicationBecameActive:)
                 name:UIApplicationDidBecomeActiveNotification
               object:nil];

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

  [center addObserver:self
             selector:@selector(keyboardWasShown:)
                 name:UIKeyboardDidShowNotification
               object:nil];

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

  [center addObserver:self
             selector:@selector(onLocaleUpdated:)
                 name:NSCurrentLocaleDidChangeNotification
               object:nil];
121 122 123 124 125

  [center addObserver:self
             selector:@selector(onVoiceOverChanged:)
                 name:UIAccessibilityVoiceOverStatusChanged
               object:nil];
C
Chinmay Garde 已提交
126 127
}

128
#pragma mark - Initializing the engine
129

130
- (void)alertView:(UIAlertView*)alertView
131
    clickedButtonAtIndex:(NSInteger)buttonIndex {
132 133 134
  exit(0);
}

135
- (void)connectToEngineAndLoad {
C
Chinmay Garde 已提交
136
  TRACE_EVENT0("flutter", "connectToEngineAndLoad");
137

138
  _platformView->ConnectToEngineAndSetupServices();
139

140
  // We ask the VM to check what it supports.
141 142
  const enum VMType type =
      Dart_IsPrecompiledRuntime() ? VMTypePrecompilation : VMTypeInterpreter;
143

144
  [_dartProject launchInEngine:_platformView->engineProxy()
145 146 147 148 149 150
                embedderVMType:type
                        result:^(BOOL success, NSString* message) {
                          if (!success) {
                            UIAlertView* alert = [[UIAlertView alloc]
                                    initWithTitle:@"Launch Error"
                                          message:message
151
                                         delegate:self
152 153 154 155 156 157
                                cancelButtonTitle:@"OK"
                                otherButtonTitles:nil];
                            [alert show];
                            [alert release];
                          }
                        }];
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
}

#pragma mark - Loading the view

- (void)loadView {
  FlutterView* surface = [[FlutterView alloc] init];

  self.view = surface;
  self.view.multipleTouchEnabled = YES;
  self.view.autoresizingMask =
      UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

  [surface release];
}

#pragma mark - Application lifecycle notifications

- (void)applicationBecameActive:(NSNotification*)notification {
176 177 178
  auto& engine = _platformView->engineProxy();
  if (engine) {
    engine->OnAppLifecycleStateChanged(sky::AppLifecycleState::RESUMED);
179 180 181 182
  }
}

- (void)applicationWillResignActive:(NSNotification*)notification {
183 184 185
  auto& engine = _platformView->engineProxy();
  if (engine) {
    engine->OnAppLifecycleStateChanged(sky::AppLifecycleState::PAUSED);
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
  }
}

#pragma mark - Touch event handling

enum MapperPhase {
  Accessed,
  Added,
  Removed,
};

using PointerTypeMapperPhase = std::pair<pointer::PointerType, MapperPhase>;
static inline PointerTypeMapperPhase PointerTypePhaseFromUITouchPhase(
    UITouchPhase phase) {
  switch (phase) {
    case UITouchPhaseBegan:
      return PointerTypeMapperPhase(pointer::PointerType::DOWN,
                                    MapperPhase::Added);
    case UITouchPhaseMoved:
    case UITouchPhaseStationary:
      // There is no EVENT_TYPE_POINTER_STATIONARY. So we just pass a move type
      // with the same coordinates
      return PointerTypeMapperPhase(pointer::PointerType::MOVE,
                                    MapperPhase::Accessed);
    case UITouchPhaseEnded:
      return PointerTypeMapperPhase(pointer::PointerType::UP,
                                    MapperPhase::Removed);
    case UITouchPhaseCancelled:
      return PointerTypeMapperPhase(pointer::PointerType::CANCEL,
                                    MapperPhase::Removed);
  }

  return PointerTypeMapperPhase(pointer::PointerType::CANCEL,
                                MapperPhase::Accessed);
}
C
Chinmay Garde 已提交
221 222

- (void)dispatchTouches:(NSSet*)touches phase:(UITouchPhase)phase {
223
  auto eventTypePhase = PointerTypePhaseFromUITouchPhase(phase);
C
Chinmay Garde 已提交
224
  const CGFloat scale = [UIScreen mainScreen].scale;
225
  auto pointer_packet = pointer::PointerPacket::New();
C
Chinmay Garde 已提交
226 227

  for (UITouch* touch in touches) {
228 229 230 231
    int touch_identifier = 0;

    switch (eventTypePhase.second) {
      case Accessed:
232
        touch_identifier = _touchMapper.identifierOf(touch);
233 234
        break;
      case Added:
235
        touch_identifier = _touchMapper.registerTouch(touch);
236 237
        break;
      case Removed:
238
        touch_identifier = _touchMapper.unregisterTouch(touch);
239 240
        break;
    }
241

242
    DCHECK(touch_identifier != 0);
C
Chinmay Garde 已提交
243
    CGPoint windowCoordinates = [touch locationInView:nil];
244 245 246

    auto pointer_time =
        base::TimeDelta::FromSecondsD(touch.timestamp).InMicroseconds();
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273

    auto pointer_data = pointer::Pointer::New();

    pointer_data->time_stamp = pointer_time;
    pointer_data->type = eventTypePhase.first;
    pointer_data->kind = pointer::PointerKind::TOUCH;
    pointer_data->pointer = touch_identifier;
    pointer_data->x = windowCoordinates.x * scale;
    pointer_data->y = windowCoordinates.y * scale;
    pointer_data->buttons = 0;
    pointer_data->down = false;
    pointer_data->primary = false;
    pointer_data->obscured = false;
    pointer_data->pressure = 1.0;
    pointer_data->pressure_min = 0.0;
    pointer_data->pressure_max = 1.0;
    pointer_data->distance = 0.0;
    pointer_data->distance_min = 0.0;
    pointer_data->distance_max = 0.0;
    pointer_data->radius_major = 0.0;
    pointer_data->radius_minor = 0.0;
    pointer_data->radius_min = 0.0;
    pointer_data->radius_max = 0.0;
    pointer_data->orientation = 0.0;
    pointer_data->tilt = 0.0;

    pointer_packet->pointers.push_back(pointer_data.Pass());
C
Chinmay Garde 已提交
274
  }
275

276
  _platformView->engineProxy()->OnPointerPacket(pointer_packet.Pass());
C
Chinmay Garde 已提交
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
}

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

295
#pragma mark - Handle view resizing
296

297 298 299 300 301 302 303 304 305 306
- (void)viewDidLayoutSubviews {
  CGSize size = self.view.bounds.size;
  CGFloat scale = [UIScreen mainScreen].scale;

  _viewportMetrics->device_pixel_ratio = scale;
  _viewportMetrics->physical_width = size.width * scale;
  _viewportMetrics->physical_height = size.height * scale;
  _viewportMetrics->physical_padding_top =
      [UIApplication sharedApplication].statusBarFrame.size.height * scale;

307 308
  _platformView->engineProxy()->OnViewportMetricsChanged(
      _viewportMetrics.Clone());
309 310
}

311
#pragma mark - Keyboard events
312

313 314 315 316 317 318
- (void)keyboardWasShown:(NSNotification*)notification {
  NSDictionary* info = [notification userInfo];
  CGFloat bottom = CGRectGetHeight(
      [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]);
  CGFloat scale = [UIScreen mainScreen].scale;
  _viewportMetrics->physical_padding_bottom = bottom * scale;
319 320
  _platformView->engineProxy()->OnViewportMetricsChanged(
      _viewportMetrics.Clone());
321 322
}

323 324
- (void)keyboardWillBeHidden:(NSNotification*)notification {
  _viewportMetrics->physical_padding_bottom = 0.0;
325 326
  _platformView->engineProxy()->OnViewportMetricsChanged(
      _viewportMetrics.Clone());
327 328
}

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
#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;

    NSNumber* update =
        info[@(flutter::platform::kOrientationUpdateNotificationKey)];

    if (update == nil) {
      return;
    }

    NSUInteger new_preferences = update.unsignedIntegerValue;

    if (new_preferences != _orientationPreferences) {
      _orientationPreferences = new_preferences;
      [UIViewController attemptRotationToDeviceOrientation];
    }
  });
350 351
}

352 353
- (BOOL)shouldAutorotate {
  return YES;
354 355
}

356 357 358 359
- (NSUInteger)supportedInterfaceOrientations {
  return _orientationPreferences;
}

360 361 362 363 364 365 366 367 368 369 370
#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.
  bool enable = true;
#else
  bool enable = UIAccessibilityIsVoiceOverRunning();
#endif
371
  _platformView->ToggleAccessibility(self.view, enable);
372 373
}

374 375 376 377 378 379
#pragma mark - Locale updates

- (void)onLocaleUpdated:(NSNotification*)notification {
  NSLocale* currentLocale = [NSLocale currentLocale];
  NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode];
  NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
380 381
  _platformView->engineProxy()->OnLocaleChanged(languageCode.UTF8String,
                                                countryCode.UTF8String);
382 383 384 385 386
}

#pragma mark - Surface creation and teardown updates

- (void)surfaceUpdated:(BOOL)appeared {
387
  CHECK(_platformView != nullptr);
388 389

  if (appeared) {
390
    _platformView->NotifyCreated();
391
  } else {
392
    _platformView->NotifyDestroyed();
393 394 395
  }
}

396 397
- (void)viewDidAppear:(BOOL)animated {
  [self surfaceUpdated:YES];
398 399
  [self onLocaleUpdated:nil];
  [self onVoiceOverChanged:nil];
400 401

  [super viewWillAppear:animated];
402
}
C
Chinmay Garde 已提交
403

404 405
- (void)viewWillDisappear:(BOOL)animated {
  [self surfaceUpdated:NO];
406 407

  [super viewWillDisappear:animated];
408 409
}

C
Chinmay Garde 已提交
410
- (void)dealloc {
411
  [[NSNotificationCenter defaultCenter] removeObserver:self];
412 413
  [super dealloc];
}
414

415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
#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;

    NSNumber* update =
        info[@(flutter::platform::kOverlayStyleUpdateNotificationKey)];

    if (update == nil) {
      return;
    }

    NSInteger style = update.integerValue;

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

442
#pragma mark - Application Messages
443

444
- (void)sendString:(NSString*)message withMessageName:(NSString*)messageName {
445 446
  NSAssert(message, @"The message must not be null");
  NSAssert(messageName, @"The messageName must not be null");
447 448 449
  _platformView->AppMessageSender()->SendString(
      messageName.UTF8String, message.UTF8String,
      [](const mojo::String& response) {});
450 451 452
}

- (void)sendString:(NSString*)message
453 454
    withMessageName:(NSString*)messageName
           callback:(void (^)(NSString*))callback {
455 456 457
  NSAssert(message, @"The message must not be null");
  NSAssert(messageName, @"The messageName must not be null");
  NSAssert(callback, @"The callback must not be null");
458
  base::mac::ScopedBlock<void (^)(NSString*)> callback_ptr(
459
      callback, base::scoped_policy::RETAIN);
460
  _platformView->AppMessageSender()->SendString(
461
      messageName.UTF8String, message.UTF8String,
462
      [callback_ptr](const mojo::String& response) {
463 464
        callback_ptr.get()(base::SysUTF8ToNSString(response));
      });
465 466
}

467
- (void)addMessageListener:(NSObject<FlutterMessageListener>*)listener {
468
  NSAssert(listener, @"The listener must not be null");
469
  NSString* messageName = listener.messageName;
470
  NSAssert(messageName, @"The messageName must not be null");
471 472
  _platformView->AppMessageReceiver().SetMessageListener(messageName.UTF8String,
                                                         listener);
473 474
}

475
- (void)removeMessageListener:(NSObject<FlutterMessageListener>*)listener {
476
  NSAssert(listener, @"The listener must not be null");
477
  NSString* messageName = listener.messageName;
478
  NSAssert(messageName, @"The messageName must not be null");
479 480
  _platformView->AppMessageReceiver().SetMessageListener(messageName.UTF8String,
                                                         nil);
481 482
}

483 484
- (void)addAsyncMessageListener:
    (NSObject<FlutterAsyncMessageListener>*)listener {
485 486 487
  NSAssert(listener, @"The listener must not be null");
  NSString* messageName = listener.messageName;
  NSAssert(messageName, @"The messageName must not be null");
488 489
  _platformView->AppMessageReceiver().SetAsyncMessageListener(
      messageName.UTF8String, listener);
490 491
}

492 493
- (void)removeAsyncMessageListener:
    (NSObject<FlutterAsyncMessageListener>*)listener {
494 495 496
  NSAssert(listener, @"The listener must not be null");
  NSString* messageName = listener.messageName;
  NSAssert(messageName, @"The messageName must not be null");
497 498
  _platformView->AppMessageReceiver().SetAsyncMessageListener(
      messageName.UTF8String, nil);
C
Chinmay Garde 已提交
499 500 501
}

@end