blob: 91a186baced3bb1ee151b1f772441e378b774dbc [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "quiview.h"
#include "qiosglobal.h"
#include "qiosintegration.h"
#include "qiosviewcontroller.h"
#include "qiostextresponder.h"
#include "qiosscreen.h"
#include "qioswindow.h"
#ifndef Q_OS_TVOS
#include "qiosmenu.h"
#endif
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qwindow_p.h>
#include <qpa/qwindowsysteminterface_p.h>
Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet")
@implementation QUIView {
QHash<UITouch *, QWindowSystemInterface::TouchPoint> m_activeTouches;
UITouch *m_activePencilTouch;
int m_nextTouchId;
NSMutableArray<UIAccessibilityElement *> *m_accessibleElements;
}
+ (void)load
{
#ifndef Q_OS_TVOS
if (QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::IOS, 11)) {
// iOS 11 handles this though [UIView safeAreaInsetsDidChange], but there's no signal for
// the corresponding top and bottom layout guides that we use on earlier versions. Note
// that we use the _will_ change version of the notification, because we want to react
// to the change as early was possible. But since the top and bottom layout guides have
// not been updated at this point we use asynchronous delivery of the event, so that the
// event is processed by QtGui just after iOS has updated the layout margins.
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillChangeStatusBarFrameNotification
object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *) {
for (QWindow *window : QGuiApplication::allWindows())
QWindowSystemInterface::handleSafeAreaMarginsChanged<QWindowSystemInterface::AsynchronousDelivery>(window);
}
];
}
#endif
}
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
- (instancetype)initWithQIOSWindow:(QT_PREPEND_NAMESPACE(QIOSWindow) *)window
{
if (self = [self initWithFrame:window->geometry().toCGRect()]) {
self.platformWindow = window;
m_accessibleElements = [[NSMutableArray<UIAccessibilityElement *> alloc] init];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
// Set up EAGL layer
CAEAGLLayer *eaglLayer = static_cast<CAEAGLLayer *>(self.layer);
eaglLayer.opaque = TRUE;
eaglLayer.drawableProperties = @{
kEAGLDrawablePropertyRetainedBacking: @(YES),
kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
};
if (isQtApplication())
self.hidden = YES;
#ifndef Q_OS_TVOS
self.multipleTouchEnabled = YES;
#endif
if (qEnvironmentVariableIntValue("QT_IOS_DEBUG_WINDOW_MANAGEMENT")) {
static CGFloat hue = 0.0;
CGFloat lastHue = hue;
for (CGFloat diff = 0; diff < 0.1 || diff > 0.9; diff = fabs(hue - lastHue))
hue = drand48();
#define colorWithBrightness(br) \
[UIColor colorWithHue:hue saturation:0.5 brightness:br alpha:1.0].CGColor
self.layer.borderColor = colorWithBrightness(1.0);
self.layer.borderWidth = 1.0;
}
if (qEnvironmentVariableIsSet("QT_IOS_DEBUG_WINDOW_SAFE_AREAS")) {
UIView *safeAreaOverlay = [[UIView alloc] initWithFrame:CGRectZero];
[safeAreaOverlay setBackgroundColor:[UIColor colorWithRed:0.3 green:0.7 blue:0.9 alpha:0.3]];
[self addSubview:safeAreaOverlay];
safeAreaOverlay.translatesAutoresizingMaskIntoConstraints = NO;
[safeAreaOverlay.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES;
[safeAreaOverlay.leftAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leftAnchor].active = YES;
[safeAreaOverlay.rightAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.rightAnchor].active = YES;
[safeAreaOverlay.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES;
}
}
return self;
}
- (void)dealloc
{
[m_accessibleElements release];
[super dealloc];
}
- (NSString *)description
{
NSMutableString *description = [NSMutableString stringWithString:[super description]];
#ifndef QT_NO_DEBUG_STREAM
QString platformWindowDescription;
QDebug debug(&platformWindowDescription);
debug.nospace() << "; " << self.platformWindow << ">";
NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1];
[description replaceCharactersInRange:lastCharacter withString:platformWindowDescription.toNSString()];
#endif
return description;
}
- (void)willMoveToWindow:(UIWindow *)newWindow
{
// UIKIt will normally set the scale factor of a view to match the corresponding
// screen scale factor, but views backed by CAEAGLLayers need to do this manually.
self.contentScaleFactor = newWindow && newWindow.screen ?
newWindow.screen.scale : [[UIScreen mainScreen] scale];
// FIXME: Allow the scale factor to be customized through QSurfaceFormat.
}
- (void)didAddSubview:(UIView *)subview
{
if ([subview isKindOfClass:[QUIView class]])
self.clipsToBounds = YES;
}
- (void)willRemoveSubview:(UIView *)subview
{
for (UIView *view in self.subviews) {
if (view != subview && [view isKindOfClass:[QUIView class]])
return;
}
self.clipsToBounds = NO;
}
- (void)setNeedsDisplay
{
[super setNeedsDisplay];
// We didn't implement drawRect: so we have to manually
// mark the layer as needing display.
[self.layer setNeedsDisplay];
}
- (void)layoutSubviews
{
// This method is the de facto way to know that view has been resized,
// or otherwise needs invalidation of its buffers. Note though that we
// do not get this callback when the view just changes its position, so
// the position of our QWindow (and platform window) will only get updated
// when the size is also changed.
if (!CGAffineTransformIsIdentity(self.transform))
qWarning() << self << "has a transform set. This is not supported.";
QWindow *window = self.platformWindow->window();
QRect lastReportedGeometry = qt_window_private(window)->geometry;
QRect currentGeometry = QRectF::fromCGRect(self.frame).toRect();
qCDebug(lcQpaWindow) << self.platformWindow << "new geometry is" << currentGeometry;
QWindowSystemInterface::handleGeometryChange(window, currentGeometry);
if (currentGeometry.size() != lastReportedGeometry.size()) {
// Trigger expose event on resize
[self setNeedsDisplay];
// A new size means we also need to resize the FBO's corresponding buffers,
// but we defer that to when the application calls makeCurrent.
}
}
- (void)displayLayer:(CALayer *)layer
{
Q_UNUSED(layer);
Q_ASSERT(layer == self.layer);
[self sendUpdatedExposeEvent];
}
- (void)sendUpdatedExposeEvent
{
QRegion region;
if (self.platformWindow->isExposed()) {
QSize bounds = QRectF::fromCGRect(self.layer.bounds).toRect().size();
Q_ASSERT(self.platformWindow->geometry().size() == bounds);
Q_ASSERT(self.hidden == !self.platformWindow->window()->isVisible());
region = QRect(QPoint(), bounds);
}
qCDebug(lcQpaWindow) << self.platformWindow << region << "isExposed" << self.platformWindow->isExposed();
QWindowSystemInterface::handleExposeEvent(self.platformWindow->window(), region);
}
- (void)safeAreaInsetsDidChange
{
QWindowSystemInterface::handleSafeAreaMarginsChanged(self.platformWindow->window());
}
// -------------------------------------------------------------------------
- (BOOL)canBecomeFirstResponder
{
return !(self.platformWindow->window()->flags() & Qt::WindowDoesNotAcceptFocus);
}
- (BOOL)becomeFirstResponder
{
{
// Scope for the duration of becoming first responder only, as the window
// activation event may trigger new responders, which we don't want to be
// blocked by this guard.
FirstResponderCandidate firstResponderCandidate(self);
qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
if (![super becomeFirstResponder]) {
qImDebug() << self << "was not allowed to become first responder";
return NO;
}
qImDebug() << self << "became first responder";
}
if (qGuiApp->focusWindow() != self.platformWindow->window())
QWindowSystemInterface::handleWindowActivated(self.platformWindow->window());
else
qImDebug() << self.platformWindow->window() << "already active, not sending window activation";
return YES;
}
- (BOOL)responderShouldTriggerWindowDeactivation:(UIResponder *)responder
{
// We don't want to send window deactivation in case the resign
// was a result of another Qt window becoming first responder.
if ([responder isKindOfClass:[QUIView class]])
return NO;
// Nor do we want to deactivate the Qt window if the new responder
// is temporarily handling text input on behalf of a Qt window.
if ([responder isKindOfClass:[QIOSTextInputResponder class]]) {
while ((responder = [responder nextResponder])) {
if ([responder isKindOfClass:[QUIView class]])
return NO;
}
}
return YES;
}
- (BOOL)resignFirstResponder
{
qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
if (![super resignFirstResponder])
return NO;
qImDebug() << self << "resigned first responder";
UIResponder *newResponder = FirstResponderCandidate::currentCandidate();
if ([self responderShouldTriggerWindowDeactivation:newResponder])
QWindowSystemInterface::handleWindowActivated(0);
return YES;
}
- (BOOL)isActiveWindow
{
// Normally this is determined exclusivly by being firstResponder, but
// since we employ a separate first responder for text input we need to
// handle both cases as this view being the active Qt window.
if ([self isFirstResponder])
return YES;
UIResponder *firstResponder = [UIResponder currentFirstResponder];
if ([firstResponder isKindOfClass:[QIOSTextInputResponder class]]
&& [firstResponder nextResponder] == self)
return YES;
return NO;
}
// -------------------------------------------------------------------------
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange: previousTraitCollection];
QTouchDevice *touchDevice = QIOSIntegration::instance()->touchDevice();
QTouchDevice::Capabilities touchCapabilities = touchDevice->capabilities();
if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable)
touchCapabilities |= QTouchDevice::Pressure;
else
touchCapabilities &= ~QTouchDevice::Pressure;
touchDevice->setCapabilities(touchCapabilities);
}
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.platformWindow->window()->flags() & Qt::WindowTransparentForInput)
return NO;
return [super pointInside:point withEvent:event];
}
- (void)handleTouches:(NSSet *)touches withEvent:(UIEvent *)event withState:(Qt::TouchPointState)state withTimestamp:(ulong)timeStamp
{
QIOSIntegration *iosIntegration = QIOSIntegration::instance();
bool supportsPressure = QIOSIntegration::instance()->touchDevice()->capabilities() & QTouchDevice::Pressure;
#if QT_CONFIG(tabletevent)
if (m_activePencilTouch && [touches containsObject:m_activePencilTouch]) {
NSArray<UITouch *> *cTouches = [event coalescedTouchesForTouch:m_activePencilTouch];
int i = 0;
for (UITouch *cTouch in cTouches) {
QPointF localViewPosition = QPointF::fromCGPoint([cTouch preciseLocationInView:self]);
QPoint localViewPositionI = localViewPosition.toPoint();
QPointF globalScreenPosition = self.platformWindow->mapToGlobal(localViewPositionI) +
(localViewPosition - localViewPositionI);
qreal pressure = cTouch.force / cTouch.maximumPossibleForce;
// azimuth unit vector: +x to the right, +y going downwards
CGVector azimuth = [cTouch azimuthUnitVectorInView: self];
// azimuthAngle given in radians, zero when the stylus points towards +x axis; converted to degrees with 0 pointing straight up
qreal azimuthAngle = [cTouch azimuthAngleInView: self] * 180 / M_PI + 90;
// altitudeAngle given in radians, pi / 2 is with the stylus perpendicular to the iPad, smaller values mean more tilted, but never negative.
// Convert to degrees with zero being perpendicular.
qreal altitudeAngle = 90 - cTouch.altitudeAngle * 180 / M_PI;
qCDebug(lcQpaTablet) << i << ":" << timeStamp << localViewPosition << pressure << state << "azimuth" << azimuth.dx << azimuth.dy
<< "angle" << azimuthAngle << "altitude" << cTouch.altitudeAngle
<< "xTilt" << qBound(-60.0, altitudeAngle * azimuth.dx, 60.0) << "yTilt" << qBound(-60.0, altitudeAngle * azimuth.dy, 60.0);
QWindowSystemInterface::handleTabletEvent(self.platformWindow->window(), timeStamp, localViewPosition, globalScreenPosition,
// device, pointerType, buttons
QTabletEvent::RotationStylus, QTabletEvent::Pen, state == Qt::TouchPointReleased ? Qt::NoButton : Qt::LeftButton,
// pressure, xTilt, yTilt
pressure, qBound(-60.0, altitudeAngle * azimuth.dx, 60.0), qBound(-60.0, altitudeAngle * azimuth.dy, 60.0),
// tangentialPressure, rotation, z, uid, modifiers
0, azimuthAngle, 0, 0, Qt::NoModifier);
++i;
}
}
#endif
for (UITouch *uiTouch : m_activeTouches.keys()) {
QWindowSystemInterface::TouchPoint &touchPoint = m_activeTouches[uiTouch];
if (![touches containsObject:uiTouch]) {
touchPoint.state = Qt::TouchPointStationary;
} else {
touchPoint.state = state;
// Touch positions are expected to be in QScreen global coordinates, and
// as we already have the QWindow positioned at the right place, we can
// just map from the local view position to global coordinates.
// tvOS: all touches start at the center of the screen and move from there.
QPoint localViewPosition = QPointF::fromCGPoint([uiTouch locationInView:self]).toPoint();
QPoint globalScreenPosition = self.platformWindow->mapToGlobal(localViewPosition);
touchPoint.area = QRectF(globalScreenPosition, QSize(0, 0));
// FIXME: Do we really need to support QTouchDevice::NormalizedPosition?
QSize screenSize = self.platformWindow->screen()->geometry().size();
touchPoint.normalPosition = QPointF(globalScreenPosition.x() / screenSize.width(),
globalScreenPosition.y() / screenSize.height());
if (supportsPressure) {
// Note: iOS will deliver touchesBegan with a touch force of 0, which
// we will reflect/propagate as a 0 pressure, but there is no clear
// alternative, as we don't want to wait for a touchedMoved before
// sending a touch press event to Qt, just to have a valid pressure.
touchPoint.pressure = uiTouch.force / uiTouch.maximumPossibleForce;
} else {
// We don't claim that our touch device supports QTouchDevice::Pressure,
// but fill in a meaningful value in case clients use it anyway.
touchPoint.pressure = (state == Qt::TouchPointReleased) ? 0.0 : 1.0;
}
}
}
if (m_activeTouches.isEmpty())
return;
if ([self.window isKindOfClass:[QUIWindow class]] &&
!static_cast<QUIWindow *>(self.window).sendingEvent) {
// The event is likely delivered as part of delayed touch delivery, via
// _UIGestureEnvironmentSortAndSendDelayedTouches, due to one of the two
// _UISystemGestureGateGestureRecognizer instances on the top level window
// having its delaysTouchesBegan set to YES. During this delivery, it's not
// safe to spin up a recursive event loop, as our calling function is not
// reentrant, so any gestures used by the recursive code, e.g. a native
// alert dialog, will fail to recognize. To be on the safe side, we deliver
// the event asynchronously.
QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>(
self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
} else {
QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// UIKit generates [Began -> Moved -> Ended] event sequences for
// each touch point. Internally we keep a hashmap of active UITouch
// points to QWindowSystemInterface::TouchPoints, and assigns each TouchPoint
// an id for use by Qt.
for (UITouch *touch in touches) {
#if QT_CONFIG(tabletevent)
if (touch.type == UITouchTypeStylus) {
if (Q_UNLIKELY(m_activePencilTouch)) {
qWarning("ignoring additional Pencil while first is still active");
continue;
}
m_activePencilTouch = touch;
} else
{
Q_ASSERT(!m_activeTouches.contains(touch));
#endif
m_activeTouches[touch].id = m_nextTouchId++;
#if QT_CONFIG(tabletevent)
}
#endif
}
if (self.platformWindow->shouldAutoActivateWindow() && m_activeTouches.size() == 1) {
QPlatformWindow *topLevel = self.platformWindow;
while (QPlatformWindow *p = topLevel->parent())
topLevel = p;
if (topLevel->window() != QGuiApplication::focusWindow())
topLevel->requestActivateWindow();
}
[self handleTouches:touches withEvent:event withState:Qt::TouchPointPressed withTimestamp:ulong(event.timestamp * 1000)];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self handleTouches:touches withEvent:event withState:Qt::TouchPointMoved withTimestamp:ulong(event.timestamp * 1000)];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self handleTouches:touches withEvent:event withState:Qt::TouchPointReleased withTimestamp:ulong(event.timestamp * 1000)];
// Remove ended touch points from the active set:
for (UITouch *touch in touches) {
#if QT_CONFIG(tabletevent)
if (touch.type == UITouchTypeStylus) {
m_activePencilTouch = nil;
} else
#endif
{
m_activeTouches.remove(touch);
}
}
if (m_activeTouches.isEmpty() && !m_activePencilTouch)
m_nextTouchId = 0;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
if (m_activeTouches.isEmpty() && !m_activePencilTouch)
return;
// When four-finger swiping, we get a touchesCancelled callback
// which includes all four touch points. The swipe gesture is
// then active until all four touches have been released, and
// we start getting touchesBegan events again.
// When five-finger pinching, we also get a touchesCancelled
// callback with all five touch points, but the pinch gesture
// ends when the second to last finger is released from the
// screen. The last finger will not emit any more touch
// events, _but_, will contribute to starting another pinch
// gesture. That second pinch gesture will _not_ trigger a
// touchesCancelled event when starting, but as each finger
// is released, and we may get touchesMoved events for the
// remaining fingers. [event allTouches] also contains one
// less touch point than it should, so this behavior is
// likely a bug in the iOS system gesture recognizer, but we
// have to take it into account when maintaining the Qt state.
// We do this by assuming that there are no cases where a
// sub-set of the active touch events are intentionally cancelled.
NSInteger count = static_cast<NSInteger>([touches count]);
if (count != 0 && count != m_activeTouches.count() && !m_activePencilTouch)
qWarning("Subset of active touches cancelled by UIKit");
m_activeTouches.clear();
m_nextTouchId = 0;
m_activePencilTouch = nil;
NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime];
QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration());
QWindowSystemInterface::handleTouchCancelEvent(self.platformWindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice());
}
- (int)mapPressTypeToKey:(UIPress*)press
{
switch (press.type) {
case UIPressTypeUpArrow: return Qt::Key_Up;
case UIPressTypeDownArrow: return Qt::Key_Down;
case UIPressTypeLeftArrow: return Qt::Key_Left;
case UIPressTypeRightArrow: return Qt::Key_Right;
case UIPressTypeSelect: return Qt::Key_Select;
case UIPressTypeMenu: return Qt::Key_Menu;
case UIPressTypePlayPause: return Qt::Key_MediaTogglePlayPause;
}
return Qt::Key_unknown;
}
- (bool)processPresses:(NSSet *)presses withType:(QEvent::Type)type {
// Presses on Menu button will generate a Menu key event. By default, not handling
// this event will cause the application to return to Headboard (tvOS launcher).
// When handling the event (for example, as a back button), both press and
// release events must be handled accordingly.
bool handled = false;
for (UIPress* press in presses) {
int key = [self mapPressTypeToKey:press];
if (key == Qt::Key_unknown)
continue;
if (QWindowSystemInterface::handleKeyEvent(self.platformWindow->window(), type, key, Qt::NoModifier))
handled = true;
}
return handled;
}
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
if (![self processPresses:presses withType:QEvent::KeyPress])
[super pressesBegan:presses withEvent:event];
}
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
if (![self processPresses:presses withType:QEvent::KeyPress])
[super pressesChanged:presses withEvent:event];
}
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
if (![self processPresses:presses withType:QEvent::KeyRelease])
[super pressesEnded:presses withEvent:event];
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
#ifndef Q_OS_TVOS
// Check first if QIOSMenu should handle the action before continuing up the responder chain
return [QIOSMenu::menuActionTarget() targetForAction:action withSender:sender] != 0;
#else
Q_UNUSED(action)
Q_UNUSED(sender)
return false;
#endif
}
- (id)forwardingTargetForSelector:(SEL)selector
{
Q_UNUSED(selector)
#ifndef Q_OS_TVOS
return QIOSMenu::menuActionTarget();
#else
return nil;
#endif
}
- (void)addInteraction:(id<UIInteraction>)interaction
{
if ([NSStringFromClass(interaction.class) isEqualToString:@"UITextInteraction"])
return;
[super addInteraction:interaction];
}
@end
@implementation UIView (QtHelpers)
- (QWindow *)qwindow
{
if ([self isKindOfClass:[QUIView class]]) {
if (QT_PREPEND_NAMESPACE(QIOSWindow) *w = static_cast<QUIView *>(self).platformWindow)
return w->window();
}
return nil;
}
- (UIViewController *)viewController
{
id responder = self;
while ((responder = [responder nextResponder])) {
if ([responder isKindOfClass:UIViewController.class])
return responder;
}
return nil;
}
- (QIOSViewController*)qtViewController
{
UIViewController *vc = self.viewController;
if ([vc isKindOfClass:QIOSViewController.class])
return static_cast<QIOSViewController *>(vc);
return nil;
}
- (UIEdgeInsets)qt_safeAreaInsets
{
return self.safeAreaInsets;
}
@end
#ifndef QT_NO_ACCESSIBILITY
// Include category as an alternative to using -ObjC (Apple QA1490)
#include "quiview_accessibility.mm"
#endif