19#include <QtCore/qmath.h>
20#include <QtGui/qpointingdevice.h>
21#include <QtGui/private/qguiapplication_p.h>
22#include <QtGui/private/qwindow_p.h>
23#include <QtGui/private/qapplekeymapper_p.h>
24#include <QtGui/private/qpointingdevice_p.h>
25#include <qpa/qwindowsysteminterface_p.h>
28Q_LOGGING_CATEGORY(lcQpaInputEvents,
"qt.qpa.input.events")
31inline ulong getTimeStamp(UIEvent *event)
33#if TARGET_OS_SIMULATOR == 1
46#if defined(Q_PROCESSOR_ARM)
47 #warning The timestamp work-around for x86_64 can (probably) be removed when building for ARM
49 return ulong(NSProcessInfo.processInfo.systemUptime * 1000);
52 return ulong(event.timestamp * 1000);
56@implementation QUIView {
57 QHash<NSUInteger, QWindowSystemInterface::TouchPoint> m_activeTouches;
58 UITouch *m_activePencilTouch;
59 NSMutableArray<UIAccessibilityElement *> *m_accessibleElements;
60 UIPanGestureRecognizer *m_scrollGestureRecognizer;
61 CGPoint m_lastScrollCursorPos;
62 CGPoint m_lastScrollDelta;
63#if QT_CONFIG(tabletevent)
64 UIHoverGestureRecognizer *m_hoverGestureRecognizer;
71 return [CAEAGLLayer
class];
73 return [super layerClass];
76- (instancetype)initWithQIOSWindow:(QT_PREPEND_NAMESPACE(QIOSWindow) *)window
78 if (self = [self initWithFrame:window->geometry().toCGRect()]) {
79 self.platformWindow = window;
81 if (isQtApplication())
84 m_accessibleElements = [[NSMutableArray<UIAccessibilityElement *> alloc] init];
87 self.multipleTouchEnabled = YES;
90 m_scrollGestureRecognizer = [[UIPanGestureRecognizer alloc]
92 action:@selector(handleScroll:)];
97 m_scrollGestureRecognizer.allowedTouchTypes = [NSArray array];
98 if (@available(ios 13.4, *)) {
99 m_scrollGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskAll;
101 m_scrollGestureRecognizer.maximumNumberOfTouches = 0;
102 m_lastScrollDelta = CGPointZero;
103 m_lastScrollCursorPos = CGPointZero;
104 [self addGestureRecognizer:m_scrollGestureRecognizer];
106#if QT_CONFIG(tabletevent)
107 m_hoverGestureRecognizer = [[UIHoverGestureRecognizer alloc]
109 action:@selector(handleHover:)];
110 [self addGestureRecognizer:m_hoverGestureRecognizer];
114 if ([self.layer isKindOfClass:CAMetalLayer.
class]) {
115 QWindow *window = self.platformWindow->window();
116 if (QColorSpace colorSpace = window->format().colorSpace(); colorSpace.isValid()) {
117 QCFType<CFDataRef> iccData = colorSpace.iccProfile().toCFData();
118 QCFType<CGColorSpaceRef> cgColorSpace = CGColorSpaceCreateWithICCData(iccData);
119 CAMetalLayer *metalLayer =
static_cast<CAMetalLayer *>(self.layer);
120 metalLayer.colorspace = cgColorSpace;
121 qCDebug(lcQpaWindow) <<
"Set" << self <<
"color space to" << metalLayer.colorspace;
125 else if ([self.layer isKindOfClass:[CAEAGLLayer
class]]) {
126 CAEAGLLayer *eaglLayer =
static_cast<CAEAGLLayer *>(self.layer);
127 eaglLayer.opaque = TRUE;
128 eaglLayer.drawableProperties = @{
129 kEAGLDrawablePropertyRetainedBacking: @(YES),
130 kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
135#if defined(Q_OS_VISIONOS)
140 self.contentScaleFactor = self.platformWindow->screen()->devicePixelRatio();
149 [m_accessibleElements release];
150 [m_scrollGestureRecognizer release];
157 NSMutableString *description = [NSMutableString stringWithString:[super description]];
159#ifndef QT_NO_DEBUG_STREAM
160 QString platformWindowDescription;
161 QDebug debug(&platformWindowDescription);
162 debug.nospace() <<
"; " << self.platformWindow <<
">";
163 NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1];
164 [description replaceCharactersInRange:lastCharacter withString:platformWindowDescription.toNSString()];
170#if !defined(Q_OS_VISIONOS)
171- (
void)willMoveToWindow:(UIWindow *)newWindow
175 self.contentScaleFactor = newWindow && newWindow.screen ?
176 newWindow.screen.scale : [[UIScreen mainScreen] scale];
182- (
void)didAddSubview:(UIView *)subview
184 if ([subview isKindOfClass:[QUIView
class]])
185 self.clipsToBounds = YES;
188- (
void)willRemoveSubview:(UIView *)subview
190 for (UIView *view in self.subviews) {
191 if (view != subview && [view isKindOfClass:[QUIView
class]])
195 self.clipsToBounds = NO;
198- (
void)setNeedsDisplay
200 [super setNeedsDisplay];
204 [self.layer setNeedsDisplay];
207- (
void)layoutSubviews
215 if (!CGAffineTransformIsIdentity(self.transform))
216 qWarning() << self <<
"has a transform set. This is not supported.";
218 QWindow *window = self.platformWindow->window();
219 QRect lastReportedGeometry = qt_window_private(window)->geometry;
220 QRect currentGeometry = QRectF::fromCGRect(self.frame).toRect();
221 qCDebug(lcQpaWindow) << self.platformWindow <<
"new geometry is" << currentGeometry;
222 QWindowSystemInterface::handleGeometryChange(window, currentGeometry);
224 if (currentGeometry.size() != lastReportedGeometry.size()) {
226 [self setNeedsDisplay];
233- (
void)displayLayer:(CALayer *)layer
236 Q_ASSERT(layer == self.layer);
238 if (!self.platformWindow)
241 [self sendUpdatedExposeEvent];
244- (
void)sendUpdatedExposeEvent
248 if (self.platformWindow->isExposed()) {
249 QSize bounds = QRectF::fromCGRect(self.layer.bounds).toRect().size();
251 Q_ASSERT(self.platformWindow->geometry().size() == bounds);
252 Q_ASSERT(self.hidden == !self.platformWindow->window()->isVisible());
254 region = QRect(QPoint(), bounds);
257 qCDebug(lcQpaWindow) << self.platformWindow << region <<
"isExposed" << self.platformWindow->isExposed();
258 QWindowSystemInterface::handleExposeEvent(self.platformWindow->window(), region);
261- (
void)safeAreaInsetsDidChange
263 QWindowSystemInterface::handleSafeAreaMarginsChanged(self.platformWindow->window());
268- (BOOL)canBecomeFirstResponder
270 return !(self.platformWindow->window()->flags() & (Qt::WindowDoesNotAcceptFocus
271 | Qt::WindowTransparentForInput));
274- (BOOL)becomeFirstResponder
280 FirstResponderCandidate firstResponderCandidate(self);
282 qImDebug() <<
"self:" << self <<
"first:" << [UIResponder qt_currentFirstResponder];
284 if (![super becomeFirstResponder]) {
285 qImDebug() << self <<
"was not allowed to become first responder";
289 qImDebug() << self <<
"became first responder";
292 if (qGuiApp->focusWindow() != self.platformWindow->window())
293 QWindowSystemInterface::handleFocusWindowChanged(self.platformWindow->window(), Qt::ActiveWindowFocusReason);
295 qImDebug() << self.platformWindow->window() <<
"already active, not sending window activation";
300- (BOOL)responderShouldTriggerWindowDeactivation:(UIResponder *)responder
304 if ([responder isKindOfClass:[QUIView
class]])
309 if ([responder isKindOfClass:[QIOSTextResponder
class]]) {
310 while ((responder = [responder nextResponder])) {
311 if ([responder isKindOfClass:[QUIView
class]])
319- (BOOL)resignFirstResponder
321 qImDebug() <<
"self:" << self <<
"first:" << [UIResponder qt_currentFirstResponder];
323 if (![super resignFirstResponder])
326 qImDebug() << self <<
"resigned first responder";
329 UIResponder *newResponder = FirstResponderCandidate::currentCandidate();
330 if ([self responderShouldTriggerWindowDeactivation:newResponder])
331 QWindowSystemInterface::handleFocusWindowChanged(
nullptr, Qt::ActiveWindowFocusReason);
337- (BOOL)isActiveWindow
343 if ([self isFirstResponder])
346 UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
347 if ([firstResponder isKindOfClass:[QIOSTextInputResponder
class]]
348 && [firstResponder nextResponder] == self)
356- (
void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
358 [super traitCollectionDidChange: previousTraitCollection];
360 QPointingDevice *touchDevice = QIOSIntegration::instance()->touchDevice();
361 auto *devicePriv = QPointingDevicePrivate::get(touchDevice);
363 auto capabilities = touchDevice->capabilities();
364 capabilities.setFlag(QPointingDevice::Capability::Pressure,
365 (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable));
366 devicePriv->setCapabilities(capabilities);
369-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
371 if (self.platformWindow->window()->flags() & Qt::WindowTransparentForInput)
373 return [super pointInside:point withEvent:event];
376#if QT_CONFIG(tabletevent)
377- (
void)handlePencilEventForLocationInView:(CGPoint)locationInView withState:(QEventPoint::State)state withTimestamp:(ulong)timeStamp
378 withForce:(CGFloat)force withMaximumPossibleForce:(CGFloat)maximumPossibleForce withZOffset:(CGFloat)zOffset
379 withAzimuthUnitVector:(CGVector)azimuth withAltitudeAngleRadian:(CGFloat)altitudeAngleRadian
381 QIOSIntegration *iosIntegration = QIOSIntegration::instance();
383 QPointF localViewPosition = QPointF::fromCGPoint(locationInView);
384 QPoint localViewPositionI = localViewPosition.toPoint();
385 QPointF globalScreenPosition = self.platformWindow->mapToGlobal(localViewPositionI) +
386 (localViewPosition - localViewPositionI);
388 if (force != 0 && maximumPossibleForce != 0)
389 pressure = force / maximumPossibleForce;
393 qreal altitudeAngle = 90 - qRadiansToDegrees(altitudeAngleRadian);
394 qreal xTilt = qBound(-60.0, altitudeAngle * azimuth.dx, 60.0);
395 qreal yTilt = qBound(-60.0, altitudeAngle * azimuth.dy, 60.0);
397 qCDebug(lcQpaTablet) <<
":" << timeStamp << localViewPosition << pressure << state <<
"azimuth" << azimuth.dx << azimuth.dy
398 <<
"altitude" << altitudeAngleRadian <<
"xTilt" << xTilt <<
"yTilt" << yTilt;
399 QWindowSystemInterface::handleTabletEvent(self.platformWindow->window(), timeStamp,
401 iosIntegration->pencilDevice(), localViewPosition, globalScreenPosition,
403 state == QEventPoint::State::Released ? Qt::NoButton : Qt::LeftButton,
405 pressure, xTilt, yTilt, 0, 0, zOffset, Qt::NoModifier);
409- (
void)handleTouches:(NSSet *)touches withEvent:(UIEvent *)event withState:(QEventPoint::State)state withTimestamp:(ulong)timeStamp
411 QIOSIntegration *iosIntegration = QIOSIntegration::instance();
412 bool supportsPressure = QIOSIntegration::instance()->touchDevice()->capabilities() & QPointingDevice::Capability::Pressure;
414#if QT_CONFIG(tabletevent)
415 if (m_activePencilTouch && [touches containsObject:m_activePencilTouch]) {
416 NSArray<UITouch *> *cTouches = [event coalescedTouchesForTouch:m_activePencilTouch];
417 for (UITouch *cTouch in cTouches) {
418 [self handlePencilEventForLocationInView:[cTouch preciseLocationInView:self] withState:state withTimestamp:timeStamp
419 withForce:cTouch.force withMaximumPossibleForce:cTouch.maximumPossibleForce withZOffset:0
420 withAzimuthUnitVector:[cTouch azimuthUnitVectorInView:self]
421 withAltitudeAngleRadian:cTouch.altitudeAngle];
426 if (m_activeTouches.isEmpty())
428 for (
auto it = m_activeTouches.begin(); it != m_activeTouches.end(); ++it) {
429 auto hash = it.key();
430 QWindowSystemInterface::TouchPoint &touchPoint = it.value();
431 UITouch *uiTouch = nil;
432 for (UITouch *touch in touches) {
433 if (touch.hash == hash) {
439 touchPoint.state = QEventPoint::State::Stationary;
441 touchPoint.state = state;
447 QPoint localViewPosition = QPointF::fromCGPoint([uiTouch locationInView:self]).toPoint();
448 QPoint globalScreenPosition = self.platformWindow->mapToGlobal(localViewPosition);
450 touchPoint.area = QRectF(globalScreenPosition, QSize(0, 0));
453 QSize screenSize = self.platformWindow->screen()->geometry().size();
454 touchPoint.normalPosition = QPointF(globalScreenPosition.x() / screenSize.width(),
455 globalScreenPosition.y() / screenSize.height());
457 if (supportsPressure) {
462 touchPoint.pressure = uiTouch.force / uiTouch.maximumPossibleForce;
466 touchPoint.pressure = (state == QEventPoint::State::Released) ? 0.0 : 1.0;
471 if ([self.window isKindOfClass:[QUIWindow
class]] &&
472 !
static_cast<QUIWindow *>(self.window).sendingEvent) {
481 QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>(
482 self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
487 QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>(
488 self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
492- (
void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
498 for (UITouch *touch in touches) {
499#if QT_CONFIG(tabletevent)
500 if (touch.type == UITouchTypeStylus) {
501 if (Q_UNLIKELY(m_activePencilTouch)) {
502 qWarning(
"ignoring additional Pencil while first is still active");
505 m_activePencilTouch = touch;
508 Q_ASSERT(!m_activeTouches.contains(touch.hash));
512 static quint16 nextTouchId = 0;
513 m_activeTouches[touch.hash].id = nextTouchId++;
514#if QT_CONFIG(tabletevent)
519 if (self.platformWindow->shouldAutoActivateWindow() && m_activeTouches.size() == 1) {
520 QPlatformWindow *topLevel = self.platformWindow;
521 while (QPlatformWindow *p = topLevel->parent())
523 if (topLevel->window() != QGuiApplication::focusWindow())
524 topLevel->requestActivateWindow();
527 [self handleTouches:touches withEvent:event withState:QEventPoint::State::Pressed withTimestamp:getTimeStamp(event)];
530- (
void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
532 [self handleTouches:touches withEvent:event withState:QEventPoint::State::Updated withTimestamp:getTimeStamp(event)];
535- (
void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
537 [self handleTouches:touches withEvent:event withState:QEventPoint::State::Released withTimestamp:getTimeStamp(event)];
541 for (UITouch *touch in touches) {
542#if QT_CONFIG(tabletevent)
543 if (touch.type == UITouchTypeStylus) {
544 m_activePencilTouch = nil;
548 m_activeTouches.remove(touch.hash);
553 m_activeTouches.clear();
557- (
void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
559 if (m_activeTouches.isEmpty() && !m_activePencilTouch)
582 NSInteger count =
static_cast<NSInteger>([touches count]);
583 if (count != 0 && count != m_activeTouches.count() && !m_activePencilTouch)
584 qWarning(
"Subset of active touches cancelled by UIKit");
586 m_activeTouches.clear();
587 m_activePencilTouch = nil;
589 ulong timestamp = event ? getTimeStamp(event) : ([[NSProcessInfo processInfo] systemUptime] * 1000);
591 QIOSIntegration *iosIntegration =
static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration());
596 QWindowSystemInterface::handleTouchCancelEvent<QWindowSystemInterface::AsynchronousDelivery>(
597 self.platformWindow->window(), timestamp, iosIntegration->touchDevice());
600- (
int)mapPressTypeToKey:(UIPress*)press withModifiers:(Qt::KeyboardModifiers)qtModifiers text:(QString &)text
602 switch (press.type) {
603 case UIPressTypeUpArrow:
return Qt::Key_Up;
604 case UIPressTypeDownArrow:
return Qt::Key_Down;
605 case UIPressTypeLeftArrow:
return Qt::Key_Left;
606 case UIPressTypeRightArrow:
return Qt::Key_Right;
607 case UIPressTypeSelect:
return Qt::Key_Select;
608 case UIPressTypeMenu:
return Qt::Key_Menu;
609 case UIPressTypePlayPause:
return Qt::Key_MediaTogglePlayPause;
611 if (@available(ios 13.4, *)) {
612 Qt::Key key = QAppleKeyMapper::fromUIKitKey(press.key.keyCode);
613 if (key != Qt::Key_unknown)
615 NSString *charactersIgnoringModifiers = press.key.charactersIgnoringModifiers;
616 key = QAppleKeyMapper::fromUIKitKey(charactersIgnoringModifiers);
617 if (key != Qt::Key_unknown)
619 key = QAppleKeyMapper::fromNSString(qtModifiers, press.key.characters,
620 charactersIgnoringModifiers, text);
621 if (key != Qt::Key_unknown)
624 return Qt::Key_unknown;
627- (
bool)isControlKey:(Qt::Key)key
642- (
bool)handlePresses:(NSSet<UIPress *> *)presses eventType:(QEvent::Type)type
648 if (!qApp->focusWindow())
651 bool eventHandled =
false;
652 const bool imEnabled = QIOSInputContext::instance()->inputMethodAccepted();
654 for (UIPress* press in presses) {
655 Qt::KeyboardModifiers qtModifiers = Qt::NoModifier;
656 if (@available(ios 13.4, *))
657 qtModifiers = QAppleKeyMapper::fromUIKitModifiers(press.key.modifierFlags);
659 int key = [self mapPressTypeToKey:press withModifiers:qtModifiers text:text];
660 if (key == Qt::Key_unknown)
662 if (imEnabled && ![self isControlKey:Qt::Key(key)])
665 bool keyHandled = QWindowSystemInterface::handleKeyEvent(
666 self.platformWindow->window(), type, key, qtModifiers, text);
667 eventHandled = eventHandled || keyHandled;
673- (
void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
675 if (![self handlePresses:presses eventType:QEvent::KeyPress])
676 [super pressesBegan:presses withEvent:event];
679- (
void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
681 if (![self handlePresses:presses eventType:QEvent::KeyPress])
682 [super pressesChanged:presses withEvent:event];
683 [super pressesChanged:presses withEvent:event];
686- (
void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
688 if (![self handlePresses:presses eventType:QEvent::KeyRelease])
689 [super pressesEnded:presses withEvent:event];
690 [super pressesEnded:presses withEvent:event];
693- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
695#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
697 return [QIOSMenu::menuActionTarget() targetForAction:action withSender:sender] != 0;
705- (
id)forwardingTargetForSelector:(SEL)selector
708#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
709 return QIOSMenu::menuActionTarget();
715- (
void)addInteraction:(id<UIInteraction>)interaction
717 if ([NSStringFromClass(interaction.
class) isEqualToString:@
"UITextInteraction"])
720 [super addInteraction:interaction];
723- (UIEditingInteractionConfiguration)editingInteractionConfiguration
731 return QIOSInputContext::instance()->inputMethodAccepted() ?
732 UIEditingInteractionConfigurationDefault : UIEditingInteractionConfigurationNone;
735#if QT_CONFIG(wheelevent)
736- (
void)handleScroll:(UIPanGestureRecognizer *)recognizer
738 if (!self.platformWindow->window())
741 if (!self.canBecomeFirstResponder)
744 CGPoint translation = [recognizer translationInView:self];
745 CGFloat deltaX = translation.x - m_lastScrollDelta.x;
746 CGFloat deltaY = translation.y - m_lastScrollDelta.y;
754 const int pixelsToDegrees = 2;
755 angleDelta.setX(deltaX * pixelsToDegrees);
756 angleDelta.setY(deltaY * pixelsToDegrees);
759 pixelDelta.setX(deltaX);
760 pixelDelta.setY(deltaY);
762 NSTimeInterval time_stamp = [[NSProcessInfo processInfo] systemUptime];
763 ulong qt_timestamp = time_stamp * 1000;
765 Qt::KeyboardModifiers qt_modifierFlags = Qt::NoModifier;
766 if (@available(ios 13.4, *))
767 qt_modifierFlags = QAppleKeyMapper::fromUIKitModifiers(recognizer.modifierFlags);
769 if (recognizer.state == UIGestureRecognizerStateBegan)
773 m_lastScrollCursorPos = [recognizer locationInView:self];
775 if (recognizer.state != UIGestureRecognizerStateEnded) {
776 m_lastScrollDelta.x = translation.x;
777 m_lastScrollDelta.y = translation.y;
779 m_lastScrollDelta = CGPointZero;
782 QPoint qt_local = QPointF::fromCGPoint(m_lastScrollCursorPos).toPoint();
783 QPoint qt_global = self.platformWindow->mapToGlobal(qt_local);
785 qCInfo(lcQpaInputEvents).nospace() <<
"wheel event" <<
" at " << qt_local
786 <<
" pixelDelta=" << pixelDelta <<
" angleDelta=" << angleDelta;
788 QWindowSystemInterface::handleWheelEvent(self.platformWindow->window(), qt_timestamp, qt_local, qt_global, pixelDelta, angleDelta, qt_modifierFlags);
792#if QT_CONFIG(tabletevent)
793- (
void)handleHover:(UIHoverGestureRecognizer *)recognizer
795 if (!self.platformWindow)
798 ulong timeStamp = [[NSProcessInfo processInfo] systemUptime] * 1000;
801 if (@available(ios 16.1, *))
802 zOffset = [recognizer zOffset];
805 CGFloat altitudeAngleRadian = 0;
806 if (@available(ios 16.4, *)) {
807 azimuth = [recognizer azimuthUnitVectorInView:self];
808 altitudeAngleRadian = recognizer.altitudeAngle;
811 [self handlePencilEventForLocationInView:[recognizer locationInView:self] withState:QEventPoint::State::Released
812 withTimestamp:timeStamp withForce:0 withMaximumPossibleForce:0 withZOffset:zOffset
813 withAzimuthUnitVector:azimuth withAltitudeAngleRadian:altitudeAngleRadian];
819@implementation UIView (QtHelpers)
823 if ([self isKindOfClass:[QUIView
class]]) {
824 if (QT_PREPEND_NAMESPACE(QIOSWindow) *w =
static_cast<QUIView *>(self).platformWindow)
833 while ((responder = [responder nextResponder])) {
834 if ([responder isKindOfClass:UIViewController.
class])
840- (QIOSViewController*)qtViewController
842 UIViewController *vc = self.viewController;
843 if ([vc isKindOfClass:QIOSViewController.
class])
844 return static_cast<QIOSViewController *>(vc);
852@implementation QUIMetalView
856 return [CAMetalLayer
class];
862#if QT_CONFIG(accessibility)
864#include "quiview_accessibility.mm"
Q_FORWARD_DECLARE_OBJC_CLASS(NSString)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
Q_FORWARD_DECLARE_OBJC_CLASS(UIViewController)